Guli College 160,000 単語のノート + 1,600 枚の写真 (8) - コース管理

プロジェクトのソースコードと必要な情報
リンク: https://pan.baidu.com/s/1azwRyyFwXz5elhQL0BhkCA?pwd=8z59
抽出コード: 8z59

記事ディレクトリ

デモ08-コース管理

1.コースリリースの流れ

ここに画像の説明を挿入

2. コース関連表

2.1 コース関連テーブル間の関係

ここに画像の説明を挿入

講師テーブルとコーステーブルが多対多の関係ではなく、1対多の関係にある理由を説明してください。

  • まず第一に、教師が複数のコースに対応していることを知っておく必要があります
  • 複数の教師に対応するコースが不可能な理由について話しましょう
    • 例: Zhang San は Java コースを教え、Li Si も Java コースを教えました。まず第一に、これら 2 つの Java コースは 2 人の教師によって教えられていることがわかっているため、2 つの Java コースの ID は異なるはずです。ID が異なるため、これらを 2 つの異なるコースとみなすことができます。1 つのコースは複数の教師に対応します。
  • したがって、講師テーブルとクラステーブルの間には 1 対多の関係があります。

2.2 データテーブルの作成

コース管理の実装には、edu_course、edu_course_description、edu_chapter、edu_video、edu_Teacher、edu_subject の 6 つのテーブルが必要であることがわかりました。このうち、edu_Teacher と edu_subject はすでに作成されているため、残りの 4 つのデータ テーブルを作成するだけで済みます。 [データ] –> [データベース スクリプト] –> guli_edu.sql に移動して、これら 4 つのデータ テーブルを作成するための SQL ステートメントを見つけてコピーします。データベースにアクセスしてこれらのステートメントを実行します

CREATE TABLE `edu_chapter` (
  `id` char(19) NOT NULL COMMENT '章节ID',
  `course_id` char(19) NOT NULL COMMENT '课程ID',
  `title` varchar(50) NOT NULL COMMENT '章节名称',
  `sort` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '显示排序',
  `gmt_create` datetime NOT NULL COMMENT '创建时间',
  `gmt_modified` datetime NOT NULL COMMENT '更新时间',
  PRIMARY KEY (`id`),
  KEY `idx_course_id` (`course_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=COMPACT COMMENT='课程';

CREATE TABLE `edu_course` (
  `id` char(19) NOT NULL COMMENT '课程ID',
  `teacher_id` char(19) NOT NULL COMMENT '课程讲师ID',
  `subject_id` char(19) NOT NULL COMMENT '课程专业ID',
  `subject_parent_id` char(19) NOT NULL COMMENT '课程专业父级ID',
  `title` varchar(50) NOT NULL COMMENT '课程标题',
  `price` decimal(10,2) unsigned NOT NULL DEFAULT '0.00' COMMENT '课程销售价格,设置为0则可免费观看',
  `lesson_num` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '总课时',
  `cover` varchar(255) CHARACTER SET utf8 NOT NULL COMMENT '课程封面图片路径',
  `buy_count` bigint(10) unsigned NOT NULL DEFAULT '0' COMMENT '销售数量',
  `view_count` bigint(10) unsigned NOT NULL DEFAULT '0' COMMENT '浏览数量',
  `version` bigint(20) unsigned NOT NULL DEFAULT '1' COMMENT '乐观锁',
  `status` varchar(10) NOT NULL DEFAULT 'Draft' COMMENT '课程状态 Draft未发布  Normal已发布',
  `is_deleted` tinyint(3) DEFAULT NULL COMMENT '逻辑删除 1(true)已删除, 0(false)未删除',
  `gmt_create` datetime NOT NULL COMMENT '创建时间',
  `gmt_modified` datetime NOT NULL COMMENT '更新时间',
  PRIMARY KEY (`id`),
  KEY `idx_title` (`title`),
  KEY `idx_subject_id` (`subject_id`),
  KEY `idx_teacher_id` (`teacher_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=COMPACT COMMENT='课程';

CREATE TABLE `edu_course_description` (
  `id` char(19) NOT NULL COMMENT '课程ID',
  `description` text COMMENT '课程简介',
  `gmt_create` datetime NOT NULL COMMENT '创建时间',
  `gmt_modified` datetime NOT NULL COMMENT '更新时间',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='课程简介';

CREATE TABLE `edu_video` (
  `id` char(19) NOT NULL COMMENT '视频ID',
  `course_id` char(19) NOT NULL COMMENT '课程ID',
  `chapter_id` char(19) NOT NULL COMMENT '章节ID',
  `title` varchar(50) NOT NULL COMMENT '节点名称',
  `video_source_id` varchar(100) DEFAULT NULL COMMENT '云端视频资源',
  `video_original_name` varchar(100) DEFAULT NULL COMMENT '原始文件名称',
  `sort` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '排序字段',
  `play_count` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT '播放次数',
  `is_free` tinyint(1) unsigned NOT NULL DEFAULT '0' COMMENT '是否可以试听:0收费 1免费',
  `duration` float NOT NULL DEFAULT '0' COMMENT '视频时长(秒)',
  `status` varchar(20) NOT NULL DEFAULT 'Empty' COMMENT 'Empty未上传 Transcoding转码中  Normal正常',
  `size` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT '视频源文件大小(字节)',
  `version` bigint(20) unsigned NOT NULL DEFAULT '1' COMMENT '乐观锁',
  `gmt_create` datetime NOT NULL COMMENT '创建时间',
  `gmt_modified` datetime NOT NULL COMMENT '更新时间',
  PRIMARY KEY (`id`),
  KEY `idx_course_id` (`course_id`),
  KEY `idx_chapter_id` (`chapter_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=COMPACT COMMENT='课程视频';

ここに画像の説明を挿入

3. コース基本情報バックエンドを追加する

3.1 コードの生成

1. コード ジェネレーターの Strategy.setInclude("edu_subject"); を Strategy.setInclude("edu_course", "edu_course_description", "edu_chapter", "edu_video"); に変更します。

ここに画像の説明を挿入

2. コードジェネレーターを実行してコードを生成します。

3. コース紹介はコースの基本情報に依存します。コース基本情報内のコース紹介を変更しますが、コース紹介を個別に変更することはありません。そのため、コース紹介のコントローラーは必要ないため、削除します。

ここに画像の説明を挿入

4. コントローラー EduCourseController のコードを変更します。

  • クロスドメインの問題を解決するには、このコントローラーに @CrossOrigin アノテーションを追加します。

  • 私は強迫性障害を持っています。教師のようになりたいのです。このコントローラー コードを次の@RequestMapping("/eduservice/edu-course")ように変更します。@RequestMapping("/eduservice/course")

ここに画像の説明を挿入

5. エンティティ クラス EduCourse および EduCourseDescription の 2 つの属性 gmtCreate および gmtModified に注釈 @TableField を追加します。

ここに画像の説明を挿入

ここに画像の説明を挿入

3.2 分析

1. フロントエンドはデータをバックエンドに送信しますが、これらのデータはどのように受信しますか? 以前と同様にエンティティ クラス EduCourse を使用してデータをカプセル化しますか? しかし、フロントエンドによって送信されるデータの 1 つはコースですはじめに、EduCourse エンティティ クラスにはこの属性がありません! 正しい方法は、フロント エンドから渡されたデータをカプセル化する別の vo クラスを作成することです。

2. フォームから送信されたデータをデータベースに追加するときは、コース スケジュール テーブルとコース プロファイル テーブルの 2 つのテーブルにデータを追加する必要があります。

3.コースの基本情報の講師とカテゴリは、フロントエンドのドロップダウン リストを使用して実現できます。カテゴリは二次的な連携効果である必要があります (コース カテゴリが主カテゴリと二次カテゴリに分かれているため)カテゴリー)

3.3 voクラスの作成

1. vo クラス CourseInfoVo をentity–>vo パッケージの下に作成します。

@ApiModel(value = "课程基本信息", description = "编辑课程基本信息的表单对象")
@Data
public class CourseInfoVo {
    
    
    @ApiModelProperty(value = "课程ID")
    private String id;

    @ApiModelProperty(value = "课程讲师ID")
    private String teacherId;

    @ApiModelProperty(value = "课程专业ID")
    private String subjectId;

    @ApiModelProperty(value = "课程标题")
    private String title;

    @ApiModelProperty(value = "课程销售价格,设置为0则可免费观看")
    private BigDecimal price;

    @ApiModelProperty(value = "总课时")
    private Integer lessonNum;

    @ApiModelProperty(value = "课程封面图片路径")
    private String cover;

    @ApiModelProperty(value = "课程简介")
    private String description;
}

ここに画像の説明を挿入

  • Double または Float を使用して価格を表現する場合には精度の問題が発生する可能性がありますが、BigDecimal を使用する場合には精度の問題は発生しません。

2. 今回作成した vo クラスには subjectParentId 属性がありません。つまり、フロントエンドから渡されたデータ内の subjectParentId はエンティティ クラスにカプセル化されないため、エンティティ クラスにデータを挿入するときに subject_parent_id が与えられません。 edu_course テーブルのフィールド割り当てですが、edu_course テーブルの subject_parent_id フィールドは空であってはいけないため、データ テーブル edu_course の subject_parent_id フィールドを変更し、一時的にデフォルト値を設定する必要があります。

ここに画像の説明を挿入

3.4 制御層

コントローラー EduCourseController にコードを記述します。

@RestController
@RequestMapping("/eduservice/course")
@CrossOrigin
public class EduCourseController {
    
    

    @Autowired
    private EduCourseService courseService;

    //添加课程基本信息的方法
    @PostMapping("addCourseInfo")
    public R addCourseInfo(@RequestBody CourseInfoVo courseInfoVo) {
    
    
        courseService.saveCourseInfo(courseInfoVo);
        return R.ok();
    }
}

ここに画像の説明を挿入

3.5 ビジネス層

1. インターフェース EduCourseService で抽象メソッドを定義します。

public interface EduCourseService extends IService<EduCourse> {
    
    

    //添加课程基本信息的方法
    void saveCourseInfo(CourseInfoVo courseInfoVo);
}

ここに画像の説明を挿入

2. 実装クラス EduCourseServiceImpl に定義したばかりの抽象メソッドを実装します。

@Service
public class EduCourseServiceImpl extends ServiceImpl<EduCourseMapper, EduCourse> implements EduCourseService {
    
    

    //注入EduCourseDescriptionService
    @Autowired
    private EduCourseDescriptionService courseDescriptionService;

    //添加课程基本信息的方法
    @Override
    public void saveCourseInfo(CourseInfoVo courseInfoVo) {
    
    
        //1.向课程表添加课程基本信息
        //①将CourseInfoVo对象转换为EduCourse对象
        EduCourse eduCourse = new EduCourse();
        BeanUtils.copyProperties(courseInfoVo, eduCourse);
        //②执行课程模块的持久层方法向课程表插入数据
        int insert = baseMapper.insert(eduCourse);
        //③判断是否插入成功
        if (insert <= 0) {
    
    
            //添加失败
            throw new GuliException(20001, "添加课程信息失败");
        }

        //2.向课程简介表添加课程简介
        //①将CourseInfoVo对象转换为courseDescription对象
        EduCourseDescription courseDescription = new EduCourseDescription();
        BeanUtils.copyProperties(courseInfoVo, courseDescription);
        //②设置课程简介id和刚刚插入的课程id相同,这样才可以一对一
        courseDescription.setId(eduCourse.getId());
        //③执行课程简介模块的业务层方法向课程简介表插入数据
        courseDescriptionService.save(courseDescription);
    }
}

ここに画像の説明を挿入

  • 忘れてしまった人は、BeanUtils.copyProperties(courseInfoVo, eduCourse);baseMapper.insert(eduCourse);demo07-コース分類管理」の「7.4.2 ビジネス層実装クラス」の下部に記載した 2 つの段落を戻って読んでください。

  • EduCourseDescriptionService を挿入する理由: 後でコース導入モジュールのビジネス層メソッドを使用するためです (スクリーンショットの 52 行目courseDescriptionService.save(courseDescription);:)

  • では、なぜここで EduCourseDescriptionService をインジェクションできるのかというと、 EduCourseServiceImpl と EduCourseDescriptionServiceImpl は両方とも Spring 管理に渡されるため、インジェクションが実現できるからです (分からない場合は、「demo07-Course Classification」の「4.7.2 遭遇した問題」を参照してください) 「管理」のステップ 1

  • スクリーンショットの 50 行目ではeduCourse.getId()、挿入したばかりのコース ID を取得できます。これは、何度も述べたように、ID が挿入操作 (スクリーンショットの 38 行目) の後にeduCourse オブジェクトに自動的にバックフィルされるためです。忘れた場合は、もう一度行く 「demo02-MybatisPlus」の「2.9 追加操作」を参照

3. コース プロファイル テーブルにデータを挿入するときに、データ ID が自分で設定されることがわかっているため、EduCourseDescription エンティティ クラスの ID 生成戦略を変更する必要があります。

IdType.ID_WORKER_STRに変更されますIdType.INPUT

ここに画像の説明を挿入

3.6 テスト

1.service_edu プロジェクトを開始し、http://localhost:8001/swagger-ui.htmにアクセスします。

2. データを入力し、「Try it out!」をクリックしてテストします。

{
  "cover": "string",
  "description": "string0821",
  "lessonNum": 0,
  "price": 0,
  "subjectId": "string",
  "teacherId": "string",
  "title": "string0821"
}

ここに画像の説明を挿入

3. データベースに移動してデータを表示すると、テストが成功したことがわかります。

ここに画像の説明を挿入

ここに画像の説明を挿入

4. フロントエンドにコースの基本情報を追加する

4.1 コース管理ルートの追加

src–>router–>index.js にルーティングを追加します。

{
    
    
  path: '/course',
  component: Layout,
  redirect: '/course/list',
  name: '课程管理',
  meta: {
    
     title: '课程管理', icon: 'example' },
  children: [
    {
    
    
      path: 'list',
      name: '课程列表',
      component: () => import('@/views/edu/course/list'),
      meta: {
    
     title: '课程列表', icon: 'table' }
    },
    {
    
    
      path: 'info',
      name: '添加课程',
      component: () => import('@/views/edu/course/info'),
      meta: {
    
     title: '添加课程', icon: 'tree' }
    },
    {
    
    
      path: 'info/:id',
      name: 'EduCourseInfoEdit',
      component: () => import('@/views/edu/course/info'),
      meta: {
    
     title: '编辑课程基本信息', noCache: true },
      hidden: true
    },
    {
    
    
      path: 'chapter/:id',
      name: 'EduCourseChapterEdit',
      component: () => import('@/views/edu/course/chapter'),
      meta: {
    
     title: '编辑课程大纲', noCache: true },
      hidden: true
    },
    {
    
    
      path: 'publish/:id',
      name: 'EduCoursePublishEdit',
      component: () => import('@/views/edu/course/publish'),
      meta: {
    
     title: '发布课程', noCache: true },
      hidden: true
    }
  ]
},

ここに画像の説明を挿入

4.2 vue ページの作成

前のステップでルートに追加したパスに従って、src–>views–>edu ディレクトリの下にディレクトリ course を作成し、次に course ディレクトリの下にディレクトリを作成します。

1. 3 つの vue ページ、info.vue、chapter.vue、publish.vue を作成します。これら 3 つのページの役割: 「1. コースの公開プロセス」で、コースを追加するには 3 つのステップがあると述べました。コースの基本情報の編集 -> コース概要の編集 -> 最終的にコースが公開され、作成した 3 つの vue ページは、それぞれこれら 3 つのステップに対応しています

2. コースリストページとして list.vue ページを作成します

ここに画像の説明を挿入

4.3 info.vue ページにステップバーを追加する

通常の状況では、element-ui 内の適切な例のコードを info.vue にコピーし、必要に応じて変更します。ここでは先生が直接コードを教えてくれました

<template>

  <div class="app-container">

    <h2 style="text-align: center;">发布新课程</h2>

    <el-steps :active="1" process-status="wait" align-center style="margin-bottom: 40px;">
      <el-step title="填写课程基本信息"/>
      <el-step title="创建课程大纲"/>
      <el-step title="提交审核"/>
    </el-steps>

    <el-form label-width="120px">

      <el-form-item>
        <el-button :disabled="saveBtnDisabled" type="primary" @click="next">保存并下一步</el-button>
      </el-form-item>
    </el-form>
  </div>
</template>
<script>
export default {
  data() {
    return {
      saveBtnDisabled: false //保存按钮是否禁用
    }
  },
  created() {
  },
  methods: {
    next() {
      //跳转到第二步
      this.$router.push({path: '/course/chapter/1'})
    }
  }
}
</script>

ここに画像の説明を挿入

  • 「保存して次へ」をクリックすると next メソッドが呼び出され、内部的に 2 番目のステップにジャンプします。
  • スクリーンショットの33行目のコースIDはthis.$router.push({path: '/course/chapter/1'})一時的に1に固定されています。

4.4 ステップバーをchapter.vueページに追加する

<template>

  <div class="app-container">

    <h2 style="text-align: center;">发布新课程</h2>

    <el-steps :active="2" process-status="wait" align-center style="margin-bottom: 40px;">
      <el-step title="填写课程基本信息"/>
      <el-step title="创建课程大纲"/>
      <el-step title="最终发布"/>
    </el-steps>

    <el-form label-width="120px">

      <el-form-item>
        <el-button @click="previous">上一步</el-button>
        <el-button :disabled="saveBtnDisabled" type="primary" @click="next">下一步</el-button>
      </el-form-item>
    </el-form>
  </div>
</template>
<script>
export default {
    data() {
        return {
            saveBtnDisabled: false
        }
    },
    created() {
    },
    methods: {
        //跳转到第一步
        previous() {
            this.$router.push({path: '/course/info/1'})
        },
        //跳转到第三步
        next() {
            this.$router.push({path: '/course/publish/1'})
        }
    }
}
</script>

ここに画像の説明を挿入

同様に、コース ID は一時的に 1 に固定されます。

4.5 ステップバーをpublish.vueページに追加する

<template>

  <div class="app-container">

    <h2 style="text-align: center;">发布新课程</h2>

    <el-steps :active="3" process-status="wait" align-center style="margin-bottom: 40px;">
      <el-step title="填写课程基本信息"/>
      <el-step title="创建课程大纲"/>
      <el-step title="提交审核"/>
    </el-steps>

    <el-form label-width="120px">

      <el-form-item>
        <el-button @click="previous">返回修改</el-button>
        <el-button :disabled="saveBtnDisabled" type="primary" @click="publish">发布课程</el-button>
      </el-form-item>
    </el-form>
  </div>
</template>
<script>
export default {
    data() {
        return {
            saveBtnDisabled: false
        }
    },
    created() {
    },
    methods: {
        //跳转到第二步
        previous() {
            this.$router.push({path: '/course/chapter/1'})
        },
        //跳转到list.vue页面
        publish() {
            this.$router.push({path: '/course/list'})
        }
    }
}
</script>

ここに画像の説明を挿入

同様に、2 番目のステップにジャンプするとき、パス内のコース ID は一時的に 1 に固定されます。

4.6 info.vueを再度書き込む

4.6.1 ページにフォームコンポーネントを追加する

info.vueの<el-form>タグ(「4.3 info.vueページにステップバーを追加する」のスクリーンショットの緑色の枠で囲った部分)を削除し、以下のコードに置き換えます。

<el-form label-width="120px">
  <el-form-item label="课程标题">
    <el-input v-model="courseInfo.title" placeholder=" 示例:机器学习项目课:从基础到搭建项目视频课程。专业名称注意大小写"/>
  </el-form-item>

  <!-- 所属分类 TODO -->

  <!-- 课程讲师 TODO -->

  <el-form-item label="总课时">
    <el-input-number :min="0" v-model="courseInfo.lessonNum" controls-position="right" placeholder="请填写课程的总课时数"/>
  </el-form-item>

  <el-form-item label="课程简介">
    <el-input v-model="courseInfo.description" placeholder=""/>
  </el-form-item>

  <!-- 课程封面 TODO -->

  <el-form-item label="课程价格">
    <el-input-number :min="0" v-model="courseInfo.price" controls-position="right" placeholder="免费课程请设置为0元"/> 元
  </el-form-item>

  <el-form-item>
    <el-button :disabled="saveBtnDisabled" type="primary" @click="next">保存并下一步</el-button>
  </el-form-item>
</el-form>

ここに画像の説明を挿入

4.6.2 データモデルの定義

courseInfo オブジェクトは、前の手順で info.vue に記述されたコードで使用されているため、data() {...}その中にデータ モデル courseInfo を定義する必要があります。このオブジェクトのプロパティを定義するかどうかは関係ありません (要素 ui に移動します)。サンプル コードを見つけるには」という手順 2) のスクリーンショットを参照してください。ここで、 courseInfo オブジェクトのプロパティを定義します。

courseInfo: {
    
    
  title: '',
  subjectId: '',
  teacherId: '',
  lessonNum: 0,
  description: '',
  cover: '',
  price: 0
}

ここに画像の説明を挿入

4.7 API でのメソッドの定義

src–>api–>edu ディレクトリに course.js ファイルを作成し、メソッドを記述します

import request from '@/utils/request'

export default {
    
    
  //1.添加课程基本信息
  addCourseInfo(courseInfo) {
    
    
    return request({
    
    
      url: `/eduservice/course/addCourseInfo`,
      method: 'post',
      data: courseInfo
    })
  }
}

ここに画像の説明を挿入

4.8 info.vueを再度書き込む

4.8.1 jsファイルをインポートする

前の手順で作成した js ファイル内のメソッドを使用したいので、前の手順で作成した js ファイルをインポートする必要があります

import course from '@/api/edu/course'

ここに画像の説明を挿入

4.8.2 関数名の変更

クリック イベントの関数の名前を saveOrUpdate に変更します。

理由: このページは src–>views–>edu–>Teacher–>save.vue ページと同じであり、追加機能と変更機能の両方があるため、このページのボタンにも追加と変更の 2 つの機能があります。これは名前がわかるという原理を見て、関数名をsaveOrUpdateに変更します。

ここに画像の説明を挿入

4.8.3 関数saveOrUpdateの内容を変更する

saveOrUpdate() {
    
    
  course.addCourseInfo(this.courseInfo)
    .then(response => {
    
    
      //1.提示成功
      this.$message({
    
    
          type: 'success',
          message: '添加课程基本信息成功'
      })
      //2.跳转到第二步
      this.$router.push({
    
    path: '/course/chapter/1'})
    })
}

ここに画像の説明を挿入

4.9 テスト

1.service_edu、nginx、フロントエンドプロジェクトを開始し、http://localhost:9528にアクセスします。

2. 「コースの追加」ルートの下のページに情報を入力し、「保存して次へ」をクリックします。

ここに画像の説明を挿入

3. テストが成功したことがわかります

ここに画像の説明を挿入

ここに画像の説明を挿入

ここに画像の説明を挿入

ただし、最初のスクリーンショットの path パラメータが 1 に固定されていることに注意してください。これは明らかに不合理です。この問題は、次の「4.10 フロントエンドにコース ID を追加させる」で解決します。

4.10 追加後にフロントエンドがコース ID を取得できるようにする

1. 「4.3 info.vue ページにステップバーを追加する」のスクリーンショットの 33 行目: はthis.$router.push({path: '/course/chapter/1'})、コースの基本情報が正常に追加された後、ルートがジャンプするときにコース ID が 1 に固定されることを意味します。この現象を解決するフロントエンド追加後にコースIDが取得できることが前提

2.フロントエンドの追加後にコース ID を取得したい場合は、バックエンドがコース ID を返すように、最初にバックエンド コードを変更する必要があります。

①コントロール層:

courseService.saveCourseInfo(courseInfoVo);に変更されますString id = courseService.saveCourseInfo(courseInfoVo);

return R.ok();に変更されますreturn R.ok().data("courseId", id);

ここに画像の説明を挿入

②ビジネス層:

インターフェイスの saveCourseInfo メソッドの戻り値を void から String に変更します。

ここに画像の説明を挿入

実装クラスの saveCourseInfo メソッドの戻り値を void から String に変更し、メソッドの最後に戻り値のコード行を追加します。

ここに画像の説明を挿入

3. info.vue ページの saveOrUpdate メソッドの this.$router.push({path: '/course/chapter/1'}) を this.$router.push({path: '/course/chapter/) に変更します。 ' + 応答.データ.コースId})

ここに画像の説明を挿入

4. 再度テストします。

バックエンドで service_edu プロジェクトを再起動し、http://localhost:9528にアクセスして、「コースの追加」ルートの下のページに情報を入力して「保存して次へ」をクリックすると、パス パラメータが設定されていることがわかります。アドレスバーが追加されたコースIDに変更されました

ここに画像の説明を挿入

4.11 講師ドロップダウンリストを表示

4.11.1 ドロップダウン リスト コンポーネントを info.vue に追加する

1. element-ui のコンポーネントに移動してドロップダウン リストの例を見つけ、コードをコピーし、要件に従って変更します。

ここに画像の説明を挿入

2. 先生が私たちにコードを教えてくれました

<!-- 课程讲师 -->
<el-form-item label="课程讲师">
  <el-select
    v-model="courseInfo.teacherId"
    placeholder="请选择">
    <el-option
      v-for="teacher in teacherList"
      :key="teacher.id"
      :label="teacher.name"
      :value="teacher.id"/>
  </el-select>
</el-form-item>

ここに画像の説明を挿入

スクリーンショットの行 24v-model="courseInfo.teacherId"と行 30を組み合わせた効果:value="teacher.id"は、フォーム データの送信時に、courseInfo オブジェクトの TeacherId 属性に講師 ID を割り当てることです。

3. 前のスクリーンショットの 27 行目の TeacherList は講師データであるため、モデル データで TeacherList を定義する必要があります。

ここに画像の説明を挿入

4.11.2 API でのメソッドの定義

1. まず第一に、講師のドロップダウン リストはページに表示されず、表示できないことを知っておく必要があります。そのため、バックエンドでは「クエリ」メソッドの代わりに「すべての講師をクエリ」メソッドが呼び出されます。すべての講師がページに表示されます。」注: 「demo03-バックグラウンド講師管理モジュール」の「10.4 カスタム例外処理」の3番目のステップ「全講師をクエリする」メソッドに意図的に例外を追加しましたが、今回は削除します。

ここに画像の説明を挿入

2. src–>api–>edu–>course.js でメソッドを定義します。

//2.查询所有讲师
getListTeacher() {
    
    
  return request({
    
    
    url: `/eduservice/edu-teacher/findAll`,
    method: 'get'
  })
}

ここに画像の説明を挿入

4.11.3 API でメソッドを呼び出す

info.vuemethods: {...}の API で定義したばかりのメソッドを呼び出します。

//查询所有讲师
getListTeacher() {
    
    
  course.getListTeacher()
    .then(response => {
    
    
      this.teacherList = response.data.items
    })
},

ここに画像の説明を挿入

4.11.4 すべての講師を初期化する

created() {...}前の手順で定義した getListTeacher メソッドを呼び出して、すべての教師を初期化します

created() {
    
    
  //初始化所有讲师
  this.getListTeacher()
},

ここに画像の説明を挿入

4.11.5 テスト

1. http://localhost:9528/にアクセスし、「コースの追加」ルートをクリックすると、ドロップダウン リストが正常に表示されることがわかります。

ここに画像の説明を挿入

2. 情報を入力したら、「保存して次へ」をクリックします。

ここに画像の説明を挿入

3. テストは成功しました

ここに画像の説明を挿入

ここに画像の説明を挿入

ここに画像の説明を挿入

4.12 コースの一次分類を表示する

4.12.1 分析

info.vue ページに入ると、第 1 レベルのカテゴリがすべて表示され、第 2 レベルのカテゴリは空になります。第 1 レベルのカテゴリを選択すると、第 1 レベルのカテゴリの下にあるすべての第 2 レベルのカテゴリが表示されます。表示されます。

4.12.2 第 1 レベルの分類のドロップダウン リスト コンポーネントを追加する

1. info.vue に第 1 レベル分類のドロップダウン リスト コンポーネントを追加します。

<el-form-item label="课程分类">
  <!-- 一级分类 -->
  <el-select
    v-model="courseInfo.subjectParentId"
    placeholder="一级分类">
    <el-option
      v-for="subject in subjectOneList"
      :key="subject.id"
      :label="subject.title"
      :value="subject.id"/>
  </el-select>
</el-form-item>

ここに画像の説明を挿入

2. 前のスクリーンショットの 22 行目と 25 行目は、それぞれcourseInfo.subjectParentIdデータsubjectOneListモデルとデータ モデルを使用しているので、これら 2 つのデータ モデルを定義しましょう。

ここに画像の説明を挿入

しかし、実際には、 courseInfo オブジェクトで subjectParentId 属性を定義することができます。「demo05-lecturer Management フロント エンド」の「7.1 element-ui に移動してサンプル コードを見つける」のステップ 2 のスクリーンショットで述べました。双方向データ バインディング (前のスクリーンショットの 22 行目v-model="courseInfo.subjectParentId") なので、オブジェクト内のプロパティが定義されているかどうかは関係ありません。

3. Vo クラス CourseInfoVo に属性を追加します。

基本的なコース情報を追加する場合、フロントエンドは API の addCourseInfo メソッドを呼び出すことで実装されます (このメソッドは src–>api–>edu–>course.js にあります)。これを呼び出すときにメソッドにパラメータが渡されることがわかります。 Vue のデータ モデル courseInfo オブジェクトでは、前の手順でデータ モデルに subjectParentId 属性を定義しましたが、この場合、フロントエンド データをカプセル化するためにバックエンドによって使用される vo クラス CourseInfoVo には属性 subjectParentId が必要です。しかし、私たちは「3.3 vo クラスを作成するときに、クラス CourseInfoVo に subjectParentId 属性を追加するのを忘れました。これはあまりにも致命的です。この属性をクラス CourseInfoVo にすぐに追加しましょう。

ここに画像の説明を挿入

4.12.3 API でのメソッドの定義

バックエンドのインターフェースに「コースの第1段階分類を問い合わせる」メソッドをAPIに定義する必要があります。この手順は「demo07-コース分類管理」の「8.1 APIメソッドの定義」ですでに行っています。 "。src–>api ですでに実行しています。このメソッドは –>edu–>subject.js で定義されています。

ここに画像の説明を挿入

4.12.4 API でメソッドを呼び出す

1. subject.js のメソッドを呼び出したい場合は、最初にこのファイルをインポートする必要があります

import subject from '@/api/edu/subject'

ここに画像の説明を挿入

2. 次に、API で getSubjectList メソッドを呼び出すメソッドを定義します。

//查询所有的一级分类
getOneSubject() {
    
    
  subject.getSubjectList()
    .then(response => {
    
    
      this.subjectOneList = response.data.list
    })
},

ここに画像の説明を挿入

4.12.5 すべての第 1 レベルのカテゴリを初期化する

created() {...}前の手順で定義した getOneSubject メソッドを呼び出して、すべての第 1 レベルの分類を初期化します

//初始化所有一级分类
this.getOneSubject()

ここに画像の説明を挿入

4.12.6 テスト

「コースを追加」ルートをクリックして、テストが成功したことを確認します。

ここに画像の説明を挿入

4.13 コースの二次分類を表示する

4.13.1 二次分類のドロップダウン リスト コンポーネントを追加する

1. info.vue に二次分類のドロップダウン リスト コンポーネントを追加します。

<!-- 二级分类 -->
<el-select
  v-model="courseInfo.subjectId"
  placeholder="二级分类">
  <el-option
    v-for="subject in subjectTwoList"
    :key="subject.id"
    :label="subject.title"
    :value="subject.id"/>
</el-select>

ここに画像の説明を挿入

2. 前のスクリーンショットの 36 行目ではsubjectTwoListデータ モデルを使用しています。このデータ モデルを定義しましょう。

ここに画像の説明を挿入

4.13.2 イベントを第 1 レベルのカテゴリ ドロップダウン ボックスにバインドする

「4.12.1 分析」で述べたように、第 1 レベルの分類を選択すると、第 1 レベルの分類の下にあるすべての第 2 レベルの分類が表示されるのに、なぜ第 1 レベルの分類を選択したのでしょうか。レベル カテゴリのドロップダウン ボックスは onclick と同様のイベントにバインドされていますが、onclick イベントはボタンにバインドされており、ドロップダウン リストをバインドするには onchange イベントが必要です

ここに画像の説明を挿入

4.13.3 変更イベントメソッドの定義

1. 前のスクリーンショットから、変更イベントがトリガーされた後に呼び出されるメソッドは subjectLevelOneChanged であることがわかります。そのため、このメソッドを以下に記述します。メソッドは内部に実装できる必要があります。特定の第 1 レベルの分類をクリックした後、第 1 レベルの分類を表示できます。

2. 偽の実装方法:

まず第一に、第 1 レベルのカテゴリを選択した後、第 1 レベルのカテゴリの ID を取得することがわかります(「4.12.2 第 1 レベルのカテゴリのドロップダウン リスト コンポーネントの追加」のスクリーンショットの 22 行目) " v-model="courseInfo.subjectParentId")。次に、①バックエンドにインターフェイスを作成します。その機能は、第 1 レベルの分類の ID を通じて第 1 レベルの分類にあるすべての 2 次カテゴリをクエリして返します。次に、フロントエンド API でメソッドを定義します。バックエンドでこのインターフェイスを呼び出します。③ 最後に、subjectLevelOneChanged メソッドで API を呼び出し、プライマリ カテゴリの下にあるすべてのセカンダリ カテゴリを取得します。ただし、これはお勧めできません。これを行うと、バックエンドに別のインターフェイスを作成する必要があるだけでなく、データベースにクエリを実行する必要があるからです。

3. 実際の実装方法:

①subject.jsで定義したAPIはバックエンドのgetAllSubjectインターフェースを呼び出します。このインターフェイスによって返されるデータは List<OneSubject> であり、OneSubject エンティティ クラスの属性は型 id、title、および List<TwoSubject> の子であり、TwoSubject エンティティ クラスの属性は id と title です。したがって、subject.js で getSubjectList メソッドを呼び出した後に取得するデータには、第 1 レベルの分類の ID とタイトルだけでなく、第 2 レベルの分類の ID とタイトルも含まれています。

ここに画像の説明を挿入

ここに画像の説明を挿入

ここに画像の説明を挿入

ここに画像の説明を挿入

②つまり、subjectLevelOneChanged メソッド内を走査してバックエンドからデータを取得し、2 次分類を取得できます。この方法では、バックエンドでインターフェイスを作成したり、データベースにクエリを実行したりする必要はありません。

//点击某个一级分类后,能显示该一级分类下的所有二级分类
subjectLevelOneChanged(value) {
    
     //value是传过来的一级分类id
  //遍历所有分类,包含一级和二级
  for(var i = 0;i < this.subjectOneList.length;i++) {
    
    
    //遍历得到的一级分类
    var oneSubject = this.subjectOneList[i]
    //判断遍历得到的一级分类id和我们点击的一级分类id是否一样
    if(oneSubject.id == value)
      //获取该一级分类下所有的二级分类
      this.subjectTwoList = oneSubject.children
  }
},

ここに画像の説明を挿入

  • スクリーンショットの109 行目this.subjectOneList.lengthと 113 行目oneSubject.id: subjectOneList を使用する場合はこれを追加する必要があるのに、oneSubject を使用する場合はこれを追加する必要がないのはなぜですか?

    • subjectOneList はデータ モデル ( ) でdata() {...}定義されており、oneSubject はメソッド subjectLevelOneChanged 内で定義されているため、
  • 定義された subjectLevelOneChanged メソッドにはパラメーターがあります。このメソッドを呼び出すときに、第 1 レベルの分類の ID をパラメーターとして渡す必要があるのは当然ですが、イベントをバインドするときに手動でパラメーターを subjectLevelOneChanged メソッドに渡しませんでした (" 4.13. 2. 「イベントを第 1 レベルの分類ドロップダウン ボックスにバインドする」のスクリーンショットの 23 行目: ) @change="subjectLevelOneChanged"、これはフレームワークによってカプセル化されているためです。ユーザーがsubjectLevelOneChanged メソッドを呼び出すと、最初のメソッドを渡すことができます。 -level 分類 ID 自体は手動で行う必要はありません。パラメータの受け渡し(「demo05-講師管理フロントエンド」の「6.3 getList メソッドの変更」にも記載されています)

4.13.4 テスト

ここに画像の説明を挿入

4.13.5 改善

1. 第 1 レベルの分類で「フロントエンド開発」を選択し、第 2 レベルの分類で「vue」を選択します

ここに画像の説明を挿入

2. 第 1 レベルの分類を「バックエンド開発」として再選択すると、第 2 レベルの分類の「vue」がまだ存在していることがわかります。

ここに画像の説明を挿入

ここに画像の説明を挿入

3. まずスクリーンショットを見てください。

ここに画像の説明を挿入

スクリーンショットの 33 行目からv-model="courseInfo.subjectId"、第 2 レベルの分類ドロップダウン リストとデータ モデルの courseInfo オブジェクトの subjectId 属性、そしてデータ モデルの courseInfo.subjectId の間で双方向バインディングが作成されていることがわかります。第一レベルの分類を再選択した後もデータが残っているため、上記の状況が発生します。次に、subjectLevelOneChanged メソッドにコード行を追加します。これにより、次のことが可能になります。第 1 レベルの分類を再選択した後、データ モデル内の courseInfo.subjectId データをクリアする必要があります。

//清空二级分类的id值
this.courseInfo.subjectId = ''

ここに画像の説明を挿入

4.14 表紙のアップロード

Alibaba Cloud oss へのファイルのアップロードはバックエンドが行う必要があることですが、すでに実装されているため、次にフロントエンドのコードを記述するだけで済みます。

4.14.1 アップロードコンポーネントの追加

1. info.vue にアップロードコンポーネントテンプレートを追加します

<!-- 课程封面-->
<el-form-item label="课程封面">
  <el-upload
    :show-file-list="false"
    :on-success="handleAvatarSuccess"
    :before-upload="beforeAvatarUpload"
    :action="BASE_API+'/eduoss/fileoss'"
    class="avatar-uploader">
    <img :src="courseInfo.cover">
  </el-upload>
</el-form-item>

ここに画像の説明を挿入

  • :show-file-list="false": このコードを使用すると、ファイルアップロードリストが表示されなくなります

ここに画像の説明を挿入

  • :on-success="handleAvatarSuccess": アップロードが成功した後に呼び出されるメソッド。このメソッドは通常、ファイルの URL を取得するために使用されます。
  • :before-upload="beforeAvatarUpload": アップロード前に呼び出されるメソッド。このメソッドは通常、メソッド内でファイルの種類とファイル サイズを指定するために使用されます。
  • :action="BASE_API+'/eduoss/fileoss'": バックエンドインターフェースアドレス
  • class="avatar-uploader":スタイル
  • 「demo07-コースカテゴリ管理」の「5.2 Vueページの作成」のステップ3のステップ①で述べたことをまだ覚えていますか:auto-upload="false"? このコード行の機能は、自動アップロードをオフにすることです。ここではアップロード コンポーネントにこのコード行を追加していないため、写真を選択すると、自動的にサーバーにアップロードされます。

2. 前のスクリーンショットの 72 行目ではBASE_APIデータ モデルが使用されています。このデータ モデルを定義します。

ここに画像の説明を挿入

赤枠で追加したコードの機能がわからない場合は、「demo06-講師アバターのアップロード」の「6.3 コンポーネントの使い方」へお進みください。

4.14.2 メソッドの定義

次に、アップロード前に呼び出される beforeAvatarUpload メソッドと、アップロードが成功した後に呼び出される handleAvatarSuccess メソッドを記述します。

//上传封面成功调用的方法
handleAvatarSuccess(response) {
    
     //response就是后端返回的数据
  this.courseInfo.cover = response.data.url
},
//上传封面之前调用的方法
beforeAvatarUpload(file) {
    
    
  const isJPG = file.type === 'image/jpeg'
  const isLt2M = file.size / 1024 / 1024 < 2
  if (!isJPG) {
    
    
    this.$message.error('上传头像图片只能是 JPG 格式!')
  }
  if (!isLt2M) {
    
    
    this.$message.error('上传头像图片大小不能超过 2MB!')
  }
  return isJPG && isLt2M
},

ここに画像の説明を挿入

4.14.3 テスト

1.service_edu、service_oss、フロントエンド プロジェクト、nginx を起動し、http://localhost:9528/にアクセスします。

2.「コースを追加」ルートをクリックします

ここに画像の説明を挿入

「4.14.1 アップロードコンポーネントの追加」で述べたように、今回追加したコンポーネントは自動的にアップロードされるため、ここでは空白になっています(教師)と言われましたが、因果関係はまだ分かりません)、ユーザーエクスペリエンスのためにデフォルトのカバーを追加できます

3. タイプが .jpg で、サイズが 2MB を超えない写真を見つけて、それを静的ディレクトリに置きます。

ここに画像の説明を挿入

4. デフォルトのカバーのパスをデータ モデルの courseInfo オブジェクトの cover 属性に割り当てます。

ここに画像の説明を挿入

5. ページを保存して更新すると、デフォルトの表紙が表示されます。

ここに画像の説明を挿入

6. デフォルトの表紙をクリックすると、コンピューター内の写真を表紙として選択できます。

7. 写真を選択して「開く」をクリックすると、表紙が自動的に Alibaba Cloud oss にアップロードされ、表示されます

ここに画像の説明を挿入

ここに画像の説明を挿入

8. Aliyun の oss コンソールに移動すると、このカバーが実際にアップロードされていることがわかります。

ここに画像の説明を挿入

4.14.4 1 つのキーで複数のプロジェクトを開始する

テスト中に 2 つの Spring Boot プロジェクト service_edu と service_oss を開始する必要があるだけでした。以前はプロジェクトをクリックしてプロジェクトを開始していましたが、今後はさらに多くのプロジェクトを開始する予定です。プロジェクトをクリックするだけでは開始できませんこの状況の解決策は、ワンクリックで複数のプロジェクトを開始できるように設定することです。

1. ワンクリックで開始する SpringBoot プロジェクトがこの時点で実行されていることを確認します。

ここに画像の説明を挿入

2. [表示] –> [ツール ウィンドウ] –> [サービス] をクリックします。

ここに画像の説明を挿入

3. まずプラス記号 (「+」) をクリックし、次に「実行構成タイプ」をクリックします。

ここに画像の説明を挿入

4.「Spring Boot」を見つけてクリックします

ここに画像の説明を挿入

5. ワンクリックで開始したいプロジェクトを選択し (複数選択するには Ctrl または Shift を押します)、右クリックして [グループ構成...] を選択します。

ここに画像の説明を挿入

6. 名前を選択し、「OK」をクリックします。

ここに画像の説明を挿入

7. 設定が完了したので、テストしてみましょう。

①まずは全てのプロジェクトを停止する

ここに画像の説明を挿入

②「サービス」をクリックします

ここに画像の説明を挿入

③ポップアップインターフェイスで、mxy(手順6で付けた名前)を右クリックし、「実行」を選択すると、ワンクリックで複数のプロジェクトを開始できます。

ここに画像の説明を挿入

4.15 テキストエディタの統合

コース導入部分はもともと非常にシンプルな入力ボックスでしたが、今回はリッチテキストエディターを統合しました(統合プロセスを忘れないでください。このプロセスは将来フロントエンドに引き継がれます。このプロセスはvue プラグインの開発プロセス)

4.15.1 コンポーネントの初期化 - テキストエディタコンポーネントの複製

1. データ内の Tinymce フォルダーをフロントエンド プロジェクトのコンポーネント ディレクトリにコピーします。

ここに画像の説明を挿入

ここに画像の説明を挿入

2. データ内の tinymce4.7.5 フォルダーをフロントエンド プロジェクトの静的ディレクトリにコピーします

ここに画像の説明を挿入

ここに画像の説明を挿入

4.15.2 コンポーネントの初期化 - HTML 変数の構成

build–>webpack.dev.conf.js に設定を追加します

templateParameters: {
    
    
  BASE_URL: config.dev.assetsPublicPath + config.dev.assetsSubDirectory
}

ここに画像の説明を挿入

4.15.3 コンポーネントの初期化 - js スクリプトのインポート

Index.htmlにjsスクリプトを導入する

<script src=<%= BASE_URL %>/tinymce4.7.5/tinymce.min.js></script>
<script src=<%= BASE_URL %>/tinymce4.7.5/langs/zh_CN.js></script>

ここに画像の説明を挿入

  • これら 2 行のコードは、index.html ファイル内のどこにでも配置できます。
  • 保存すると人気になります(スクリーンショットの緑色の枠で囲った部分)。これは気にしません、動作には影響しません、これはフレームワーク自体の問題です

4.15.4 コンポーネントの使用 – コンポーネントのインポートと宣言

1. info.vue にコンポーネントを導入する

import Tinymce from '@/components/Tinymce'

ここに画像の説明を挿入

2. コンポーネントの宣言

components: {
    
     Tinymce }, //声明组件

ここに画像の説明を挿入

4.15.5 コンポーネントの使用 – リッチ テキスト エディター コンポーネントの追加

1.スクリーンショットの赤枠で囲った部分を削除します

ここに画像の説明を挿入

2. 以下のコードを前の手順で削除した場所にコピーします。

<!-- 课程简介-->
<el-form-item label="课程简介">
    <tinymce :height="300" v-model="courseInfo.description"/>
</el-form-item>

ここに画像の説明を挿入

4.15.6 コンポーネントの使用 – コンポーネント スタイル

スタイルはケーキをより美しくするための飾りです。もちろん、スタイルを使用する必要はありません

info.vue ファイルの末尾に次のコードを追加して、画像アップロード ボタンの高さを調整します。

<style scoped>
.tinymce-container {
  line-height: 29px;
}
</style>

ここに画像の説明を挿入

スクリーンショットの行 181 は、scopedこのスタイルが現在のページでのみ有効であることを示しています

4.15.7 テスト

1. 構成ファイルを変更したため、フロントエンド プロジェクトを再起動する必要があります

ここに画像の説明を挿入

2. 基本情報を入力して最終テストを行います。プロフィールを入力するときに、「写真をアップロード」をクリックしてプロフィールに写真を挿入できます。

ここに画像の説明を挿入

3. このデータが正常に追加されたことがわかります。

ここに画像の説明を挿入

ここに画像の説明を挿入

4. edu_course_description テーブルに挿入したデータの description フィールド (コースの紹介を格納する) の値をコピーして学習します。

ここに画像の説明を挿入

img タグの属性はsrc="data:image/jpeg;base64,iVBORw0KG...写真のアドレスです。講師のプロフィール写真やコースの表紙を保存する場合、その写真を Alibaba Cloud oss に保存し、その写真の URL をデータベースに保存しますが、リッチ テキスト エディターは画像ファイルをデータベースに直接保存します。画像サーバーは必要ありませんが、最初にファイルと画像を Base64 でエンコードし、次に Base64 コードをデータベースに保存します。

5. もう 1 つ、edu_course_description テーブルの説明フィールドはテキスト型であり、より多くのデータを保存するのに適していますが、無限に大きくないため、大きすぎる画像を保存することはできません。そうしないと収まりません。

ここに画像の説明を挿入

おすすめ

転載: blog.csdn.net/maxiangyu_/article/details/127026589