谷粒学苑 —— 4、讲师管理模块

目录

1、项目路由

1.1、项目入口文件如何调用路由

2、讲师列表前端实现

2.1、添加路由

2.2、创建路由对应的页面

2.3、创建 JS 文件并定义方法

2.4、在 Controller 类上添加注解 @CrossOrigin

2.5、编写页面条件分页讲师数据

3、讲师删除

3.1、在 JS 中定义方法

3.2、页面调用方法

4、讲师添加

4.1、在 JS 文件添加方法

4.1、讲师添加页面

关于表单验证的问题

5、讲师修改

5.1、添加路由

5.2、数据回显

5.3、实现修改

5.4、问题:路由问题

6、阿里云对象存储 OSS

6.1、创建 Bucket

6.2、上传文件

6.3、创建许可证(Access Key)

7、后端集成 OSS

7.1、新建云存储微服务模块

7.1.1、创建 service_oss 模块并引入依赖

7.1.2、配置 application.yml

7.1.3、问题:启动失败,没有配置数据库连接

7.2、实现文件上传

7.2.1、创建常量类读取配置文件内容

7.2.2、controller、service 代码

7.3、配置 nginx 反向代理

7.3.1、配置 nginx.conf

7.3.2、修改前端的  BASE_API

7.3.3、重启 nginx 并测试

7.4、上传讲师头像

7.4.1、复制所需的组件

7.4.2、页面添加上传组件


1、项目路由

1.1、项目入口文件如何调用路由

在 src\main.js 中,先引入路由模块,再挂载路由

......
import router from './router' //引入路由模块
......
new Vue({
  el: '#app',
  router, //挂载路由
  store,
  render: h => h(App)
})

可以看到路由模块在 src\router\index.js 中设置,通过 component 设置展示的内容

{
    path: '/example',
    component: Layout,  // Layout表示第一级标签
    redirect: '/example/table',
    name: 'Example',
    meta: { title: 'Example', icon: 'example' },
    children: [  // 子标签
      {
        path: 'table',
        name: 'Table',
        component: () => import('@/views/table/index'), // 引入页面
        meta: { title: 'Table', icon: 'table' }
      },
      {
        path: 'tree',
        name: 'Tree',
        component: () => import('@/views/tree/index'), // 引入页面
        meta: { title: 'Tree', icon: 'tree' }
      }
    ]
  }

查看 src\views\table\index.vue,发现它引入了 src\api\table.js 文件,并调用里面的方法获取表格数据

2、讲师列表前端实现

2.1、添加路由

在 src\router\index.js 中添加路由

  {
    path: '/teacher',
    component: Layout,
    redirect: '/teacher/table',
    name: '讲师管理',
    meta: { title: '讲师管理', icon: 'example' },
    children: [  // 子标签
      {
        path: 'table',
        name: '讲师列表',
        component: () => import('@/views/edu/teacher/list'), // 引入页面
        meta: { title: '讲师列表', icon: 'table' }
      },
      {
        path: 'save',
        name: '添加讲师',
        component: () => import('@/views/edu/teacher/save'), // 引入页面
        meta: { title: '添加讲师', icon: 'tree' }
      }
    ]
  },

2.2、创建路由对应的页面

可以先随意添加一些内容进行测试,注意 template 标签

<template>
    <div class="app-container">
        讲师列表
    </div>
</template>

2.3、创建 JS 文件并定义方法

创建 src\api\edu\teacher.js 文件,在里面定义 条件分页获取讲师列表 的方法

import request from '@/utils/request'

export default {

    /**
     * 获取讲师列表(条件查询分页)
     * @param {*} current 当前页码
     * @param {*} limit 每页记录数
     * @param {*} teacherQuery 查询对象
     * @returns 
     */
    getTeacherListPage(current, limit, teacherQuery) {
        return request({
            // 注意url使用 着重号 `
            url: `/eduservice/teacher/pageTeacherCondition/${current}/${limit}`,
            method: 'post',
            // data表示将对象转换为JSON传递,后端可以用@RequestBody接收
            data: teacherQuery
        })
    }

}

2.4、在 Controller 类上添加注解 @CrossOrigin

 在 EduTeacherController 上添加注解,解决跨域问题

@Api(description = "讲师管理")
@RestController
@RequestMapping("/eduservice/teacher")
@CrossOrigin
public class EduTeacherController{}

2.5、编写页面条件分页讲师数据

修改 src\views\edu\teacher\list.vue

<template>
    <div class="app-container">
        <!-- 表格 -->
        <el-table
        :data="list"
        border
        fit
        highlight-current-row>
        <el-table-column
            label="序号"
            width="70"
            align="center">
            <template slot-scope="scope">
            {{ (page - 1) * limit + scope.$index + 1 }}
            </template>
        </el-table-column>

        <el-table-column prop="name" label="名称" width="80" />

        <el-table-column label="头衔" width="80">
            <template slot-scope="scope">
                <!-- scope.row就是本行的teacher对象 -->
                <!-- 
                    == 两个等号仅判断值,例如 1 == '1' 成立
                    === 三个等号不仅判断值,还判断类型,上述等式不成立
                 -->
                {{ scope.row.level===1?'高级讲师':'首席讲师' }}
            </template>
        </el-table-column>

        <el-table-column prop="intro" label="资历" />

        <el-table-column prop="gmtCreate" label="添加时间" width="160"/>

        <el-table-column prop="sort" label="排序" width="60" />

        <el-table-column label="操作" width="200" align="center">
            <template slot-scope="scope">
            <router-link :to="'/edu/teacher/edit/'+scope.row.id">
                <el-button type="primary" size="mini" icon="el-icon-edit">修改</el-button>
            </router-link>
            <el-button type="danger" size="mini" icon="el-icon-delete" @click="removeDataById(scope.row.id)">删除</el-button>
            </template>
        </el-table-column>
        </el-table>
    </div>
</template>

<script>
    import teacher from '@/api/edu/teacher.js';

    export default {
        // new Vue 在 main.js 已经做了封装
        // 写代码核心位置
        data() {
            return {
                list: null, // 查询后接口返回集合
                page: 1,   // 当前页
                limit: 10,  // 每页记录数
                total: 0,   // 总记录数
                teacherQuery: {}    // 条件封装对象
            }
        },
        created() {
            this.getList();
        },
        methods: {
            getList(){
                teacher.getTeacherListPage(this.page,this.limit,this.teacherQuery)
                    .then(response => {
                        // console.log(response);
                        this.list = response.data.records;
                        this.total = response.data.total;
                        // console.log(this.list);
                        // console.log(this.total);
                    })
                    .catch(error => {
                        console.log(error);
                    })
            }
        }
    }
</script>


3、讲师删除

3.1、在 JS 中定义方法

在 src\api\edu\teacher.js 定义删除讲师的方法

    /**
     * 根据id删除讲师
     * @param {*} id 要删除的讲师的id
     */
    deleteTeacherById(id){
        return request({
            // 注意url使用 着重号 `
            url: `/eduservice/teacher/${id}`,
            method: 'DELETE',
        })
    }

3.2、页面调用方法

在 src\views\edu\teacher\list.vue 中调用方法

            /**
             * 删除讲师
             */
            removeDataById(id) {
                this.$confirm('此操作将永久删除该讲师记录, 是否继续?', '提示', {
                    confirmButtonText: '确定',
                    cancelButtonText: '取消',
                    type: 'warning'
                })
                // 确认删除
                .then(() => {
                    teacher.deleteTeacherById(id)
                        // 删除成功
                        .then(response => {
                            // 提示信息
                            this.$message({
                                type: 'success',
                                message: '删除成功!'
                            });
                            // 重新加载列表
                            if (this.list.length == 1){
                                this.page = this.page - 1;
                            }
                            this.getList(this.page);
                        })
                })
                // 取消删除
                .catch(() => {
                    this.$message({
                        type: 'info',
                        message: '已取消删除'
                    });
                });

4、讲师添加

4.1、在 JS 文件添加方法

在 src\api\edu\teacher.js 中添加方法

    /**
     * 添加讲师
     * @param {*} teacher 要添加的讲师
     * @returns 
     */
    addTeacher(teacher) {
        return request({
            url: `/eduservice/teacher/addTeacher`,
            method: 'POST',
            data: teacher
        })
    }

4.1、讲师添加页面

为了方便,可以在获取讲师列表的方法添加按创建事件降序的条件

src\views\edu\teacher\save.vue 作为讲师添加的页面,后面修改会复用这个页面

<template>
    <div class="app-container">
        <!-- 加上rules属性即开启表单验证规则,:rules动态绑定了一个对象,该对象在data里面定义,对象里面包含的是校验规则 -->
        <!-- :ref相当于唯一标识表单 -->
        <!-- 注意el-form使用:model来绑定,el-form-item使用v-model来绑定 -->
        <el-form :model="teacher" :rules="rules" label-width="120px" ref="loginForm">
            <el-form-item label="讲师名称" prop="name">
                <el-input v-model="teacher.name" />
            </el-form-item>
            <el-form-item label="讲师排序">
                <el-input-number v-model="teacher.sort" controls-position="right" :min="0" />
            </el-form-item>
            <el-form-item label="讲师头衔" prop="level">
                <el-select v-model="teacher.level" clearable placeholder="请选择讲师头衔">
                    <!--
                    数据类型一定要和取出的json中的一致,否则没法回填
                    因此,这里value使用动态绑定的值,保证其数据类型是number
                  -->
                    <el-option :value="1" label="高级讲师" />
                    <el-option :value="2" label="首席讲师" />
                </el-select>
            </el-form-item>
            <el-form-item label="讲师资历" prop="career">
                <el-input v-model="teacher.career" />
            </el-form-item>
            <el-form-item label="讲师简介" prop="intro">
                <el-input v-model="teacher.intro" :rows="10" type="textarea" />
            </el-form-item>
        
            <!-- 讲师头像:TODO -->
        
            <el-form-item>
                <el-button :disabled="saveBtnDisabled" type="primary" @click="saveOrUpdate">保存</el-button>
            </el-form-item>
        </el-form>
    </div>
</template>

<script>
    import teacherApi from '@/api/edu/teacher'

    export default {
        data() {
            return {
                teacher: {
                    name: '',
                    sort: 0,
                    level: null,
                    career: '',
                    intro: '',
                    avatar: ''
                },
                saveBtnDisabled: false,  // 添加按钮是否禁用

                rules: {
                    name: [
                        { required: true, message: '请输入讲师名称', trigger: 'blur' },
                        { min: 2, max: 15, message: '长度在 2 到 15 个字符', trigger: 'blur' }
                    ],
                    level: [
                        { required: true, message: '请选择讲师头衔', trigger: 'blur' }
                    ],
                    career: [
                        { required: true, message: '请输入讲师资历', trigger: 'blur' }
                    ],
                    intro: [
                        { required: true, message: '请填写讲师简介', trigger: 'blur' }
                    ]
                }
            }
        },
        created() {
            this.init();
        },
        methods: {
            saveOrUpdate() {
                // 根据teacher对象是否有id值区分添加还是修改
                if (this.teacher.id) {
                    this.updateTeacherInfo();
                } else {
                    this.saveTeacher();
                }
            },

            saveTeacher() {
                // 这里的 'loginForm' 与要提交的表单的 :ref 对应
                // 
                this.$refs['loginForm'].validate((valid) => {
                    if (valid) {
                        teacherApi.addTeacher(this.teacher)
                            .then(response => {
                                // 提示信息
                                this.$message({
                                    type: 'success',
                                    message: '添加成功!'
                                });

                                // 回到列表页面 路由跳转
                                this.$router.push({ path: '/teacher/table' });
                        });
                    } else {
                        return false;
                    }
                });
                
            },

            /**
             * 根据id获取讲师信息
             */
            getInfo(id) {
                teacherApi.getTeacherInfo(id)
                    .then(response => {
                        this.teacher = response.data.teacher;
                        console.log(this.teacher);
                    });
            },

            /**
             * 修改讲师信息
             */
            updateTeacherInfo() {
                teacherApi.updateTeacher(this.teacher)
                    .then(response => {
                        // 提示信息
                        this.$message({
                            type: 'success',
                            message: '修改成功!'
                        });

                        // 回到列表页面 路由跳转
                        this.$router.push({ path: '/teacher/table' });
                    })
            }

        }
    }
</script>

关于表单验证的问题

首先是输入了值却一直提示要输入值。这个问题与格式有关。

el-form 中要用 :model 绑定表单要提交的数据的对象,用 :rules 绑定 data 中的校验规则

el-form-item 中要用 v-model 绑定这个对象的属性,并且 prop 为该属性名

然后是报错 TypeError: Cannot read properties of undefined (reading ‘validate‘),这个问题是由于表单提交时没有与表单绑定

如下两图,在 el-form 中设置 ref 来唯一标识该表单,然后在提交方法中通过改值来校验要提交的表单(如下图红线处)

5、讲师修改

5.1、添加路由

在 src\router\index.js 中添加

      {
        path: '/teacher/edit/:id',   // :id相当于Java中的占位符
        name: 'EduTeacherEdit',
        component: () => import('@/views/edu/teacher/save'),
        meta: { title: '编辑讲师', noCache: true },
        hidden: true  // 路由标签不显示
      }

5.2、数据回显

① 在 src\api\edu\teacher.js 中定义根据 id 获取讲师信息的方法

    /**
     * 根据id获取讲师信息
     * @param {} id 要获取的讲师的id
     * @returns 
     */
    getTeacherInfo(id) {
        return request({
            url: `/eduservice/teacher/getTeacher/${id}`,
            method: 'GET',
        })
    }

②调用定义的方法

在 src\views\edu\teacher\save.vue 的 method 中添加方法

            /**
             * 根据id获取讲师信息
             */
            getInfo(id) {
                teacherApi.getTeacherInfo(id)
                    .then(response => {
                        this.teacher = response.data.teacher;
                        console.log(this.teacher);
                    });
            }

然后在 created 中使用

        created() {
            // 根据路径是否有id值区分添加还是修改
            if (this.$route.params && this.$route.params.id) {
                const id = this.$route.params.id;
                this.getInfo(id);
            } else {
                this.teacher = {};
            }
        },

5.3、实现修改

① 在 src\api\edu\teacher.js 中添加修改讲师的方法

    /**
     * 修改讲师
     * @param {*} teacher 
     * @returns 
     */
    updateTeacher(teacher) {
        return request({
            url: `/eduservice/teacher/updateTeacher`,
            method: 'POST',
            data: teacher
        })
    }

② 调用定义的方法

在 src\views\edu\teacher\save.vue 的 methods 中添加修改讲师的方法

            /**
             * 修改讲师信息
             */
            updateTeacherInfo() {
                teacherApi.updateTeacher(this.teacher)
                    .then(response => {
                        // 提示信息
                        this.$message({
                            type: 'success',
                            message: '修改成功!'
                        });

                        // 回到列表页面 路由跳转
                        this.$router.push({ path: '/teacher/table' });
                    })
            }

修改 saveOrUpdate 方法为根据修改还是添加讲师调用对应的方法

            saveOrUpdate() {
                // 根据teacher对象是否有id值区分添加还是修改
                if (this.teacher.id) {
                    this.updateTeacherInfo();
                } else {
                    this.saveTeacher();
                }
            },

5.4、问题:路由问题

问题:多次跳转到同一个路由,create 只执行一次,在目前这里没有遇到,但在 nginx 代理后会出现

vue-router导航切换 时,如果两个路由都渲染同个组件,组件会重(chong)用,

组件的生命周期钩子(created)不会再被调用, 使得组件的一些数据无法根据 path的改变得到更新

因此:

1、我们可以在 watch 中监听路由的变化,当路由变化时,重新调用 created 中的内容

2、在 init 方法中我们判断路由的变化,如果是修改路由,则从api获取表单数据,如果是新增路由,则重新初始化表单数据

        watch: {
            // 路由发生变化就会执行
            $route(to, from) {
                this.init();
            }
        },
        created() {
            this.init();
        },
        methods: {
            ...

            init(){
                // 根据路径是否有id值区分添加还是修改
                if (this.$route.params && this.$route.params.id) {
                    const id = this.$route.params.id;
                    this.getInfo(id);
                } else {
                    this.teacher = {};
                }
            },

            ...
        }

6、阿里云对象存储 OSS

为了解决海量数据存储与弹性扩容,项目中我们采用云存储的解决方案 阿里云OSS。 

这里需要先注册和开通阿里云对象存储 OSS。

官方文档(Java):前言

6.1、创建 Bucket

按照提示输入或选择即可。

地域默认是当前地区的服务器,确认后不可更改。

存储类型由于这里是用作头像且为个人项目,这里选择低频访问存储。

读写权限选择公共读。

6.2、上传文件

进入创建的 Bucket,在文件管理中上传文件

上传后点击文件可以查看 URL 等信息

6.3、创建许可证(Access Key)

这里选择使用子用户 AccessKey,创建用户后再授予权限

7、后端集成 OSS

7.1、新建云存储微服务模块

7.1.1、创建 service_oss 模块并引入依赖

在 service 模块下创建 Maven 模块 service_oss,并创建启动类

引入依赖(由于我启动时报错 MetaObjectHandler.class 找不到,所以加了 mybatisplus 的依赖)

    <dependencies>
        <!-- 阿里云OSS -->
        <dependency>
            <groupId>com.aliyun.oss</groupId>
            <artifactId>aliyun-sdk-oss</artifactId>
            <version>3.15.0</version>
        </dependency>

        <!-- 日期工具栏依赖 -->
        <dependency>
            <groupId>joda-time</groupId>
            <artifactId>joda-time</artifactId>
        </dependency>

        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
        </dependency>
    </dependencies>

7.1.2、配置 application.yml

server:
  port: 8002  #服务端口
  
spring:
  application:
    name: service-oss   # 服务名
  profiles:
    active: dev   #环境设置:dev、test、prod

#阿里云OSS
aliyun:
  oss:
    file:
      endpoint: https://oss-cn-hangzhou.aliyuncs.com   #Bucket所在地域对应的Endpoint,地址不同,这里是华东1(杭州)
      keyid: your accessKeyId
      keysecret: your accessKeySecret
      bucketname: zyj-guli  #bucket可以在控制台创建,也可以使用java代码创建

7.1.3、问题:启动失败,没有配置数据库连接

解决方法:

方案一:在 application.yml 配置数据源

方案二:设置启动类注解

@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)

7.2、实现文件上传

官方文档(Java):前言

7.2.1、创建常量类读取配置文件内容

@Component
public class ConstantPropertiesUtils {

    @Value("${aliyun.oss.file.endpoint}")
    private String endpoint;

    @Value("${aliyun.oss.file.keyid}")
    private String keyId;

    @Value("${aliyun.oss.file.keysecret}")
    private String keySecret;

    @Value("${aliyun.oss.file.bucketname}")
    private String bucketName;

}

7.2.2、controller、service 代码

参考:简单上传  中的上传文件流

① OssService

public interface OssService {

    /**
     * 上传头像到OSS
     * @param file 上传的文件
     * @return
     */
    String uploadFileAvatar(MultipartFile file);

}

② OssServiceImpl

@Service
public class OssServiceImpl implements OssService {

    /**
     * 上传头像到OSS
     * @param file 上传的文件
     * @return
     */
    @Override
    public String uploadFileAvatar(MultipartFile file) {
        // 通过常量类获取值
        String endpoint = ConstantPropertiesUtils.END_POINT;
        String accessKeyId = ConstantPropertiesUtils.ACCESS_KEY_ID;
        String accessKeySecret = ConstantPropertiesUtils.ACCESS_KEY_SECRET;
        String bucketName = ConstantPropertiesUtils.BUCKET_NAME;

        // 获取文件名称:生成随机值,按照日期分类
        String uuid = UUID.randomUUID().toString().replaceAll("-", "");
        String datePath = new DateTime().toString("yyyy/MM/dd");
        String objectName = datePath + uuid + file.getOriginalFilename();

        // 创建OSSClient实例。
        OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);

        try {
            // 获取上传文件输入流
            InputStream inputStream = file.getInputStream();

            // 创建PutObject请求。
            ossClient.putObject(bucketName, objectName, inputStream);

        } catch (Exception e) {
            e.printStackTrace();
            return null;
        } finally {
            // 关闭OSSClient
            if (ossClient != null) {
                ossClient.shutdown();
            }
        }

        // 拼接文件url
        // https://zyj-guli.oss-cn-hangzhou.aliyuncs.com/a.jpg
        // endpoint 前面有https://
        String url = "https://" + bucketName + "." + endpoint.substring(8) + "/" + objectName;
        return url;
    }

}

③ OssController

@RestController
@RequestMapping("/eduoss/fileoss")
@CrossOrigin
public class OssController {

    @Autowired
    private OssService ossService;

    /**
     * 上传头像到OSS
     * @param file 上传的文件
     * @return
     */
    @PostMapping("")
    public R uploadOssFile(
            @ApiParam(name = "file", value = "文件", required = true) MultipartFile file
    ){
        // 返回上传到OSS的路径
        String url = ossService.uploadFileAvatar(file);
        return R.ok().data("url", url);
    }

}

7.3、配置 nginx 反向代理

7.3.1、配置 nginx.conf

① 设置监听的端口,这里设为 81

server {
        listen       81;
        ……
}

② 配置 nginx 转发规则

	server {
		listen 9001;
		server_name localhost;
		
		# 当请求路径包含 /eduservice/ 时,转发到8001接口
		# ~ 标识正则,没有~标识完全匹配
		location ~ /eduservice/ {
			proxy_pass http://localhost:8001;
		}
		
		location ~ /eduoss/ {
			proxy_pass http://localhost:8002;
		}
	}

7.3.2、修改前端的  BASE_API

修改前端 config\dev.env.js 的 BASE_API 为 nginx 监听的 9001 端口

module.exports = merge(prodEnv, {
  NODE_ENV: '"development"',
  // BASE_API: '"https://easy-mock.com/mock/5950a2419adc231f356a6636/vue-admin"',
  BASE_API: '"http://localhost:9001"',
})

7.3.3、重启 nginx 并测试

重启后,打开 8001 和 8002 两个服务,打开前端项目,可以正常使用

7.4、上传讲师头像

7.4.1、复制所需的组件

从 vue-element-admin-master.zip 中的 src\components 复制下面两个组件到 src\components 中

7.4.2、页面添加上传组件

① 页面引入并声明刚添加的上传组件

    // 引入上传组件模块
    import ImageCropper from '@/components/ImageCropper'
    import PanThumb from '@/components/PanThumb'

    export default {
        // 声明组件
        components: {ImageCropper, PanThumb},

        ……
    }

② 页面加入头像上传组件

            <!-- 讲师头像 -->
            <el-form-item label="讲师头像">
                <!-- 头像缩略图 -->
                <pan-thumb :image="teacher.avatar" />

                <!-- 文件上传按钮 -->
                <el-button type="primary" icon="el-icon-upload" @click="imagecropperShow=true">更换头像</el-button>

                <!--
                    v-show:是否显示上传组件
                    :key:类似于id,如果一个页面多个图片上传控件,可以做区分
                    :url:后台上传的url地址
                    field:前端的field必须等于后端MultipartFile的名字
                    @close:点击上传弹框右上角的叉,关闭上传组件的方法
                    @crop-upload-success:点击上传弹框保存按钮调用的方法,上传成功后的回调 
                -->
                <image-cropper v-show="imagecropperShow" :width="300" :height="300" :key="imagecropperKey"
                    :url="BASE_API+'/eduoss/fileoss/upload'" field="file" @close="close" @crop-upload-success="cropSuccess" />
            </el-form-item>

③ 在 data() 中添加上传头像所需的值,顺便设置默认头像

                teacher: {
                    name: '',
                    sort: 0,
                    level: null,
                    career: '',
                    intro: '',
                    avatar: 'https://zyj-guli.oss-cn-hangzhou.aliyuncs.com/a.jpg'   /* 默认头像 */
                },

                imagecropperShow: false, // 上传弹框是否显示
                imagecropperKey: 0,      // 上传组件key值
                BASE_API: process.env.BASE_API,  // 获取config\dev.env.js中的BASE_API的值

注意在 init() 方法中也设置以下默认头像

④ 在 methods 中添加上传所需的方法

            /**
             * 关闭上传弹框
             */
            close() {
                // 关闭上传弹框
                this.imagecropperShow = false;

                // 将图片上传后的url赋值给teacher.avatar:上传之后的接口会返回图片的url
                this.teacher.avatar = data.url;
                console.log();

                // 上传组件初始化
                this.imagecropperKey = this.imagecropperKey + 1;
            },

            /**
             * 上传成功方法
             * @param {*} data 请求返回的data
             */
            cropSuccess(data) {
                this.imagecropperShow = false;
                this.teacher.avatar = data.url;
                // 上传成功后,重新打开上传组件时初始化组件,否则显示上一次的上传结果
                this.imagecropperKey = this.imagecropperKey + 1;
            }

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值