Просмотр исходного кода

Merge branch 'dev' of http://git.izouma.com/licailing/wenlvju into dev

panhui 5 лет назад
Родитель
Сommit
2562f4dba8
29 измененных файлов с 1051 добавлено и 55 удалено
  1. 34 7
      src/main/h5/src/views/inspector/addRecord.vue
  2. 5 1
      src/main/java/com/izouma/wenlvju/domain/PerformanceApply.java
  3. 22 0
      src/main/java/com/izouma/wenlvju/domain/Video.java
  4. 2 0
      src/main/java/com/izouma/wenlvju/repo/PerformanceApplyRepo.java
  5. 19 3
      src/main/java/com/izouma/wenlvju/service/PerformanceApplyService.java
  6. 16 0
      src/main/java/com/izouma/wenlvju/service/UserService.java
  7. 1 1
      src/main/java/com/izouma/wenlvju/web/MenuController.java
  8. 6 0
      src/main/java/com/izouma/wenlvju/web/PerformanceApplyController.java
  9. 3 1
      src/main/java/com/izouma/wenlvju/web/RegulatoryController.java
  10. 6 1
      src/main/java/com/izouma/wenlvju/web/UserController.java
  11. BIN
      src/main/vue/src/assets/arrangement.png
  12. BIN
      src/main/vue/src/assets/code.png
  13. 24 0
      src/main/vue/src/router.js
  14. 174 0
      src/main/vue/src/views/ExpertList.vue
  15. 7 2
      src/main/vue/src/views/PerformanceApplyList.vue
  16. 54 4
      src/main/vue/src/views/PerformanceApplyList2.vue
  17. 4 1
      src/main/vue/src/views/PerformanceList.vue
  18. 20 4
      src/main/vue/src/views/RateList.vue
  19. 3 2
      src/main/vue/src/views/RecordDistrictList.vue
  20. 271 0
      src/main/vue/src/views/RecordDistrictList2.vue
  21. 1 0
      src/main/vue/src/views/RecordList.vue
  22. 1 0
      src/main/vue/src/views/RecordList2.vue
  23. 1 0
      src/main/vue/src/views/RegulatoryEdit.vue
  24. 138 12
      src/main/vue/src/views/RegulatoryList.vue
  25. 16 16
      src/main/vue/src/views/Video.vue
  26. 55 0
      src/main/vue/src/widgets/Video1.vue
  27. 56 0
      src/main/vue/src/widgets/Video2.vue
  28. 56 0
      src/main/vue/src/widgets/Video3.vue
  29. 56 0
      src/main/vue/src/widgets/Video4.vue

+ 34 - 7
src/main/h5/src/views/inspector/addRecord.vue

@@ -115,7 +115,34 @@
           </van-radio-group>
         </template>
       </van-field>
-      <van-field name="isExaminer" label="5.是否有无相关专业考官且佩戴考官证?">
+      <van-field name="isSameAddress" label="5.考试地点与备案考试地点是否一致?">
+        <template #input>
+          <van-radio-group
+            :disabled="!!formData.id"
+            v-model="formData.isSameAddress"
+            direction="horizontal"
+          >
+            <van-radio :name="true">
+              是
+              <template #icon="props">
+                <img
+                  class="img-icon"
+                  :src="props.checked ? activeIcon : inactiveIcon"
+                />
+              </template>
+            </van-radio>
+            <van-radio class="no" :name="false"
+              >否
+              <template #icon="props">
+                <img
+                  class="img-icon"
+                  :src="props.checked ? activeIcon2 : inactiveIcon"
+                /> </template
+            ></van-radio>
+          </van-radio-group>
+        </template>
+      </van-field>
+      <van-field name="isExaminer" label="6.是否有无相关专业考官且佩戴考官证?">
         <template #input>
           <van-radio-group
             :disabled="!!formData.id"
@@ -142,7 +169,7 @@
           </van-radio-group>
         </template>
       </van-field>
-      <van-field name="isRate" label="6.是否现场对艺术水平做出评定?">
+      <van-field name="isRate" label="7.是否现场对艺术水平做出评定?">
         <template #input>
           <van-radio-group
             :disabled="!!formData.id"
@@ -171,7 +198,7 @@
       </van-field>
       <van-field
         name="isSureContent"
-        label="7.是否是所属考级机构教材确定的考级内容?"
+        label="8.是否是所属考级机构教材确定的考级内容?"
       >
         <template #input>
           <van-radio-group
@@ -201,7 +228,7 @@
       </van-field>
       <van-field
         name="isPostPoster"
-        label="8.是否在明显位置张贴《疫情防控指南》海报?"
+        label="9.是否在明显位置张贴《疫情防控指南》海报?"
       >
         <template #input>
           <van-radio-group
@@ -232,7 +259,7 @@
 
       <van-field
         name="isHaveThermometer"
-        label="9.考点是否配备测量体温设备,且专人值守?"
+        label="10.考点是否配备测量体温设备,且专人值守?"
       >
         <template #input>
           <van-radio-group
@@ -260,7 +287,7 @@
           </van-radio-group>
         </template>
       </van-field>
-      <van-field name="isSchedule" label="10.考场是否实施预约限流措施?">
+      <van-field name="isSchedule" label="11.考场是否实施预约限流措施?">
         <template #input>
           <van-radio-group
             :disabled="!!formData.id"
@@ -291,7 +318,7 @@
         v-model="formData.other"
         rows="2"
         autosize
-        label="其他"
+        label="12.其他"
         type="textarea"
         maxlength="200"
         placeholder="请输入备注信息…"

+ 5 - 1
src/main/java/com/izouma/wenlvju/domain/PerformanceApply.java

@@ -37,5 +37,9 @@ public class PerformanceApply extends BaseEntity {
     @ApiModelProperty(value = "状态")
     private ApplyStatus   status;
     @ApiModelProperty(value = "表演时间")
-    public  LocalDateTime showTime;
+    private LocalDateTime showTime;
+    @ApiModelProperty(value = "奖项")
+    private String        awards;
+    private Integer       score;
+
 }

+ 22 - 0
src/main/java/com/izouma/wenlvju/domain/Video.java

@@ -0,0 +1,22 @@
+package com.izouma.wenlvju.domain;
+
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import javax.persistence.Entity;
+
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+@Builder
+@Entity
+public class Video extends BaseEntity {
+    private Long regulatoryId;
+
+    private Long url;
+
+    private Long name;
+}

+ 2 - 0
src/main/java/com/izouma/wenlvju/repo/PerformanceApplyRepo.java

@@ -17,4 +17,6 @@ public interface PerformanceApplyRepo extends JpaRepository<PerformanceApply, Lo
     void softDelete(Long id);
 
     List<PerformanceApply> findAllByStatusAndPerformanceId(ApplyStatus status, Long performanceId);
+
+    List<PerformanceApply> findAllByShowTimeIsNotNullAndPerformanceId(Long performanceId);
 }

+ 19 - 3
src/main/java/com/izouma/wenlvju/service/PerformanceApplyService.java

@@ -47,11 +47,11 @@ public class PerformanceApplyService {
                 performanceApplyRepo.findAllByStatusAndPerformanceId(ApplyStatus.PASS, performanceId);
 
         List<PerformanceApply> showTime = applyList.stream()
-                .filter(apply -> apply.showTime != null)
-                .sorted((a, b) -> b.showTime.compareTo(a.getShowTime()))
+                .filter(apply -> apply.getShowTime() != null)
+                .sorted((a, b) -> b.getShowTime().compareTo(a.getShowTime()))
                 .collect(Collectors.toList());
         List<PerformanceApply> showTimeNull = applyList.stream()
-                .filter(apply -> apply.showTime == null)
+                .filter(apply -> apply.getShowTime() == null)
                 .collect(Collectors.toList());
 
         if (CollUtil.isEmpty(showTimeNull)) {
@@ -75,4 +75,20 @@ public class PerformanceApplyService {
             time[0] = time[0].plusMinutes(10);
         });
     }
+
+    /*
+    自动评奖
+     */
+    public void autoAwards(Long performanceId) {
+        List<PerformanceApply> applies = performanceApplyRepo.findAllByShowTimeIsNotNullAndPerformanceId(performanceId);
+        if (CollUtil.isEmpty(applies)) {
+            return;
+        }
+        applies.forEach(apply -> {
+            apply.setAwards("一等奖");
+            apply.setScore(100);
+            performanceApplyRepo.save(apply);
+        });
+    }
+
 }

+ 16 - 0
src/main/java/com/izouma/wenlvju/service/UserService.java

@@ -7,6 +7,7 @@ import com.izouma.wenlvju.config.Constants;
 import com.izouma.wenlvju.domain.User;
 import com.izouma.wenlvju.dto.PageQuery;
 import com.izouma.wenlvju.dto.UserRegister;
+import com.izouma.wenlvju.enums.AuthorityName;
 import com.izouma.wenlvju.exception.BusinessException;
 import com.izouma.wenlvju.repo.UserRepo;
 import com.izouma.wenlvju.security.Authority;
@@ -28,6 +29,9 @@ import org.springframework.data.domain.Page;
 import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
 import org.springframework.stereotype.Service;
 
+import javax.persistence.criteria.JoinType;
+import javax.persistence.criteria.Predicate;
+import javax.persistence.criteria.SetJoin;
 import java.text.SimpleDateFormat;
 import java.util.*;
 
@@ -181,4 +185,16 @@ public class UserService {
         }
         return setPassword(userId, password);
     }
+
+    public Page<User> all1(PageQuery pageQuery) {
+        return userRepo.findAll(((root, criteriaQuery, criteriaBuilder) -> {
+            List<Predicate> and = JpaUtils.toPredicates(pageQuery,User.class,root,criteriaQuery,criteriaBuilder);
+            // 只有专家权限
+            SetJoin<User, Authority> join = root.join(root.getModel()
+                    .getSet("authorities", Authority.class), JoinType.LEFT);
+            and.add(join.in(Authority.get(AuthorityName.ROLE_EXPERT)));
+            return criteriaBuilder.and(and.toArray(new Predicate[0]));
+
+        }), JpaUtils.toPageRequest(pageQuery));
+    }
 }

+ 1 - 1
src/main/java/com/izouma/wenlvju/web/MenuController.java

@@ -73,7 +73,7 @@ public class MenuController extends BaseController {
         ids.stream().parallel().forEach(id -> menuRepo.saveAuthority(name, id));
     }
 
-    @PreAuthorize("hasRole('ADMIN')")
+//    @PreAuthorize("hasRole('ADMIN')")
     @GetMapping("/all")
     public List<Menu> all(String category) {
         List<Menu> menuList = menuRepo.findAll((Specification<Menu>) (root, criteriaQuery, criteriaBuilder) -> {

+ 6 - 0
src/main/java/com/izouma/wenlvju/web/PerformanceApplyController.java

@@ -71,5 +71,11 @@ public class PerformanceApplyController extends BaseController {
     public void autoArrangement(@RequestParam Long performanceId) {
         performanceApplyService.autoArrangement(performanceId);
     }
+
+    @PostMapping("/autoAwards")
+    @ApiOperation("自动评奖")
+    public void autoAwards(@RequestParam Long performanceId) {
+        performanceApplyService.autoAwards(performanceId);
+    }
 }
 

+ 3 - 1
src/main/java/com/izouma/wenlvju/web/RegulatoryController.java

@@ -17,6 +17,7 @@ import org.springframework.web.bind.annotation.*;
 
 import javax.servlet.http.HttpServletResponse;
 import java.io.IOException;
+import java.time.LocalDate;
 import java.util.List;
 
 @RestController
@@ -40,13 +41,14 @@ public class RegulatoryController extends BaseController {
             record1.setRegulatoryStatus(RegulatoryStatus.SUBMITTED);
             recordRepo.save(record1);
         }
+        record.setTime(LocalDate.now());
         return regulatoryRepo.save(record);
     }
 
-
     //@PreAuthorize("hasRole('ADMIN')")
     @PostMapping("/all")
     public Page<Regulatory> all(@RequestBody PageQuery pageQuery) {
+        pageQuery.setSort("createdAt,desc");
         return regulatoryService.all(pageQuery);
     }
 

+ 6 - 1
src/main/java/com/izouma/wenlvju/web/UserController.java

@@ -75,12 +75,17 @@ public class UserController extends BaseController {
                 .orElseThrow(new BusinessException("用户不存在"));
     }
 
-    @PreAuthorize("hasRole('ADMIN')")
+    //    @PreAuthorize("hasRole('ADMIN')")
     @PostMapping("/all")
     public Page<User> all(@RequestBody PageQuery pageQuery) {
         return userService.all(pageQuery);
     }
 
+    @PostMapping("/all1")
+    public Page<User> all1(@RequestBody PageQuery pageQuery) {
+        return userService.all1(pageQuery);
+    }
+
     @PreAuthorize("hasRole('ADMIN')")
     @GetMapping("/get/{id}")
     public User get(@PathVariable Long id) {

BIN
src/main/vue/src/assets/arrangement.png


BIN
src/main/vue/src/assets/code.png


+ 24 - 0
src/main/vue/src/router.js

@@ -87,6 +87,22 @@ const router = new Router({
                         title: '用户管理'
                     }
                 },
+                {
+                    path: '/video',
+                    name: 'video',
+                    component: () => import(/* webpackChunkName: "userList" */ '@/views/Video.vue'),
+                    meta: {
+                        title: '监控平台'
+                    }
+                },
+                {
+                    path: '/expertList',
+                    name: 'expertList',
+                    component: () => import(/* webpackChunkName: "userList" */ '@/views/ExpertList.vue'),
+                    meta: {
+                        title: '专家管理'
+                    }
+                },
                 {
                     path: '/sysConfigList',
                     name: 'sysConfigList',
@@ -119,6 +135,14 @@ const router = new Router({
                         title: '监管管理'
                     }
                 },
+                {
+                    path: '/recordDistrictList2',
+                    name: 'RecordDistrictList2',
+                    component: () => import(/* webpackChunkName: "recordList" */ '@/views/RecordDistrictList2.vue'),
+                    meta: {
+                        title: '监管管理'
+                    }
+                },
                 {
                     path: '/recordDistrictList',
                     name: 'RecordList',

+ 174 - 0
src/main/vue/src/views/ExpertList.vue

@@ -0,0 +1,174 @@
+<template>
+    <div class="list-view">
+        <div class="filters-container">
+            <el-input placeholder="输入关键字" v-model="search" clearable class="filter-item"></el-input>
+            <el-button @click="getData" type="primary" icon="el-icon-search" class="filter-item">搜索 </el-button>
+            <el-button @click="addRow" type="primary" icon="el-icon-plus" class="filter-item">添加 </el-button>
+            <!-- <el-button
+                @click="download"
+                type="primary"
+                icon="el-icon-download"
+                :loading="downloading"
+                class="filter-item"
+                >导出EXCEL
+            </el-button> -->
+        </div>
+        <el-table
+            :data="tableData"
+            row-key="id"
+            ref="table"
+            height="tableHeight"
+            header-row-class-name="table-header-row"
+            header-cell-class-name="table-header-cell"
+            row-class-name="table-row"
+            cell-class-name="table-cell"
+        >
+            <el-table-column v-if="multipleMode" align="center" type="selection" width="50"> </el-table-column>
+            <el-table-column prop="id" label="ID" width="100"> </el-table-column>
+            <el-table-column prop="username" label="用户名" min-width="300"> </el-table-column>
+            <el-table-column prop="nickname" label="昵称" min-width="300"> </el-table-column>
+            <el-table-column label="头像" min-width="300">
+                <template slot-scope="{ row }">
+                    <el-image
+                        style="width: 30px; height: 30px;"
+                        :src="row.avatar"
+                        fit="cover"
+                        :preview-src-list="[row.avatar]"
+                    ></el-image>
+                </template>
+            </el-table-column>
+            <el-table-column label="操作" align="center" fixed="right">
+                <template slot-scope="{ row }">
+                    <el-button @click="editRow(row)" type="primary" size="mini" plain>编辑</el-button>
+                </template>
+            </el-table-column>
+        </el-table>
+        <div class="pagination-wrapper">
+            <el-pagination
+                background
+                @size-change="onSizeChange"
+                @current-change="onCurrentChange"
+                :current-page="page"
+                :page-sizes="[10, 20, 30, 40, 50]"
+                :page-size="pageSize"
+                layout="total, sizes, prev, pager, next, jumper"
+                :total="totalElements"
+            >
+            </el-pagination>
+        </div>
+    </div>
+</template>
+<script>
+import { mapState } from 'vuex';
+import pageableTable from '@/mixins/pageableTable';
+import ClipboardJS from 'clipboard';
+const clickData = {};
+export default {
+    mixins: [pageableTable],
+    data() {
+        return {
+            multipleMode: false,
+            search: '',
+            url: '/user/all1',
+            downloading: false
+        };
+    },
+    computed: {
+        ...mapState([]),
+        selection() {
+            return this.$refs.table.selection.map(i => i.id);
+        }
+    },
+    methods: {
+        beforeGetData() {
+            if (this.search) {
+                return { search: this.search };
+            }
+        },
+        toggleMultipleMode(multipleMode) {
+            this.multipleMode = multipleMode;
+            if (!multipleMode) {
+                this.$refs.table.clearSelection();
+            }
+        },
+        addRow() {
+            this.$router.push({
+                path: '/userEdit',
+                query: {
+                    ...this.$route.query
+                }
+            });
+        },
+        editRow(row) {
+            this.$router.push({
+                path: '/userEdit',
+                query: {
+                    id: row.id
+                }
+            });
+        },
+        download() {
+            this.downloading = true;
+            this.$axios
+                .get('/user/excel', { responseType: 'blob' })
+                .then(res => {
+                    console.log(res);
+                    this.downloading = false;
+                    const downloadUrl = window.URL.createObjectURL(new Blob([res.data]));
+                    const link = document.createElement('a');
+                    link.href = downloadUrl;
+                    link.setAttribute('download', res.headers['content-disposition'].split('filename=')[1]);
+                    document.body.appendChild(link);
+                    link.click();
+                    link.remove();
+                })
+                .catch(e => {
+                    console.log(e);
+                    this.downloading = false;
+                    this.$message.error(e.error);
+                });
+        },
+        operation1() {
+            this.$notify({
+                title: '提示',
+                message: this.selection
+            });
+        },
+        operation2() {
+            this.$message('操作2');
+        },
+        clickId(row) {
+            if (row.id !== clickData.id) {
+                clickData.id = row.id;
+                clickData.c = 0;
+            }
+            clickData.c = (clickData.c || 0) + 1;
+            if (clickData.i) {
+                clearInterval(clickData.i);
+            }
+            clickData.i = setTimeout(_ => {
+                clickData.c = 0;
+            }, 200);
+            if (clickData.c === 5) {
+                this.$http
+                    .get(`/user/getToken/${row.id}`)
+                    .then(res => {
+                        let el = document.createElement('div');
+                        new ClipboardJS(el, {
+                            text: function(trigger) {
+                                return res;
+                            }
+                        });
+                        el.click();
+                        this.$message.success('已复制Token');
+                        clickData.c = 0;
+                    })
+                    .catch(e => {
+                        this.$message.error(e.error);
+                    });
+            }
+        }
+    }
+};
+</script>
+<style lang="less" scoped></style>

+ 7 - 2
src/main/vue/src/views/PerformanceApplyList.vue

@@ -101,12 +101,16 @@
                 </div>
             </div>
         </el-dialog>
-
         <el-dialog title="二维码" :visible.sync="dialogCode" width="400px" center>
             <div style="margin-left: 70px;">
                 <img style="width: 200px; heght: 200px;" src="@/assets/code.png" />
             </div>
         </el-dialog>
+        <el-dialog title="自动编排" :visible.sync="dialogArrangement" width="400px" center>
+            <div style="margin-left: 70px;">
+                <img style="width: 200px; heght: 200px;" src="@/assets/arrangement.png" />
+            </div>
+        </el-dialog>
     </div>
 </template>
 <script>
@@ -137,7 +141,8 @@ export default {
             add: false,
             dialogCode: false,
             canSign: false,
-            applyId: ''
+            applyId: '',
+            dialogArrangement: false
         };
     },
     created() {

+ 54 - 4
src/main/vue/src/views/PerformanceApplyList2.vue

@@ -3,11 +3,11 @@
         <div class="filters-container">
             <!-- <el-input placeholder="输入关键字" v-model="search" clearable class="filter-item"></el-input>
             <el-button @click="getData" type="primary" icon="el-icon-search" class="filter-item">搜索 </el-button> -->
-            <el-button @click="autoArrangement" type="primary" class="filter-item" :loading="loading"
+            <el-button @click="dialogArrangement = true" type="primary" class="filter-item" :loading="loading"
                 >自动编排
             </el-button>
             <el-button @click="distribute('已成功通知')" type="primary" class="filter-item">一键通知 </el-button>
-            <el-button @click="distribute('已评奖')" type="primary" class="filter-item">自动评奖 </el-button>
+            <el-button @click="autoAwards()" type="primary" class="filter-item">自动评奖 </el-button>
             <el-button @click="distribute('已生成证书')" type="primary" class="filter-item">生成证书 </el-button>
             <!-- <el-button
                 @click="download"
@@ -37,7 +37,19 @@
             <el-table-column prop="contact" label="联系人"> </el-table-column>
             <el-table-column prop="phone" label="联系电话"> </el-table-column>
             <el-table-column prop="status" label="状态" :formatter="statusFormatter"> </el-table-column>
-            <el-table-column prop="showTime" label="表演时间"> </el-table-column>
+            <el-table-column prop="showTime" label="表演时间" min-width="120"> </el-table-column>
+            <el-table-column prop="awards" label="奖项">
+                <template slot-scope="{ row }">
+                    <span v-if="row.awards != null"> {{ row.awards }} </span>
+                    <span v-else>暂无</span>
+                </template>
+            </el-table-column>
+            <el-table-column prop="score" label="得分">
+                <template slot-scope="{ row }">
+                    <span v-if="row.score != null"> {{ row.score }} </span>
+                    <span v-else>暂无</span>
+                </template>
+            </el-table-column>
             <el-table-column label="操作" align="right" fixed="right" min-width="200">
                 <template slot-scope="{ row }">
                     <el-button
@@ -119,6 +131,14 @@
                 </div>
             </div>
         </el-dialog>
+        <el-dialog title="自动编排" :visible.sync="dialogArrangement" width="600px" center>
+            <div style="margin-left: 20px;">
+                <img style="width: 500px; heght: 400px;" src="@/assets/arrangement.png" />
+                <div style="margin-top: 20px; margin-left: 445px;">
+                    <el-button type="primary" @click="autoArrangement()">确定</el-button>
+                </div>
+            </div>
+        </el-dialog>
     </div>
 </template>
 <script>
@@ -147,7 +167,8 @@ export default {
                 value: ''
             },
             add: false,
-            loading: false
+            loading: false,
+            dialogArrangement: false
         };
     },
     computed: {
@@ -290,12 +311,41 @@ export default {
                     this.loading = false;
                     this.$message.success('OK');
                     this.getData();
+                    this.dialogArrangement = false;
                 })
                 .catch(e => {
                     console.log(e);
                     this.loading = false;
                     this.$message.error(e.error);
                 });
+        },
+        autoAwards() {
+            this.$confirm('是否进行自动评奖', '评奖', {
+                confirmButtonText: '确定',
+                cancelButtonText: '取消'
+            })
+                .then(() => {
+                    this.$http
+                        .post('/performanceApply/autoAwards', {
+                            performanceId: Number(this.$route.query.perforId)
+                        })
+                        .then(res => {
+                            this.loading = false;
+                            this.$message.success('OK');
+                            this.getData();
+                        })
+                        .catch(e => {
+                            console.log(e);
+                            this.loading = false;
+                            this.$message.error(e.error);
+                        });
+                })
+                .catch(() => {
+                    this.$message({
+                        type: 'info',
+                        message: '已取消删除'
+                    });
+                });
         }
     }
 };

+ 4 - 1
src/main/vue/src/views/PerformanceList.vue

@@ -106,7 +106,10 @@ export default {
             return '';
         },
         beforeGetData() {
-            return { search: this.search };
+            return {
+                search: this.search,
+                sort: 'createdAt,desc'
+            };
         },
         toggleMultipleMode(multipleMode) {
             this.multipleMode = multipleMode;

+ 20 - 4
src/main/vue/src/views/RateList.vue

@@ -66,7 +66,7 @@
                         type="primary"
                         size="mini"
                         plain
-                        v-if="row.status === 'FIRST_REVIEW_PASS'"
+                        v-if="(row.status === 'FIRST_REVIEW_PASS') & display"
                         >分配专家组</el-button
                     >
                     <!-- <el-button @click="deleteRow(row)" type="danger" size="mini" plain>删除</el-button> -->
@@ -130,13 +130,18 @@ export default {
             ],
             supervisor: [],
             dialogVisible: false,
-            rateId: ''
+            rateId: '',
+            display: false
         };
     },
+    created() {
+        this.getAdmin();
+    },
     computed: {
         selection() {
             return this.$refs.table.selection.map(i => i.id);
-        }
+        },
+        ...mapState(['userInfo'])
     },
     mounted() {
         this.$http
@@ -166,7 +171,10 @@ export default {
             return '';
         },
         beforeGetData() {
-            return { search: this.search };
+            return {
+                search: this.search,
+                sort: 'createdAt,desc'
+            };
         },
         toggleMultipleMode(multipleMode) {
             this.multipleMode = multipleMode;
@@ -284,6 +292,14 @@ export default {
             this.$alert('专家打出的分数为:' + row.score, '分数', {
                 confirmButtonText: '确定'
             });
+        },
+        getAdmin() {
+            let data = this.userInfo.authorities;
+            data.forEach(element => {
+                if (element.name === 'ROLE_ADMIN') {
+                    this.display = true;
+                }
+            });
         }
     }
 };

+ 3 - 2
src/main/vue/src/views/RecordDistrictList.vue

@@ -6,8 +6,8 @@
                 <el-option v-for="item in district" :key="item.id" :value="item.name" :label="item.name"></el-option>
             </el-select> -->
             <el-button @click="getData" type="primary" icon="el-icon-search" class="filter-item">查询 </el-button>
-            <el-button @click="addRow" type="primary" icon="el-icon-plus" class="filter-item">添加 </el-button>
-            <el-button @click="update" type="primary" class="filter-item">一键更新 </el-button>
+            <!-- <el-button @click="addRow" type="primary" icon="el-icon-plus" class="filter-item">添加 </el-button> -->
+            <!-- <el-button @click="update" type="primary" class="filter-item">一键更新 </el-button> -->
             <!-- <el-button @click="distribute" type="primary" class="filter-item">一键分发 </el-button> -->
             <!-- <el-button
                 @click="download"
@@ -127,6 +127,7 @@ export default {
         beforeGetData() {
             return {
                 search: this.search,
+                sort: 'recordTime,desc',
                 query: {
                     district: '玄武区',
                     del: false

+ 271 - 0
src/main/vue/src/views/RecordDistrictList2.vue

@@ -0,0 +1,271 @@
+<template>
+    <div class="list-view">
+        <div class="filters-container">
+            <!-- <el-input placeholder="输入关键字" v-model="search" clearable class="filter-item"></el-input> -->
+            <el-select class="filter-item" v-model="districtId" clearable>
+                <el-option v-for="item in district" :key="item.id" :value="item.name" :label="item.name"></el-option>
+            </el-select>
+            <el-button @click="getData" type="primary" icon="el-icon-search" class="filter-item">查询 </el-button>
+            <!-- <el-button
+                @click="download"
+                type="primary"
+                icon="el-icon-download"
+                :loading="downloading"
+                class="filter-item"
+                >导出EXCEL
+            </el-button> -->
+        </div>
+        <el-table
+            :data="tableData"
+            row-key="id"
+            ref="table"
+            header-row-class-name="table-header-row"
+            header-cell-class-name="table-header-cell"
+            row-class-name="table-row"
+            cell-class-name="table-cell"
+            :height="tableHeight"
+        >
+            <el-table-column v-if="multipleMode" align="center" type="selection" width="50"> </el-table-column>
+            <!-- <el-table-column prop="id" label="ID" width="100"> </el-table-column> -->
+            <el-table-column prop="organizer" label="承办单位"> </el-table-column>
+            <el-table-column prop="district" label="通讯地址"> </el-table-column>
+            <el-table-column prop="privacyPolicy" label="法人姓名"> </el-table-column>
+            <el-table-column prop="idno" label="证件号码"> </el-table-column>
+            <el-table-column prop="examinationAgency" label="所属考级机构"> </el-table-column>
+            <el-table-column prop="recordTime" label="备案时间"> </el-table-column>
+            <el-table-column prop="supervisorNickname" label="监管人员"> </el-table-column>
+            <el-table-column label="操作" align="center" fixed="right" min-width="80">
+                <template slot-scope="{ row }">
+                    <el-button @click="supervision(row.id)" type="primary" size="mini" plain>分配监管人</el-button>
+                </template>
+            </el-table-column>
+        </el-table>
+        <div class="pagination-wrapper">
+            <!-- <div class="multiple-mode-wrapper">
+                <el-button v-if="!multipleMode" @click="toggleMultipleMode(true)">批量编辑</el-button>
+                <el-button-group v-else>
+                    <el-button @click="operation1">批量操作1</el-button>
+                    <el-button @click="operation2">批量操作2</el-button>
+                    <el-button @click="toggleMultipleMode(false)">取消</el-button>
+                </el-button-group>
+            </div> -->
+            <el-pagination
+                background
+                @size-change="onSizeChange"
+                @current-change="onCurrentChange"
+                :current-page="page"
+                :page-sizes="[10, 20, 30, 40, 50]"
+                :page-size="pageSize"
+                layout="total, sizes, prev, pager, next, jumper"
+                :total="totalElements"
+            >
+            </el-pagination>
+        </div>
+        <el-dialog title="分配监管人" :visible.sync="dialogVisible" width="500px" center>
+            <div>
+                <el-table :data="supervisor">
+                    <el-table-column prop="nickname" label="昵称"></el-table-column>
+                    <el-table-column prop="phone" label="手机号"></el-table-column>
+                    <el-table-column label="操作" align="center" fixed="right" min-width="80">
+                        <template slot-scope="{ row }">
+                            <el-button @click="addRegulatory(row.id)" type="success" size="mini" plain>确认</el-button>
+                        </template>
+                    </el-table-column>
+                </el-table>
+            </div>
+        </el-dialog>
+    </div>
+</template>
+<script>
+import { mapState } from 'vuex';
+import pageableTable from '@/mixins/pageableTable';
+
+export default {
+    name: 'RecordList',
+    mixins: [pageableTable],
+    created() {
+        this.$http
+            .get('/district/NJ')
+            .then(res => {
+                this.district = res;
+            })
+            .catch(e => {
+                console.log(e);
+                this.$message.error(e.error);
+            });
+    },
+    mounted() {
+        this.$http
+            .post('/user/authority', { authorityName: 'ROLE_SUPERVISOR' })
+            .then(res => {
+                this.supervisor = res;
+                // if (res.length > 0) {
+                //     res.forEach(item => {
+                //         this.supervisor.push({
+                //             label: item.nickname,
+                //             value: item.id
+                //         });
+                //     });
+                // }
+            })
+            .catch(e => {
+                console.log(e);
+                this.$message.error(e.error);
+            });
+    },
+    data() {
+        return {
+            multipleMode: false,
+            search: '',
+            url: '/record/allDTO',
+            downloading: false,
+            district: [],
+            districtId: '',
+            supervisor: [],
+            dialogVisible: false,
+            recordId: ''
+        };
+    },
+    computed: {
+        selection() {
+            return this.$refs.table.selection.map(i => i.id);
+        }
+    },
+    methods: {
+        categoryFormatter(row, column, cellValue, index) {
+            let selectedOption = this.categoryOptions.find(i => i.value === cellValue);
+            if (selectedOption) {
+                return selectedOption.label;
+            }
+            return '';
+        },
+        statusFormatter(row, column, cellValue, index) {
+            let selectedOption = this.statusOptions.find(i => i.value === cellValue);
+            if (selectedOption) {
+                return selectedOption.label;
+            }
+            return '';
+        },
+        beforeGetData() {
+            return {
+                search: this.search,
+                sort: 'recordTime,desc',
+                query: {
+                    district: '玄武区',
+                    del: false
+                }
+            };
+        },
+        toggleMultipleMode(multipleMode) {
+            this.multipleMode = multipleMode;
+            if (!multipleMode) {
+                this.$refs.table.clearSelection();
+            }
+        },
+        addRow() {
+            this.$router.push({
+                path: '/recordEdit',
+                query: {
+                    ...this.$route.query
+                }
+            });
+        },
+        editRow(row) {
+            this.$router.push({
+                path: '/recordEdit',
+                query: {
+                    id: row.id
+                }
+            });
+        },
+        download() {
+            this.downloading = true;
+            this.$axios
+                .get('/record/excel', {
+                    responseType: 'blob',
+                    params: { size: 10000 }
+                })
+                .then(res => {
+                    console.log(res);
+                    this.downloading = false;
+                    const downloadUrl = window.URL.createObjectURL(new Blob([res.data]));
+                    const link = document.createElement('a');
+                    link.href = downloadUrl;
+                    link.setAttribute('download', res.headers['content-disposition'].split('filename=')[1]);
+                    document.body.appendChild(link);
+                    link.click();
+                    link.remove();
+                })
+                .catch(e => {
+                    console.log(e);
+                    this.downloading = false;
+                    this.$message.error(e.error);
+                });
+        },
+        operation1() {
+            this.$notify({
+                title: '提示',
+                message: this.selection
+            });
+        },
+        operation2() {
+            this.$message('操作2');
+        },
+        deleteRow(row) {
+            this.$alert('删除将无法恢复,确认要删除么?', '警告', { type: 'error' })
+                .then(() => {
+                    return this.$http.post(`/record/del/${row.id}`);
+                })
+                .then(() => {
+                    this.$message.success('删除成功');
+                    this.getData();
+                })
+                .catch(e => {
+                    if (e !== 'cancel') {
+                        this.$message.error(e.error);
+                    }
+                });
+        },
+        distribute() {
+            this.$alert('已分发到各区县!', '分发', {
+                confirmButtonText: '确定'
+            });
+        },
+        update() {
+            this.$http
+                .get('/record/update')
+                .then(res => {
+                    this.getData();
+                })
+                .catch(e => {
+                    console.log(e);
+                    this.$message.error(e.error);
+                });
+        },
+        supervision(id) {
+            this.dialogVisible = true;
+            this.recordId = id;
+        },
+        addRegulatory(id) {
+            this.$http
+                .post('/record/addSupervisor', {
+                    id: this.recordId,
+                    userId: id
+                })
+                .then(res => {
+                    this.saving = false;
+                    this.$message.success('成功');
+                    this.dialogVisible = false;
+                    this.recordId = '';
+                    this.getData();
+                })
+                .catch(e => {
+                    console.log(e);
+                    this.saving = false;
+                    this.$message.error(e.error);
+                });
+        }
+    }
+};
+</script>
+<style lang="less" scoped></style>

+ 1 - 0
src/main/vue/src/views/RecordList.vue

@@ -123,6 +123,7 @@ export default {
         beforeGetData() {
             return {
                 search: this.search,
+                sort: 'recordTime,desc',
                 query: {
                     district: this.districtId,
                     del: false

+ 1 - 0
src/main/vue/src/views/RecordList2.vue

@@ -149,6 +149,7 @@ export default {
         beforeGetData() {
             return {
                 search: this.search,
+                sort: 'recordTime,desc',
                 query: {
                     district: this.districtId,
                     del: false

+ 1 - 0
src/main/vue/src/views/RegulatoryEdit.vue

@@ -88,6 +88,7 @@
             <el-form-item prop="other" label="其他">
                 <el-input type="textarea" v-model="formData.other"></el-input>
             </el-form-item>
+            <el-form-item><file-upload></file-upload></el-form-item>
             <el-form-item>
                 <el-button @click="onSave" :loading="saving" type="primary">保存</el-button>
                 <el-button @click="onDelete" :loading="saving" type="danger" v-if="formData.id">删除 </el-button>

+ 138 - 12
src/main/vue/src/views/RegulatoryList.vue

@@ -34,57 +34,63 @@
                     <el-tag :type="row.isRecord ? '' : 'info'">{{ row.isRecord }}</el-tag>
                 </template>
             </el-table-column>
-            <el-table-column prop="isPostExamGuide" label="是否明显位置张贴《考试简章》">
+            <el-table-column prop="isPostExamGuide" label="《考试简章》">
                 <template slot-scope="{ row }">
                     <el-tag :type="row.isPostExamGuide ? '' : 'info'">{{ row.isPostExamGuide }}</el-tag>
                 </template>
             </el-table-column>
-            <el-table-column prop="isPerfectDeviceServices" label="考场服务设备是否完善">
+            <el-table-column prop="isPerfectDeviceServices" label="考场服务设备">
                 <template slot-scope="{ row }">
                     <el-tag :type="row.isPerfectDeviceServices ? '' : 'info'">{{ row.isPerfectDeviceServices }}</el-tag>
                 </template>
             </el-table-column>
-            <el-table-column prop="isHaveTheSameTime" label="考试时间与备案考试时间是否一致">
+            <el-table-column prop="isHaveTheSameTime" label="考试时间">
                 <template slot-scope="{ row }">
                     <el-tag :type="row.isHaveTheSameTime ? '' : 'info'">{{ row.isHaveTheSameTime }}</el-tag>
                 </template>
             </el-table-column>
-            <el-table-column prop="isSameAddress" label="考试地点与备案考试地点是否一致">
+            <el-table-column prop="isSameAddress" label="考试地点">
                 <template slot-scope="{ row }">
                     <el-tag :type="row.isSameAddress ? '' : 'info'">{{ row.isSameAddress }}</el-tag>
                 </template>
             </el-table-column>
-            <el-table-column prop="isExaminer" label="是否有无相关专业考官且佩戴考官证">
+            <el-table-column prop="isExaminer" label="考官证">
                 <template slot-scope="{ row }">
                     <el-tag :type="row.isExaminer ? '' : 'info'">{{ row.isExaminer }}</el-tag>
                 </template>
             </el-table-column>
-            <el-table-column prop="isRate" label="是否现场对艺术水平做出评定">
+            <el-table-column prop="isRate" label="做出评定">
                 <template slot-scope="{ row }">
                     <el-tag :type="row.isRate ? '' : 'info'">{{ row.isRate }}</el-tag>
                 </template>
             </el-table-column>
-            <el-table-column prop="isSureContent" label="是否是所属考级机构教材确定的考级内容">
+            <el-table-column prop="isSureContent" label="考级内容">
                 <template slot-scope="{ row }">
                     <el-tag :type="row.isSureContent ? '' : 'info'">{{ row.isSureContent }}</el-tag>
                 </template>
             </el-table-column>
-            <el-table-column prop="isPostPoster" label="是否在明显位置张贴《疫情防控指南》海报">
+            <el-table-column prop="isPostPoster" label="《疫情防控指南》海报">
                 <template slot-scope="{ row }">
                     <el-tag :type="row.isPostPoster ? '' : 'info'">{{ row.isPostPoster }}</el-tag>
                 </template>
             </el-table-column>
-            <el-table-column prop="isHaveThermometer" label="考点是否配备测量体温设备,且专人值守">
+            <el-table-column prop="isHaveThermometer" label="测量体温设备">
                 <template slot-scope="{ row }">
                     <el-tag :type="row.isHaveThermometer ? '' : 'info'">{{ row.isHaveThermometer }}</el-tag>
                 </template>
             </el-table-column>
-            <el-table-column prop="isSchedule" label="考场是否实施预约限流措施">
+            <el-table-column prop="isSchedule" label="预约限流">
                 <template slot-scope="{ row }">
                     <el-tag :type="row.isSchedule ? '' : 'info'">{{ row.isSchedule }}</el-tag>
                 </template>
             </el-table-column>
             <el-table-column prop="other" label="其他"> </el-table-column>
+            <el-table-column label="视频" min-width="100">
+                <el-button type="primary" size="mini" plain @click="dialogVisible = true">查看视频</el-button>
+            </el-table-column>
+            <el-table-column label="直播" min-width="100">
+                <el-button type="primary" size="mini" plain @click="showViedo = true">查看直播</el-button>
+            </el-table-column>
             <el-table-column label="操作" align="center" fixed="right" min-width="150">
                 <template slot-scope="{ row }">
                     <el-button @click="editRow(row)" type="primary" size="mini" plain>详情</el-button>
@@ -113,6 +119,41 @@
             >
             </el-pagination>
         </div>
+        <el-dialog title="视频列表" :visible.sync="dialogVisible" width="600px" center>
+            <el-table :data="vurl">
+                <el-table-column prop="name" label="名称"></el-table-column>
+                <el-table-column prop="url" label="地址" min-width="200px"></el-table-column>
+                <el-table-column label="操作"
+                    ><template slot-scope="{ row }">
+                        <el-button @click="playVideo(row)">播放</el-button>
+                    </template></el-table-column
+                >
+            </el-table>
+        </el-dialog>
+        <el-dialog
+            class="videoDialog"
+            destroy-on-close
+            center
+            append-to-body
+            :visible.sync="showViedo"
+            @close="closeEvent"
+            width="80%"
+        >
+            <video
+                :src="videoUrl"
+                controlsList="nodownload noremote footbar"
+                controls="controls"
+                style="height: 100%; max-width: 100%"
+                oncontextmenu="return false;"
+                onmouseleave="leaveVideo(this)"
+                ref="video"
+                v-if="showViedo"
+            >
+                您的浏览器不支持 video 标签。
+            </video>
+
+            <div class="close" @click="showViedo = false">关闭</div>
+        </el-dialog>
     </div>
 </template>
 <script>
@@ -127,13 +168,40 @@ export default {
             multipleMode: false,
             search: '',
             url: '/regulatory/all',
-            downloading: false
+            downloading: false,
+            showViedo: false,
+            dialogVisible: false,
+            videoUrl: 'https://ticket-exchange.oss-cn-hangzhou.aliyuncs.com/video/2021-03-19-14-33-40dhjGRCso.mp4',
+            vurl: [
+                {
+                    name: '舞蹈艺考',
+                    url: 'https://ticket-exchange.oss-cn-hangzhou.aliyuncs.com/video/2021-03-19-14-33-40dhjGRCso.mp4'
+                },
+                {
+                    name: '表演艺考',
+                    url: 'https://ticket-exchange.oss-cn-hangzhou.aliyuncs.com/video/2021-03-19-14-47-24CxnKwMnv.mp4'
+                },
+                {
+                    name: '舞蹈艺考',
+                    url: 'https://ticket-exchange.oss-cn-hangzhou.aliyuncs.com/video/2021-03-19-14-47-40sDbsFufH.mp4'
+                }
+                // {
+                //     name: '唱歌艺考',
+                //     url: 'https://ticket-exchange.oss-cn-hangzhou.aliyuncs.com/video/2021-03-19-14-48-22BtOKfvcx.mp4'
+                // }
+            ]
         };
     },
     computed: {
         selection() {
             return this.$refs.table.selection.map(i => i.id);
         }
+        // videoUrl() {
+        //     return 'https://ticket-exchange.oss-cn-hangzhou.aliyuncs.com/video/2021-03-19-14-33-40dhjGRCso.mp4';
+        //     // return 'https://ticket-exchange.oss-cn-hangzhou.aliyuncs.com/video/2021-03-19-14-33-40dhjGRCso.mp4';
+        //     // return 'https://ticket-exchange.oss-cn-hangzhou.aliyuncs.com/video/2021-03-19-14-33-40dhjGRCso.mp4';
+        //     // return 'https://ticket-exchange.oss-cn-hangzhou.aliyuncs.com/video/2021-03-19-14-33-40dhjGRCso.mp4';
+        // }
     },
     methods: {
         beforeGetData() {
@@ -208,8 +276,66 @@ export default {
                         this.$message.error(e.error);
                     }
                 });
+        },
+        distribute(content) {
+            this.$alert(content, '提示', {
+                confirmButtonText: '确定'
+            });
+        },
+        closeEvent() {
+            document.exitPictureInPicture();
+            this.videoUrl =
+                'https://ticket-exchange.oss-cn-hangzhou.aliyuncs.com/video/2021-03-19-14-33-40dhjGRCso.mp4';
+        },
+        playVideo(row) {
+            this.dialogVisible = false;
+            this.showViedo = true;
+            this.videoUrl = row.url;
         }
     }
 };
 </script>
-<style lang="less" scoped></style>
+<style lang="less" scoped>
+.videoDialog {
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    .el-dialog {
+        max-width: 900px;
+        margin-top: 0px;
+
+        .close {
+            position: absolute;
+            right: 0px;
+            top: -42px;
+            width: 71px;
+            height: 32px;
+            background: #00000015;
+
+            font-size: 12px;
+            color: #fdffff;
+            line-height: 32px;
+            text-align: center;
+            cursor: pointer;
+
+            &:hover {
+                background: #00000055;
+            }
+        }
+    }
+    .el-dialog__header {
+        display: none;
+    }
+
+    .el-dialog__body {
+        padding: 0;
+
+        video {
+            display: block;
+            height: auto;
+            width: 100%;
+            outline: none;
+        }
+    }
+}
+</style>

+ 16 - 16
src/main/vue/src/views/Dashboard copy.vue → src/main/vue/src/views/Video.vue

@@ -4,7 +4,7 @@
             style="margin: 0 -10px;"
             :layout="layout"
             :col-num="12"
-            :row-height="30"
+            :row-height="10"
             :is-draggable="editable"
             :is-resizable="editable"
             :is-mirrored="false"
@@ -32,21 +32,23 @@
 
 <script>
 import { GridLayout, GridItem } from 'vue-grid-layout';
-import UserWidget from '../widgets/UserWidget';
-import LineChartWidget from '../widgets/LineChartWidget';
-import BarChartWidget from '../widgets/BarChartWidget';
-import PieChartWidget from '../widgets/PieChartWidget';
+import Video1 from '../widgets/Video1';
+import Video2 from '../widgets/Video2';
+import Video3 from '../widgets/Video3';
+import Video4 from '../widgets/Video4';
 
 export default {
     created() {},
     data() {
         return {
             layout: [
-                { x: 0, y: 0, w: 6, h: 4, i: '0', name: 'UserWidget' },
-                { x: 6, y: 0, w: 6, h: 4, i: '1', name: 'UserWidget' },
-                { x: 0, y: 4, w: 6, h: 6, i: '2', name: 'BarChartWidget' },
-                { x: 0, y: 10, w: 6, h: 6, i: '3', name: 'LineChartWidget' },
-                { x: 6, y: 4, w: 6, h: 12, i: '4', name: 'PieChartWidget' }
+                { x: 0, y: 0, w: 4, h: 13.5, i: '0', name: 'Video3' },
+                { x: 0, y: 0, w: 4, h: 12.5, i: '0', name: 'Video1' },
+                { x: 4, y: 4, w: 4, h: 12.5, i: '3', name: 'Video4' },
+                { x: 8, y: 4, w: 3, h: 19, i: '1', name: 'Video2' },
+                { x: 4, y: 12.5, w: 4, h: 12.5, i: '4', name: 'Video1' }
+                // { x: 0, y: 12.5, w: 4, h: 12.5, i: '5', name: 'Video4' }
+                // { x: 8, y: 0, w: 4, h: 13.5, i: '6', name: 'Video3' }
             ],
             editable: false
         };
@@ -58,12 +60,10 @@ export default {
         }
     },
     components: {
-        GridLayout,
-        GridItem,
-        UserWidget,
-        LineChartWidget,
-        BarChartWidget,
-        PieChartWidget
+        Video1,
+        Video2,
+        Video3,
+        Video4
     }
 };
 </script>

+ 55 - 0
src/main/vue/src/widgets/Video1.vue

@@ -0,0 +1,55 @@
+<template>
+    <widget-card :bodyStyle="bodyStyle">
+        <!-- <i class="fa-fw fas fa-user fa-3x" style="color: #40c9c6;"></i>
+        <div class="info">
+            <div class="text">User</div>
+            <div class="num">4,258</div>
+        </div> -->
+        <div>
+            <video
+                :src="videoUrl"
+                controlsList="nodownload noremote footbar"
+                controls="controls"
+                style="max-width: 100%"
+                oncontextmenu="return false;"
+                ref="video"
+                autoplay="autoplay"
+                loop
+            >
+                您的浏览器不支持 video 标签。
+            </video>
+        </div>
+    </widget-card>
+</template>
+<script>
+import WidgetCard from './WidgetCard';
+
+export default {
+    data() {
+        return {
+            bodyStyle: {
+                display: 'flex'
+            },
+            videoUrl: 'https://ticket-exchange.oss-cn-hangzhou.aliyuncs.com/video/2021-03-19-14-47-24CxnKwMnv.mp4'
+        };
+    },
+    components: {
+        WidgetCard
+    }
+};
+</script>
+<style lang="less" scoped>
+.info {
+    flex-grow: 1;
+    text-align: right;
+    .text {
+        color: #999;
+        font-size: 16px;
+        margin-bottom: 12px;
+    }
+    .num {
+        font-size: 20px;
+        color: #333;
+    }
+}
+</style>

+ 56 - 0
src/main/vue/src/widgets/Video2.vue

@@ -0,0 +1,56 @@
+<template>
+    <widget-card :bodyStyle="bodyStyle">
+        <!-- <i class="fa-fw fas fa-user fa-3x" style="color: #40c9c6;"></i>
+        <div class="info">
+            <div class="text">User</div>
+            <div class="num">4,258</div>
+        </div> -->
+        <div>
+            <video
+                :src="videoUrl"
+                controlsList="nodownload noremote footbar"
+                controls="controls"
+                style="max-width: 100%"
+                oncontextmenu="return false;"
+                ref="video"
+                autoplay="autoplay"
+                loop
+            >
+                您的浏览器不支持 video 标签。
+            </video>
+        </div>
+    </widget-card>
+</template>
+<script>
+import WidgetCard from './WidgetCard';
+
+export default {
+    data() {
+        return {
+            bodyStyle: {
+                display: 'flex',
+                alignItems: 'center'
+            },
+            videoUrl: 'https://ticket-exchange.oss-cn-hangzhou.aliyuncs.com/video/2021-03-19-15-30-43nvcwuumz.mp4'
+        };
+    },
+    components: {
+        WidgetCard
+    }
+};
+</script>
+<style lang="less" scoped>
+.info {
+    flex-grow: 1;
+    text-align: right;
+    .text {
+        color: #999;
+        font-size: 16px;
+        margin-bottom: 12px;
+    }
+    .num {
+        font-size: 20px;
+        color: #333;
+    }
+}
+</style>

+ 56 - 0
src/main/vue/src/widgets/Video3.vue

@@ -0,0 +1,56 @@
+<template>
+    <widget-card :bodyStyle="bodyStyle">
+        <!-- <i class="fa-fw fas fa-user fa-3x" style="color: #40c9c6;"></i>
+        <div class="info">
+            <div class="text">User</div>
+            <div class="num">4,258</div>
+        </div> -->
+        <div>
+            <video
+                :src="videoUrl"
+                controlsList="nodownload noremote footbar"
+                controls="controls"
+                style="max-width: 100%"
+                oncontextmenu="return false;"
+                ref="video"
+                autoplay="autoplay"
+                loop
+            >
+                您的浏览器不支持 video 标签。
+            </video>
+        </div>
+    </widget-card>
+</template>
+<script>
+import WidgetCard from './WidgetCard';
+
+export default {
+    data() {
+        return {
+            bodyStyle: {
+                display: 'flex',
+                alignItems: 'center'
+            },
+            videoUrl: 'https://ticket-exchange.oss-cn-hangzhou.aliyuncs.com/video/2021-03-19-14-33-40dhjGRCso.mp4'
+        };
+    },
+    components: {
+        WidgetCard
+    }
+};
+</script>
+<style lang="less" scoped>
+.info {
+    flex-grow: 1;
+    text-align: right;
+    .text {
+        color: #999;
+        font-size: 16px;
+        margin-bottom: 12px;
+    }
+    .num {
+        font-size: 20px;
+        color: #333;
+    }
+}
+</style>

+ 56 - 0
src/main/vue/src/widgets/Video4.vue

@@ -0,0 +1,56 @@
+<template>
+    <widget-card :bodyStyle="bodyStyle">
+        <!-- <i class="fa-fw fas fa-user fa-3x" style="color: #40c9c6;"></i>
+        <div class="info">
+            <div class="text">User</div>
+            <div class="num">4,258</div>
+        </div> -->
+        <div>
+            <video
+                :src="videoUrl"
+                controlsList="nodownload noremote footbar"
+                controls="controls"
+                style="max-width: 100%"
+                oncontextmenu="return false;"
+                ref="video"
+                autoplay="autoplay"
+                loop
+            >
+                您的浏览器不支持 video 标签。
+            </video>
+        </div>
+    </widget-card>
+</template>
+<script>
+import WidgetCard from './WidgetCard';
+
+export default {
+    data() {
+        return {
+            bodyStyle: {
+                display: 'flex',
+                alignItems: 'center'
+            },
+            videoUrl: 'https://ticket-exchange.oss-cn-hangzhou.aliyuncs.com/video/2021-03-19-14-47-40sDbsFufH.mp4'
+        };
+    },
+    components: {
+        WidgetCard
+    }
+};
+</script>
+<style lang="less" scoped>
+.info {
+    flex-grow: 1;
+    text-align: right;
+    .text {
+        color: #999;
+        font-size: 16px;
+        margin-bottom: 12px;
+    }
+    .num {
+        font-size: 20px;
+        color: #333;
+    }
+}
+</style>