diff --git a/.env.lt b/.env.old similarity index 93% rename from .env.lt rename to .env.old index 79007b1..8640aa3 100644 --- a/.env.lt +++ b/.env.old @@ -1,5 +1,5 @@ # 页面标题 -VITE_APP_TITLE = 文枢课堂 +VITE_APP_TITLE = AIX智慧课堂 # 生产环境配置 VITE_APP_ENV = 'production' diff --git a/.env.production b/.env.production index 8640aa3..79007b1 100644 --- a/.env.production +++ b/.env.production @@ -1,5 +1,5 @@ # 页面标题 -VITE_APP_TITLE = AIX智慧课堂 +VITE_APP_TITLE = 文枢课堂 # 生产环境配置 VITE_APP_ENV = 'production' diff --git a/electron-builder-lt.yml b/electron-builder-old.yml similarity index 96% rename from electron-builder-lt.yml rename to electron-builder-old.yml index f8e0b9a..9dfb51e 100644 --- a/electron-builder-lt.yml +++ b/electron-builder-old.yml @@ -1,10 +1,10 @@ appId: com.electron.app -productName: 文枢课堂 +productName: AIx directories: output: dist buildResources: build win: - executableName: 文枢课堂 + executableName: AIx icon: resources/logo2.ico files: - '!**/.vscode/*' diff --git a/electron-builder-prod.yml b/electron-builder-prod.yml index 9dfb51e..f8e0b9a 100644 --- a/electron-builder-prod.yml +++ b/electron-builder-prod.yml @@ -1,10 +1,10 @@ appId: com.electron.app -productName: AIx +productName: 文枢课堂 directories: output: dist buildResources: build win: - executableName: AIx + executableName: 文枢课堂 icon: resources/logo2.ico files: - '!**/.vscode/*' diff --git a/package.json b/package.json index 825ea85..c1e7e1c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "aix-win", - "version": "2.0.6", + "version": "2.1.1", "description": "", "main": "./out/main/index.js", "author": "example.com", @@ -26,6 +26,14 @@ "@electron/remote": "^2.1.2", "@element-plus/icons-vue": "^2.3.1", "@vitejs/plugin-vue-jsx": "^4.0.0", + "@antv/x6": "^2.18.1", + "@antv/x6-plugin-clipboard": "^2.1.6", + "@antv/x6-plugin-dnd": "^2.1.1", + "@antv/x6-plugin-export": "^2.1.6", + "@antv/x6-plugin-keyboard": "^2.2.3", + "@antv/x6-plugin-selection": "^2.2.2", + "@antv/x6-plugin-snapline": "^2.1.7", + "@antv/x6-plugin-transform": "^2.1.8", "@vue-office/docx": "^1.6.2", "@vue-office/excel": "^1.7.11", "@vue-office/pdf": "^2.0.2", @@ -39,7 +47,7 @@ "electron-store": "8.0.0", "electron-updater": "^6.1.7", "element-china-area-data": "^6.1.0", - "element-plus": "^2.7.6", + "element-plus": "^2.8.0", "fabric": "^5.3.0", "im_electron_sdk": "^8.0.5904", "js-cookie": "^3.0.5", @@ -55,7 +63,10 @@ "vue-qr": "^4.0.9", "vue-router": "^4.4.0", "xgplayer": "^3.0.19", - "xlsx": "^0.18.5" + "xlsx": "^0.18.5", + "less": "^4.2.0", + "less-loader": "^7.3.0", + "whiteboard_lyc": "^0.0.8" }, "devDependencies": { "@electron-toolkit/eslint-config": "^1.0.2", diff --git a/src/main/index.js b/src/main/index.js index 49433ec..0b84cd4 100644 --- a/src/main/index.js +++ b/src/main/index.js @@ -273,9 +273,14 @@ function handleAll() { }) // 用于监听-状态管理变化-同步所有窗口 ipcMain.handle('pinia-state-change', (e, storeName, jsonStr) => { + console.log('pinia-state-change-1', storeName, jsonStr) + for(const curWin of BrowserWindow.getAllWindows()){ const id = curWin.webContents.id const bool = id !== e.sender.id && !curWin.isDestroyed() + if (id === e.sender.id) { + console.log('pinia-state-change-2', 'windows-send', curWin.type) + } if (bool) { // 除了消息发送窗口和销毁的窗口 其他都发送 curWin.webContents.send('pinia-state-set', storeName, jsonStr) } diff --git a/src/renderer/src/api/classTask/index.js b/src/renderer/src/api/classTask/index.js index 61154ff..8f8672b 100644 --- a/src/renderer/src/api/classTask/index.js +++ b/src/renderer/src/api/classTask/index.js @@ -62,3 +62,108 @@ export function updateClassworkdata(data) { data: data }) } + +// 修改classwork +export function updateClasswork(data) { + return request({ + url: '/education/classwork', + method: 'put', + data: data + }) +} + + + +// 查询evaluationclue列表 +export function listEvaluationclue(query) { + return request({ + url: '/education/evaluationclue/list', + method: 'get', + params: query + }) +} + +// 查询evaluationclue详细 +export function getEvaluationclue(id) { + return request({ + url: '/education/evaluationclue/' + id, + method: 'get' + }) +} + +// 新增evaluationclue +export function addEvaluationclueReturnId(data) { + return request({ + url: '/education/evaluationclue/addReturnId', + method: 'post', + data: data + }) +} + +// 新增evaluationclue +export function addEvaluationclue(data) { + return request({ + url: '/education/evaluationclue', + method: 'post', + data: data + }) +} + +// 修改evaluationclue +export function updateEvaluationclue(data) { + return request({ + url: '/education/evaluationclue', + method: 'put', + data: data + }) +} + +// 删除evaluationclue +export function delEvaluationclue(id) { + return request({ + url: '/education/evaluationclue/' + id, + method: 'delete' + }) +} + +// 新增evaluationclue,保存base64图片 +export function saveBase64File(data) { + return request({ + url: '/education/evaluationclue/saveBase64File', + method: 'post', + data: data + }) +} + +// 新增evaluationclue,上传 +export function saveEvaluationClueUploadFile(data) { + return request({ + url: '/education/evaluationclue/saveUploadFile', + method: 'post', + data: data + }) +} + +// 读取文件内容 +export function readFile(data) { + return fetch(import.meta.env.VITE_APP_RES_FILE_PATH + data.cluelink, { + method: "get", + headers: { + 'Content-Type': 'text/plain', // 请求头设置为纯文本 + 'Accept': 'text/plain' // 接受头设置为纯文本 + }, + }) + .then(response => response.text()) + .then(text => { + return Promise.resolve(text); + }) + .catch(error => { + console.error('读取文件出错:', error); + return Promise.reject(); + }); + /*return request({ + url: '/education/evaluationclue/readFile', + method: 'post', + data: data + })*/ +} diff --git a/src/renderer/src/api/knowledge/knowledgePoint.js b/src/renderer/src/api/knowledge/knowledgePoint.js new file mode 100644 index 0000000..2b867c4 --- /dev/null +++ b/src/renderer/src/api/knowledge/knowledgePoint.js @@ -0,0 +1,53 @@ +import request from '@/utils/request' + +// 查询KnowledgePoint列表 +export function listKnowledgePoint(query) { + return request({ + url: '/point/list', + method: 'get', + params: query + }) +} + +// 查询KnowledgePoint详细 +export function getKnowledgePoint(id) { + return request({ + url: '/point/' + id, + method: 'get' + }) +} + +// 新增KnowledgePoint +export function addKnowledgePointBase(data) { + return request({ + url: '/point/addBase', + method: 'post', + data: data + }) +} + +// 新增KnowledgePoint +export function addKnowledgePoint(data) { + return request({ + url: '/point/add', + method: 'post', + data: data + }) +} + +// 修改KnowledgePoint +export function updateKnowledgePoint(data) { + return request({ + url: '/point/update', + method: 'put', + data: data + }) +} + +// 删除KnowledgePoint +export function delKnowledgePoint(id) { + return request({ + url: '/point/' + id, + method: 'delete' + }) +} \ No newline at end of file diff --git a/src/renderer/src/components/FileUpload/index.vue b/src/renderer/src/components/FileUpload/index.vue new file mode 100644 index 0000000..e1ce984 --- /dev/null +++ b/src/renderer/src/components/FileUpload/index.vue @@ -0,0 +1,208 @@ + + + + + diff --git a/src/renderer/src/components/Flowchart/FlowContentMenu.vue b/src/renderer/src/components/Flowchart/FlowContentMenu.vue new file mode 100644 index 0000000..f2b6da8 --- /dev/null +++ b/src/renderer/src/components/Flowchart/FlowContentMenu.vue @@ -0,0 +1,100 @@ + + + + + + \ No newline at end of file diff --git a/src/renderer/src/components/Flowchart/FlowDrawer.vue b/src/renderer/src/components/Flowchart/FlowDrawer.vue new file mode 100644 index 0000000..ff0f67e --- /dev/null +++ b/src/renderer/src/components/Flowchart/FlowDrawer.vue @@ -0,0 +1,91 @@ + + + + + \ No newline at end of file diff --git a/src/renderer/src/components/Flowchart/FlowLibrary.vue b/src/renderer/src/components/Flowchart/FlowLibrary.vue new file mode 100644 index 0000000..5359e6b --- /dev/null +++ b/src/renderer/src/components/Flowchart/FlowLibrary.vue @@ -0,0 +1,235 @@ + + + + \ No newline at end of file diff --git a/src/renderer/src/components/Flowchart/config/index.js b/src/renderer/src/components/Flowchart/config/index.js new file mode 100644 index 0000000..cb4f29a --- /dev/null +++ b/src/renderer/src/components/Flowchart/config/index.js @@ -0,0 +1,333 @@ + +import { Shape } from '@antv/x6' +/** + * @desc 初始化面板配置 + * @param check 查看模式 + */ +export const graphOptions = (check = false) => { + return { + container: document.getElementById('flow-container'), + // 定制节点和边的交互行为 ==> boolean 节点或边是否可交互 + interacting: check + ? { + nodeMovable: false, + edgeMovable: false, + magnetConnectable: false, + vertexDeletable: false + } + : true, + // 对齐线 + snapline: true, + // 显示网格 // 'dot' | 'fixedDot' | 'mesh' + grid: { + visible: true, + size: 20, // 网格大小 + type: 'mesh', + args: { + color: '#e9e9e9', + thickness: 2 // 网格线宽度/网格点大小 + } + }, + // 平移 + panning: true, + // 滚轮缩放 MouseWheel + mousewheel: { + enabled: true, + zoomAtMousePosition: true, + modifiers: ['ctrl', 'meta'], + maxScale: 3, + minScale: 0.3 + }, + // 连线规则 + connecting: { + // 路由类型 + router: { + // 连线类型在此修改 + // 曼哈顿路由 'manhattan' 路由是正交路由 'orth' 的智能版本,该路由由水平或垂直的正交线段组成,并自动避开路径上的其他节点(障碍)。 + name: 'manhattan', + args: { + padding: 1 + } + }, + // 圆角连接器,将起点、路由点、终点通过直线按顺序连接,并在线段连接处通过圆弧连接(倒圆角)。 + connector: { + name: 'rounded', + args: { + radius: 8 + } + }, + anchor: 'center', + connectionPoint: 'anchor', + // 是否允许连接到画布空白位置的点,默认为 true。 + allowBlank: false, + // 距离节点或者连接桩 20px 时会触发自动吸附 + snap: { + radius: 20 + }, + // 拽出新的边 + createEdge() { + return new Shape.Edge({ + // markup: [ + // { + // tagName: 'path', + // selector: 'stroke' + // } + // ], + // connector: { name: 'rounded' }, + // attrs: { + // stroke: { + // fill: 'none', + // connection: true, + // strokeWidth: 4, + // strokeLinecap: 'round', + // stroke: '#666' + // } + // }, + attrs: { + line: { + stroke: '#A2B1C3', + strokeWidth: 3, + targetMarker: { + name: 'block', + width: 12, + height: 8, + }, + }, + }, + zIndex: 0 + }) + }, + validateConnection({ targetMagnet }) { + return !!targetMagnet + } + }, + // 连线高亮 + highlighting: { + // 连线过程中,自动吸附到链接桩时被使用。 + magnetAdsorbed: { + name: 'stroke', + args: { + attrs: { + width: 12, + r: 6, + magnet: true, + stroke: '#008CFF', + strokeWidth: 2, + fill: '#0F67FF' + } + } + } + }, + rotating: false, // 不能旋转 + keyboard: !check, // 按键操作 + clipboard: true, // 剪切板 + autoResize: true, + onToolItemCreated({ tool }) { + const options = tool.options + if (options && options.index % 2 === 1) { + tool.setAttrs({ fill: 'red' }) + } + } + } +} + +// 链接桩样式 +export const portStyle = { + // width: 12, + // r: 6, // 半径 + // // 当 magnet 属性为 true 时,表示该元素可以被链接,即在连线过程中可以被当做连线的起点或终点,与链接桩类似。 + // magnet: true, + // stroke: '#008CFF', + // strokeWidth: 2, + // fill: '#fff', + // zIndex: 1, + // style: { + // visibility: 'hidden', + // }, + r: 6, + magnet: true, + stroke: '#5F95FF', + strokeWidth: 2, + fill: '#fff', + style: { + visibility: 'hidden', + }, +} + +// 链接桩配置 +export const ports = { + // 设置链接桩分组 + groups: { + top: { + // 定义连接柱的位置,如果不配置,将显示为默认样式 + position: 'top', + // 定义连接柱的样式 + attrs: { + circle: { + ...portStyle + } + } + }, + right: { + position: 'right', + attrs: { + circle: { + ...portStyle + } + } + }, + bottom: { + position: 'bottom', + attrs: { + circle: { + ...portStyle + } + } + }, + left: { + position: 'left', + attrs: { + circle: { + ...portStyle + } + } + }, + absolute: { + position: 'absolute', + attrs: { + circle: { + r: 6, + magnet: true, + stroke: '#008CFF', + strokeWidth: 2, + fill: '#fff' + } + } + } + }, + // 链接桩 + items: [ + { + group: 'top' + }, + { + group: 'right' + }, + { + group: 'bottom' + }, + { + group: 'left' + } + ] +} + +// 动态计算宽高比 +export const transformToPercent = (target, sum, font) => { + // https://x6.antv.vision/zh/docs/tutorial/intermediate/attrs + // 相对节点的大小 + const percent = (target / sum).toFixed(2) * 100 + return `${percent}${font ? 'px' : '%'}` +} + +// 注册节点配置信息 注册以后就可以像使用内置节点那样使用该节点 +export const registerNodeOpeions = { + 'custom-rect': { + inherit: 'rect', + width: 70, + height: 40, + attrs: { + body: { + strokeWidth: 1, + stroke: '#5F95FF', + fill: '#EFF4FF', + }, + text: { + fontSize: 12, + fill: '#262626', + }, + }, + ports: { ...ports }, + }, + 'custom-polygon' : { + inherit: 'polygon', + width: 70, + height: 40, + attrs: { + body: { + strokeWidth: 1, + stroke: '#5F95FF', + fill: '#EFF4FF', + }, + text: { + fontSize: 12, + fill: '#262626', + }, + }, + ports: { + ...ports, + items: [ + { + group: 'top', + }, + { + group: 'bottom', + }, + ], + }, + }, + 'custom-circle' : { + inherit: 'circle', + width: 50, + height: 50, + attrs: { + body: { + strokeWidth: 1, + stroke: '#5F95FF', + fill: '#EFF4FF', + }, + text: { + fontSize: 12, + fill: '#262626', + }, + }, + ports: { ...ports }, + }, +} + +// 图形变换配置 +export const transFormOptions = { + // 调整尺寸 + resizing: { + enabled: true, + minWidth: 1, + maxWidth: 200, + minHeight: 1, + maxHeight: 150, + restrict: false, + preserveAspectRatio: false, + }, + // 调整角度---旋转 + rotating: { + enabled: true, + } +} + +// 拖动添加节点样式配置 +export const addNodeAttrStyle = { + '可选过程': { + rx: 6, + ry: 6, + }, + '开始': { + rx: 20, + ry: 26, + }, + '决策': { + refPoints: '0,10 10,0 20,10 10,20', + }, + '数据': { + refPoints: '10,0 40,0 30,20 0,20', + } + +} \ No newline at end of file diff --git a/src/renderer/src/components/Flowchart/index.vue b/src/renderer/src/components/Flowchart/index.vue new file mode 100644 index 0000000..d808786 --- /dev/null +++ b/src/renderer/src/components/Flowchart/index.vue @@ -0,0 +1,391 @@ + + + + + \ No newline at end of file diff --git a/src/renderer/src/components/whiteboard/components/ColorPicker.vue b/src/renderer/src/components/whiteboard/components/ColorPicker.vue new file mode 100644 index 0000000..a819480 --- /dev/null +++ b/src/renderer/src/components/whiteboard/components/ColorPicker.vue @@ -0,0 +1,130 @@ + + + + + diff --git a/src/renderer/src/components/whiteboard/components/Contextmenu.vue b/src/renderer/src/components/whiteboard/components/Contextmenu.vue new file mode 100644 index 0000000..691f0bd --- /dev/null +++ b/src/renderer/src/components/whiteboard/components/Contextmenu.vue @@ -0,0 +1,213 @@ + + + + + diff --git a/src/renderer/src/components/whiteboard/constants.js b/src/renderer/src/components/whiteboard/constants.js new file mode 100644 index 0000000..5209a6e --- /dev/null +++ b/src/renderer/src/components/whiteboard/constants.js @@ -0,0 +1,120 @@ +// 描边颜色 +export const strokeColorList = [ + '#000000', + '#343a40', + '#495057', + '#c92a2a', + '#a61e4d', + '#862e9c', + '#5f3dc4', + '#364fc7', + '#1864ab', + '#0b7285', + '#087f5b', + '#2b8a3e', + '#5c940d', + '#e67700', + '#d9480f' +] + +// 填充颜色 +export const fillColorList = [ + 'transparent', + '#ced4da', + '#868e96', + '#fa5252', + '#e64980', + '#be4bdb', + '#7950f2', + '#4c6ef5', + '#228be6', + '#15aabf', + '#12b886', + '#40c057', + '#82c91e', + '#fab005', + '#fd7e14' +] + +// 背景颜色 +export const backgroundColorList = [ + '#ffffff', + '#f8f9fa', + '#f1f3f5', + '#fff5f5', + '#fff0f6', + '#f8f0fc', + '#f3f0ff', + '#edf2ff', + '#e7f5ff', + '#e3fafc', + '#e6fcf5', + '#ebfbee', + '#f4fce3', + '#fff9db', + '#fff4e6' +] + +// 字体列表 +export const fontFamilyList = [ + { + name: '微软雅黑', + value: '微软雅黑, Microsoft YaHei' + }, + { + name: '宋体', + value: '宋体, SimSun, Songti SC' + }, + { + name: '楷体', + value: '楷体, 楷体_GB2312, SimKai, STKaiti' + }, + { + name: '黑体', + value: '黑体, SimHei, Heiti SC' + }, + { + name: '隶书', + value: '隶书, SimLi' + }, + { + name: 'Andale Mono', + value: 'andale mono' + }, + { + name: 'Arial', + value: 'arial, helvetica, sans-serif' + }, + { + name: 'arialBlack', + value: 'arial black, avant garde' + }, + { + name: 'Comic Sans Ms', + value: 'comic sans ms' + }, + { + name: 'Impact', + value: 'impact, chicago' + }, + { + name: 'Times New Roman', + value: 'times new roman' + }, + { + name: 'Sans-Serif', + value: 'sans-serif' + }, + { + name: 'serif', + value: 'serif' + } +] + +// 字号 +export const fontSizeList = [10, 12, 16, 18, 24, 32, 48].map(item => { + return { + name: item, + value: item + } +}) diff --git a/src/renderer/src/components/whiteboard/libs/icons.svg b/src/renderer/src/components/whiteboard/libs/icons.svg new file mode 100644 index 0000000..cc8298a --- /dev/null +++ b/src/renderer/src/components/whiteboard/libs/icons.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/renderer/src/components/whiteboard/libs/jsonTree.css b/src/renderer/src/components/whiteboard/libs/jsonTree.css new file mode 100644 index 0000000..3812440 --- /dev/null +++ b/src/renderer/src/components/whiteboard/libs/jsonTree.css @@ -0,0 +1,107 @@ +/* + * JSON Tree Viewer + * http://github.com/summerstyle/jsonTreeViewer + * + * Copyright 2017 Vera Lobacheva (http://iamvera.com) + * Released under the MIT license (LICENSE.txt) + */ + +/* Background for the tree. May use for element */ +.jsontree_bg { + background: #FFF; +} + +/* Styles for the container of the tree (e.g. fonts, margins etc.) */ +.jsontree_tree { + margin-left: 30px; + font-family: 'PT Mono', monospace; + font-size: 14px; +} + +/* Styles for a list of child nodes */ +.jsontree_child-nodes { + display: none; + margin-left: 35px; + margin-bottom: 5px; + line-height: 2; +} +.jsontree_node_expanded > .jsontree_value-wrapper > .jsontree_value > .jsontree_child-nodes { + display: block; +} + +/* Styles for labels */ +.jsontree_label-wrapper { + float: left; + margin-right: 8px; +} +.jsontree_label { + font-weight: normal; + vertical-align: top; + color: #000; + position: relative; + padding: 1px; + border-radius: 4px; + cursor: default; +} +.jsontree_node_marked > .jsontree_label-wrapper > .jsontree_label { + background: #fff2aa; +} + +/* Styles for values */ +.jsontree_value-wrapper { + display: block; + overflow: hidden; +} +.jsontree_node_complex > .jsontree_value-wrapper { + overflow: inherit; +} +.jsontree_value { + vertical-align: top; + display: inline; +} +.jsontree_value_null { + color: #777; + font-weight: bold; +} +.jsontree_value_string { + color: #025900; + font-weight: bold; +} +.jsontree_value_number { + color: #000E59; + font-weight: bold; +} +.jsontree_value_boolean { + color: #600100; + font-weight: bold; +} + +/* Styles for active elements */ +.jsontree_expand-button { + position: absolute; + top: 3px; + left: -15px; + display: block; + width: 11px; + height: 11px; + background-image: url('icons.svg'); +} +.jsontree_node_expanded > .jsontree_label-wrapper > .jsontree_label > .jsontree_expand-button { + background-position: 0 -11px; +} +.jsontree_show-more { + cursor: pointer; +} +.jsontree_node_expanded > .jsontree_value-wrapper > .jsontree_value > .jsontree_show-more { + display: none; +} +.jsontree_node_empty > .jsontree_label-wrapper > .jsontree_label > .jsontree_expand-button, +.jsontree_node_empty > .jsontree_value-wrapper > .jsontree_value > .jsontree_show-more { + display: none !important; +} +.jsontree_node_complex > .jsontree_label-wrapper > .jsontree_label { + cursor: pointer; +} +.jsontree_node_empty > .jsontree_label-wrapper > .jsontree_label { + cursor: default !important; +} diff --git a/src/renderer/src/components/whiteboard/libs/jsonTree.js b/src/renderer/src/components/whiteboard/libs/jsonTree.js new file mode 100644 index 0000000..b2de406 --- /dev/null +++ b/src/renderer/src/components/whiteboard/libs/jsonTree.js @@ -0,0 +1,822 @@ +/** + * JSON Tree library (a part of jsonTreeViewer) + * http://github.com/summerstyle/jsonTreeViewer + * + * Copyright 2017 Vera Lobacheva (http://iamvera.com) + * Released under the MIT license (LICENSE.txt) + */ + +var jsonTree = (function() { + + /* ---------- Utilities ---------- */ + var utils = { + + /* + * Returns js-"class" of value + * + * @param val {any type} - value + * @returns {string} - for example, "[object Function]" + */ + getClass : function(val) { + return Object.prototype.toString.call(val); + }, + + /** + * Checks for a type of value (for valid JSON data types). + * In other cases - throws an exception + * + * @param val {any type} - the value for new node + * @returns {string} ("object" | "array" | "null" | "boolean" | "number" | "string") + */ + getType : function(val) { + if (val === null) { + return 'null'; + } + + switch (typeof val) { + case 'number': + return 'number'; + + case 'string': + return 'string'; + + case 'boolean': + return 'boolean'; + } + + switch(utils.getClass(val)) { + case '[object Array]': + return 'array'; + + case '[object Object]': + return 'object'; + } + + throw new Error('Bad type: ' + utils.getClass(val)); + }, + + /** + * Applies for each item of list some function + * and checks for last element of the list + * + * @param obj {Object | Array} - a list or a dict with child nodes + * @param func {Function} - the function for each item + */ + forEachNode : function(obj, func) { + var type = utils.getType(obj), + isLast; + + switch (type) { + case 'array': + isLast = obj.length - 1; + + obj.forEach(function(item, i) { + func(i, item, i === isLast); + }); + + break; + + case 'object': + var keys = Object.keys(obj).sort(); + + isLast = keys.length - 1; + + keys.forEach(function(item, i) { + func(item, obj[item], i === isLast); + }); + + break; + } + + }, + + /** + * Implements the kind of an inheritance by + * using parent prototype and + * creating intermediate constructor + * + * @param Child {Function} - a child constructor + * @param Parent {Function} - a parent constructor + */ + inherits : (function() { + var F = function() {}; + + return function(Child, Parent) { + F.prototype = Parent.prototype; + Child.prototype = new F(); + Child.prototype.constructor = Child; + }; + })(), + + /* + * Checks for a valid type of root node* + * + * @param {any type} jsonObj - a value for root node + * @returns {boolean} - true for an object or an array, false otherwise + */ + isValidRoot : function(jsonObj) { + switch (utils.getType(jsonObj)) { + case 'object': + case 'array': + return true; + default: + return false; + } + }, + + /** + * Extends some object + */ + extend : function(targetObj, sourceObj) { + for (var prop in sourceObj) { + if (sourceObj.hasOwnProperty(prop)) { + targetObj[prop] = sourceObj[prop]; + } + } + } + }; + + + /* ---------- Node constructors ---------- */ + + /** + * The factory for creating nodes of defined type. + * + * ~~~ Node ~~~ is a structure element of an onject or an array + * with own label (a key of an object or an index of an array) + * and value of any json data type. The root object or array + * is a node without label. + * {... + * [+] "label": value, + * ...} + * + * Markup: + *
  • + * + * + * + * "label" + * + * : + * + * <(div|span) class="jsontree_value jsontree_value_(object|array|boolean|null|number|string)"> + * ... + * + *
  • + * + * @param label {string} - key name + * @param val {Object | Array | string | number | boolean | null} - a value of node + * @param isLast {boolean} - true if node is last in list of siblings + * + * @return {Node} + */ + function Node(label, val, isLast) { + var nodeType = utils.getType(val); + + if (nodeType in Node.CONSTRUCTORS) { + return new Node.CONSTRUCTORS[nodeType](label, val, isLast); + } else { + throw new Error('Bad type: ' + utils.getClass(val)); + } + } + + Node.CONSTRUCTORS = { + 'boolean' : NodeBoolean, + 'number' : NodeNumber, + 'string' : NodeString, + 'null' : NodeNull, + 'object' : NodeObject, + 'array' : NodeArray + }; + + + /* + * The constructor for simple types (string, number, boolean, null) + * {... + * [+] "label": value, + * ...} + * value = string || number || boolean || null + * + * Markup: + *
  • + * + * "age" + * : + * + * 25 + * , + *
  • + * + * @abstract + * @param label {string} - key name + * @param val {string | number | boolean | null} - a value of simple types + * @param isLast {boolean} - true if node is last in list of parent childNodes + */ + function _NodeSimple(label, val, isLast) { + if (this.constructor === _NodeSimple) { + throw new Error('This is abstract class'); + } + + var self = this, + el = document.createElement('li'), + labelEl, + template = function(label, val) { + var str = '\ + \ + "' + + label + + '" : \ + \ + \ + ' + + val + + '' + + (!isLast ? ',' : '') + + ''; + + return str; + }; + + self.label = label; + self.isComplex = false; + + el.classList.add('jsontree_node'); + el.innerHTML = template(label, val); + + self.el = el; + + labelEl = el.querySelector('.jsontree_label'); + + labelEl.addEventListener('click', function(e) { + if (e.altKey) { + self.toggleMarked(); + return; + } + + if (e.shiftKey) { + document.getSelection().removeAllRanges(); + alert(self.getJSONPath()); + return; + } + }, false); + } + + _NodeSimple.prototype = { + constructor : _NodeSimple, + + /** + * Mark node + */ + mark : function() { + this.el.classList.add('jsontree_node_marked'); + }, + + /** + * Unmark node + */ + unmark : function() { + this.el.classList.remove('jsontree_node_marked'); + }, + + /** + * Mark or unmark node + */ + toggleMarked : function() { + this.el.classList.toggle('jsontree_node_marked'); + }, + + /** + * Expands parent node of this node + * + * @param isRecursive {boolean} - if true, expands all parent nodes + * (from node to root) + */ + expandParent : function(isRecursive) { + if (!this.parent) { + return; + } + + this.parent.expand(); + this.parent.expandParent(isRecursive); + }, + + /** + * Returns JSON-path of this + * + * @param isInDotNotation {boolean} - kind of notation for returned json-path + * (by default, in bracket notation) + * @returns {string} + */ + getJSONPath : function(isInDotNotation) { + if (this.isRoot) { + return "$"; + } + + var currentPath; + + if (this.parent.type === 'array') { + currentPath = "[" + this.label + "]"; + } else { + currentPath = isInDotNotation ? "." + this.label : "['" + this.label + "']"; + } + + return this.parent.getJSONPath(isInDotNotation) + currentPath; + } + }; + + + /* + * The constructor for boolean values + * {... + * [+] "label": boolean, + * ...} + * boolean = true || false + * + * @constructor + * @param label {string} - key name + * @param val {boolean} - value of boolean type, true or false + * @param isLast {boolean} - true if node is last in list of parent childNodes + */ + function NodeBoolean(label, val, isLast) { + this.type = "boolean"; + + _NodeSimple.call(this, label, val, isLast); + } + utils.inherits(NodeBoolean,_NodeSimple); + + + /* + * The constructor for number values + * {... + * [+] "label": number, + * ...} + * number = 123 + * + * @constructor + * @param label {string} - key name + * @param val {number} - value of number type, for example 123 + * @param isLast {boolean} - true if node is last in list of parent childNodes + */ + function NodeNumber(label, val, isLast) { + this.type = "number"; + + _NodeSimple.call(this, label, val, isLast); + } + utils.inherits(NodeNumber,_NodeSimple); + + + /* + * The constructor for string values + * {... + * [+] "label": string, + * ...} + * string = "abc" + * + * @constructor + * @param label {string} - key name + * @param val {string} - value of string type, for example "abc" + * @param isLast {boolean} - true if node is last in list of parent childNodes + */ + function NodeString(label, val, isLast) { + this.type = "string"; + + _NodeSimple.call(this, label, '"' + val + '"', isLast); + } + utils.inherits(NodeString,_NodeSimple); + + + /* + * The constructor for null values + * {... + * [+] "label": null, + * ...} + * + * @constructor + * @param label {string} - key name + * @param val {null} - value (only null) + * @param isLast {boolean} - true if node is last in list of parent childNodes + */ + function NodeNull(label, val, isLast) { + this.type = "null"; + + _NodeSimple.call(this, label, val, isLast); + } + utils.inherits(NodeNull,_NodeSimple); + + + /* + * The constructor for complex types (object, array) + * {... + * [+] "label": value, + * ...} + * value = object || array + * + * Markup: + *
  • + * + * + * + * "label" + * + * : + * + *
    + * { + *
      + * } + * , + *
    + *
  • + * + * @abstract + * @param label {string} - key name + * @param val {Object | Array} - a value of complex types, object or array + * @param isLast {boolean} - true if node is last in list of parent childNodes + */ + function _NodeComplex(label, val, isLast) { + if (this.constructor === _NodeComplex) { + throw new Error('This is abstract class'); + } + + var self = this, + el = document.createElement('li'), + template = function(label, sym) { + var comma = (!isLast) ? ',' : '', + str = '\ +
    \ +
    \ + ' + sym[0] + '\ + \ + \ + ' + sym[1] + '' + + '
    ' + comma + + '
    '; + + if (label !== null) { + str = '\ + \ + ' + + '' + + '"' + label + + '" : \ + ' + str; + } + + return str; + }, + childNodesUl, + labelEl, + moreContentEl, + childNodes = []; + + self.label = label; + self.isComplex = true; + + el.classList.add('jsontree_node'); + el.classList.add('jsontree_node_complex'); + el.innerHTML = template(label, self.sym); + + childNodesUl = el.querySelector('.jsontree_child-nodes'); + + if (label !== null) { + labelEl = el.querySelector('.jsontree_label'); + moreContentEl = el.querySelector('.jsontree_show-more'); + + labelEl.addEventListener('click', function(e) { + if (e.altKey) { + self.toggleMarked(); + return; + } + + if (e.shiftKey) { + document.getSelection().removeAllRanges(); + alert(self.getJSONPath()); + return; + } + + self.toggle(e.ctrlKey || e.metaKey); + }, false); + + moreContentEl.addEventListener('click', function(e) { + self.toggle(e.ctrlKey || e.metaKey); + }, false); + + self.isRoot = false; + } else { + self.isRoot = true; + self.parent = null; + + el.classList.add('jsontree_node_expanded'); + } + + self.el = el; + self.childNodes = childNodes; + self.childNodesUl = childNodesUl; + + utils.forEachNode(val, function(label, node, isLast) { + self.addChild(new Node(label, node, isLast)); + }); + + self.isEmpty = !Boolean(childNodes.length); + if (self.isEmpty) { + el.classList.add('jsontree_node_empty'); + } + } + + utils.inherits(_NodeComplex, _NodeSimple); + + utils.extend(_NodeComplex.prototype, { + constructor : _NodeComplex, + + /* + * Add child node to list of child nodes + * + * @param child {Node} - child node + */ + addChild : function(child) { + this.childNodes.push(child); + this.childNodesUl.appendChild(child.el); + child.parent = this; + }, + + /* + * Expands this list of node child nodes + * + * @param isRecursive {boolean} - if true, expands all child nodes + */ + expand : function(isRecursive){ + if (this.isEmpty) { + return; + } + + if (!this.isRoot) { + this.el.classList.add('jsontree_node_expanded'); + } + + if (isRecursive) { + this.childNodes.forEach(function(item, i) { + if (item.isComplex) { + item.expand(isRecursive); + } + }); + } + }, + + /* + * Collapses this list of node child nodes + * + * @param isRecursive {boolean} - if true, collapses all child nodes + */ + collapse : function(isRecursive) { + if (this.isEmpty) { + return; + } + + if (!this.isRoot) { + this.el.classList.remove('jsontree_node_expanded'); + } + + if (isRecursive) { + this.childNodes.forEach(function(item, i) { + if (item.isComplex) { + item.collapse(isRecursive); + } + }); + } + }, + + /* + * Expands collapsed or collapses expanded node + * + * @param {boolean} isRecursive - Expand all child nodes if this node is expanded + * and collapse it otherwise + */ + toggle : function(isRecursive) { + if (this.isEmpty) { + return; + } + + this.el.classList.toggle('jsontree_node_expanded'); + + if (isRecursive) { + var isExpanded = this.el.classList.contains('jsontree_node_expanded'); + + this.childNodes.forEach(function(item, i) { + if (item.isComplex) { + item[isExpanded ? 'expand' : 'collapse'](isRecursive); + } + }); + } + }, + + /** + * Find child nodes that match some conditions and handle it + * + * @param {Function} matcher + * @param {Function} handler + * @param {boolean} isRecursive + */ + findChildren : function(matcher, handler, isRecursive) { + if (this.isEmpty) { + return; + } + + this.childNodes.forEach(function(item, i) { + if (matcher(item)) { + handler(item); + } + + if (item.isComplex && isRecursive) { + item.findChildren(matcher, handler, isRecursive); + } + }); + } + }); + + + /* + * The constructor for object values + * {... + * [+] "label": object, + * ...} + * object = {"abc": "def"} + * + * @constructor + * @param label {string} - key name + * @param val {Object} - value of object type, {"abc": "def"} + * @param isLast {boolean} - true if node is last in list of siblings + */ + function NodeObject(label, val, isLast) { + this.sym = ['{', '}']; + this.type = "object"; + + _NodeComplex.call(this, label, val, isLast); + } + utils.inherits(NodeObject,_NodeComplex); + + + /* + * The constructor for array values + * {... + * [+] "label": array, + * ...} + * array = [1,2,3] + * + * @constructor + * @param label {string} - key name + * @param val {Array} - value of array type, [1,2,3] + * @param isLast {boolean} - true if node is last in list of siblings + */ + function NodeArray(label, val, isLast) { + this.sym = ['[', ']']; + this.type = "array"; + + _NodeComplex.call(this, label, val, isLast); + } + utils.inherits(NodeArray, _NodeComplex); + + + /* ---------- The tree constructor ---------- */ + + /* + * The constructor for json tree. + * It contains only one Node (Array or Object), without property name. + * CSS-styles of .tree define main tree styles like font-family, + * font-size and own margins. + * + * Markup: + * + * + * @constructor + * @param jsonObj {Object | Array} - data for tree + * @param domEl {DOMElement} - DOM-element, wrapper for tree + */ + function Tree(jsonObj, domEl) { + this.wrapper = document.createElement('ul'); + this.wrapper.className = 'jsontree_tree clearfix'; + + this.rootNode = null; + + this.sourceJSONObj = jsonObj; + + this.loadData(jsonObj); + this.appendTo(domEl); + } + + Tree.prototype = { + constructor : Tree, + + /** + * Fill new data in current json tree + * + * @param {Object | Array} jsonObj - json-data + */ + loadData : function(jsonObj) { + if (!utils.isValidRoot(jsonObj)) { + alert('The root should be an object or an array'); + return; + } + + this.sourceJSONObj = jsonObj; + + this.rootNode = new Node(null, jsonObj, 'last'); + this.wrapper.innerHTML = ''; + this.wrapper.appendChild(this.rootNode.el); + }, + + /** + * Appends tree to DOM-element (or move it to new place) + * + * @param {DOMElement} domEl + */ + appendTo : function(domEl) { + domEl.appendChild(this.wrapper); + }, + + /** + * Expands all tree nodes (objects or arrays) recursively + * + * @param {Function} filterFunc - 'true' if this node should be expanded + */ + expand : function(filterFunc) { + if (this.rootNode.isComplex) { + if (typeof filterFunc == 'function') { + this.rootNode.childNodes.forEach(function(item, i) { + if (item.isComplex && filterFunc(item)) { + item.expand(); + } + }); + } else { + this.rootNode.expand('recursive'); + } + } + }, + + /** + * Collapses all tree nodes (objects or arrays) recursively + */ + collapse : function() { + if (typeof this.rootNode.collapse === 'function') { + this.rootNode.collapse('recursive'); + } + }, + + /** + * Returns the source json-string (pretty-printed) + * + * @param {boolean} isPrettyPrinted - 'true' for pretty-printed string + * @returns {string} - for exemple, '{"a":2,"b":3}' + */ + toSourceJSON : function(isPrettyPrinted) { + if (!isPrettyPrinted) { + return JSON.stringify(this.sourceJSONObj); + } + + var DELIMETER = "[%^$#$%^%]", + jsonStr = JSON.stringify(this.sourceJSONObj, null, DELIMETER); + + jsonStr = jsonStr.split("\n").join("
    "); + jsonStr = jsonStr.split(DELIMETER).join("    "); + + return jsonStr; + }, + + /** + * Find all nodes that match some conditions and handle it + */ + findAndHandle : function(matcher, handler) { + this.rootNode.findChildren(matcher, handler, 'isRecursive'); + }, + + /** + * Unmark all nodes + */ + unmarkAll : function() { + this.rootNode.findChildren(function(node) { + return true; + }, function(node) { + node.unmark(); + }, 'isRecursive'); + } + }; + + + /* ---------- Public methods ---------- */ + return { + /** + * Creates new tree by data and appends it to the DOM-element + * + * @param jsonObj {Object | Array} - json-data + * @param domEl {DOMElement} - the wrapper element + * @returns {Tree} + */ + create : function(jsonObj, domEl) { + return new Tree(jsonObj, domEl); + } + }; +})(); +export default { + jsonTree +} \ No newline at end of file diff --git a/src/renderer/src/components/whiteboard/whiteboard.vue b/src/renderer/src/components/whiteboard/whiteboard.vue new file mode 100644 index 0000000..16dfc93 --- /dev/null +++ b/src/renderer/src/components/whiteboard/whiteboard.vue @@ -0,0 +1,913 @@ + + + + + + diff --git a/src/renderer/src/main.js b/src/renderer/src/main.js index be69725..395ec45 100644 --- a/src/renderer/src/main.js +++ b/src/renderer/src/main.js @@ -15,6 +15,7 @@ import App from './App.vue' import router from './router' import log from 'electron-log/renderer' // 渲染进程日志-文件记录 import customComponent from '@/components/common' // 自定义组件 +import plugins from './plugins' // plugins插件 if(process.env.NODE_ENV != 'development') { // 非开发环境,将日志打印到日志文件 Object.assign(console, log.functions) // 渲染进程日志-控制台替换 @@ -40,4 +41,5 @@ app.use(router) .use(store) .use(ElementPlus, { locale: zhLocale }) .use(customComponent) // 自定义组件 + .use(plugins) .mount('#app') \ No newline at end of file diff --git a/src/renderer/src/plugins/index.js b/src/renderer/src/plugins/index.js new file mode 100644 index 0000000..8b5c431 --- /dev/null +++ b/src/renderer/src/plugins/index.js @@ -0,0 +1,18 @@ +// import tab from './tab' +// import auth from './auth' +// import cache from './cache' +import modal from './modal' +// import download from './download' + +export default function installPlugins(app){ + // 页签操作 + // app.config.globalProperties.$tab = tab + // // 认证对象 + // app.config.globalProperties.$auth = auth + // // 缓存对象 + // app.config.globalProperties.$cache = cache + // 模态框对象 + app.config.globalProperties.$modal = modal + // 下载文件 + // app.config.globalProperties.$download = download +} diff --git a/src/renderer/src/plugins/modal.js b/src/renderer/src/plugins/modal.js new file mode 100644 index 0000000..b59e14d --- /dev/null +++ b/src/renderer/src/plugins/modal.js @@ -0,0 +1,82 @@ +import { ElMessage, ElMessageBox, ElNotification, ElLoading } from 'element-plus' + +let loadingInstance; + +export default { + // 消息提示 + msg(content) { + ElMessage.info(content) + }, + // 错误消息 + msgError(content) { + ElMessage.error(content) + }, + // 成功消息 + msgSuccess(content) { + ElMessage.success(content) + }, + // 警告消息 + msgWarning(content) { + ElMessage.warning(content) + }, + // 弹出提示 + alert(content) { + ElMessageBox.alert(content, "系统提示") + }, + // 错误提示 + alertError(content) { + ElMessageBox.alert(content, "系统提示", { type: 'error' }) + }, + // 成功提示 + alertSuccess(content) { + ElMessageBox.alert(content, "系统提示", { type: 'success' }) + }, + // 警告提示 + alertWarning(content) { + ElMessageBox.alert(content, "系统提示", { type: 'warning' }) + }, + // 通知提示 + notify(content) { + ElNotification.info(content) + }, + // 错误通知 + notifyError(content) { + ElNotification.error(content); + }, + // 成功通知 + notifySuccess(content) { + ElNotification.success(content) + }, + // 警告通知 + notifyWarning(content) { + ElNotification.warning(content) + }, + // 确认窗体 + confirm(content) { + return ElMessageBox.confirm(content, "系统提示", { + confirmButtonText: '确定', + cancelButtonText: '取消', + type: "warning", + }) + }, + // 提交内容 + prompt(content) { + return ElMessageBox.prompt(content, "系统提示", { + confirmButtonText: '确定', + cancelButtonText: '取消', + type: "warning", + }) + }, + // 打开遮罩层 + loading(content) { + loadingInstance = ElLoading.service({ + lock: true, + text: content, + background: "rgba(0, 0, 0, 0.7)", + }) + }, + // 关闭遮罩层 + closeLoading() { + loadingInstance.close(); + } +} diff --git a/src/renderer/src/router/index.js b/src/renderer/src/router/index.js index 9f1fc0e..2fa6890 100644 --- a/src/renderer/src/router/index.js +++ b/src/renderer/src/router/index.js @@ -82,9 +82,9 @@ export const constantRoutes = [ }, { path: '/classTaskAssign', - component: () => import('@/views/classTaskAssign/index.vue'), + component: () => import('@/views/classTask/classTaskAssign.vue'), name: 'classTaskAssign', - meta: {title: '作业设计'}, + meta: {title: '作业布置'}, }, { path: '/classTask', diff --git a/src/renderer/src/views/classTask/classTask.vue b/src/renderer/src/views/classTask/classTask.vue index 2c23d0a..4254de3 100644 --- a/src/renderer/src/views/classTask/classTask.vue +++ b/src/renderer/src/views/classTask/classTask.vue @@ -142,8 +142,9 @@ const getClassWorkList = () => { edusubject: userStore.edusubject,//学科 deaddate: tabActive.value === '进行中'? getTomorrow() : EndDate.value,// 进行中:明天,已结束:选择的日期 status: '1', // 作业状态:1-已发布 - orderby: 'concat(deaddate,uniquekey) DESC', - pageSize: 100 + // orderby: 'concat(deaddate,uniquekey) DESC', + orderby: 'uniquekey DESC', + pageSize: 100, }).then((response) => { for (var i = 0; i < response.rows.length; i++) { // 初始化部分新增字段值 @@ -325,7 +326,7 @@ const escapeHtmlQuotes = (str) => { // 后端已replace双引号, 故前端不用在处理 const regex1 = /\\+/g; // 匹配多个反斜杠 let result = str.replace(regex1, '\\'); - + result = str.replace(/(?'); //替换\n而不替换\\n 为 \\n return result; } const pollingST = ref(null) //轮询定时器标识 @@ -373,7 +374,8 @@ const getStudentVisible = async () => { edusubject: userStore.edusubject,//学科 deaddate: tabActive.value === '进行中'? getTomorrow() : EndDate.value,// 进行中:明天,已结束:选择的日期 status: '1', // 作业状态:1-已发布 - orderby: 'concat(deaddate,uniquekey) DESC', + // orderby: 'concat(deaddate,uniquekey) DESC', + orderby: 'uniquekey DESC', pageSize: 100 }) const curWorkList = response.rows diff --git a/src/renderer/src/views/classTask/classTaskAssign.vue b/src/renderer/src/views/classTask/classTaskAssign.vue new file mode 100644 index 0000000..7af3e3a --- /dev/null +++ b/src/renderer/src/views/classTask/classTaskAssign.vue @@ -0,0 +1,527 @@ + + + + + + + diff --git a/src/renderer/src/views/classTask/container/item-dialog-score.vue b/src/renderer/src/views/classTask/container/item-dialog-score.vue index df31dd6..4c33627 100644 --- a/src/renderer/src/views/classTask/container/item-dialog-score.vue +++ b/src/renderer/src/views/classTask/container/item-dialog-score.vue @@ -1,7 +1,7 @@ \ No newline at end of file diff --git a/src/renderer/src/views/desktop/index.vue b/src/renderer/src/views/desktop/index.vue index 9d92677..0fc0315 100644 --- a/src/renderer/src/views/desktop/index.vue +++ b/src/renderer/src/views/desktop/index.vue @@ -105,6 +105,10 @@ const menuList = [{ icon: 'icon-jiaoxuefansi', isOuter: true, path: '/teaching/classtaskassign?titleName=作业布置&openDialog=newClassTask', + // path: '/newClassTask' + //path: '/classTaskAssign' + //isOuter: true, + //path: '/teaching/classtaskassign?titleName=作业布置&&openDialog=newClassTask' id: '2-1' }, { @@ -113,6 +117,7 @@ const menuList = [{ isOuter: true, path: '/teaching/classtaskassign?titleName=作业布置', id: '2-2' + // path: '/classTaskAssign' }, { name: '作业批改', diff --git a/src/renderer/src/views/examReport/index.vue b/src/renderer/src/views/examReport/index.vue index 8548fa9..37d73e2 100644 --- a/src/renderer/src/views/examReport/index.vue +++ b/src/renderer/src/views/examReport/index.vue @@ -51,11 +51,11 @@