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 @@ + + + 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 @@ + + + + + \ 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 @@
+ 上课
- 上课 + 上课
+ +