diff --git a/package.json b/package.json
index 80f69fc..6b2d3aa 100644
--- a/package.json
+++ b/package.json
@@ -48,6 +48,7 @@
"pinia": "^2.1.7",
"pinia-plugin-persistedstate": "^3.2.1",
"spark-md5": "^3.0.2",
+ "vue-qr": "^4.0.9",
"vue-router": "^4.4.0",
"xgplayer": "^3.0.19",
"xlsx": "^0.18.5"
diff --git a/src/main/index.js b/src/main/index.js
index a4875ac..9603296 100644
--- a/src/main/index.js
+++ b/src/main/index.js
@@ -133,7 +133,7 @@ async function createLinkWin(data) {
contextIsolation: true
}
})
- linkWin[data.key].type = 'link' // 唯一标识
+ linkWin[data.key].type = 'link'+data.key // 唯一标识
let cookieDetails = { ...data.cookieData }
await linkWin[data.key].webContents.session.cookies
diff --git a/src/renderer/public/img/logo.png b/src/renderer/public/img/logo.png
new file mode 100644
index 0000000..45f6e65
Binary files /dev/null and b/src/renderer/public/img/logo.png differ
diff --git a/src/renderer/src/api/teaching/classcourse.js b/src/renderer/src/api/teaching/classcourse.js
new file mode 100644
index 0000000..665489c
--- /dev/null
+++ b/src/renderer/src/api/teaching/classcourse.js
@@ -0,0 +1,89 @@
+import request from '@/utils/request'
+
+// 查询classcourse列表
+export function listClasscourse(query) {
+ return request({
+ url: '/education/classcourse/list',
+ method: 'get',
+ params: query
+ })
+}
+
+// 查询classcourse详细
+export function getClasscourse(id) {
+ return request({
+ url: '/education/classcourse/' + id,
+ method: 'get'
+ })
+}
+
+// 新增classcourse
+export function addClasscourse(data) {
+ return request({
+ url: '/education/classcourse',
+ method: 'post',
+ data: data
+ })
+}
+
+// 新增classcourse
+export function addClasscourseReturnId(data) {
+ return request({
+ url: '/education/classcourse/saveReturnId',
+ method: 'post',
+ data: data
+ })
+}
+
+// 修改classcourse
+export function updateClasscourse(data) {
+ return request({
+ url: '/education/classcourse',
+ method: 'put',
+ data: data
+ })
+}
+
+// 删除classcourse
+export function delClasscourse(id) {
+ return request({
+ url: '/education/classcourse/' + id,
+ method: 'delete'
+ })
+}
+
+
+// 删除classcourse
+export function delClasscourseWithData(id) {
+ return request({
+ url: '/education/classcourse/removeData/' + id,
+ method: 'delete'
+ })
+}
+
+
+// classcourse开始上课
+export function startCourseTeaching(id) {
+ return request({
+ url: '/education/classcourse/startCourseTeaching/'+id,
+ method: 'post',
+ })
+}
+
+// 老师学生发送新的消息
+export function sendCourseTeachingMsg(data) {
+ return request({
+ url: '/education/classcourse/sendCourseTeachingMsg',
+ method: 'post',
+ data: data
+ })
+}
+
+// 老师学生获取新的交互消息
+export function getCourseTeachingMsg(id) {
+ return request({
+ url: '/education/classcourse/getCourseTeachingMsg/'+id,
+ method: 'post',
+ })
+}
+
diff --git a/src/renderer/src/components/common/cForm.vue b/src/renderer/src/components/common/cForm.vue
new file mode 100644
index 0000000..c84dbcb
--- /dev/null
+++ b/src/renderer/src/components/common/cForm.vue
@@ -0,0 +1,209 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{btnText}}
+
+
+
+
+
+
+
+
+
+
diff --git a/src/renderer/src/components/common/index.js b/src/renderer/src/components/common/index.js
new file mode 100644
index 0000000..d31d868
--- /dev/null
+++ b/src/renderer/src/components/common/index.js
@@ -0,0 +1,64 @@
+
+/**
+ * 使用-批量导入--方式
+ */
+
+// 导入 import.meta.glob 用于获取所有符合要求的 .vue 文件
+const files = import.meta.glob('./!(index).vue', { eager: true });
+
+export default {
+ install(Vue, options) {
+ Object.entries(files).forEach(([path, file]) => {
+ const fileName = path.split('/').pop().replace(/\.\w+$/, '')
+ // fileName == 'cDialog' && initDialog(Vue, file) // 弹窗--组件化
+ Vue.component(fileName, file.default)
+ });
+ }
+}
+
+// 弹窗--函数化
+function initDialog(Vue, dialog) {
+ // 全局绑定
+ Vue.prototype.$cDialog = (props) => {
+ // props.dialog = Object.assign({ isOpen: true }, props.dialog) // 默认配置 propsData: option
+ props.dialog.isOpen == null && (props.dialog.isOpen = true) // 默认打开
+ props.isRemove == null && (props.isRemove = true) // 默认关闭后移除
+ const Constructor = Vue.extend(dialog)
+ const Instance = new Constructor({ propsData: props })
+ props.slots && (Instance.$slots = props.slots) // 插槽内容
+ props.scopedSlots && (Instance.$scopedSlots = props.scopedSlots) // 作用域插槽内容
+ props.content && (Instance.$slots.default = props.content) // 插槽内容
+ document.body.appendChild(Instance.$mount().$el)
+ return Instance.showBox().then(v => {
+ props.callback && props.callback(v)
+ return Promise.resolve(v)
+ }).catch(v => {
+ props.callback && props.callback(v)
+ // 移除弹窗
+ props.isRemove && document.body.removeChild(Instance.$mount().$el)
+ return Promise.reject(v)
+ })
+ }
+ // 全局绑定2
+ Vue.prototype.$cDialog2 = (props) => {
+ // props.dialog = Object.assign({ isOpen: true }, props.dialog) // 默认配置 propsData: option
+ props.dialog.isOpen == null && (props.dialog.isOpen = true) // 默认打开
+ props.isRemove == null && (props.isRemove = true) // 默认关闭后移除
+ const Constructor = Vue.extend(dialog)
+ const Instance = new Constructor({ propsData: props })
+ props.slots && (Instance.$slots = props.slots) // 插槽内容
+ props.scopedSlots && (Instance.$scopedSlots = props.scopedSlots) // 作用域插槽内容
+ props.content && (Instance.$slots.default = props.content) // 插槽内容
+ document.body.appendChild(Instance.$mount().$el)
+ Instance.showBox().then(v => {
+ props.callback && props.callback(v)
+ return Promise.resolve(v)
+ }).catch(v => {
+ props.callback && props.callback(v)
+ // 移除弹窗
+ props.isRemove && document.body.removeChild(Instance.$mount().$el)
+ return Promise.reject(v)
+ })
+ return Instance
+ }
+}
diff --git a/src/renderer/src/main.js b/src/renderer/src/main.js
index b5fb488..be69725 100644
--- a/src/renderer/src/main.js
+++ b/src/renderer/src/main.js
@@ -14,6 +14,7 @@ import { store } from '@/store'
import App from './App.vue'
import router from './router'
import log from 'electron-log/renderer' // 渲染进程日志-文件记录
+import customComponent from '@/components/common' // 自定义组件
if(process.env.NODE_ENV != 'development') { // 非开发环境,将日志打印到日志文件
Object.assign(console, log.functions) // 渲染进程日志-控制台替换
@@ -37,4 +38,6 @@ app.config.globalProperties.$requestGetJYW = (url,config)=>{
app.use(router)
.use(store)
- .use(ElementPlus, { locale: zhLocale }).mount('#app')
\ No newline at end of file
+ .use(ElementPlus, { locale: zhLocale })
+ .use(customComponent) // 自定义组件
+ .mount('#app')
\ No newline at end of file
diff --git a/src/renderer/src/plugins/imChat/index.js b/src/renderer/src/plugins/imChat/index.js
index dda4bfa..6cf8934 100644
--- a/src/renderer/src/plugins/imChat/index.js
+++ b/src/renderer/src/plugins/imChat/index.js
@@ -173,6 +173,7 @@ export class ImChat {
return this.timChat.TIMLogout().then(res => {
this.setConsole('%cim-chat: logout', '登出成功')
this.status.isLogin = false
+ this.status.isConnect = false
this.timChat.TIMUninit() // 反初始化
return res
}).catch(error => {
diff --git a/src/renderer/src/utils/comm.js b/src/renderer/src/utils/comm.js
new file mode 100644
index 0000000..29e7a48
--- /dev/null
+++ b/src/renderer/src/utils/comm.js
@@ -0,0 +1,312 @@
+
+/**
+ * @description 公共工具类
+ * @author zdg
+ * @date 2024-4-26
+ */
+
+// ============= 文件工具--相关 ===================
+/**
+ * 获取上传文件
+ */
+export function getFiles() {
+ const cb = resolve => {
+ const fileDom = document.createElement('input')
+ fileDom.type = 'file'
+ fileDom.onchange = e => {
+ resolve(e.target.files)
+ return fileDom.remove()
+ }
+ fileDom.click()
+ }
+ return new Promise(cb)
+}
+
+// ============= 数学公式--相关 ===================
+/**
+ * @description 计算两点直线距离 (获取直径)
+ * (欧几里得距离公式): [ \text{distance} = \sqrt{(x2 - x1)^2 + (y2 - y1)^2} ]
+ * @param {*} x1
+ * @param {*} y1
+ * @param {*} x2
+ * @param {*} y2
+ */
+export function getDistance(x1,y1,x2,y2) {
+ return Math.sqrt(Math.pow((x2 - x1), 2) + Math.pow((y2 - y1), 2))
+}
+// 获取半径
+export function getRadius(x1,y1,x2,y2) { return getDistance(x1,y1,x2,y2) / 2 }
+
+/**
+ * 计算某个值在总数中所占的百分比。
+ *
+ * 此函数用于根据给定的值和总数,计算该值占总数的百分比。它还支持指定百分比的小数位数。
+ * 如果计算结果小于0,则返回0;如果大于100,则返回100,以确保百分比的合理范围。
+ *
+ * @param {number} v - 待计算的值。
+ * @param {number} total - 总数。
+ * @param {number} [step=2] - 百分比的小数位数,默认为2。
+ * @returns {number} - 返回计算后的百分比,保证在0到100之间。
+ */
+export function getPercent(v, total, step=2) {
+ !v && (v = 0)
+ !total && (total = 1)
+ // 计算百分比,保留指定的小数位,并转换为数字类型
+ let res = (v / total * 100).toFixed(step)-0
+
+ // 确保百分比在0到100之间
+ return res < 0 ? 0 : res > 100 ? 100 : res
+}
+
+// ============= 格式化--相关 ===================
+
+/**
+ * @description 手机号隐藏中间几位
+ * @param {*} phone 手机号
+ * @param {*} start 前面保留几位 默认 3位
+ * @param {*} end 后面保留几位 默认 3位
+ * @param {*} rstr 替换字符 默认 ****
+ * @returns
+ */
+export function phoneHideFormat(phone, start = 3, end = 4, rstr = '****') {
+ // const reg = /^(\d{3})\d*(\d{4})$/
+ if (!phone) return ''
+ const reg = new RegExp(`(\\d\{${start}\})\\d*(\\d\{${end}\})`)
+ return phone.replace(reg, `$1${rstr}$2`)
+}
+
+// ============= 习题工具--相关 ===================
+/**
+ * @description 将字符串转换为数组
+ * @param {*} str
+ * @returns
+ */
+export function quizStrToList(str = '') {
+ if (!str) return []
+ let resList = []
+ if (isJson(str, true)) resList = JSON.parse(str) // 数组对象
+ else if (str.includes('#&')) resList = str.split('#&') // 字符串数组 #&
+ else if (str.includes(',')) resList = str.split(',') // 字符串数组 ,
+ else resList = [str]
+ return resList
+}
+
+// ============= 常用工具--相关 ===================
+
+export function isJson(str, isArray = false) {
+ if(typeof str == 'string'){
+ try {
+ const res = JSON.parse(str)
+ let isBool = typeof res == 'object' && res
+ if(isBool && isArray) isBool = Array.isArray(res)
+ return isBool
+ } catch (error) {}
+ }
+ return false
+}
+
+//获取临时唯一ID
+export const generateUniqueID = ()=> {
+ const date = new Date();
+ const timestamp = date.getTime().toString(36); // 使用36进制转换时间戳
+ const random = Math.random().toString(36).substring(2, 9); // 获取随机数的一部分并转换为36进制
+ return timestamp + random;
+}
+
+// 清理参数中的undefined、null
+export const removePropertyOf = function(obj){
+ Object.keys(obj).forEach(item=>{
+ if(obj[item] === undefined || obj[item] === null) delete obj[item]
+ })
+ return obj;
+}
+
+/**
+ * 移除children 为空 -- tree
+ */
+export function removeTree(list) {
+ var this_ = this
+ for (var i in list) {
+ if (list[i].children.length == 0) {
+ list[i].children = undefined
+ } else {
+ this_.removeTree(list[i].children)
+ }
+ }
+ return list
+}
+
+// ============= 校验工具--相关 ===================
+
+// url校验
+export const validateUrl = (url) => {
+ const regex = /^(https?:\/\/)?([\da-z\.-]+)\.([a-z\.]{2,6})([\/\w \.-]*)*\/?$/;
+ if (!regex.test(url.trim())) {
+ return false;
+ } else {
+ return true;
+ }
+}
+
+// ============= 时间工具--相关 ===================
+
+/**
+ * @description 时间 秒 转化 时分秒
+ * @param {*} seconds
+ * @returns
+ */
+export function formatTime(seconds) {
+ seconds = parseInt(seconds) // 转换整数
+ const h = (Math.floor(seconds / 3600)+'').padStart(2,'0')
+ const m = (Math.floor((seconds % 3600) / 60)+'').padStart(2,'0')
+ const s = ((seconds % 60)+'').padStart(2,'0')
+ return `${h}:${m}:${s}`
+}
+
+/**
+ * @description 时间格式化
+ * @param {*} time
+ * @param {*} fmt
+ * @returns
+ */
+export function formatDate(time, fmt = 'yyyy-MM-dd') {
+ let date
+ if (time) {
+ if (typeof time === 'object') {
+ date = time
+ } else {
+ if ((typeof time === 'string') && (/^[0-9]+$/.test(time))) {
+ time = parseInt(time)
+ }
+ if ((typeof time === 'number') && (time.toString().length === 10)) {
+ time = time * 1000
+ }
+ date = new Date(time)
+ }
+ } else {
+ date = new Date()
+ }
+ if (/(y+)/.test(fmt)) {
+ fmt = fmt.replace(RegExp.$1, (date.getFullYear() + '').substr(4 - RegExp.$1.length))
+ }
+ const o = {
+ 'M+': date.getMonth() + 1,
+ 'd+': date.getDate(),
+ 'h+': date.getHours(),
+ 'm+': date.getMinutes(),
+ 's+': date.getSeconds(),
+ 'a': date.getDay()
+ }
+ for (const k in o) {
+ if (new RegExp(`(${k})`).test(fmt)) {
+ if (k === 'a') {
+ const str = ['日', '一', '二', '三', '四', '五', '六'][o[k]]
+ fmt = fmt.replace(RegExp.$1, str)
+ continue
+ }
+ const str = o[k] + ''
+ // fmt = fmt.replace(RegExp.$1, (RegExp.$1.length === 1) ? str : padLeftZero(str))
+ fmt = fmt.replace(RegExp.$1, str.padStart(2, '0'))
+ }
+ }
+ return fmt
+}
+/**
+ * 快速获取日期
+ * num: 1 今日 2 昨日 3 本周 4 上周 5 本月
+ * bool: true 有时分秒 false | null 不要时分秒
+ */
+export function getDateStr(num, bool) {
+ let now = new Date() // 当前日期
+ let nowDayOfWeek = now.getDay() // 今天本周的第几天
+ let nowDay = now.getDate() // 当前日
+ let nowMonth = now.getMonth() // 当前月
+ let nowYear = now.getYear() // 当前年
+ nowYear += (nowYear < 2000) ? 1900 : 0 //
+ let monthStartDate = new Date(nowYear, nowMonth, 1)
+ let monthEndDate = new Date(nowYear, nowMonth + 1, 1)
+ let days = (monthEndDate - monthStartDate) / (1000 * 60 * 60 * 24)
+
+ let fmt = 'yyyy-MM-dd'
+ let startTime = ''
+ let endTime = ''
+
+ if (num == 1) { // 今日
+ startTime = formatDate(now, fmt)
+ endTime = startTime
+ }
+ if (num == 2) { // 昨日
+ now.setDate(nowDay - 1)
+ startTime = formatDate(now, fmt)
+ endTime = startTime
+ }
+ if (num == 3) { // 本周
+ startTime = formatDate(new Date(nowYear, nowMonth, nowDay - nowDayOfWeek), fmt)
+ endTime = formatDate(new Date(nowYear, nowMonth, nowDay + (6 - nowDayOfWeek)), fmt)
+ }
+ if (num == 4) { // 上周
+ startTime = formatDate(new Date(nowYear, nowMonth, nowDay - nowDayOfWeek - 7), fmt)
+ endTime = formatDate(new Date(nowYear, nowMonth, nowDay - nowDayOfWeek - 1), fmt)
+ }
+ if (num == 5) { // 本月
+ startTime = formatDate(new Date(nowYear, nowMonth, 1), fmt)
+ endTime = formatDate(new Date(nowYear, nowMonth, days), fmt)
+ }
+ if (num == 6) { // 本年
+ startTime = formatDate(new Date(nowYear, 0, 1), fmt)
+ endTime = formatDate(new Date(nowYear, 11, 31), fmt)
+ }
+ if (bool) {
+ startTime += ' 00:00:00'
+ endTime += ' 23:59:59'
+ }
+ return [startTime, endTime]
+}
+/**
+ * 获取 昨天 今天 明天 一周前 一月前 1-5
+ */
+export function getDateStr1(num, fmt = 'yyyy-MM-dd hh:mm:ss') {
+ const now = new Date() // 当前日期
+ const curr = Date.now() // 当前时间戳
+ if (num == 1) return formatDate(new Date(curr-(24*60*60*1000)),fmt) // 昨天
+ else if (num == 2) return formatDate(now,fmt) // 今天
+ else if (num == 3) return formatDate(new Date(curr+(24*60*60*1000)),fmt) // 明天
+ else if (num == 4) return formatDate(new Date(curr-(7*24*60*60*1000)),fmt) // 一周前
+ else if (num == 5) return formatDate(new Date(curr-(30*24*60*60*1000)),fmt) // 一月前
+ return ''
+}
+/**
+ * 获取当前 0:0:0:0 时间
+ * @param {*} [fmt] 格式 'yyyy-MM-dd hh:mm:ss'
+ */
+export function getDateNow(fmt) {
+ const date = new Date()
+ date.setHours(0, 0, 0, 0)
+ if (fmt) return formatDate(date, fmt)
+ return date
+}
+/** 默认加上-时分秒 */
+export function toTimeStr(arr = ['','']) {
+ return [`${arr[0]} 00:00:00`, `${arr[1]} 23:59:59`]
+}
+
+/**
+ * 秒转时分
+ */
+export function timeToStr(time,str = '时分秒', isPad = false) {
+ let s = parseInt(time)
+ let h = 0, m = 0 // 初始化时|分
+ if (s >= 60) {
+ // 如果秒数大于60,将秒数转换成整数
+ m = parseInt(s / 60) // 获取分钟,除以60取整数,得到整数分钟
+ s = parseInt(s % 60) // 获取秒数,秒数取佘,得到整数秒数
+ if (m >= 60) { // 如果分钟大于60,将分钟转换成小时
+ h = parseInt(m / 60) // 获取小时,获取分钟除以60,得到整数小时
+ m = parseInt(m % 60) // 获取小时后取佘的分,获取分钟除以60取佘的分
+ }
+ }
+ const toStr = v => v.toString().padStart(2, '0') // 转换字符
+ const arr = str.split('')
+ if (isPad) return `${h?toStr(h)+arr[0]:''}${m?toStr(m)+arr[1]:''}${toStr(s)}${arr[2]||''}`
+ return `${h?h+arr[0]:''}${m?m+arr[1]:''}${s?s+arr[2]||'':''}`
+}
diff --git a/src/renderer/src/utils/linkConfig.js b/src/renderer/src/utils/linkConfig.js
index f03f3e9..e5da707 100644
--- a/src/renderer/src/utils/linkConfig.js
+++ b/src/renderer/src/utils/linkConfig.js
@@ -1,6 +1,6 @@
import useUserStore from '@/store/modules/user'
-const baseConfig = () => {
- const userStore = useUserStore()
+export const baseConfig = (token) => {
+ const userStore = token ? {} : useUserStore()
return {
// Electron 设置cookie
url: import.meta.env.VITE_APP_BUILD_BASE_PATH,
@@ -8,7 +8,7 @@ const baseConfig = () => {
//cookie 名称 这里为 token
name: 'Admin-Token',
//cookie 值
- value: userStore.token,
+ value: token ? '' : userStore.token,
// 域名
domain: import.meta.env.VITE_APP_DOMAIN
}
diff --git a/src/renderer/src/utils/tool.js b/src/renderer/src/utils/tool.js
index e7fcf86..a4160d4 100644
--- a/src/renderer/src/utils/tool.js
+++ b/src/renderer/src/utils/tool.js
@@ -12,8 +12,8 @@ const Remote = isNode?require('@electron/remote'):{}
const { ipcRenderer } = isNode?require('electron'):window.electron || {}
const API = isNode?window.api:{} // preload-api
import { useToolState } from '@/store/modules/tool' // 获取store状态
-// const Store = isNode?require('electron-store'):null // 持久化存储
import store from './store'
+import { baseConfig } from './linkConfig' // 外部连接-配置
// 常用变量
const BaseUrl = isNode?process.env['ELECTRON_RENDERER_URL']+'/#':''
const isDev = isNode?process.env.NODE_ENV !== 'production':''
@@ -297,6 +297,23 @@ const eventHandles = (type, win) => {
}
}
+/**
+ * @description 外部跳转-web网页
+ * @param {*} path
+ * @param {*} params
+ */
+export const toLinkWeb = (path) => {
+ const config = baseConfig()
+ console.log(config)
+ const fullPath = config.url + path
+ // 通知主进程
+ ipcRenderer.send('openWindow', {
+ key: `win-${Date.now()}`,
+ fullPath: fullPath,
+ cookieData: { ...config }
+ })
+}
+
// const taskHandles = () => {
// // 设置任务栏上下文菜单
// const contextMenu = new Remote.Menu()
diff --git a/src/renderer/src/views/desktop/container/class-start.vue b/src/renderer/src/views/desktop/container/class-start.vue
new file mode 100644
index 0000000..84b792c
--- /dev/null
+++ b/src/renderer/src/views/desktop/container/class-start.vue
@@ -0,0 +1,361 @@
+
+
+ 准备开始上课
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ item.name }}
+
+
+
+
+
+
+
+
+
+
+ 开始新课
+
+
+ {{ item.opendate }} ({{ item.teachername }})
+
+
+ {{ item.opendate }} ({{ item.teachername }})
+
+
+ 历史记录
+
+
+
+
+
+
+
+ 删除记录
+
+
+
+
+
开始新的课堂,需要点击先创建课堂,才能显示手机二维码
+
创建课堂
+
+
+
+
+
+
如果手机扫码后进入课堂,但本页面没自动跳转,请点击下面按钮
+
开始上课
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/renderer/src/views/desktop/container/work-trend.vue b/src/renderer/src/views/desktop/container/work-trend.vue
index e4ca636..6ae0969 100644
--- a/src/renderer/src/views/desktop/container/work-trend.vue
+++ b/src/renderer/src/views/desktop/container/work-trend.vue
@@ -10,6 +10,7 @@
- 上课
+ 上课
+
+