<template>
  <div :class="['panel', { active: pannelVisible }]" id="panel">
    <el-container class="scrollbar" id="container" v-show="pannelVisible">
      <el-header class="panel-header" id="panelHeader">
        <div class="project">
          <div class="inline-center">
            <p class="whitespace-nowrap">实训项目</p>
            <el-tooltip effect="dark" content="点击右侧“下三角”按钮可切换实验项目" placement="bottom">
              <i class="info el-icon-warning" style="margin-left: 10px"></i>
            </el-tooltip>
          </div>
          <el-select v-model="projectId" placeholder="请选择" class="select" :disabled="projectPermissions != 0"
            @change="selectProject" style="flex: 1">
            <el-option v-for="(item, i) in projectList" :key="item.projectId" :label="i + 1 + '. ' + item.projectName"
              :value="item.projectId"></el-option>
          </el-select>
        </div>
        <div class="item">
          <div class="count">
            实训{{ text }}时间
            <span>{{ day }}</span>天
            <span>{{ hour }}</span>小时
            <span>{{ minutes }}</span>分
            <span>{{ seconds }}</span>秒
          </div>
        </div>
        <div v-if="!competitionId" class="item">
          <div>
            总得分：
            <span class="total-score">{{ grade }}</span>
          </div>
        </div>
        <div class="actions">
          <el-button @click="toggleReport"
            v-if="$parent.language === 'vscode' && !isSubmit && !competitionId">填写实验报告</el-button>
          <el-button @click="toReport" v-if="isSubmit && !competitionId">查看实验报告</el-button>
          <el-button class="reload" @click="reloadConfirm" v-show="projectPermissions == 0">重新开始</el-button>
          <el-button type="primary" class="submit btn" :loading="submiting" @click="confirmSubmit"
            :disabled="isSubmit || !projectList.length">提交</el-button>
        </div>
      </el-header>
      <div class="report-wrap" v-if="reportVisible">
        <i class="el-icon-caret-top close-report"></i>
        <el-table class="report-table" :data="taskList" :max-height="tableHeight">
          <el-table-column prop="name" label="判分点" width="210" align="center"></el-table-column>
          <el-table-column prop="score" label="答案" align="center">
            <template slot-scope="scope">
              <codemirror v-model="scope.row.code" :options="cmOption" class="code-mirror"
                :ref="'codemirror' + scope.$index" @ready="() => ready(scope.$index)"></codemirror>
            </template>
          </el-table-column>
          <el-table-column prop="score" label="运行结果" align="center">
            <template slot-scope="scope">
              <quill class="quill" :tools="false" :border="true" v-model="scope.row.codeResult" :height="300"
                :index="1" />
            </template>
          </el-table-column>
        </el-table>
      </div>
      <el-container id="infoContainer" v-else>
        <el-aside id="aside" width="30%">
          <div class="aside-header">
            <div :class="['p-title color', 'system' + $themeId]">
              <i class="el-icon-s-order"></i>
              <p>实验目标</p>
            </div>
            <div class="goal">
              <div v-if="pd.experimentTargetType == 0 || !pd.experimentTargetType" class="ql-snow">
                <div class="ql-editor" v-html="pd.experimentTarget"></div>
              </div>

              <mavon-editor v-else class="md" v-model="pd.experimentTarget" defaultOpen="preview" :ishljs="true"
                :subfield="false" :editable="false" :toolbarsFlag="false" boxShadowStyle="none" />
            </div>
          </div>
          <div class="aside-footer">
            <div :class="['p-title color', 'system' + $themeId]">
              <i class="el-icon-s-management"></i>
              <p>实验任务</p>
            </div>
            <div>
              <el-row>
                <el-col :span="24">
                  <el-card shadow="never" :border="false">
                    <el-table class="task-table" :data="taskList" :stripe="true">
                      <el-table-column type="index"></el-table-column>
                      <el-table-column prop="name" label="判分点" align="center"></el-table-column>
                      <el-table-column prop="score" label="分值" width="60" align="center"></el-table-column>
                      <template v-if="!competitionId">
                        <el-table-column label="结果" width="60" align="center">
                          <template slot-scope="scope">
                            <template v-if="isSubmit">
                              <template v-if="!competitionId">
                                <i v-if="scope.row.finishedResult" class="el-icon-check right"></i>
                                <i v-else class="el-icon-close wrong"></i>
                              </template>
                              <template v-else>-</template>
                            </template>
                          </template>
                        </el-table-column>
                        <el-table-column prop="score" label="得分" width="60" align="center">
                          <template slot-scope="scope">
                            <template v-if="isSubmit">{{ competitionId ? '-' : scope.row.examScore }}</template>
                          </template>
                        </el-table-column>
                      </template>
                    </el-table>
                  </el-card>
                </el-col>
              </el-row>
            </div>
          </div>
        </el-aside>
        <el-main id="main">
          <el-tabs class="info-tab" v-model="pannelTab" type="card">
            <el-tab-pane label="项目背景" name="first">
              <div v-if="pd.experimentDescriptionType == 0 || !pd.experimentDescriptionType" class="ql-snow">
                <div class="ql-editor" v-html="pd.experimentDescription"></div>
              </div>

              <mavon-editor v-else class="md" v-model="pd.experimentDescription" defaultOpen="preview" :subfield="false"
                :editable="false" :toolbarsFlag="false" boxShadowStyle="none" />
            </el-tab-pane>
            <el-tab-pane label="实验要求" name="second">
              <el-collapse v-model="curReq">
                <el-collapse-item v-for="item in points" :name="item.judgmentId" :key="item.judgmentId">
                  <template slot="title">
                    <i class="el-icon-s-ticket"></i>
                    <div class="break-all des" v-html="item.name"></div>
                  </template>
                  <div v-if="item.experimentalRequirementsType == 0 || !item.experimentalRequirementsType"
                    class="ql-snow">
                    <div class="ql-editor" v-html="item.experimentalRequirements"></div>
                  </div>

                  <mavon-editor v-else class="md" v-model="item.experimentalRequirements" defaultOpen="preview"
                    :ishljs="true" :subfield="false" :editable="false" :toolbarsFlag="false" boxShadowStyle="none" />
                </el-collapse-item>
              </el-collapse>
            </el-tab-pane>
            <el-tab-pane label="实验提示" name="third" v-if="hintOpen">
              <div v-if="pd.experimentHintType == 0 || !pd.experimentHintType" class="ql-snow">
                <div class="ql-editor" v-html="pd.experimentHint"></div>
              </div>

              <mavon-editor v-else class="md" v-model="pd.experimentHint" defaultOpen="preview" :ishljs="true"
                :subfield="false" :editable="false" :toolbarsFlag="false" boxShadowStyle="none" />
            </el-tab-pane>
          </el-tabs>
        </el-main>
      </el-container>
    </el-container>

    <div :class="['toggle-panel', { active: pannelVisible, ai: third }]">
      <!-- <div @click="togglePannel"> -->
      <div>
        <i class="el-icon-rank drag-icon" id="drag"></i>
      </div>
      <img :src="require(`@/assets/images/system/${$themeId}/left.png`)" alt class="c-p" @click="togglePannel"
        v-if="pannelVisible" />
      <img :src="require(`@/assets/images/system/${$themeId}/right.png`)" alt class="c-p" @click="togglePannel"
        v-if="!pannelVisible" />
    </div>
  </div>
</template>

<script>
import newmain from "../util/newMain";
import quill from "@/components/quill";
import Util from '@/util'
import Config from '@/config'
import Cookie from 'js-cookie'
import 'quill/dist/quill.core.css';
import 'quill/dist/quill.snow.css';
import 'quill/dist/quill.bubble.css';

import { codemirror } from "vue-codemirror";
import "codemirror/theme/ambiance.css"; // 这里引入的是主题样式
import "codemirror/mode/javascript/javascript.js";
import "codemirror/mode/python/python.js";
import "codemirror/lib/codemirror.css";
// require active-line.js
import "codemirror/addon/selection/active-line.js";
// styleSelectedText
import "codemirror/addon/selection/mark-selection.js";
// hint
import "codemirror/addon/hint/show-hint.js";
import "codemirror/addon/hint/sql-hint.js";
import "codemirror/addon/hint/show-hint.css";
import "codemirror/addon/hint/javascript-hint.js";
// highlightSelectionMatches
import "codemirror/addon/scroll/annotatescrollbar.js";
import "codemirror/addon/search/matchesonscrollbar.js";
import "codemirror/addon/search/match-highlighter.js";
// keyMap
import "codemirror/mode/clike/clike.js";
import "codemirror/mode/sql/sql.js";
import "codemirror/addon/edit/matchbrackets.js";
import "codemirror/addon/comment/comment.js";
import "codemirror/addon/dialog/dialog.js";
import "codemirror/addon/dialog/dialog.css";
import "codemirror/addon/search/searchcursor.js";
import "codemirror/addon/search/search.js";
import "codemirror/keymap/sublime.js";
// foldGutter
import "codemirror/addon/fold/foldgutter.css";
import "codemirror/addon/fold/brace-fold.js";
import "codemirror/addon/fold/comment-fold.js";
import "codemirror/addon/fold/foldcode.js";
import "codemirror/addon/fold/foldgutter.js";
import "codemirror/addon/fold/indent-fold.js";
import "codemirror/addon/fold/markdown-fold.js";
import "codemirror/addon/fold/xml-fold.js";
// 编辑的主题文件
import "codemirror/theme/monokai.css";
import "codemirror/theme/base16-light.css";
import { mavonEditor } from 'mavon-editor'
import 'mavon-editor/dist/css/index.css'
import Stomp from 'stompjs'

export default {
  data () {
    return {
      third: Cookie.get('admin-third'), // 第三方平台用iframe嵌入
      token: Cookie.get('admin-token'),
      systemId: Cookie.get('admin-systemId') || 1,
      classId: Cookie.get('admin-classId'),
      className: Cookie.get('admin-className') ? Cookie.get('admin-className') : '',
      courseId: Cookie.get('admin-courseId'),
      projectId: Cookie.get('admin-projectId') ? Number(Cookie.get('admin-projectId')) : '',
      assessmentId: Cookie.get('admin-assessmentId'),
      competitionId: Cookie.get('admin-competitionId'),
      stageId: Cookie.get('admin-stageId'),
      teamId: Cookie.get('admin-teamId'),
      mallId: Cookie.get('admin-mallId'),
      resultsDetails: Cookie.get('admin-resultsDetails'),
      resultAnnouncementTime: Cookie.get('admin-resultAnnouncementTime'),
      curriculumName: Cookie.get('admin-curriculumName') ? decodeURIComponent(Cookie.get('admin-curriculumName')) : 'python', // 课程名称
      fromManager: Cookie.get('admin-fromManager'), // 是否是从教师端进入
      curSystemId: 1,
      projectPermissions: 0, // 项目权限(0、练习 1、考核 2、竞赛)
      isSubmit: Cookie.get('admin-isSubmit') == 'true' ? true : false, // 是否提交的标识
      entryTime: new Date(),
      startTime: Cookie.get('admin-startTime'),
      endTime: Cookie.get('admin-stopTime'),
      pannelVisible: true, // 实验面板显示标识
      grade: '00', // 得分
      text: '', // 倒计时前面的文字，练习：所用；考核：剩余。练习是计时，考核是倒计时
      counterTimer: null, // 获取setInterval对象值
      day: 0, // 天数
      seconds: 0, // 秒数
      minutes: 0, // 分钟数
      hour: 0, // 小时数
      now: '',
      projectList: [], // 项目列表
      pd: {},
      experimentTarget: '', //实验目标
      experimentDescription: '', //案例描述
      experimentHint: '', //实验提示
      hintOpen: 1, // 是否显示实验提示
      points: [], // 判分点列表
      judgmentId: '', // 当前判分点id
      curReq: [], // 当前实验要求
      taskList: [], // 实验任务列表
      pannelTab: 'first', // 面板信息切换值
      isSelected: false, // 是否选择过项目的标识，选择了会置为true
      reportId: '',

      cmOption: {
        scrollbarStyle: "native",
        tabSize: 2, // tab
        styleActiveLine: true, // 高亮选中行
        lineNumbers: true, // 显示行号
        styleSelectedText: true,
        line: true,
        foldGutter: true, // 块槽
        gutters: ["CodeMirror-linenumbers", "CodeMirror-foldgutter"],
        highlightSelectionMatches: { showToken: /\w/, annotateScrollbar: true }, // 可以启用该选项来突出显示当前选中的内容的所有实例
        mode: 'python',
        lineWrapping: true, //代码折叠
        autoCloseTags: true,// 自动闭合标签
        autoCloseBrackets: true,// 自动闭合括号
        // hint.js options
        hintOptions: {
          // 当匹配只有一项的时候是否自动补全
          completeSingle: false
        },
        // 快捷键 可提供三种模式 sublime、emacs、vim
        keyMap: "sublime",
        matchBrackets: true,
        showCursorWhenSelecting: true,
        // theme: "monokai" // 主题
      },
      reportTab: '',
      reportPoints: [],
      reportVisible: false,
      tableHeight: 0,
      dragIds: ['panelHeader', 'aside', 'main', 'infoContainer'],
      confirmSubmiting: false,
      submiting: false,
      client: '',
      accountId: '',
      runCodeType: '',
      finishVscodeItem: 0,
      vscodeRunCodeNum: 0,
      clientTopic: '',
    };
  },
  components: {
    codemirror,
    quill,
    mavonEditor
  },
  mounted () {
    this.init()
  },
  methods: {
    // 初始化
    async init () {
      // 2：竞赛，1：考核，0：练习
      this.projectPermissions = this.assessmentId ?
        1 :
        this.competitionId ?
          2 :
          0

      await this.getEntryTime()

      newmain.$on('changeRunStatus', val => {
        this.runCodeType = val
      })

      if (this.assessmentId) { // 考核（考核才会从外面带进来assessmentId，练习是默认显示第一个项目，竞赛会带进来competitionId)
        this.getAssList()
      } else { // 练习
        if (Cookie.get('doneProjectId')) { // 已经做完了实验，则直接恢复项目id
          if (!this.projectPermissions) this.projectId = +Cookie.get('doneProjectId')
        } else {
          // 没有初次加载的标识，说明不是第一次进来，而是当前页面刷新后进来这个方法，这时候则查最近一次跑的项目是哪个
          if (Cookie.get('admin-loaded')) {
            const { data } = await this.$get(`${this.api.getTheMostRecentlyRunProject}`, {
              cid: this.courseId
            })
            if (data && data.length) this.projectId = data[0].projectId
          } else {
            Cookie.set('admin-loaded', 1) // 初次加载的标识
          }
        }
        Cookie.remove('doneProjectId')
        // 获取项目列表
        await this.getList()
        if (!this.isSubmit) {
          this.getCache()
        } else {
          this.closeLoad()
        }
      }
      this.competitionId && this.getCompetitionStatus() // 查询竞赛信息
      this.getUserDetail()
      this.heartbeatDetection()
      this.drag()
      this.tableHeight = window.innerHeight - 360
    },
    // 获取项目列表
    getList () {
      let data = {
        systemId: this.systemId,
        cId: this.courseId, // 课程id
        mallId: this.mallId,
        permissions: this.projectPermissions // 练习/考核/竞赛
      }
      return new Promise((resolve, reject) => {
        this.$get(`${this.api.queryTestProject}`, data).then(async res => {
          const list = res.projects
          this.projectList = list
          if (!this.projectPermissions && !this.projectId) {
            this.projectId = list ? list[0].projectId : 0 // 默认取第一个项目
            await this.getEntryTime()
          }
          this.getProDetail().then(() => {
            resolve()
          }).catch(res => {
            reject()
          })
        }).catch(res => {
          reject()
        })
      })
    },
    // 获取项目详情
    getProDetail () {
      const { projectId } = this
      return new Promise((resolve, reject) => {
        this.$get(this.api.getProjectDetail, {
          projectId,
          stuAssessent: 1
        }).then(async (res) => {
          const points = res.projectJudgmentVos
          const project = res.projectManage
          const curReq = []
          // 跳转银行
          if (project.systemId == 11) {
            return location.href = `${Config.bankPath}/#/index/list?curriculumName=${this.curriculumName}&token=${this.token}&cid=${this.courseId}&systemId=${this.systemId}&projectId=${projectId}&assessmentId=&classId=&stopTime=&test=true`
          }
          if (!points.length) {
            this.$message.error('该项目没有判分点，请换个项目重试')
            this.projectId = this.projectList[0].projectId
            // this.getProDetail()
          }
          points.map((e, i) => {
            e.code = '' // 后端返回的字段没有code，要手动加上以存储运行的代码
            e.codeId = '' // 代码通过接口传给后端运行后，接口会返回一个codeId，提交的时候需要传这个codeId
            e.answer = '' // 代码运行结果
            e.retResult = '' // 返回结果(1为正确 0为错误)
            curReq.push(e.judgmentId)
          })
          // 考核/竞赛
          if (this.projectPermissions) {
            this.projectList = [{
              projectId,
              projectName: project.projectName
            }]
          }
          const { systemId } = project
          this.curSystemId = systemId
          this.$parent.getModelStatus(systemId)
          this.curReq = curReq // 实验要求默认全部展开，通过judgmentId来选中item
          this.points = points

          const reportPoints = JSON.parse(JSON.stringify(points))
          points.forEach(e => {
            e.codeResult = ''
          })
          this.reportPoints = reportPoints
          if (points.length) this.reportTab = reportPoints[0].judgmentId + ''
          this.taskList = this.isSubmit ? this.$store.state.taskList : points // 实验任务
          this.judgmentId = points[0].judgmentId // 默认取第一个判分点
          this.pd = project
          this.experimentTarget = project.experimentTarget
          this.experimentDescription = project.experimentDescription
          this.experimentHint = project.experimentHint
          this.hintOpen = project.founder ? !project.hintOpenBySchool : !project.hintOpen // 0显示，1不显示，系统跟老师的禁用字段不一样
          this.$emit('tell', projectId, systemId, this.points)
          const isPrac = this.projectPermissions == 0 // 是否是练习
          this.text = isPrac ? '已用' : '剩余'
          // 竞赛不需要
          if (!this.competitionId) {
            const now = await this.getNow()
            this.countVal = (isPrac ? now - this.entryTime : (new Date(this.endTime) - now)) / 1000 // 如果是考核，取考核的结束时间减去当前时间去做倒计时，练习则直接取当前时间减去上次进来的时间做计时
            this.startCount()
          }
          resolve()
        }).catch(err => {
          reject()
        })
      })
    },
    // 获取当前时间
    getSumTime () {
      return new Promise(async (resolve, reject) => {
        const res = await this.$get(this.api.getStartTime, {
          permissions: this.projectPermissions,
          projectId: this.projectId
        })
        resolve(res.startTime ? new Date(res.startTime) : '')
      })
    },
    // 获取当前时间
    getNow () {
      return new Promise(async (resolve, reject) => {
        const res = await this.$get(this.api.getCurrentTime)
        resolve(new Date(res.currentTime))
      })
    },
    // 获取进入时间
    getEntryTime () {
      return new Promise(async (resolve, reject) => {
        let now
        if (this.projectId) now = await this.getSumTime() // 获取第一次进入系统的时间
        if (!now) now = await this.getNow()
        this.entryTime = now
        resolve()
      })
    },
    // 关闭父页面的loading，并置加载完成状态为true
    closeLoad () {
      this.$parent.loadIns.close()
      this.$parent.loaded = true
    },
    // 设置isSubmit
    setSubmit (status) {
      this.isSubmit = status
      newmain.$emit('isSubmit', status)
      Cookie.set('admin-isSubmit', status)
    },
    // 获取考核列表来查询该考核是否已经考过
    getAssList () {
      this.$post(`${this.api.pageStuAssessment}`, {
        pageNum: 1,
        pageSize: 10000
      }).then(res => {
        const list = res.list
        const { assessmentId } = this
        let done = false
        const classId = this.classId
        // 匹配到该考核，并且已经提交过（有reportId则说明有实验记录），并且classId跟当前用户的classId相同，就提示提交了考核然后返回上一页
        if (list.find(e => e.assessmentId == assessmentId && e.reportId && e.classId == classId)) {
          done = true
          this.setSubmit(true)
          this.$message.error('你已经提交过该考核！')
          setTimeout(_ => {
            history.back() // 直接返回上一页面
          }, 1500)
        }
        // 如果该考核没有提交过，则取获取项目列表进行考核
        if (!done) {
          this.getList().then(() => {
            this.getCache()
          }).catch(res => { })
          // 查询考核状态定时器
          this.getAssStatus()
        }
      }).catch(res => { })
    },
    // 获取上次缓存记录
    async getCache () {
      if (this.isSubmit) {
        this.closeLoad()
        return false
      }
      const projectId = +this.projectId
      const cid = this.courseId
      const { assessmentId } = this
      const points = JSON.parse(JSON.stringify(this.points))
      // 如果是没有代码的缓存，则只需要恢复项目，否则，就调接口查询缓存代码
      if (Cookie.get('admin-projectId') && this.projectPermissions == 0) {
        Cookie.remove('admin-projectId')
      }
      let newJudgmentId = '' // 要恢复到第一个有代码的判分规则，因为有代码的规则可能不是第一个，所以要判断如果这个为空，才把索引赋给该变量
      const promiseList = [] // promise数组
      let hasCache = 0 // 是否有缓存
      points.map((e, i) => {
        const judgmentId = e.judgmentId
        promiseList.push(new Promise((resolve, reject) => {
          this.$post(this.api.getLastCache, {
            competitionId: this.competitionId,
            assessmentId: assessmentId ? Number(assessmentId) : '',
            bcId: judgmentId,
            projectId, // 项目id，同上
            cid // 课程id
          }).then(res => {
            this.closeLoad()
            const result = res.runRecord
            // 如果有缓存代码
            if (result) {
              hasCache = 1
              if (newJudgmentId === '') newJudgmentId = i
              e.finalCode = result.code
              e.code = result.code
              e.codeId = result.id
              e.answer = result.runResult
              e.retResult = result.retResult
              e.photoUrl = result.runPhotoUrl
            }
            resolve()
          }).catch(res => {
            reject()
            this.closeLoad()
          })
        }))
      })
      // 如果有缓存代码，再提示用户是否要继续上次的实验
      Promise.all(promiseList).then(_ => {
        hasCache && this.$confirm('是否要继续上次的操作记录？', '提示', {
          confirmButtonText: '是',
          cancelButtonText: '否',
          type: 'success',
          closeOnClickModal: false
        }).then(() => {
          this.projectId = projectId
          // 恢复第一个判分点的代码
          this.points.map(e => {
            const item = points.find(n => n.judgmentId === e.judgmentId)
            item.finalCode && this.$set(e, 'finalCode', item.finalCode)
            item.code && this.$set(e, 'code', item.code)
            item.codeId && this.$set(e, 'codeId', item.codeId)
            item.answer && this.$set(e, 'answer', item.answer)
            item.photoUrl && this.$set(e, 'photoUrl', item.photoUrl)
            this.$set(e, 'retResult', isNaN(item.retResult) ? '' : item.retResult)
          })
          this.$emit('tell', projectId, this.curSystemId, this.points)
          this.$emit('recoveryCode')
        }).catch(() => {
          points.map(async (e) => {
            await this.$post(this.api.deleteTheLastUncommittedRecord, {
              competitionId: this.competitionId,
              assessmentId: assessmentId ? Number(assessmentId) : '',
              bcId: e.judgmentId,
              projectId,
              cid
            })
          })
        })
      })
    },
    // 查询考核状态（查到考核如果结束后，直接提交考核）
    getAssStatus () {
      // 未提交才需要查询状态
      this.isSubmit || this.$get(this.api.getDetailById, {
        id: this.assessmentId
      }).then(res => {
        const done = res.data ? res.data.status === 2 : false // 状态(0、待开始 1、进行中 2、已结束)
        // 如果考核已结束，则清除查询考核状态的定时器，并且自动提交
        if (done) {
          this.$alert('考核时间已到，系统已自动交卷', '提示', {
            confirmButtonText: '确定'
          })
          this.submit()
        }
      }).catch(res => { })
    },
    // 查询竞赛状态（查到竞赛如果结束后，直接提交竞赛）
    getCompetitionStatus () {
      // 未提交才需要查询状态
      this.isSubmit || this.$post(`${this.api.getCompetition}?competitionId=${this.competitionId}`).then(async ({ competition }) => {
        const stages = competition.competitionStage
        if (stages) {
          const stage = stages.find(e => e.stageId == this.stageId)
          const endTime = new Date(stage.endTime)

          const res = await this.$get(this.api.getCurrentTime)
          const now = new Date(res.currentTime)
          // 如果已经结束
          if (now >= endTime) {
            this.$alert('竞赛时间已到，系统已自动交卷', '提示', {
              confirmButtonText: '确定'
            })
            this.submit()
          } else { // 没结束，则显示倒计时
            this.countVal = (endTime - now) / 1000
            this.startCount()
          }
        }
      }).catch(res => { })
    },
    // 项目选择回调
    async selectProject () {
      this.isSelected = true
      await this.getEntryTime()
      this.countVal = this.entryTime
      this.setSubmit(false)
      await this.getProDetail()
      this.getCache()
      this.grade = '00'
      this.pannelTab = 'first'
      this.clearReport()
      this.$emit('recoveryCode') // 切换实训项目后回到第一个判分点
    },
    // 切换实验报告输入
    toggleReport () {
      this.reportVisible = !this.reportVisible
    },
    // 清空实验报告填写里的内容
    clearReport () {
      this.taskList.forEach(e => {
        e.code = ''
        e.codeResult = ''
      })
    },
    // 查看实验报告
    toReport () {
      Cookie.set('doneProjectId', this.projectId) // 进入实验报告之前先存储当前项目id,从实验报告返回来后把这个id恢复
      const { reportId } = this.$store.state
      // 老师端进来的，直接跳转到实验报告，学生端进来的，跳转到实验记录里的实验报告
      if (this.fromManager) {
        this.$router.push(`/report?reportId=${reportId}`)
      } else {
        let href = location.origin
        if (this.$config.isDev) href = `http://${location.hostname}:8082`
        href += `/#/record/trialReport?python=1&reportId=${reportId}`
        // 练习
        if (this.projectPermissions == 0) {
          href += `&curriculumId=${this.courseId}&projectId=${this.projectId}`
        }
        location.href = href
      }
    },
    async reload () {
      this.reloadCount()
      this.grade = '00'
      this.setSubmit(false)
      const points = this.points
      // code和codeId,还有运行结果全部清空
      points.map(e => {
        e.finalCode = ''
        e.photoUrl = ''
        e.code = ''
        e.codeId = ''
        e.answer = ''
        e.retResult = ''
      })
      this.$emit('tell', this.projectId, this.curSystemId, points)
      this.$emit('recoveryCode')
      this.startCount()
      this.clearReport()
      this.entryTime = await this.getNow()

      // 清除代码缓存
      this.points.map(async (e) => {
        await this.$post(this.api.deleteTheLastUncommittedRecord, {
          competitionId: this.competitionId,
          assessmentId: this.assessmentId,
          bcId: e.judgmentId,
          projectId: this.projectId,
          cid: this.courseId,
        })
      })
    },
    //重新开始
    async reloadConfirm () {
      // 已提交状态则直接重新开始，未提交则询问弹框
      if (this.isSubmit) {
        this.reload()
      } else {
        try {
          await this.$confirm('<p style="color: #f56c6c;">点击重新开始，之前操作会清空。</p><p>确定重新开始吗？</p>', '提示', {
            confirmButtonText: '确定',
            cancelButtonText: '取消',
            type: 'warning',
            closeOnClickModal: false,
            dangerouslyUseHTMLString: true,
          })
          this.reload()
        } catch (e) { }
      }
    },
    // 页面加载完后重置编辑框大小
    ready (i) {
      // if (this.$refs['codemirror' + i]) {
      //   const code = this.$refs['codemirror' + i].codemirror
      //   if (code) {
      //     code.on('beforeChange', (istance, change) => {
      //       change.origin === 'paste' && change.cancel()
      //     })
      //   }
      // }
    },
    vscodeRunCode (data) {
      const i = +data.sort
      this.$parent.workbench[i].codeId = data.codeId
      this.$parent.workbench[i].retResult = 1
      this.finishVscodeItem++
      this.finishVscodeItem === this.vscodeRunCodeNum && this.submit()
    },
    emptyRunCode (data) {
      this.$parent.workbench[0].codeId = data.codeId
      this.submit()
    },
    // 提交询问
    confirmSubmit () {
      if (this.confirmSubmiting) return false
      this.confirmSubmiting = true
      const isVscode = this.$parent.language === 'vscode' // 是否选择了vscode
      const { taskList } = this
      const pointList = this.$parent.workbench
      let msg = '此操作将视为结束考试，是否继续？'
      if (!isVscode) {
        // 先判断是否有输了代码没有跑的情况，如果有，弹出该提示；
        if (pointList.find(e => !e.codeId && e.code)) msg = '有代码没有运行，该代码将不得分，确定提交？'
        // 再判断每个任务下是否有没有输代码的情况，如果有，弹出该提示
        if (pointList.find(e => !e.codeId && !e.code)) msg = '任务未完成，是否直接提交？'
      } else {
        if (!taskList.find(e => e.code || e.codeResult)) msg = '实验报告未填写，实验成绩为零，是否确认提交？'
      }
      this.vscodeRunCodeNum = 0
      this.finishVscodeItem = 0
      this.$confirm(msg, '提示', {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning',
        center: true,
        closeOnClickModal: false
      }).then(() => {
        // vscode类型的产品提交前需要自动运行每个判分点的代码以获取codeId
        if (isVscode) {
          taskList.map(async (e, i) => {
            if (e.code || e.codeResult) {
              this.vscodeRunCodeNum++
              this.runCodeType = 'vscodeRunCode'
              await this.$post(this.api.runPythonCode, {
                assessmentId: this.assessmentId,
                competitionId: this.competitionId,
                code: e.code,
                bcId: e.judgmentId,
                cid: this.courseId,
                projectId: this.projectId,
                userAnswer: e.codeResult || null,
                type: 'vscode',
                sort: i
              })
              this.$parent.workbench[i].finalCode = e.code
              this.$parent.workbench[i].answer = e.codeResult
            }
          })
        } else {
          // 如果全部都没运行直接点提交，则主动运行一个空代码。（不然会造成上次运行的结果，这次进来不运行直接提交的话，会无法取到运行结果）
          if (!pointList.find(e => e.codeId)) {
            this.runCodeType = 'emptyRunCode'
            this.$post(this.api.runPythonCode, {
              assessmentId: this.assessmentId,
              competitionId: this.competitionId,
              code: '',
              bcId: pointList[0].judgmentId,
              cid: this.courseId,
              projectId: this.projectId,
              type: this.$parent.language
            }).then(res => { }).catch(err => {
              this.confirmSubmiting = false
            })
          } else {
            this.submit()
          }
        }
      }).catch(() => {
        this.confirmSubmiting = false
      })
    },
    // 提交
    async submit () {
      if (this.isSubmit || this.submiting) return false
      this.submiting = true
      const pointList = this.$parent.workbench
      const date = await this.getNow()
      const { entryTime } = this
      const timeSum = Math.ceil((date - entryTime) / 60000) // 计算实验用时（分钟），向上取整
      const submitTime = Util.formatDate('yyyy-MM-dd hh:mm:ss', date)
      const projectId = this.projectId
      const pro = this.projectList.find(e => e.projectId == projectId)
      const projectName = pro ? pro.projectName : '' // 获取项目名称
      this.reloadCount()
      // 判分点参数
      const attributesReqList = []
      pointList.map(e => {
        attributesReqList.push({
          code: e.finalCode,
          codeId: e.codeId,
          bcId: e.judgmentId,
          isSubmit: e.codeId ? 1 : 0,
          answer: e.answer,
          photoUrl: e.photoUrl || '',
          retResult: e.retResult,
          judgmentName: e.name
        })
      })
      const data = {
        classId: this.classId ? this.classId : '',
        className: this.className ? this.className : '',
        curriculumId: this.courseId,
        startTime: Util.formatDate('yyyy-MM-dd hh:mm:ss', entryTime), // 取页面进入的时间
        endTime: this.projectPermissions ? this.endTime : submitTime, // 结束时间（考核：直接从职站取考核的结束时间；练习：取提交时间）
        submitTime, // 提交时间，即当前时间（这3个时间都是传完整的日期时间格式）
        timeSum,
        projectId,
        projectName,
        assessmentId: this.assessmentId ? this.assessmentId : '',
        totalScore: 100, // 判分点总分固定为100
        systemId: this.curSystemId,
        purpose: this.pd.experimentTarget, // 实验目的
        attributesReqList,
        competitionId: this.competitionId,
        stageId: this.stageId,
        teamId: this.teamId,
        mallId: this.mallId
      }

      this.$post(this.api.submit, data).then(({ retInfo, reportId }) => {
        this.setSubmit(true)
        this.reportVisible = false
        let list = retInfo
        let { taskList } = this
        let score = 0
        // 给判分列表添加分数和运行结果
        taskList.map(e => {
          let item = list.find(n => n.judgmentPointsId === e.judgmentId)
          if (item) {
            e.examScore = item.score
            e.finishedResult = item.finishedResult // 1：正确，2：错误
          } else {
            e.examScore = 0
          }
          score += e.examScore // 计算总分
        })
        this.grade = Util.handleZero(score) // 前置加0(竞赛不显示分数)
        this.reportId = reportId
        this.$store.commit('setReportId', reportId)
        this.$store.commit('setTaskList', taskList)
        this.confirmSubmiting = false
        this.submiting = false
        // 如果是竞赛，并且勾选了公布成绩详情的选项，则弹框提示
        if (this.competitionId) {
          const time = this.resultAnnouncementTime
          const msg =
            time === '0' ? '提交成功！成绩将在比赛结束后公布，请前往参赛信息模块查看' : time > 0 ? `提交成功！成绩将在比赛结束后${time}小时公布，请前往参赛信息模块查看` : '提交成功';
          this.$alert(msg, '提示', {
            confirmButtonText: '确定',
            callback: action => {
              this.$parent.back()
            }
          })
        } else {
          this.$message.success('提交成功！')
        }
      }).catch(err => {
        this.submiting = false
      })
    },
    // 编辑实验报告
    editReport (reportId) {
      const data = []
      const ans = []
      const promises = []
      this.taskList.map(e => {
        promises.push(new Promise((resolve, reject) => {
          data.push({
            judgmentId: e.judgmentId,
            judgmentName: e.name,
            answer: e.code,
            score: e.examScore,
            quesScore: e.score,
          })
          // 获取参考答案
          this.$get(this.api.queryBcJudgmentPointByBcId, {
            bcId: e.judgmentId
          }).then(({ judgmentPoint }) => {
            ans.push(judgmentPoint)
            resolve()
          }).catch(err => {
            reject()
          })
        }))
      })
      Promise.all(promises).then(_ => {
        data.map(e => {
          const item = ans.find(n => n.bcId === e.judgmentId)
          e.referenceAnswer = item.experimentCode
        })
        this.$post(this.api.editExperimentalData, {
          reportId,
          data: JSON.stringify(data)
        }).then(res => { }).catch(err => { })
      })
    },
    // 实验面板显示隐藏
    togglePannel () {
      document.querySelector('#panel').style.left = 0
      this.pannelVisible = !this.pannelVisible
      // if (this.pannelVisible) {
      //   document.querySelector('#toggle').onmousedown = null
      //   document.querySelector('#toggle').onmousedown = togglePannel
      // } else {
      //   this.drag()
      // }
    },
    // 倒计时
    timeFormat (param) {
      return param < 10 ? '0' + param : param
    },
    // 清除时间
    reloadCount () {
      clearInterval(this.counterTimer)
      this.countVal = ''
      this.day = '00'
      this.seconds = '00'
      this.minutes = '00'
      this.hour = '00'
    },
    // 计时前的判断
    counter (counterTime) {
      if (counterTime <= 0) {
        if (this.projectPermissions) { // 竞赛/考核
          clearInterval(this.counterTimer)
          this.$alert(`${this.projectPermissions == 2 ? '竞赛' : '考核'}时间已到，系统已自动交卷`, '提示', {
            confirmButtonText: '确定'
          })
          this.submit()
        } else {
          this.handleCounter(counterTime)
        }
      } else {
        this.handleCounter(counterTime)
      }
    },
    // 计时器（考核是倒计时，练习是计时）
    handleCounter (counterTime) {
      let leave1 = counterTime % (24 * 3600) //计算天数后剩余的毫秒数
      let leave2 = leave1 % 3600 //计算小时数后剩余的毫秒数
      let leave3 = leave2 % 60 //计算分钟数后剩余的毫秒数
      let day = Math.floor(counterTime / (24 * 3600)) //计算相差天数
      let hour = Math.floor(leave1 / 3600) //计算相差小时
      let minutes = Math.floor(leave2 / 60) //计算相差分钟
      let seconds = Math.round(leave3) //计算相差秒
      day = this.timeFormat(day)
      hour = this.timeFormat(hour)
      minutes = this.timeFormat(minutes)
      seconds = this.timeFormat(seconds)
      this.day = day
      this.hour = hour
      this.minutes = minutes
      this.seconds = seconds
    },
    // 启动倒计时
    startCount () {
      clearInterval(this.counterTimer)
      this.counterTimer = setInterval(() => {
        this.counter(this.projectPermissions ? this.countVal-- : this.countVal++)
      }, 1000)
    },
    // 拖拽面板
    drag () {
      document.querySelector('.scrollbar').onmousedown = null
      document.querySelector('#drag').onmousedown = null

      const el = document.querySelector('#panel')
      const drag = e => {
        if (this.dragIds.includes(e.target.id)) {
          e.stopPropagation && e.stopPropagation()
          e.preventDefault && e.preventDefault()
          e.cancelBubble = true
          e.returnValue = false
          let x = e.clientX - el.offsetLeft
          let y = e.clientY - el.offsetTop
          let left = 0
          let top = 0

          document.onmousemove = function (eve) {
            left = eve.clientX - x
            top = eve.clientY - y
            el.style.left = left + 'px'
            el.style.top = top + 'px'
          }
          document.onmouseup = e => {
            document.onmousemove = null
            document.onmouseup = null
          }
        }
      }
      document.querySelector('.scrollbar').onmousedown = drag


      const drag1 = e => {
        e.stopPropagation && e.stopPropagation()
        e.preventDefault && e.preventDefault()
        e.cancelBubble = true
        e.returnValue = false

        // let timer = setTimeout(this.togglePannel, 200)

        let x = e.clientX - el.offsetLeft
        let y = e.clientY - el.offsetTop

        let left = 0
        let top = 0

        document.onmousemove = function (eve) {
          // clearTimeout(timer)
          left = eve.clientX - x
          top = eve.clientY - y
          el.style.left = left + 'px'
          el.style.top = top + 'px'
        }
        document.onmouseup = e => {
          document.onmousemove = null
          document.onmouseup = null
        }
      }
      // document.querySelector('#toggle').onmousedown = this.pannelVisible ? this.togglePannel : drag1
      document.querySelector('#drag').onmousedown = drag1
    },

    // websocket获取考核及竞赛信息，用于自动提交
    // socket连接成功
    open () {
      // console.log("socket连接成功");
    },
    // socket连接失败
    error () {
      // console.log("连接错误");
    },
    // 接收消息
    getMessage (msg) {
      // console.log("==websocket接收数据==");
      // console.log(JSON.parse(msg.data));
      const { content } = JSON.parse(msg.data)
      // 1赛事、2创业、3考核、4模型。-号拼接携带id
      if (content == 1 && this.projectPermissions === 2) {
        this.getCompetitionStatus()
      } else if ((content === '3-' + this.assessmentId) && this.projectPermissions === 1) { // 考核：3-考核id
        this.getAssStatus()
      }
    },
    // 关闭socket
    close () {
      // console.log("socket已经关闭");
    },
    // 初始化socket
    initSocket ({ id, account }) {
      // 实例化socket
      this.socket = new WebSocket(`${this.$config.isTest ? 'ws' : 'wss'}://${this.$config.isDev ? '192.168.31.51:9100' : location.host}/nakadai/websocket/${id}/${account}`)
      // this.socket = new WebSocket(`ws://121.37.12.51:9100/nakadai/websocket/${id}/${account}`)
      // 监听socket连接
      this.socket.onopen = this.open;
      // 监听socket错误信息
      this.socket.onerror = this.error;
      // 监听socket消息
      this.socket.onmessage = this.getMessage;
    },

    // 消息队列获取
    connect () {
      let ws = new WebSocket(`wss://python.occupationlab.com/ws`);
      // let ws = new WebSocket(`wss://python.occupationlab.com/ws`);
      this.client = Stomp.over(ws);
      this.client.heartbeat.outgoing = 0
      this.client.heartbeat.incoming = 0
      //初始化连接
      const headers = {
        login: 'huoran',
        passcode: 'huoran2024'
      };
      //进行连接
      this.client.connect(headers.login, headers.passcode, this.onConnected, this.onFailed, this.$config.isTest ? `pytest` : 'pyhost')
    },
    onConnected () {
      //订阅频道
      this.clientTopic = '/amq/queue/pythonQueue_' + this.accountId;
      this.client.subscribe(this.clientTopic, this.responseCallback, this.onFailed);
    },
    onFailed (frame) {
      console.log("MQ Failed: " + frame);
      // this.client.reset().start();
      this.connect()
    },
    // 回传消息
    responseCallback (frame) {
      const data = JSON.parse(frame.body)

      console.log("接收信息：", data, typeof data, newmain, newmain.$emit);
      if (typeof data === 'object') {
        newmain.$emit('setPid', '')
        if (this.runCodeType) {
          this[this.runCodeType](data)
        } else {
          newmain.$emit('runCode', data)
        }
        if (this.runCodeType !== 'vscodeRunCode') this.runCodeType = ''
      } else if (typeof data === 'string' && data !== 'ping') {
        newmain.$emit('setPid', data)
      }
    },
    // 断开相应的连接
    close () {
      this.client.disconnect(function () {
        console.log("已退出账号");
      })
    },

    // 心跳检测
    heartbeatDetection () {
      setInterval(async () => {
        // await this.$get(this.api.heartbeatDetectionRabbit)
        await this.$get(this.api.heartbeatDetection)
      }, 55 * 1000)
    },
    // 获取用户详情
    getUserDetail () {
      this.$get(this.api.queryUserInfoDetails).then(async (res) => {
        this.accountId = res.result.userAccount.id
        await this.$get(this.api.createQueue)
        this.connect();
        this.initSocket(res.result.userAccount)
      }).catch(res => { })
    },
  }
};
</script>

<style lang="scss" scoped>
.el-main {
  width: 60%;
  background-color: #fff;
  color: #333;
  padding: 0;
  font-size: 16px;
  margin: 0px 20px 10px 10px;
  overflow: hidden;
}

.panel-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  flex-wrap: wrap;
  height: auto !important;

  .project {
    display: inline-flex;
    align-items: center;
    width: 28%;
  }

  .item {
    font-size: 16px;
    margin: 0 10px;
    padding: 20px 0;
  }

  .count {
    margin-left: -40px;

    span {
      padding: 5px 15px;
      margin: 0 5px;
      color: #333;
      font-size: 14px;
      text-align: center;
      background: #fff;
      border-radius: 18px;
    }
  }

  .total-score {
    padding: 10px;
    font-size: 14px;
    text-align: center;
    border-radius: 6px;
  }

  .actions {
    z-index: 1001;
  }

  .submit {
    width: 106px;
    font-size: 16px;
  }

  .reload {
    color: #d0d0d0;
    font-size: 16px;
    background-color: #202020;
  }
}

.whitespace-nowrap {
  white-space: nowrap;
}

/deep/.des {
  font-size: 16px;
  font-family: 'Microsoft YaHei';

  img {
    max-width: 100%;
  }
}

/deep/.ql-editor {
  font-family: 'Microsoft Yahei';
  font-size: 13px;

  .ql-syntax {
    font-family: 'Microsoft Yahei';
  }
}

/deep/.el-collapse-item__wrap {
  border-bottom: none;
}

/deep/.el-collapse-item__header {
  border-bottom: none;
}

/deep/.el-icon-s-ticket:before {
  padding: 5px;
  font-size: 16px;
}

/deep/.el-collapse-item__arrow {
  margin: 0 5px 0 0;
}

/deep/.info-tab.el-tabs--card {
  .el-tabs__item {
    font-size: 16px;
  }

  .el-tabs__item.is-active {
    color: #fff;
  }

  .el-tabs__header .el-tabs__nav {
    border: none;
  }

  .el-tabs__header .el-tabs__item {
    border-left: none;
  }

  .el-tabs__header {
    padding: 5px 20px;
    border-bottom: none;
  }

  &>.el-tabs__content {
    margin: 0 20px;
    max-height: calc(60vh - 70px);
    overflow: auto;
  }
}

/deep/.el-collapse {
  border-bottom: none;
  border-top: none;
}

.el-aside {
  margin-bottom: 10px;
  color: #333;
  background-color: #fff;
}

.el-aside /deep/[class*=' el-icon-'],
[class^='el-icon-'] {
  line-height: 40px;
  font-size: 16px;
}

.aside-header {
  margin: 0px 10px 10px 10px;
  background-color: #fff;
}

.aside-footer {
  margin: 0px 10px 10px 10px;
  background-color: #fff;
}

.p-title {
  display: flex;
  justify-content: center;
  height: 40px;

  &.system4 {
    background-size: 100% 58px;
  }

  &.system7,
  &.system9 {
    background-size: 100% 40px;
  }

  &.system8,
  &.system6 {
    background-size: 100% 61px;
  }

  p {
    padding-left: 10px;
    line-height: 40px;
    font-size: 16px;
    color: #fff;
  }

  i {
    color: #fff;
  }
}

/deep/.el-card__body {
  padding: 0;
}

/deep/.task-table {
  font-size: 12px;

  thead {
    color: #fff;
    font-size: 10px;
  }

  th>.cell {
    font-weight: 100;
  }

  td,
  th.is-leaf {
    border-bottom: 0 !important;
  }

  .el-table__cell {
    padding: 6px 0;
  }
}

.goal {
  padding: 10px 0;
  margin: 0 10px;
  font-size: 14px;
}

/deep/.select {
  min-width: 180px;

  .el-select__caret:before {
    content: '\e78f';
    padding: 3px;
    font-size: 16px;
    color: #fff;
    border-radius: 50%;
  }

  .el-input__icon {
    line-height: 60px;
  }

  .el-input {
    padding: 10px 0;
  }

  .el-input--suffix .el-input__inner {
    height: 40px !important;
    padding-right: 50px;
    margin-left: 15px;
    color: #333;
    font-size: 14px;
    border-radius: 30px;
    border: none;
    background-color: #fff;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }
}

.panel {
  z-index: 1000;
  position: relative;
  top: 200px;
  bottom: 20px;
  left: 0;
  width: 0;
  height: 0;

  .toggle-panel {
    position: absolute;
    top: 60%;
    text-align: center;

    &.ai:not(.active) {
      position: fixed;
      top: 45%;
    }

    &.active {
      left: 100%;
    }

    img {
      height: 150px;
    }

    .drag-icon {
      font-size: 20px;
      line-height: 1;
      color: #fff;
      cursor: pointer;
    }
  }

  &.active {
    position: fixed;
    width: 85%;
    height: 70%;

    .toggle-panel {
      top: 38%;
    }
  }
}

/deep/.el-container {
  height: 100%;

  &.is-vertical {
    background-color: #f5f5f5;
  }
}

.right {
  color: #00af00;
  font-size: 20px;
}

.wrong {
  color: #f00;
  font-size: 20px;
}

.info {
  color: #bfbfbf;
  cursor: pointer;

  &:hover {
    opacity: 0.9;
  }
}

/deep/.report-table.el-table {
  th {
    background-color: #c6daff !important;
  }
}

.report-wrap {
  position: relative;
  padding-top: 5px;
}

.close-report {
  z-index: 2;
  position: absolute;
  top: -21px;
  right: 296px;
  font-size: 32px;
  color: #c6daff;
  cursor: pointer;
}

/deep/.v-note-wrapper {
  min-height: 0;
  height: auto;
  font-family: 'Microsoft Yahei';

  .v-note-panel .v-note-show {
    font-family: 'Microsoft Yahei';
    overflow: visible;

    .v-show-content {
      font-family: 'Microsoft Yahei';
      overflow: visible;
    }

    pre,
    code {
      font-family: 'Microsoft Yahei';
    }
  }
}

.text-danger {
  color: #f56c6c;
}

@media (max-width: 1550px) {
  .panel-header {
    .count {
      margin-left: 0;
    }
  }
}

@media (max-width: 1400px) {
  .panel-header {
    .actions {
      position: absolute;
      top: 70px;
      right: 30px;
    }
  }
}
</style>