278c88d55beb177a9420113d84be774d97c797f5..d7e53e63dd6c435e226d9f08cde31ca35131c911
2025-04-14 闫增涛
Merge branch 'master' of http://182.92.203.7:2001/r/TextbookReader
d7e53e 对比 | 目录
2025-04-14 闫增涛
番茄闹钟
2cac4a 对比 | 目录
7个文件已修改
2个文件已添加
419 ■■■■■ 已修改文件
.gitignore 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/App.vue 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/js/config.ts 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/main.css 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/style/examination.less 54 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/child.ts 54 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/hooks/public.ts 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/components/pomodoro.vue 197 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/readerPages/webHome.vue 80 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.gitignore
@@ -11,7 +11,7 @@
node_modules
*.lock
package-lock.json
*.mp3
# 编译文件
dist
dist-electron
src/App.vue
@@ -142,7 +142,6 @@
</script>
<style lang="less">
@import './assets/style/examination.less';
.updateDownloadInfo {
  position: fixed;
  top: 2px;
src/assets/js/config.ts
@@ -1,8 +1,8 @@
// 测试
export const requestCtx = "https://jsek.bnuic.com"; //jsek
export const appId = 3;
// export const requestCtx = "http://182.92.203.7:3007"; //jsek
// export const appId = 27
// export const requestCtx = "https://jsek.bnuic.com"; //jsek
// export const appId = 3;
export const requestCtx = "http://182.92.203.7:3007"; //jsek
export const appId = 27
export const requestTimeOut = 300000; // 请求超时时间
export const tokenKey = "token";
export const userInfoKey = "website-front-userInfo"; // 用户信息key 
src/assets/main.css
@@ -1,5 +1,5 @@
@import './base.css';
@import './style/examination.less';
#parentApp {
  width: 100%;
  height: 100%;
src/assets/style/examination.less
@@ -45,13 +45,13 @@
    text-indent: 0em !important;
    margin-left: 20px;
    /deep/ .el-textarea__inner:focus {
    .el-textarea__inner:focus {
      border-color: #15c0f2;
    }
  }
  .input {
    /deep/ .el-input__inner {
    .el-input__inner {
      height: 26px;
    }
  }
@@ -186,11 +186,11 @@
          border-bottom: 1px solid #000;
        }
        /deep/ .el-textarea.is-disabled .el-textarea__inner {
        .el-textarea.is-disabled .el-textarea__inner {
          background-color: #fff;
        }
        /deep/ .el-input.is-disabled .el-input__inner {
        .el-input.is-disabled .el-input__inner {
          background-color: #fff;
        }
@@ -199,11 +199,8 @@
          align-items: center;
        }
      }
      ::v-deep {
        .el-input__wrapper {
          border-top: 0 !important;
        }
      .el-input__wrapper {
        border-top: 0 !important;
      }
    }
@@ -232,12 +229,12 @@
      white-space: pre-wrap !important;
      color: #000;
      /deep/ .el-checkbox__label {
      .el-checkbox__label {
        line-height: 1.5;
      }
    }
    /deep/ .el-radio__inner {
    .el-radio__inner {
      border-color: #a5a3a3;
    }
@@ -262,7 +259,7 @@
  /** 解析 */
  .objective {
    /deep/ .el-collapse-item__header {
    .el-collapse-item__header {
      min-height: 48px;
      height: min-content;
      padding: 0 20px;
@@ -332,11 +329,8 @@
              overflow: hidden;
              text-overflow: ellipsis;
              white-space: nowrap;
              ::v-deep {
                img {
                  max-height: 48px;
                }
              img {
                max-height: 48px;
              }
            }
          }
@@ -352,11 +346,11 @@
    }
  }
  /deep/ .el-collapse-item__header:focus:not(:hover) {
  .el-collapse-item__header:focus:not(:hover) {
    color: #333;
  }
  /deep/ .el-collapse-item__content {
  .el-collapse-item__content {
    width: 93%;
    padding: 0 20px;
    background-color: #f4f4f4;
@@ -364,7 +358,7 @@
    color: #333;
  }
  /deep/ .el-collapse-item__arrow {
  .el-collapse-item__arrow {
    display: none;
  }
@@ -378,7 +372,7 @@
  }
  .subjective {
    /deep/ .el-collapse-item__header {
    .el-collapse-item__header {
      height: 80px;
      background-color: #f4f4f4;
      width: 100%;
@@ -457,7 +451,7 @@
    height: min-content;
    margin-bottom: 6px;
    /deep/ img {
    img {
      margin-left: 10px;
      object-fit: contain;
      vertical-align: middle;
@@ -480,13 +474,11 @@
  }
  .titleTextArea {
    ::v-deep {
      img,
      .el-image__inner {
        max-width: 150px !important;
        max-height: 100px !important;
        object-fit: contain;
      }
    img,
    .el-image__inner {
      max-width: 150px !important;
      max-height: 100px !important;
      object-fit: contain;
    }
  }
@@ -636,9 +628,9 @@
    object-fit: contain;
  }
  /deep/ .examination-math {
  .examination-math {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
  }
}
}
src/child.ts
@@ -106,6 +106,60 @@
    entry: '//182.92.203.7:3007/books/book/botany',
    container: '#container',
    activeRule: '/home' //匹配所有以/subPath开头的为子应用
  },
  aurturingAndEducationAged0to3:{
    name: 'app-content',
    entry: '//182.92.203.7:3007/books/book/aurturingAndEducationAged0to3',
    container: '#container',
    activeRule: '/home' //匹配所有以/subPath开头的为子应用
  },
  childcareInstitutionsManagement:{
    name: 'app-content',
    entry: '//182.92.203.7:3007/books/book/childcareInstitutionsManagement',
    container: '#container',
    activeRule: '/home' //匹配所有以/subPath开头的为子应用
  },
  preschoolGameGuidance:{
    name: 'app-content',
    entry: '//182.92.203.7:3007/books/book/preschoolGameGuidance',
    container: '#container',
    activeRule: '/home'
  },
  kindergartenLanguageActivity:{
    name: 'app-content',
    entry: '//182.92.203.7:3007/books/book/kindergartenLanguageActivity',
    container: '#container',
    activeRule: '/home'
  },
  preschoolEvaluation:{
    name: 'app-content',
    entry: '//182.92.203.7:3007/books/book/preschoolEvaluation',
    container: '#container',
    activeRule: '/home'
  },
  preschoolBasicKnowledge:{
    name: 'app-content',
    entry: '//182.92.203.7:3007/books/book/preschoolBasicKnowledge',
    container: '#container',
    activeRule: '/home'
  },
  kindergartenActivitiesDesignGuidance:{
    name: 'app-content',
    entry: '//182.92.203.7:3007/books/book/kindergartenActivitiesDesignGuidance',
    container: '#container',
    activeRule: '/home'
  },
  policiesAndRegulations:{
    name: 'app-content',
    entry: '//182.92.203.7:3007/books/book/policiesAndRegulations',
    container: '#container',
    activeRule: '/home'
  },
  OralAndBroadcasting:{
    name: 'app-content',
    entry: '//182.92.203.7:3007/books/book/OralAndBroadcasting',
    container: '#container',
    activeRule: '/home'
  }
}
src/hooks/public.ts
New file
@@ -0,0 +1,21 @@
export const useFormatData = () => {
  const formatTime = (time:number) => {
    if(time == 0) return '00:00'
    const minutes = Math.floor(time / 60)
    const remainingSeconds = time % 60
    return `${String(minutes).padStart(2, '0')}:${String(remainingSeconds).padStart(2, '0')}`
  }
  return {
    formatTime
  }
}
export const useCoordinateData = () => {
  const getPageXY = (e:MouseEvent) => {
  }
  return {
    getPageXY
  }
}
src/views/components/pomodoro.vue
New file
@@ -0,0 +1,197 @@
<template>
  <el-dialog v-model="dialogVisable" title="番茄闹钟" width="500" >
    <div class="ring-box">
      <span class="title-txt">响铃时长</span>
      <el-switch v-model="pageData.isRing" :disabled="isRuning" />
      <el-input-number v-model="pageData.ringTime" :disabled="isRuning" :min="1" step-strictly />
    </div>
    <p class="prompt-txt ring-title">以秒为单位</p>
    <div class="border" ></div>
    <ul class="sustain-box">
      <li>
        <span class="title-txt">持续时间</span>
        <span class="label-txt" >专注时间</span>
        <el-input-number v-model="pageData.workTime" :disabled="isRuning" :min="1" step-strictly />
      </li>
      <li>
        <span class="title-txt"></span>
        <span class="label-txt">短休息</span>
        <el-input-number v-model="pageData.shortTime" :disabled="isRuning" :min="0" step-strictly />
      </li>
      <li>
        <span class="title-txt"></span>
        <span class="label-txt">长休息</span>
        <el-input-number v-model="pageData.longTime" :disabled="isRuning" :min="0" step-strictly />
      </li>
    </ul>
    <p  class="prompt-txt ring-title" >以分钟为单位</p>
    <div class="border" ></div>
    <div>
      <el-button type="primary" @click="startFun" >开始</el-button>
      <el-button type="primary" @click="pauseFun" >停止</el-button>
      <el-button @click="resetFun">重置</el-button>
      <el-button @click="handleRestFun(false)" >长休息</el-button>
      <el-button @click="handleRestFun(true)">短休息</el-button>
    </div>
    {{ formatTime(pageData.currentTime) }}
  </el-dialog>
  <!-- <video src="../../assets/audio/买辣椒也用券.mp3"  ref="audioRef"></video> -->
</template>
<script setup lang="ts">
import { ElMessage } from 'element-plus'
import { ref, reactive, defineExpose ,watchEffect } from 'vue'
import {useFormatData} from '@/hooks/public'
const isShow =ref<boolean>(false)  // 悬浮框是否显示
const audioRef = ref<HTMLAudioElement>()  // 音频实例
const dialogVisable = ref<boolean>(false)  // 弹窗状态
const timerInterval = ref<any>(null)  // 计时器实例
const isRuning = ref<boolean>(false)  // 计时器是否运行
const isBreak = ref<boolean>(false)  // true 休息  false 工作
const isShortTime = ref<boolean>(false)  // 长短休息模式
const pageData = reactive({
  isRing: true, // 是否响铃
  ringTime: 5, // 响铃时长  秒
  workTime: 25, // 工作时长  分钟
  saveWorkTime: 0,  // 剩余工作时间
  shortTime: 5, // 短休息
  longTime: 5, // 长休息
  currentTime:0,  // 显示时间  秒
})
const { formatTime } = useFormatData()
watchEffect(() => {
  pageData.currentTime = pageData.workTime * 60
})
watchEffect(() => {
  if(!isBreak.value) pageData.saveWorkTime = pageData.currentTime
})
const setDialogVisable = (value: boolean) => {
  dialogVisable.value = value
}
  // 开始计时
const startFun = () => {
  isShow.value = true
  isRuning.value = true
  timerInterval.value = setInterval(() => {
    if(pageData.currentTime > 0) {
      pageData.currentTime--
    } else {
      playAudio()
      if(isBreak.value) {
        // 工作时间赋值
        isBreak.value = false
        if( pageData.saveWorkTime) {
          pageData.currentTime =  pageData.saveWorkTime
          ElMessage.success('休息结束,开始学习吧!')
        } else {
          clearInterval(timerInterval.value)
          isRuning.value = false
          ElMessage.success('学习结束')
        }
      } else {
        // 休息时间赋值
        isBreak.value = true
        pageData.currentTime = isShortTime.value ? pageData.shortTime * 60 : pageData.longTime * 60
        ElMessage.success('休息一下吧!')
      }
    }
  }, 1000)
  dialogVisable.value = false
}
// 暂停计时
const pauseFun = () => {
  isRuning.value =  false
  if(timerInterval.value) {
    clearInterval(timerInterval.value)
  }
}
// 重置计时器
const resetFun = () =>  {
  isRuning.value = false
  isBreak.value = false
  pauseFun()
  pageData.currentTime = pageData.workTime * 60
}
// 休息按钮
const startBreak = () => {
  if(isRuning.value && !isBreak.value) {
    if(pageData.shortTime || pageData.longTime) {
      pageData.saveWorkTime = pageData.currentTime
      isBreak.value = true
      pageData.currentTime = isShortTime.value ? pageData.shortTime * 60 : pageData.longTime * 60
      ElMessage.success('开始休息')
    } else {
      ElMessage.warning('暂未设置休息时间')
    }
  }
}
// 开始响铃
const playAudio = () => {
  audioRef.value!.currentTime = 0
  audioRef.value?.play()
  setTimeout(() => {
    pauseAudio()
  },pageData.ringTime * 1000)
}
 // 暂停响铃
const pauseAudio = () => {
  audioRef.value?.pause()
}
// 长短休息
const handleRestFun = (value:boolean) => {
  isShortTime.value = value
  startBreak()
}
defineExpose({
  isShow,
  isRuning,
  pageData,
  setDialogVisable,
  startFun,
  pauseFun,
  resetFun,
  handleRestFun
})
</script>
<style lang="less" scoped>
.prompt-txt {
  margin-top: 10px;
  font-size: 12px;
  color: #b7b7b7;
}
.title-txt {
  color: #2c2c2c;
  width: 4em;
}
.label-txt {
  width: 4em;
  color:#707070 ;
}
.ring-box {
  width: 300px;
  display: flex;
  justify-content: space-between;
  align-items: center;
}
.ring-title {
  padding-left: 150px;
}
.sustain-box {
  width: 300px;
  li {
    display: flex;
    justify-content: space-between;
    margin-bottom: 10px;
  }
}
.border {
  height: 1px;
  background-color:#cecece ;
  margin: 10px 0;
}
</style>
src/views/readerPages/webHome.vue
@@ -649,6 +649,18 @@
            <div class="canvas-box" v-show="canvasShow">
              <canvas id="canvasRef" :width="canvasWith" :height="canvasheight"></canvas>
            </div>
            <!-- 番茄闹钟组件 -->
            <div
              class="watch-box"
              v-if="pomodoroRef && pomodoroRef.isShow"
              :style="{ top:position.x + 'px', left:position.y + 'px'}"
            >
              <p @mousedown.native="mouseDown">{{ formatTime(pomodoroRef.pageData.currentTime) }}</p>
              <span @click="()=> pomodoroRef.isRuning ? pomodoroRef.pauseFun() : pomodoroRef.startFun()">{{pomodoroRef.isRuning ? '暂停': '开始'}}</span>
              <span @click="pomodoroRef.resetFun" >重置</span>
              <span @click="pomodoroRef.handleRestFun(false)">长休息</span>
              <span @click="pomodoroRef.handleRestFun(true)">短休息</span>
            </div>
          </div>
        </div>
        <!-- 画笔组件 -->
@@ -1325,14 +1337,19 @@
    <!-- <calculatorNew></calculatorNew> -->
    <iframe src="https://www.geogebra.org/scientific" frameborder="0" class="iframe-box"></iframe>
  </el-dialog>
  <!-- 番茄闹钟 -->
  <!-- <pomodoro ref="pomodoroRef" /> -->
</template>
<script setup lang="ts">
import examination from '@/views/examination/index.vue'
import pomodoro from '@/views/components/pomodoro.vue'
import { ref, reactive, watch, onMounted, onBeforeMount, onBeforeUnmount, inject } from 'vue'
import { useRouter, useRoute } from 'vue-router'
import { ElMessage, ElMessageBox, valueEquals } from 'element-plus'
import { useFormatData } from '@/hooks/public'
import useClipboard from 'vue-clipboard3'
const { toClipboard } = useClipboard()
const { formatTime } = useFormatData()
const MG: any = inject('MG')
const toolClass = inject('toolClass')
//获取路由器
@@ -1642,6 +1659,7 @@
  }
  MG.store.getProductList(obj).then((res) => {
    console.log(res.datas, '图书信息')
    if (!res.datas.length) return
    bookInfo.value = res.datas[0]
    if (res.datas[0].purchasedSaleMethodIdList.indexOf(res.datas[0].defaultSaleMethodId) > -1) {
      isBuy.value = true
@@ -1815,6 +1833,11 @@
      icon: fanqiezhong,
      isShow: bookConfig.value.textbookComponents.indexOf('E4DC9777') > -1
    }
    // {
    //   name: '番茄闹钟',
    //   icon: fanqiezhong,
    //   isShow: true
    // }
  ]
  floatingToolBox = [
    {
@@ -2921,7 +2944,7 @@
      break
  }
}
type pomodoroType = InstanceType<typeof Pomodoro>
const baiduVisible = ref(false)
const wendaVisible = ref(false)
const cidianVisible = ref(false)
@@ -2931,13 +2954,14 @@
const modelTool = ref(false)
const shengciVisble = ref(false)
const calculatorVisble = ref(false)
const pomodoroRef = ref<pomodoroType>()
const activeTool = ref(0)
const toolState = reactive({
  open: true
})
const resourceUrl = ref('')
const selectTeachTools = (item) => {
const selectTeachTools = (item: any) => {
  if (token) {
    activeTool.value = item.name
    switch (item.name) {
@@ -2962,6 +2986,8 @@
        break
      case '计算器':
        calculatorVisble.value = true
      case '番茄闹钟':
        pomodoroRef.value.setDialogVisable(true)
        break
    }
  } else {
@@ -4464,6 +4490,29 @@
const openFormulaDialog = () => {
  formulaDialog.value = true
}
// 番茄钟移动
const isMove = ref<boolean>(false)
const position = reactive({x:100,y:100})
const dragOffset = reactive({x:0,y:0})
const mouseDown = (e: MouseEvent) => {
  isMove.value = true
  dragOffset.x = e.clientX - position.x
  dragOffset.y = e.clientY - position.y
  document.addEventListener('mousemove', mouseMove)
  document.addEventListener('mouseup', mouseUp)
}
const mouseUp = (e: MouseEvent) => {
  isMove.value = false
  document.removeEventListener('mousemove', mouseMove)
  document.removeEventListener('mouseup', mouseUp)
}
const mouseMove = (e: MouseEvent) => {
  if (isMove.value) {
    position.x = e.clientX - dragOffset.x
    position.y = e.clientY - dragOffset.y
  }
}
</script>
<style lang="less">
@@ -5214,6 +5263,33 @@
          z-index: 96;
          background: rgba(255, 255, 255, 0.2);
        }
        .watch-box {
          display: flex;
          align-items: center;
          position: absolute;
          background-color: #555;
          color: #fff;
          padding: 10px;
          border-radius: 5px;
          width: max-content;
          p {
            cursor: move;
            padding: 0 8px;
            font-size: 18px;
            border-radius: 4px;
            margin-right: 10px;
            background-color: #666;
          }
          span {
            cursor: pointer;
            border-right: 1px solid #fff;
            padding: 0 4px;
            font-size: 14px;
            &:last-child {
              border-right: none;
            }
          }
        }
      }
    }