Compare commits
59 Commits
Author | SHA1 | Date |
---|---|---|
yangws | 77b24581e2 | |
小杨 | 9f1c28ac86 | |
zouyf | f8a877f62f | |
“zouyf” | 813af948b5 | |
“zouyf” | 497f4dd778 | |
朱浩 | 5d6a7dfe8d | |
朱浩 | 7607fb2516 | |
朱浩 | b88e794dce | |
朱浩 | c093fbfd6a | |
朱浩 | 519565f463 | |
qinqing | 1dac204c25 | |
qinqing | 5df96fe26c | |
qinqing | bb0fb16ede | |
朱浩 | 7b5988c979 | |
朱浩 | cabced383a | |
朱浩 | d400911bfc | |
朱浩 | 66ff0ae6fd | |
朱浩 | 5e2a2d90fc | |
lyc | 8bd1eec801 | |
lyc | 875cccae79 | |
lyc | cc39253a79 | |
lyc | 865574ec4c | |
yangws | 532e724351 | |
小杨 | ead9ebd6eb | |
yangws | a147c60d78 | |
小杨 | a0477a18df | |
yangws | f3f3ada1ed | |
小杨 | 5ad37f60f0 | |
yangws | 2cecbc14fd | |
小杨 | 8d8ff06c5e | |
小杨 | 0a3ab2eff5 | |
yangws | 21a4f4cfac | |
小杨 | 16f696bd4f | |
朱浩 | 19494eb9e7 | |
朱浩 | 4820405ca4 | |
朱浩 | 36f3018269 | |
yangws | b5a5381e5b | |
小杨 | de8e058197 | |
yangws | 35d07291d3 | |
小杨 | 9418998ec5 | |
小杨 | 64d9e9c18b | |
yangws | 28d6d2d705 | |
小杨 | 4ba1039ea9 | |
朱浩 | 1417919f74 | |
yangws | c691612bff | |
小杨 | baa1fa7483 | |
朱浩 | 0f211628c5 | |
yangws | 9afd5f417d | |
小杨 | fb4908ee1d | |
yangws | fc181559e2 | |
小杨 | 24f2d0ef4b | |
朱浩 | eba83889b0 | |
zhengdegang | bd485409e3 | |
zdg | 6303b442a4 | |
yangws | 5a92b6dedf | |
小杨 | 09df401cf1 | |
“zouyf” | 33287aad57 | |
朱浩 | 966a64faa3 | |
qinqing | f72b2f8341 |
|
@ -18,4 +18,4 @@ VITE_APP_RES_FILE_PATH = 'https://prev.ysaix.com:7868/src/assets/textbook/booktx
|
|||
|
||||
VITE_APP_BUILD_BASE_PATH = 'https://prev.ysaix.com:7868/'
|
||||
|
||||
VITE_SHOW_DEV_TOOLS = 'false'
|
||||
VITE_SHOW_DEV_TOOLS = 'true'
|
||||
|
|
|
@ -17,3 +17,5 @@ VITE_BUILD_COMPRESS = gzip
|
|||
VITE_APP_RES_FILE_PATH = 'https://file.ysaix.com:7868/src/assets/textbook/booktxt/'
|
||||
|
||||
VITE_APP_BUILD_BASE_PATH = 'https://file.ysaix.com:7868/'
|
||||
|
||||
VITE_SHOW_DEV_TOOLS = 'true'
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
# 页面标题
|
||||
VITE_APP_TITLE = AIX智慧课堂
|
||||
VITE_APP_TITLE = 文枢课堂
|
||||
|
||||
# 生产环境配置
|
||||
VITE_APP_ENV = 'production'
|
||||
|
@ -17,3 +17,5 @@ VITE_BUILD_COMPRESS = gzip
|
|||
VITE_APP_RES_FILE_PATH = 'https://prev.ysaix.com:7868/src/assets/textbook/booktxt/'
|
||||
|
||||
VITE_APP_BUILD_BASE_PATH = 'https://prev.ysaix.com:7868/'
|
||||
|
||||
VITE_SHOW_DEV_TOOLS = 'false'
|
|
@ -0,0 +1,21 @@
|
|||
# 页面标题
|
||||
VITE_APP_TITLE = 实训教学
|
||||
|
||||
# 生产环境配置
|
||||
VITE_APP_ENV = 'production'
|
||||
|
||||
# AIx融合数字管理系统/生产环境
|
||||
VITE_APP_BASE_API = 'https://prev.ysaix.com:7868/prod-api'
|
||||
|
||||
VITE_APP_DOMAIN = 'prev.ysaix.com'
|
||||
|
||||
VITE_APP_UPLOAD_API = 'https://prev.ysaix.com:7868/prod-api'
|
||||
|
||||
# 是否在打包时开启压缩,支持 gzip 和 brotli
|
||||
VITE_BUILD_COMPRESS = gzip
|
||||
|
||||
VITE_APP_RES_FILE_PATH = 'https://prev.ysaix.com:7868/src/assets/textbook/booktxt/'
|
||||
|
||||
VITE_APP_BUILD_BASE_PATH = 'https://prev.ysaix.com:7868/'
|
||||
|
||||
VITE_SHOW_DEV_TOOLS = 'false'
|
|
@ -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/*'
|
||||
|
@ -17,7 +17,7 @@ asarUnpack:
|
|||
nsis:
|
||||
oneClick: false
|
||||
allowToChangeInstallationDirectory: true
|
||||
artifactName: ${name}-${version}-setup.${ext}
|
||||
artifactName: ${name}-yc-${version}-setup.${ext}
|
||||
shortcutName: ${productName}
|
||||
uninstallDisplayName: ${productName}
|
||||
createDesktopShortcut: always
|
||||
|
@ -30,7 +30,7 @@ mac:
|
|||
- NSDownloadsFolderUsageDescription: Application requests access to the user's Downloads folder.
|
||||
notarize: false
|
||||
dmg:
|
||||
artifactName: ${name}-${version}.${ext}
|
||||
artifactName: ${name}-yc-${version}.${ext}
|
||||
linux:
|
||||
target:
|
||||
- AppImage
|
||||
|
@ -39,11 +39,11 @@ linux:
|
|||
maintainer: electronjs.org
|
||||
category: Utility
|
||||
appImage:
|
||||
artifactName: ${name}-${version}.${ext}
|
||||
artifactName: ${name}-yc-${version}.${ext}
|
||||
npmRebuild: false
|
||||
publish:
|
||||
provider: generic
|
||||
url: https://prev.ysaix.com:7868/src/assets/smarttalk/
|
||||
url: https://prev.ysaix.com:7868/src/assets/smarttalkyc/
|
||||
electronDownload:
|
||||
mirror: https://npmmirror.com/mirrors/electron/
|
||||
# 额外依赖打包到输出目录
|
|
@ -0,0 +1,54 @@
|
|||
appId: com.electron.app.yc2
|
||||
productName: 实训教学
|
||||
directories:
|
||||
output: dist
|
||||
buildResources: build
|
||||
win:
|
||||
executableName: 实训教学
|
||||
icon: resources/logo2.ico
|
||||
files:
|
||||
- '!**/.vscode/*'
|
||||
- '!src/*'
|
||||
- '!electron.vite.config.{js,ts,mjs,cjs}'
|
||||
- '!{.eslintignore,.eslintrc.cjs,.prettierignore,.prettierrc.yaml,dev-app-update.yml,CHANGELOG.md,README.md}'
|
||||
- '!{.env,.env.*,.npmrc,pnpm-lock.yaml}'
|
||||
asarUnpack:
|
||||
- resources/**
|
||||
nsis:
|
||||
oneClick: false
|
||||
allowToChangeInstallationDirectory: true
|
||||
artifactName: ${name}-yc-${version}-setup.${ext}
|
||||
shortcutName: ${productName}
|
||||
uninstallDisplayName: ${productName}
|
||||
createDesktopShortcut: always
|
||||
mac:
|
||||
entitlementsInherit: build/entitlements.mac.plist
|
||||
extendInfo:
|
||||
- NSCameraUsageDescription: Application requests access to the device's camera.
|
||||
- NSMicrophoneUsageDescription: Application requests access to the device's microphone.
|
||||
- NSDocumentsFolderUsageDescription: Application requests access to the user's Documents folder.
|
||||
- NSDownloadsFolderUsageDescription: Application requests access to the user's Downloads folder.
|
||||
notarize: false
|
||||
dmg:
|
||||
artifactName: ${name}-yc-${version}.${ext}
|
||||
linux:
|
||||
target:
|
||||
- AppImage
|
||||
- snap
|
||||
- deb
|
||||
maintainer: electronjs.org
|
||||
category: Utility
|
||||
appImage:
|
||||
artifactName: ${name}-yc-${version}.${ext}
|
||||
npmRebuild: false
|
||||
publish:
|
||||
provider: generic
|
||||
url: https://prev.ysaix.com:7868/src/assets/smarttalkyc/
|
||||
electronDownload:
|
||||
mirror: https://npmmirror.com/mirrors/electron/
|
||||
# 额外依赖打包到输出目录
|
||||
extraFiles:
|
||||
- from: ./node_modules/im_electron_sdk/lib/
|
||||
to: ./resources
|
||||
filter:
|
||||
- '**/*'
|
24
package.json
24
package.json
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "aix-win",
|
||||
"version": "2.1.30",
|
||||
"version": "2.1.37",
|
||||
"description": "",
|
||||
"main": "./out/main/index.js",
|
||||
"author": "上海交大重庆人工智能研究院",
|
||||
|
@ -16,16 +16,12 @@
|
|||
"build:dev": "npm run build && electron-builder --win --config ./electron-builder-test.yml",
|
||||
"build:test": "electron-vite build --mode test && electron-builder --win --config ./electron-builder.yml",
|
||||
"build:prod": "electron-vite build --mode production && electron-builder --win --config ./electron-builder-prod.yml",
|
||||
"build:lt": "electron-vite build --mode lt && electron-builder --win --config ./electron-builder-lt.yml",
|
||||
"build:yc": "electron-vite build --mode yc && electron-builder --win --config ./electron-builder-yc.yml",
|
||||
"build:yc2": "electron-vite build --mode yc2 && electron-builder --win --config ./electron-builder-yc2.yml",
|
||||
"build:mac": "electron-vite build --mode production && electron-builder --mac --config ./electron-builder-prod.yml",
|
||||
"build:linux": "npm run build && electron-builder --linux"
|
||||
},
|
||||
"dependencies": {
|
||||
"@electron-toolkit/preload": "^3.0.1",
|
||||
"@electron-toolkit/utils": "^3.0.0",
|
||||
"@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",
|
||||
|
@ -34,6 +30,11 @@
|
|||
"@antv/x6-plugin-selection": "^2.2.2",
|
||||
"@antv/x6-plugin-snapline": "^2.1.7",
|
||||
"@antv/x6-plugin-transform": "^2.1.8",
|
||||
"@electron-toolkit/preload": "^3.0.1",
|
||||
"@electron-toolkit/utils": "^3.0.0",
|
||||
"@electron/remote": "^2.1.2",
|
||||
"@element-plus/icons-vue": "^2.3.1",
|
||||
"@vitejs/plugin-vue-jsx": "^4.0.0",
|
||||
"@vue-office/docx": "^1.6.2",
|
||||
"@vue-office/excel": "^1.7.11",
|
||||
"@vue-office/pdf": "^2.0.2",
|
||||
|
@ -53,20 +54,21 @@
|
|||
"js-cookie": "^3.0.5",
|
||||
"jsencrypt": "^3.3.2",
|
||||
"jsondiffpatch": "0.6.0",
|
||||
"less": "^4.2.0",
|
||||
"less-loader": "^7.3.0",
|
||||
"lodash": "^4.17.21",
|
||||
"node-addon-api": "^8.1.0",
|
||||
"pdfjs-dist": "4.4.168",
|
||||
"pinia": "^2.1.7",
|
||||
"pinia-plugin-persistedstate": "^3.2.1",
|
||||
"qs": "^6.13.1",
|
||||
"spark-md5": "^3.0.2",
|
||||
"vite-plugin-electron": "^0.28.8",
|
||||
"vue-qr": "^4.0.9",
|
||||
"vue-router": "^4.4.0",
|
||||
"whiteboard_lyc": "^0.1.3",
|
||||
"xgplayer": "^3.0.19",
|
||||
"xlsx": "^0.18.5",
|
||||
"less": "^4.2.0",
|
||||
"less-loader": "^7.3.0",
|
||||
"whiteboard_lyc": "^0.1.3"
|
||||
"xlsx": "^0.18.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@electron-toolkit/eslint-config": "^1.0.2",
|
||||
|
|
|
@ -284,9 +284,10 @@ export default async function ({ app, shell, BrowserWindow, ipcMain }) {
|
|||
function downloadFiles(url,fileName) {
|
||||
console.log(url,fileName)
|
||||
return new Promise((resolve, reject)=>{
|
||||
const browserWindow = BrowserWindow.getFocusedWindow()
|
||||
const browserWindow = BrowserWindow.getAllWindows()
|
||||
console.log(browserWindow)
|
||||
const id = manager.download({
|
||||
window: browserWindow,
|
||||
window: browserWindow[0],
|
||||
url: url,
|
||||
saveAsFilename: fileName,
|
||||
directory: appTempFilePath,
|
||||
|
|
|
@ -46,7 +46,7 @@ if(!gotTheLock){
|
|||
function createLoginWindow() {
|
||||
if (loginWindow) return
|
||||
loginWindow = new BrowserWindow({
|
||||
width: 888,
|
||||
width: import.meta.env.MODE==='yc'||import.meta.env.MODE==='yc2'?1160:888,
|
||||
height: 520,
|
||||
show: false,
|
||||
frame: false,
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -13097,15 +13097,16 @@ const PDFViewerApplication = {
|
|||
}
|
||||
if (isValidSpreadMode(spread)) {
|
||||
//默认双页
|
||||
// this.pdfViewer.spreadMode = spread;
|
||||
this.pdfViewer.spreadMode = 1;
|
||||
this.pdfViewer.spreadMode = spread;
|
||||
//默认双页
|
||||
// this.pdfViewer.spreadMode = 1;
|
||||
}
|
||||
};
|
||||
this.isInitialViewSet = true;
|
||||
this.pdfSidebar?.setInitialView(sidebarView);
|
||||
//默认双页
|
||||
// setViewerModes(scrollMode, spreadMode);
|
||||
setViewerModes(scrollMode, 1);
|
||||
setViewerModes(scrollMode, spreadMode);
|
||||
// setViewerModes(scrollMode, 1);
|
||||
if (this.initialBookmark) {
|
||||
setRotation(this.initialRotation);
|
||||
delete this.initialRotation;
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
import request from '@/utils/request'
|
||||
|
||||
// 查询分析列表
|
||||
export function getEvaluationclueList(params) {
|
||||
return request({
|
||||
url: '/education/evaluationclue/list',
|
||||
method: 'get',
|
||||
params
|
||||
})
|
||||
}
|
||||
|
||||
//修改分析内容
|
||||
export function updateEvaluationclue(data) {
|
||||
return request({
|
||||
url: '/education/evaluationclue',
|
||||
method: 'put',
|
||||
data
|
||||
})
|
||||
}
|
||||
// 新增分析
|
||||
export function addEvaluationclue(data) {
|
||||
return request({
|
||||
url: '/education/evaluationclue',
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
|
@ -1,9 +1,9 @@
|
|||
@font-face {
|
||||
font-family: "iconfont"; /* Project id 2794390 */
|
||||
src: url('iconfont.woff2?t=1728543886557') format('woff2'),
|
||||
url('iconfont.woff?t=1728543886557') format('woff'),
|
||||
url('iconfont.ttf?t=1728543886557') format('truetype'),
|
||||
url('iconfont.svg?t=1728543886557#iconfont') format('svg');
|
||||
src: url('iconfont.woff2?t=1732002934577') format('woff2'),
|
||||
url('iconfont.woff?t=1732002934577') format('woff'),
|
||||
url('iconfont.ttf?t=1732002934577') format('truetype'),
|
||||
url('iconfont.svg?t=1732002934577#iconfont') format('svg');
|
||||
}
|
||||
|
||||
.iconfont {
|
||||
|
@ -14,6 +14,26 @@
|
|||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
.icon-a-shiyanpingshiyanyanjiu:before {
|
||||
content: "\e9a0";
|
||||
}
|
||||
|
||||
.icon-banji2:before {
|
||||
content: "\e6c0";
|
||||
}
|
||||
|
||||
.icon-set:before {
|
||||
content: "\e691";
|
||||
}
|
||||
|
||||
.icon-shouye:before {
|
||||
content: "\e637";
|
||||
}
|
||||
|
||||
.icon-gongzuotai:before {
|
||||
content: "\e690";
|
||||
}
|
||||
|
||||
.icon-A1:before {
|
||||
content: "\e635";
|
||||
}
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -1,10 +1,45 @@
|
|||
{
|
||||
"id": "2794390",
|
||||
"name": "electron",
|
||||
"name": "文枢2.1",
|
||||
"font_family": "iconfont",
|
||||
"css_prefix_text": "icon-",
|
||||
"description": "",
|
||||
"glyphs": [
|
||||
{
|
||||
"icon_id": "41507853",
|
||||
"name": "实验瓶",
|
||||
"font_class": "a-shiyanpingshiyanyanjiu",
|
||||
"unicode": "e9a0",
|
||||
"unicode_decimal": 59808
|
||||
},
|
||||
{
|
||||
"icon_id": "1017928",
|
||||
"name": "班级",
|
||||
"font_class": "banji2",
|
||||
"unicode": "e6c0",
|
||||
"unicode_decimal": 59072
|
||||
},
|
||||
{
|
||||
"icon_id": "376364",
|
||||
"name": "设置",
|
||||
"font_class": "set",
|
||||
"unicode": "e691",
|
||||
"unicode_decimal": 59025
|
||||
},
|
||||
{
|
||||
"icon_id": "5835474",
|
||||
"name": "首页",
|
||||
"font_class": "shouye",
|
||||
"unicode": "e637",
|
||||
"unicode_decimal": 58935
|
||||
},
|
||||
{
|
||||
"icon_id": "19108133",
|
||||
"name": "工作台",
|
||||
"font_class": "gongzuotai",
|
||||
"unicode": "e690",
|
||||
"unicode_decimal": 59024
|
||||
},
|
||||
{
|
||||
"icon_id": "11657531",
|
||||
"name": "A",
|
||||
|
|
|
@ -14,6 +14,16 @@
|
|||
/>
|
||||
<missing-glyph />
|
||||
|
||||
<glyph glyph-name="a-shiyanpingshiyanyanjiu" unicode="" d="M404.602646 837.887844l-16.783367-7.405737-128.245981-56.577561-16.787416-7.405736-16.787416-7.405737 100.716399-228.298334a18.342257 18.342257 0 0 1 24.189104-9.37763l161.816764 71.384985a18.338207 18.338207 0 0 1 9.377631 24.189104L421.390062 845.293581l-16.787416-7.405737zM422.325396 869.563666L202.995737 772.80725A18.354404 18.354404 0 0 1 193.614058 748.614097l25.735846-58.342952a18.350355 18.350355 0 0 1 24.193153-9.38168L462.872715 777.645881a18.350355 18.350355 0 0 1 9.38168 24.193153l-25.739895 58.347001a18.342257 18.342257 0 0 1-24.189104 9.377631zM921.226676 261.831663L517.555315 623.259148l-12.240318-13.669638-161.812715-71.384985-18.346306 0.076932 1.886863-540.157186c-0.149815-39.065362 14.386322-70.158119 40.940079-87.540747 26.549708-17.37858 60.853372-18.265324 96.590404-2.498272L895.547517 99.994654c35.765376 15.77515 58.193137 41.677008 63.157289 72.935776 4.956053 31.254719-8.353218 62.833364-37.47813 88.901233zM921.226676 261.831663l-136.445333 122.168337H325.694501l1.348338-385.879778c-0.149815-39.065362 14.386322-70.158119 40.940079-87.540747 26.549708-17.37858 60.853372-18.265324 96.590404-2.498272l430.974195 191.909402c35.765376 15.77515 58.193137 41.677008 63.157289 72.935776 4.956053 31.258768-8.353218 62.837413-37.47813 88.905282z" horiz-adv-x="1039" />
|
||||
|
||||
<glyph glyph-name="banji2" unicode="" d="M865.793707 531.61984c0 12.357973-10.03008 22.386347-22.386347 22.386347-12.357973 0-22.386347-10.03008-22.386347-22.386347 0-61.743787-50.19136-111.93344-111.935147-111.93344s-111.935147 50.19136-111.935147 111.93344c0 12.357973-10.03008 22.386347-22.386347 22.386347-12.357973 0-22.386347-10.03008-22.386347-22.386347 0-86.413653 70.294187-156.70784 156.70784-156.70784S865.793707 445.206187 865.793707 531.61984zM1059.054933 208.631467c58.876587 23.237973 99.48672 81.08544 99.48672 145.916587 0 71.637333-47.684267 132.082347-115.24608 150.528 0.67072 8.820053 1.611093 17.55136 1.611093 26.550613 0 185.183573-150.662827 335.802027-335.802027 335.802027-185.183573 0-335.802027-150.618453-335.802027-335.802027 0-9.043627 1.477973-17.73056 2.194773-26.594987-67.428693-18.536107-115.024213-78.93504-115.024213-150.483627 0-64.832853 40.564053-122.68032 99.442347-145.916587L287.470933 1.59744c-4.118187-11.685547 2.059947-24.446293 13.70112-28.520107 2.46272-0.84992 4.969813-1.252693 7.386453-1.252693 9.222827 0 17.90976 5.77536 21.133653 14.999893l80.413013 229.643947c0.402773 1.02912 0.67072 2.148693 0.940373 3.268267 2.46272 11.55072-4.432213 23.058773-15.849813 26.238293-2.776747 0.807253-5.59616 0.896-8.193707 0.761173-47.72864 13.298347-81.75616 57.623893-81.75616 107.815253 0 49.206613 31.43168 91.24864 76.741973 106.069333 25.879893-117.306027 113.322667-215.048533 230.137173-250.374827l-72.174933-206.226773c-4.118187-11.685547 2.059947-24.446293 13.745493-28.520107 2.46272-0.896 4.92544-1.252693 7.386453-1.252693 9.268907 0 17.865387 5.77536 21.133653 14.999893l80.18944 229.15072c2.10432 6.045013 1.56672 12.670293-1.611093 18.26816-3.089067 5.59616-8.46336 9.581227-14.73024 10.970453-132.125013 29.146453-227.985067 148.60288-227.985067 283.997867 0 160.469333 130.56 291.027627 291.027627 291.027627s291.027627-130.56 291.027627-291.027627c0-12.043947-0.761173-23.86432-2.148693-35.551573-14.327467-116.993707-97.158827-213.03296-211.10784-244.642133-5.95456-1.657173-10.970453-5.686613-13.88032-11.19232-2.82112-5.46304-3.31264-11.910827-1.252693-17.73056l81.62304-233.270613c3.268267-9.222827 11.864747-14.999893 21.133653-14.999893 2.46272 0 4.92544 0.3584 7.386453 1.252693 11.685547 4.07552 17.819307 16.83456 13.745493 28.520107l-74.011307 211.46624c110.052693 38.99904 190.55616 131.76832 214.95808 245.225813 45.535573-14.685867 77.190827-56.818347 77.190827-106.15808 0-50.773333-34.833067-95.501653-83.457707-108.30848-2.194773 0-4.522667-0.31232-6.760107-1.02912-11.461973-3.403093-18.044587-15.849813-15.179093-27.446613 0.402773-1.431893 0.896-2.776747 1.477973-4.118187l79.428267-226.822827c3.223893-9.222827 11.820373-14.999893 21.087573-14.999893 2.46272 0 4.969813 0.402773 7.432533 1.252693 11.641173 4.07552 17.819307 16.83456 13.70112 28.520107L1059.054933 208.631467z" horiz-adv-x="1366" />
|
||||
|
||||
<glyph glyph-name="set" unicode="" d="M512 558.182c-115.665 0-209.455-93.789-209.455-209.455 0-115.665 93.789-209.455 209.455-209.455s209.455 93.789 209.455 209.455c0 115.665-93.789 209.455-209.455 209.455zM512 185.818c-89.972 0-162.909 72.937-162.909 162.909 0 89.972 72.937 162.909 162.909 162.909s162.909-72.937 162.909-162.909c0-89.972-72.937-162.909-162.909-162.909zM907.636 465.091l-66.746 0c-4.143 11.59-8.797 22.9-14.057 33.932l47.197 47.197c18.199 18.199 18.199 47.663 0 65.815l-98.723 98.723c-18.199 18.199-47.663 18.199-65.815 0l-47.43-47.43c-10.938 5.213-22.109 10.1-33.652 14.196l0 66.839c0 25.693-20.852 46.545-46.545 46.545l-139.636 0c-25.693 0-46.545-20.852-46.545-46.545l0-66.839c-11.59-4.096-22.807-8.89-33.792-14.15l-47.383 47.383c-18.153 18.199-47.663 18.199-65.815 0l-98.723-98.723c-18.199-18.199-18.199-47.663 0-65.815l47.29-47.29c-5.26-10.985-10.1-22.249-14.196-33.839l-66.7 0c-25.693 0-46.545-20.852-46.545-46.545l0-139.636c0-25.693 20.852-46.545 46.545-46.545l66.746 0c4.143-11.59 8.797-22.9 14.057-33.885l-47.197-47.197c-18.199-18.153-18.199-47.663 0-65.815l98.723-98.723c18.199-18.199 47.663-18.199 65.815 0l47.43 47.43c10.938-5.213 22.109-10.1 33.652-14.196l0-66.886c0-25.693 20.852-46.545 46.545-46.545l139.636 0c25.693 0 46.545 20.852 46.545 46.545l0 66.746c11.59 4.143 22.9 8.797 33.885 14.057l47.197-47.197c18.199-18.199 47.663-18.199 65.815 0l98.723 98.723c18.199 18.199 18.199 47.663 0 65.815l-47.43 47.43c5.213 10.938 10.1 22.109 14.196 33.652l66.932 0c25.693 0 46.545 20.852 46.545 46.545l0 139.636c0 25.74-20.852 46.592-46.545 46.592zM907.636 302.182c0-12.847-10.426-23.273-23.273-23.273l-78.243 0c-7.54-31.837-20.108-61.673-36.817-88.762l55.343-55.389c9.076-9.076 9.076-23.831 0-32.908l-65.815-65.815c-9.076-9.076-23.831-9.076-32.908 0l-55.343 55.343c-27.089-16.71-56.925-29.277-88.762-36.817l0-78.196c0-12.847-10.426-23.273-23.273-23.273l-93.091 0c-12.847 0-23.273 10.426-23.273 23.273l0 78.243c-31.837 7.54-61.673 20.108-88.762 36.817l-55.343-55.343c-9.076-9.076-23.831-9.076-32.908 0l-65.815 65.815c-9.076 9.076-9.076 23.831 0 32.908l55.343 55.389c-16.71 27.043-29.277 56.879-36.817 88.716l-78.243 0c-12.847 0-23.273 10.426-23.273 23.273l0 93.091c0 12.847 10.426 23.273 23.273 23.273l78.243 0c7.54 31.837 20.108 61.673 36.817 88.762l-55.389 55.343c-9.076 9.076-9.076 23.831 0 32.908l65.815 65.815c9.076 9.076 23.831 9.076 32.908 0l55.343-55.343c27.136 16.71 56.972 29.277 88.809 36.817l0 78.243c0 12.847 10.426 23.273 23.273 23.273l93.091 0c12.847 0 23.273-10.426 23.273-23.273l0-78.243c31.837-7.54 61.673-20.108 88.762-36.817l55.343 55.343c9.076 9.076 23.831 9.076 32.908 0l65.815-65.815c9.076-9.076 9.076-23.831 0-32.908l-55.343-55.343c16.71-27.089 29.277-56.879 36.817-88.762l78.243 0c12.847 0 23.273-10.426 23.273-23.273l0-93.091z" horiz-adv-x="1024" />
|
||||
|
||||
<glyph glyph-name="shouye" unicode="" d="M960 448c-6.4 0-12.9 1.9-18.6 6L512 760.7 82.6 454c-14.4-10.2-34.4-6.9-44.6 7.5-10.3 14.4-6.9 34.4 7.5 44.6l448 320c11.1 7.9 26.1 7.9 37.2 0l448-320c14.4-10.3 17.7-30.3 7.5-44.6-6.4-8.8-16.2-13.5-26.2-13.5zM608-64H416c-17.7 0-32 14.3-32 32V256c0 17.7 14.3 32 32 32h192c17.7 0 32-14.3 32-32v-288c0-17.7-14.3-32-32-32z m-160 64h128V224H448v-224zM832-64H192c-52.9 0-96 43.1-96 96V512c0 17.7 14.3 32 32 32s32-14.3 32-32v-480c0-17.6 14.4-32 32-32h640c17.6 0 32 14.4 32 32V512c0 17.7 14.3 32 32 32s32-14.3 32-32v-480c0-52.9-43.1-96-96-96z" horiz-adv-x="1024" />
|
||||
|
||||
<glyph glyph-name="gongzuotai" unicode="" d="M320.284444 810.666667a85.333333 85.333333 0 0 0 85.333334-85.333334v-234.951111H170.666667a85.333333 85.333333 0 0 0-85.333334 85.333334V725.333333a85.333333 85.333333 0 0 0 85.333334 85.333334h149.617777m0 85.333333H170.666667a170.666667 170.666667 0 0 1-170.666667-170.666667v-149.617777a170.666667 170.666667 0 0 1 170.666667-170.666667h320.284444V725.333333a170.666667 170.666667 0 0 1-170.666667 170.666667zM405.617778 277.617778V42.666667a85.333333 85.333333 0 0 0-85.333334-85.333334H170.666667a85.333333 85.333333 0 0 0-85.333334 85.333334v149.617777a85.333333 85.333333 0 0 0 85.333334 85.333334h234.951111m85.333333 85.333333H170.666667a170.666667 170.666667 0 0 1-170.666667-170.666667V42.666667a170.666667 170.666667 0 0 1 170.666667-170.666667h149.617777a170.666667 170.666667 0 0 1 170.666667 170.666667V362.951111zM853.333333 277.617778a85.333333 85.333333 0 0 0 85.333334-85.333334V42.666667a85.333333 85.333333 0 0 0-85.333334-85.333334h-149.617777a85.333333 85.333333 0 0 0-85.333334 85.333334v234.951111H853.333333m0 85.333333H533.048889V42.666667a170.666667 170.666667 0 0 1 170.666667-170.666667H853.333333a170.666667 170.666667 0 0 1 170.666667 170.666667v149.617777a170.666667 170.666667 0 0 1-170.666667 170.666667zM853.333333 810.666667a85.333333 85.333333 0 0 0 85.333334-85.333334v-149.617777a85.333333 85.333333 0 0 0-85.333334-85.333334h-234.951111V725.333333a85.333333 85.333333 0 0 0 85.333334 85.333334H853.333333m0 85.333333h-149.617777a170.666667 170.666667 0 0 1-170.666667-170.666667v-320.284444H853.333333a170.666667 170.666667 0 0 1 170.666667 170.666667V725.333333a170.666667 170.666667 0 0 1-170.666667 170.666667z" horiz-adv-x="1024" />
|
||||
|
||||
<glyph glyph-name="A1" unicode="" d="M449 339h121.5L507.5 505.5zM782 834H242C143 834 62 753 62 654v-540c0-99 81-180 180-180h540c99 0 180 81 180 180V654c0 99-81 180-180 180z m-139.5-675l-40.5 103.5H422L381.5 159H287L462.5 609H557l180-450h-94.5z" horiz-adv-x="1024" />
|
||||
|
||||
<glyph glyph-name="A" unicode="" d="M841.589844 779.507813a65.917969 65.917969 0 0 0 65.917969-65.917969v-659.179688a65.917969 65.917969 0 0 0-65.917969-65.917969H182.410156a65.917969 65.917969 0 0 0-65.917969 65.917969V713.589844a65.917969 65.917969 0 0 0 65.917969 65.917969h659.179688z m0-49.438477H182.410156a16.479492 16.479492 0 0 1-16.21582-13.513183L165.930664 713.589844v-659.179688a16.479492 16.479492 0 0 1 13.513183-16.21582L182.410156 37.930664h659.179688a16.479492 16.479492 0 0 1 16.21582 13.513183L858.069336 54.410156V713.589844a16.479492 16.479492 0 0 1-13.513183 16.21582L841.589844 730.069336zM539.059326 614.712891l188.85498-461.425782h-69.543456l-53.822022 139.746094H411.606933L360.916016 153.287109H296.118652l177.1875 461.425782h65.786133z m-34.277344-48.449707c-5.899658-29.597168-14.172364-58.996582-24.884033-88.165284L429.207031 342.80127h156.456299l-48.153076 127.781982-4.779053 12.689209a1170.900879 1170.900879 0 0 0-27.982178 82.990723z" horiz-adv-x="1024" />
|
||||
|
|
Before Width: | Height: | Size: 362 KiB After Width: | Height: | Size: 371 KiB |
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
After Width: | Height: | Size: 51 KiB |
Binary file not shown.
After Width: | Height: | Size: 1.1 MiB |
Binary file not shown.
After Width: | Height: | Size: 520 KiB |
|
@ -139,16 +139,23 @@ const handleNodeClick = (data) => {
|
|||
* data : 当前节点数据
|
||||
*/
|
||||
let nodeData = cloneDeep(toRaw(data));
|
||||
|
||||
//增加一个label 之前取的label
|
||||
nodeData.label = nodeData.itemtitle
|
||||
// 父级节点 如果当前是一级节点 父级则为null
|
||||
let parent = {
|
||||
id: nodeData.parentid,
|
||||
label: nodeData.parenttitle,
|
||||
itemtitle: nodeData.parenttitle
|
||||
}
|
||||
const parentNode = nodeData.parentid ? parent : null
|
||||
|
||||
let parentNode
|
||||
// 存在children 则为一级节点
|
||||
if(nodeData.children){
|
||||
// 为一级节点
|
||||
parentNode = null
|
||||
}
|
||||
else{
|
||||
parentNode = {
|
||||
id: nodeData.parentid,
|
||||
label: nodeData.parenttitle,
|
||||
itemtitle: nodeData.parenttitle
|
||||
}
|
||||
}
|
||||
|
||||
nodeData.parentNode = parentNode
|
||||
let curData = {
|
||||
textBook: {
|
||||
|
|
|
@ -8,9 +8,10 @@
|
|||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { ElMessageBox } from 'element-plus'
|
||||
import useUserStore from '@/store/modules/user'
|
||||
const Remote = require('@electron/remote')
|
||||
|
||||
const userStore = useUserStore()
|
||||
const { ipcRenderer } = window.electron || {}
|
||||
|
@ -37,6 +38,7 @@ const closeWindow = () => {
|
|||
ElMessageBox.confirm('确认退出系统吗?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
customClass: 'login-close-tool',
|
||||
type: 'warning'
|
||||
}).then(() => {
|
||||
userStore.logOut().then(() => {
|
||||
|
@ -47,8 +49,16 @@ const closeWindow = () => {
|
|||
})
|
||||
}).catch(() => { });
|
||||
}
|
||||
</script>
|
||||
|
||||
onMounted(() =>{
|
||||
isMaxSize.value = Remote.getCurrentWindow().isMaximized()
|
||||
})
|
||||
</script>
|
||||
<style>
|
||||
.login-close-tool {
|
||||
-webkit-app-region: no-drag;
|
||||
}
|
||||
</style>
|
||||
<style lang="scss" scoped>
|
||||
.header-tool {
|
||||
width: 100%;
|
||||
|
@ -75,4 +85,4 @@ const closeWindow = () => {
|
|||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
|
|
@ -121,6 +121,12 @@ const headerMenus = [
|
|||
id: 4,
|
||||
icon: '#icon-iconfontzhizuobiaozhunbduan3-1',
|
||||
disabled: true
|
||||
},
|
||||
{
|
||||
name: '实验室',
|
||||
id: 5,
|
||||
icon: '#icon-a-shiyanpingshiyanyanjiu',
|
||||
path: '/experiment'
|
||||
}
|
||||
]
|
||||
|
||||
|
|
|
@ -49,6 +49,12 @@ export const constantRoutes = [
|
|||
name: 'resource',
|
||||
meta: {title: '资源库'}
|
||||
},
|
||||
{
|
||||
path: '/experiment',
|
||||
component: () => import('@/views/experiment/index.vue'),
|
||||
name: 'experiment',
|
||||
meta: {title: '实验室'}
|
||||
},
|
||||
{
|
||||
path: '/prepare',
|
||||
component: () => import('@/views/prepare/index.vue'),
|
||||
|
|
|
@ -127,7 +127,10 @@ export const removePropertyOf = function(obj){
|
|||
export function removeTree(list) {
|
||||
var this_ = this
|
||||
for (var i in list) {
|
||||
if (list[i].children.length == 0) {
|
||||
if (list[i].children == null) {
|
||||
delete list[i].children;
|
||||
}
|
||||
else if (list[i].children.length == 0) {
|
||||
list[i].children = undefined
|
||||
} else {
|
||||
this_.removeTree(list[i].children)
|
||||
|
|
|
@ -0,0 +1,224 @@
|
|||
import request from '@/utils/request'
|
||||
import axios from 'axios';
|
||||
import { ElMessage, ElMessageBox, ElNotification } from "element-plus";
|
||||
import qs from 'qs';
|
||||
import { uploadServer, getJSONFile } from '@/utils/common';
|
||||
import { getEvaluationclueList, updateEvaluationclue, addEvaluationclue } from '@/api/pdf';
|
||||
|
||||
export const pdfCallBack = (pdfInfo) => {
|
||||
if(pdfInfo.toString() !== '{}' || pdfInfo !== null){
|
||||
if(pdfInfo.storageInfo){
|
||||
//圈点勾画操作返回
|
||||
// saveJSON(pdfInfo.storageInfo)
|
||||
saveAnnotationStorage(pdfInfo.storageInfo)
|
||||
return 'draw'
|
||||
}else if(pdfInfo.quoteInfo){
|
||||
//引用文本操作返回
|
||||
quoteWords(pdfInfo.quoteInfo)
|
||||
return 'quote'
|
||||
}else if(pdfInfo.imgInfo){
|
||||
//OCR截图返回
|
||||
ocrCallBack(pdfInfo.imgInfo)
|
||||
return 'ocr'
|
||||
}
|
||||
}else{
|
||||
return ''
|
||||
}
|
||||
}
|
||||
//百度OCR识别配置
|
||||
const baidubceConfig = {
|
||||
// Header
|
||||
'Content-Type': "application/x-www-form-urlencoded",
|
||||
// 格式
|
||||
'Accept' : 'application/json',
|
||||
// id(临时测试)
|
||||
'client_id': "U0DrGBE6X92IXgV6cJMNON8F",
|
||||
// 密钥(临时测试)
|
||||
'client_secret': 'oWb0M0YWMmZPMQIhIUkJX99ddr7h61qf',
|
||||
};
|
||||
|
||||
//获取百度token
|
||||
const getBaiduToken = async () => {
|
||||
let config = {
|
||||
headers: {
|
||||
'Content-Type': `${baidubceConfig['Content-Type']}`
|
||||
},
|
||||
url: `/baidubce/oauth/2.0/token?grant_type=client_credentials&client_id=${baidubceConfig['client_id']}&client_secret=${baidubceConfig['client_secret']}`,
|
||||
method: 'POST'
|
||||
}
|
||||
return await axios(config)
|
||||
}
|
||||
//图片识别方法
|
||||
const getBaiduOCR = async (token,params) => {
|
||||
let config = {
|
||||
headers: {
|
||||
'Content-Type': `${baidubceConfig['Content-Type']}`,
|
||||
'Accept': `${baidubceConfig['Accept']}`,
|
||||
},
|
||||
method: 'POST',
|
||||
url: `/baidubce/rest/2.0/ocr/v1/doc_analysis?access_token=${token}`,
|
||||
data: qs.stringify(params),
|
||||
}
|
||||
try{
|
||||
return await axios(config)
|
||||
}catch{
|
||||
return null
|
||||
}
|
||||
}
|
||||
const ocrCallBack = async (ocrInfo) => {
|
||||
ElMessage({
|
||||
type: 'info',
|
||||
message: '正在识别请稍后',
|
||||
grouping: true,
|
||||
duration: 3000
|
||||
})
|
||||
const baseUrl = ocrInfo.base64;
|
||||
const imgurl = baseUrl.split(",")[1];
|
||||
//获取百度智能云token
|
||||
const tokenData = await getBaiduToken();
|
||||
console.log('----tokenData',tokenData);
|
||||
if(tokenData.status !== 200){
|
||||
ElMessage({
|
||||
type: 'error',
|
||||
message: '文字识别获取用户标识失败',
|
||||
grouping: true,
|
||||
showClose: true,
|
||||
duration: 5000
|
||||
})
|
||||
return;
|
||||
}
|
||||
//获取到的token
|
||||
const token = tokenData.data?.access_token;
|
||||
const query = {
|
||||
image: imgurl, //图片地址(base64)
|
||||
line_probability: false, //是否返回每行识别结果的置信度。默认为false
|
||||
disp_line_poly: false, //是否返回每行的四角点坐标。默认为false
|
||||
words_type: 'handprint_mix', //文字类型。 默认:印刷文字识别 = handwring_only:手写文字识别 = handprint_mix: 手写印刷混排识别
|
||||
layout_analysis: false, //是否分析文档版面:包括layout(图、表、标题、段落、目录);attribute(栏、页眉、页脚、页码、脚注)的分析输出
|
||||
recg_long_division: false, //是否检测并识别手写竖式
|
||||
recg_formula: true, //控制是否检测并识别公式,默认为false
|
||||
}
|
||||
const ocrData = await getBaiduOCR(token,query);
|
||||
if(ocrData && ocrData.status === 200){
|
||||
const ocrList = ocrData.data?.words_result;
|
||||
let words = '';
|
||||
ocrList.map(item => {
|
||||
words += item.words
|
||||
})
|
||||
ElMessageBox.alert(words, '识别结果', {
|
||||
confirmButtonText: '引用文本',
|
||||
type: 'info',
|
||||
cancelButtonText: '取消'
|
||||
}).then(() => {
|
||||
window.navigator.clipboard.writeText(words).then(() => {
|
||||
ElMessage({
|
||||
type: 'success',
|
||||
message: '已复制到粘贴板',
|
||||
grouping: true,
|
||||
duration: 3000
|
||||
})
|
||||
}).catch(() => {
|
||||
ElMessage({
|
||||
type: 'error',
|
||||
message: '复制信息出错',
|
||||
grouping: true,
|
||||
duration: 3000
|
||||
})
|
||||
})
|
||||
})
|
||||
}else{
|
||||
ElMessage({
|
||||
type: 'error',
|
||||
message: '识别信息出错',
|
||||
grouping: true,
|
||||
duration: 3000
|
||||
})
|
||||
}
|
||||
}
|
||||
const saveAnnotationStorage = (storage) => {
|
||||
localStorage.setItem('PDFJS_Annotation', JSON.stringify(storage));
|
||||
}
|
||||
//保存json文件
|
||||
export const saveJSON = (data) => {
|
||||
let filename = ''
|
||||
if (!data) {
|
||||
console.log('传入的data数据为null');
|
||||
return;
|
||||
}
|
||||
if (!filename) {
|
||||
filename = `json${Date.now()}.json`
|
||||
console.log('未传入文件名,采用默认文件名' + filename);
|
||||
}
|
||||
let newdata = null;
|
||||
if (typeof data === 'object') {
|
||||
newdata = JSON.stringify(data, undefined, 4)
|
||||
}
|
||||
// 创建json文件blob流
|
||||
const blob = new Blob([newdata], { type: 'text/json' });
|
||||
// 创建file文件
|
||||
const file = new File([blob],filename, {type: blob.type})
|
||||
// 创建上传文件流
|
||||
const formdata = new FormData();
|
||||
formdata.append('file', file);
|
||||
//其他参数待添加
|
||||
|
||||
//上传
|
||||
uploadServer(formdata).then(res => {
|
||||
console.log('+++++++++++++');
|
||||
console.log(res.data);
|
||||
})
|
||||
//下载json文件
|
||||
// let e = document.createEvent('MouseEvents');
|
||||
// let a = document.createElement('a');
|
||||
// a.download = filename;
|
||||
// a.href = window.URL.createObjectURL(blob);
|
||||
// a.dataset.downloadurl = ['text/json', a.download, a.href].join(':');
|
||||
// e.initMouseEvent('click', true, false, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
|
||||
// a.dispatchEvent(e);
|
||||
}
|
||||
//引用文本
|
||||
const quoteWords = (quoteInfo) => {
|
||||
const { textStr, StartStr, EndStr } = quoteInfo;
|
||||
if(textStr === StartStr || textStr === EndStr){
|
||||
ElNotification({
|
||||
title: '引用内容',
|
||||
message: textStr,
|
||||
duration: 0,
|
||||
type: 'info',
|
||||
offset: 120
|
||||
})
|
||||
//如果开头和结尾的文字跟内容相同,那么它要么是开头,要么是整段,不需要替换操作
|
||||
console.log('无需替换------',textStr);
|
||||
return
|
||||
}
|
||||
let midStr = ''
|
||||
if(StartStr === '' && EndStr === '' && textStr === '') return
|
||||
if(StartStr === '' && EndStr !== '') {
|
||||
midStr = textStr.replace(EndStr,'eeeeee').split('eeeeee')[0]
|
||||
}else if(StartStr !== '' && EndStr === ''){
|
||||
midStr = textStr.replace(StartStr,'ssssss').split('ssssss')[1]
|
||||
}else{
|
||||
midStr = textStr.replace(StartStr, 'ssssss').replace(EndStr,'eeeeee').split('ssssss')[1].split('eeeeee')[0];
|
||||
}
|
||||
ElNotification({
|
||||
title: '引用内容',
|
||||
message: StartStr + midStr + EndStr,
|
||||
duration: 0,
|
||||
type: 'info',
|
||||
offset: 120
|
||||
})
|
||||
console.log('中间文字------',midStr);
|
||||
console.log('转换后整体文字------',StartStr + midStr + EndStr);
|
||||
}
|
||||
//获取分析列表
|
||||
export const getAnalysisList = async (params) => {
|
||||
return await getEvaluationclueList(params)
|
||||
}
|
||||
//新增分析数据
|
||||
export const addAnalysis = async (params) => {
|
||||
return await addEvaluationclue(params);
|
||||
}
|
||||
//修改分析数据
|
||||
export const updateAnalusis = async (params) => {
|
||||
return await updateEvaluationclue(params);
|
||||
}
|
|
@ -96,5 +96,34 @@ const getProgress = async (id) => {
|
|||
throw error;
|
||||
}
|
||||
};
|
||||
const getBackGroundV2 = async () => {
|
||||
try {
|
||||
const response = await req("/api/aipptV2/themeListV2", "GET");
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error("请求失败:", error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
const createPPTV2 = async (data) => {
|
||||
try {
|
||||
const response = await req("/api/aipptV2/createV2", "POST", data);
|
||||
console.log("createOutline response:", response);
|
||||
|
||||
export { createOutline, getBackGround, createPPT, getProgress, createByOutline };
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error("请求失败:", error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
const getProgressV2 = async (id) => {
|
||||
try {
|
||||
const response = await req(`/api/aipptV2/progressV2?sid=${id}`, "GET");
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error("请求失败:", error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
export { createOutline, getBackGround, createPPT, getProgress, getBackGroundV2, createPPTV2, getProgressV2, createByOutline };
|
||||
|
|
|
@ -117,4 +117,8 @@ export const coursewareTypeList = [
|
|||
label:'素材',
|
||||
value:6
|
||||
},
|
||||
{
|
||||
label:'视频',
|
||||
value:12
|
||||
},
|
||||
]
|
||||
|
|
|
@ -57,7 +57,7 @@ function initChart() {
|
|||
series: [{
|
||||
name: '数据',
|
||||
type: 'pie',
|
||||
radius: '50%', // 设置饼图的半径为实心
|
||||
radius: '90%', // 设置饼图的半径为实心
|
||||
data: filteredData.map(item => ({
|
||||
name: item.name,
|
||||
value: item.value,
|
||||
|
|
|
@ -151,8 +151,13 @@ const getXValue = () => {
|
|||
};
|
||||
|
||||
watch(() => useOverview.tableList, () => {
|
||||
expectedDuration.value = useOverview.tableList.map(item => (Number(item.timelength) * 60 / useOverview.allData.length).toFixed(2));
|
||||
|
||||
const time = useOverview.tableList.map(item => Number(item.timelength))
|
||||
if(time.length === 0) return;
|
||||
const avatarTime = time.reduce((acc, cur) => {
|
||||
return acc + cur
|
||||
},0) / time.length
|
||||
|
||||
expectedDuration.value = useOverview.allData.map(() => (Number(avatarTime) * 60 / useOverview.allData.length).toFixed(2));
|
||||
// 获取横纵坐标
|
||||
nextTick(() => {
|
||||
initChart();
|
||||
|
|
|
@ -286,6 +286,7 @@ import { updateClasswork, listEvaluationclue, listClassworkeval,delClassworkeval
|
|||
import { listEvaluation } from '@/api/subject'
|
||||
import { listEntpcoursefile } from '@/api/education/entpcoursefile'
|
||||
import { listKnowledgePoint } from "@/api/knowledge/knowledgePoint";
|
||||
import { isJson } from "@/utils/comm";
|
||||
|
||||
|
||||
import { useGetHomework } from '@/hooks/useGetHomework'
|
||||
|
@ -483,12 +484,13 @@ function Apis(key) {
|
|||
*/
|
||||
const t = function(name, time) {
|
||||
return new Promise(resolve => {
|
||||
const evalId = props.bookobj.levelSecondId=='' ? props.bookobj.levelFirstId : props.bookobj.levelSecondId;
|
||||
const queryForm = {
|
||||
// 分页参数
|
||||
currentPage: paginationParams.pageNum,
|
||||
pageSize: paginationParams.pageSize,
|
||||
// 课程相关参数
|
||||
eid: props.bookobj.levelSecondId,
|
||||
eid: evalId,
|
||||
sectionName: props.bookobj.coursetitle,
|
||||
edusubject: userStore.edusubject,
|
||||
edustage: userStore.edustage,
|
||||
|
@ -583,10 +585,14 @@ const getQueryFromEvaluationclue = () => {
|
|||
}
|
||||
|
||||
if (clueres.rows[i].childlist != '') {
|
||||
clueres.rows[i].childArray = JSON.parse('['+clueres.rows[i].childlist+']');
|
||||
for (var j=0; j<clueres.rows[i].childArray.length; j++) {
|
||||
clueres.rows[i].childArray[j].title = clueres.rows[i].childArray[j].title.replace(/(<([^>]+)>)/ig, '');
|
||||
const tmpJson = '['+clueres.rows[i].childlist+']';
|
||||
if (isJson(tmpJson)){
|
||||
clueres.rows[i].childArray = JSON.parse(tmpJson);
|
||||
for (var j=0; j<clueres.rows[i].childArray.length; j++) {
|
||||
clueres.rows[i].childArray[j].title = clueres.rows[i].childArray[j].title.replace(/(<([^>]+)>)/ig, '');
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
clueres.rows[i].childArray = {};
|
||||
}
|
||||
|
@ -1062,9 +1068,27 @@ watch(() => props.propsformobj.uniquekey, (newVal) => {
|
|||
classWorkForm.uniquekey = props.propsformobj.uniquekey?cloneDeep(props.propsformobj.uniquekey):''; // 作业唯一标识 作业名称
|
||||
}
|
||||
})
|
||||
watch(() => props.bookobj.levelSecondId, (newVal, oldVal) => {
|
||||
console.log(props.bookobj,'课程选择')
|
||||
debounceQueryData();
|
||||
watch(
|
||||
[
|
||||
() => props.bookobj.levelSecondId,
|
||||
() => props.bookobj.levelFirstId
|
||||
],
|
||||
([newLevelSecondId, newLevelFirstId], [oldLevelSecondId, oldLevelFirstId]) => {
|
||||
if(props.bookobj.node.edusubject == '英语' && props.bookobj.node.edustage == '高中'){
|
||||
if(newLevelFirstId != oldLevelFirstId){
|
||||
console.log(props.bookobj,'高中英语-课程选择')
|
||||
debounceQueryData();
|
||||
}
|
||||
else{
|
||||
// 习题清空
|
||||
workResource.entpCourseWorkList = [];
|
||||
return;
|
||||
}
|
||||
}
|
||||
else{
|
||||
console.log(props.bookobj,'课程选择')
|
||||
debounceQueryData();
|
||||
}
|
||||
})
|
||||
|
||||
</script>
|
||||
|
|
|
@ -680,7 +680,7 @@ const handleClassOverviewOpen = (type) =>{
|
|||
const allTeacherRating = allTopic.reduce((acc, cur) => acc + cur.teacherRating, 0)
|
||||
|
||||
rightAnswer > 0?item.scoingRate = (score/allTeacherRating * 100).toFixed(0):item.scoingRate = ''
|
||||
item.getScore = score
|
||||
item.getScore = allTeacherRating
|
||||
}else{
|
||||
item.scoingRate = ''
|
||||
item.getScore = 0
|
||||
|
|
|
@ -0,0 +1,333 @@
|
|||
<template>
|
||||
<div class="book-wrap">
|
||||
<el-scrollbar height="100%">
|
||||
<div class="book-name flex" @click="dialogVisible = true">
|
||||
<span>{{ curBook.data.itemtitle }}</span>
|
||||
<i class="iconfont icon-xiangyou"></i>
|
||||
</div>
|
||||
<div class="book-list" v-loading="treeLoading">
|
||||
<el-tree :data="treeData" accordion :props="defaultProps" node-key="id"
|
||||
:default-expanded-keys="defaultExpandedKeys" :current-node-key="curNode.data.id" highlight-current
|
||||
@node-click="handleNodeClick">
|
||||
<template #default="{ node }">
|
||||
<span :title="node.label" class="tree-label">{{ node.label }}</span>
|
||||
</template>
|
||||
</el-tree>
|
||||
</div>
|
||||
</el-scrollbar>
|
||||
</div>
|
||||
<!--弹窗 选择教材-->
|
||||
<el-dialog v-model="dialogVisible" append-to-body :show-close="false" width="550"
|
||||
style="border-radius: 10px; padding: 10px 15px;">
|
||||
<template #header>
|
||||
<div class="choose-book-header flex">
|
||||
<span>切换教材</span>
|
||||
<i class="iconfont icon-guanbi" @click="dialogVisible = false"></i>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div class="textbook-container">
|
||||
<el-scrollbar height="450px">
|
||||
<div class="textbook-item flex" v-for="item in subjectList" :class="curBook.data.id == item.id ? 'active-item' : ''"
|
||||
:key="item.id" @click="changeBook(item)">
|
||||
<img v-if="item.avartar" :src="item.avartar.indexOf('http') === 0 ? item.avartar : BaseUrl + item.avartar" class="textbook-img" alt="">
|
||||
<div v-else class="textbook-img">
|
||||
<i class="iconfont icon-jiaocaixuanze" style="font-size: 40px;"></i>
|
||||
</div>
|
||||
<span class="book-name">{{ item.itemtitle }}</span>
|
||||
</div>
|
||||
</el-scrollbar>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { onMounted, ref, nextTick, toRaw, reactive } from 'vue';
|
||||
import { cloneDeep } from 'lodash'
|
||||
import { listEvaluation } from '@/api/subject'
|
||||
import { sessionStore } from '@/utils/store'
|
||||
|
||||
const BaseUrl = import.meta.env.VITE_APP_BUILD_BASE_PATH
|
||||
// 定义要发送的emit事件
|
||||
const emit = defineEmits(['nodeClick', 'changeBook'])
|
||||
// 章节List
|
||||
const unitList = ref([])
|
||||
const subjectList = ref([])
|
||||
const dialogVisible = ref(false)
|
||||
// 当前教材下面单元内容数据
|
||||
const treeData = ref([])
|
||||
const defaultProps = {
|
||||
children: 'children',
|
||||
label: 'itemtitle',
|
||||
class: 'textbook-tree'
|
||||
}
|
||||
//查当前学科
|
||||
const subjectParams = reactive(
|
||||
{
|
||||
edusubject: '科学',
|
||||
edustage:'小学',
|
||||
itemkey: 'version',
|
||||
orderby: 'orderidx asc',
|
||||
pageSize: 10000
|
||||
}
|
||||
)
|
||||
// 查所有的学科
|
||||
const unitParams = reactive({
|
||||
edusubject:'科学',
|
||||
edustage:'小学',
|
||||
itemgroup: 'textbook',
|
||||
orderby: 'orderidx asc',
|
||||
pageSize: 10000
|
||||
})
|
||||
// 当前选中的教材
|
||||
const curBook = reactive({
|
||||
data: {}
|
||||
})
|
||||
// 当前节点
|
||||
const curNode = reactive({
|
||||
data:{}
|
||||
})
|
||||
const treeLoading = ref(false)
|
||||
// 默认展开的节点
|
||||
const defaultExpandedKeys = ref([])
|
||||
|
||||
//选择教材
|
||||
const changeBook = (data) => {
|
||||
curBook.data = data
|
||||
treeData.value = getTreeData(data.id)
|
||||
//切换教材后默认展开第一个并选中
|
||||
nextTick(() =>{
|
||||
defaultExpandedKeys.value = [treeData.value[0].id]
|
||||
curNode.data = getLastLevelData(treeData.value)[0]
|
||||
handleNodeClick(curNode.data)
|
||||
})
|
||||
// 延迟关闭 视觉上选中
|
||||
setTimeout(() => {
|
||||
dialogVisible.value = false
|
||||
}, 100);
|
||||
}
|
||||
|
||||
const getLastLevelData = (tree) => {
|
||||
let lastLevelData = [];
|
||||
// 递归函数遍历树形结构
|
||||
function traverseTree(nodes) {
|
||||
nodes.forEach((node) => {
|
||||
// 如果当前节点有子节点,继续遍历
|
||||
if (node.children && node.children.length > 0) {
|
||||
traverseTree(node.children);
|
||||
} else {
|
||||
// 如果没有子节点,说明是最后一层的节点
|
||||
lastLevelData.push(node);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 调用递归函数开始遍历
|
||||
traverseTree(tree);
|
||||
|
||||
// 返回最后一层的数据
|
||||
return lastLevelData;
|
||||
}
|
||||
// 根据id 拿到父节点数据
|
||||
const findParentByChildId = (treeData, targetNodeId) => {
|
||||
// 递归查找函数
|
||||
// 遍历树中的每个节点
|
||||
for (let node of treeData) {
|
||||
// 检查当前节点的子节点是否包含目标子节点 ID
|
||||
if (node.children && node.children.some(child => child.id === targetNodeId)) {
|
||||
// 如果当前节点的某个子节点的 ID 匹配目标子节点 ID,则当前节点即为父节点
|
||||
return node;
|
||||
}
|
||||
// 如果当前节点没有匹配的子节点,则递归检查当前节点的子节点
|
||||
if (node.children) {
|
||||
let parentNode = findParentByChildId(node.children, targetNodeId);
|
||||
if (parentNode) {
|
||||
return parentNode;
|
||||
}
|
||||
}
|
||||
}
|
||||
// 如果未找到匹配的父节点,则返回 null 或者适当的默认值
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
const handleNodeClick = (data) => {
|
||||
/**
|
||||
* data : 当前节点数据
|
||||
*/
|
||||
let nodeData = cloneDeep(toRaw(data));
|
||||
|
||||
//增加一个label 之前取的label
|
||||
nodeData.label = nodeData.itemtitle
|
||||
// 父级节点 如果当前是一级节点 父级则为null
|
||||
let parent = {
|
||||
id: nodeData.parentid,
|
||||
label: nodeData.parenttitle,
|
||||
itemtitle: nodeData.parenttitle
|
||||
}
|
||||
const parentNode = nodeData.parentid ? parent : null
|
||||
nodeData.parentNode = parentNode
|
||||
let curData = {
|
||||
textBook: {
|
||||
curBookId: curBook.data.id,
|
||||
curBookName: curBook.data.itemtitle,
|
||||
curBookImg: BaseUrl + curBook.data.avartar,
|
||||
curBookPath: curBook.data.fileurl
|
||||
},
|
||||
node: nodeData
|
||||
}
|
||||
// 本地存储:electron-store
|
||||
emit('nodeClick', curData)
|
||||
}
|
||||
// 单元章节数据转为“树”结构
|
||||
const getTreeData = (bookId) =>{
|
||||
// 根据当前教材的id 查找出对应的章节
|
||||
let data = unitList.value.filter(item => item.rootid == bookId && item.level == 1)
|
||||
data.forEach( item => {
|
||||
item.children = unitList.value.filter( item2 => item2.parentid == item.id && item2.level == 2)
|
||||
})
|
||||
return data
|
||||
}
|
||||
|
||||
|
||||
onMounted( async () => {
|
||||
treeLoading.value = true
|
||||
try{
|
||||
//获取学科列表
|
||||
const { rows } = await listEvaluation(subjectParams)
|
||||
// 获取所有的教材
|
||||
subjectList.value = rows
|
||||
|
||||
const res = await listEvaluation(unitParams)
|
||||
unitList.value = [...res.rows]
|
||||
// 当前教材
|
||||
curBook.data = rows[0]
|
||||
|
||||
// 章节"树"rows
|
||||
treeData.value = getTreeData(rows[0].id)
|
||||
|
||||
nextTick(() =>{
|
||||
// 默认展开 选中
|
||||
defaultExpandedKeys.value = [treeData.value[0].id]
|
||||
curNode.data = getLastLevelData(treeData.value)[0]
|
||||
handleNodeClick(curNode.data)
|
||||
})
|
||||
|
||||
} finally{
|
||||
treeLoading.value = false
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.book-wrap {
|
||||
width: 300px;
|
||||
height: 100%;
|
||||
background: #ffffff;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0px 0px 20px 0px rgba(99, 99, 99, 0.06);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
|
||||
.book-name {
|
||||
background-color: #ffffff;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 45px;
|
||||
padding: 0 15px;
|
||||
z-index: 1;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
color: #3b3b3b;
|
||||
cursor: pointer;
|
||||
border-bottom: solid #f4f5f7 1px;
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
border-radius: 10px 10px 0 0;
|
||||
}
|
||||
|
||||
.book-list {
|
||||
padding: 45px 10px 0 10px;
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.choose-dialog) {
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.choose-book-header {
|
||||
justify-content: space-between;
|
||||
font-size: 15px;
|
||||
font-weight: bold;
|
||||
|
||||
.icon-guanbi {
|
||||
font-size: 20px;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.textbook-container {
|
||||
.textbook-item {
|
||||
padding: 10px 20px;
|
||||
align-items: center;
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
|
||||
.book-name {
|
||||
margin-left: 20px;
|
||||
color: #3b3b3b;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: #f4f7f9;
|
||||
}
|
||||
}
|
||||
|
||||
.active-item {
|
||||
background-color: #f4f7f9;
|
||||
|
||||
.book-name {
|
||||
color: #368fff;
|
||||
font-weight: bold
|
||||
}
|
||||
}
|
||||
|
||||
.textbook-img {
|
||||
width: 55px;
|
||||
height: 70px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.el-tree-node) {
|
||||
.el-tree-node__content {
|
||||
height: 40px;
|
||||
border-radius: 10px;
|
||||
|
||||
&:hover {
|
||||
background-color: #eaf3ff;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.tree-label {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
:deep(.el-tree--highlight-current .el-tree-node.is-current>.el-tree-node__content) {
|
||||
background-color: #eaf3ff !important;
|
||||
color: #409EFF
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
<template>
|
||||
<el-dialog v-model="model" class="preview-drawer" :title="row.fileShowName" :modal="true" :destroy-on-close="true" :with-header="false" :append-to-body="true"
|
||||
width="60%">
|
||||
<video style="margin: 0 auto;" :src="row.fileFullPath" controls autoplay></video>
|
||||
</el-dialog>
|
||||
</template>
|
||||
<script setup>
|
||||
const model = defineModel()
|
||||
const props = defineProps({
|
||||
row: {
|
||||
type: Object,
|
||||
default(){
|
||||
return {}
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
</script>
|
||||
<style scoped>
|
||||
.header-close {
|
||||
padding: 0;
|
||||
cursor: pointer;
|
||||
text-align: right;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,259 @@
|
|||
<template>
|
||||
<div class="page-resource flex">
|
||||
<!-- 左侧 教材 目录 -->
|
||||
<experimentBook @node-click="getData"/>
|
||||
<div class="page-right">
|
||||
<!-- 排序 -->
|
||||
<div style="margin-left: 5px;margin-top: 10px;height: 45px;">
|
||||
<el-form size="large">
|
||||
<el-form-item label="排序:">
|
||||
<div
|
||||
:class="['score-circle', { 'active': active == item.active }]"
|
||||
v-for="(item,index) in screenList" :key="index" @click="chooseItem(item)">
|
||||
<el-text
|
||||
:style="{fontWeight:'bold', color: active == item.active ? 'rgb(57, 184, 244)':'rgb(131,131,131)' }"
|
||||
size="large">{{ item.title }}</el-text>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
<div class="list-content">
|
||||
<div class="list-container" v-loading="loading">
|
||||
<div v-for="(item, index) in experimentList" :key="index" class="content">
|
||||
<div class="content-list">
|
||||
<!-- 封面 -->
|
||||
<el-image style="width: 100%;border-radius: 8px;" :src="item.coverPic" fit="contain" @click="chooseVedio(item)"/>
|
||||
|
||||
</div>
|
||||
<!-- 标题 -->
|
||||
<div style="text-align: left;">
|
||||
<el-text>{{ item.fileShowName }}</el-text>
|
||||
</div>
|
||||
<!-- 观看人数 -->
|
||||
<!-- <div style="text-align: left;display: flex;align-items: center;">
|
||||
<el-icon type="info"><View /></el-icon><el-text size="small" type="info">{{ item.nums }}</el-text>
|
||||
</div> -->
|
||||
</div>
|
||||
</div>
|
||||
<div class="pagination-box">
|
||||
<el-pagination
|
||||
v-model:current-page="query.pageNum"
|
||||
v-model:page-size="query.pageSize"
|
||||
:page-sizes="[20, 30, 50, 100]"
|
||||
background
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
:total="resultTotal"
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handleCurrentChange"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 播放视频 -->
|
||||
<VideoLog v-model="isShow" :row="curRow"></VideoLog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted } from 'vue'
|
||||
import experimentBook from './components/experimentBook.vue';
|
||||
import { View } from '@element-plus/icons-vue'
|
||||
import { getSmarttalkPage } from '@/api/file/index'
|
||||
import VideoLog from './components/VideoLog.vue'
|
||||
|
||||
// 排序列表
|
||||
const screenList = ref([
|
||||
{
|
||||
title: '最新发布',
|
||||
active: 1,
|
||||
}
|
||||
])
|
||||
const active = ref(1)
|
||||
// 获取视频的总条数
|
||||
const resultTotal = ref(0)
|
||||
// 弹出视频
|
||||
const isShow = ref(false)
|
||||
const curRow = ref({})
|
||||
// loading框
|
||||
const loading = ref(false)
|
||||
|
||||
// 实验图片结构
|
||||
const experimentList = ref([])
|
||||
// 请求视频资源的参数
|
||||
const query = ref({
|
||||
textbookId: '',
|
||||
fileSource: '平台',
|
||||
// 资源格式 mp3 ppt ...
|
||||
fileSuffix: 'mp4',
|
||||
fileFlags: "'素材'",
|
||||
fileRoot: '资源',
|
||||
fileName: '',
|
||||
orderByColumn: 'uploadTime',
|
||||
isAsc: 'desc',
|
||||
pageNum: 1,
|
||||
pageSize: 20,
|
||||
levelFirstId: 0,
|
||||
levelSecondId: 0
|
||||
})
|
||||
|
||||
const getData = (data) => {
|
||||
const { textBook, node } = data
|
||||
if (node.parentNode) {
|
||||
query.value.levelFirstId = node.parentNode.id
|
||||
query.value.levelSecondId = node.id
|
||||
} else {
|
||||
query.value.levelFirstId = node.id
|
||||
query.value.levelSecondId = ''
|
||||
}
|
||||
query.value.textbookId = node.rootid
|
||||
getVideoList()
|
||||
}
|
||||
|
||||
const chooseItem = (item) => {
|
||||
active.value = item.active
|
||||
}
|
||||
|
||||
// 获取视频资源
|
||||
const getVideoList = async () => {
|
||||
loading.value = true
|
||||
const res = await getSmarttalkPage(query.value)
|
||||
loading.value = false
|
||||
experimentList.value = [...res.rows]
|
||||
resultTotal.value = res.total
|
||||
}
|
||||
|
||||
const handleSizeChange = (limit) => {
|
||||
query.pageNum = limit
|
||||
getVideoList()
|
||||
}
|
||||
const handleCurrentChange = (page) => {
|
||||
query.pageSize = page
|
||||
getVideoList()
|
||||
}
|
||||
const chooseVedio = (item) => {
|
||||
isShow.value = true
|
||||
curRow.value = item
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.page-resource {
|
||||
height: 100%;
|
||||
padding: 10px 15px 0;
|
||||
|
||||
.page-right {
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
margin-left: 20px;
|
||||
height: 100%;
|
||||
background: #ffffff;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0px 0px 20px 0px rgba(99, 99, 99, 0.06);
|
||||
}
|
||||
.icon-jiahao {
|
||||
font-size: 12px;
|
||||
margin-right: 3px;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
.create-btn {
|
||||
font-size: 13px;
|
||||
padding: 5px 13px;
|
||||
}
|
||||
.list-content {
|
||||
border-radius: 8px;
|
||||
height: 90%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.list-container {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.content {
|
||||
border-radius: 8px;
|
||||
// box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
|
||||
width: calc(20%);
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
padding: 5px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
// justify-content: space-between;
|
||||
}
|
||||
|
||||
.content:hover {
|
||||
transform: translateY(-4px);
|
||||
// box-shadow: 0 4px 16px 0 rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
.content-list{
|
||||
height: 150px;
|
||||
display: flex;
|
||||
align-items: center
|
||||
}
|
||||
|
||||
.item-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.item-icon {
|
||||
font-size: 24px;
|
||||
color: #409eff;
|
||||
margin-right: 16px;
|
||||
}
|
||||
|
||||
.item-text {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.item-title {
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
color: #303133;
|
||||
margin-bottom: 4px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.title-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.item-bottom {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
/* 过渡动画 */
|
||||
.fade-enter-active, .fade-leave-active {
|
||||
transition: opacity 0.3s;
|
||||
}
|
||||
|
||||
.fade-enter, .fade-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
.score-circle {
|
||||
background-color: #fff;
|
||||
cursor: pointer;
|
||||
margin-right: 5px;
|
||||
width: auto;
|
||||
text-align: center;
|
||||
padding: 0 10px;
|
||||
}
|
||||
|
||||
.score-circle.active {
|
||||
background-color: rgb(218, 236, 255);
|
||||
color: white;
|
||||
}
|
||||
.pagination-box {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
height: 65px;
|
||||
}
|
||||
</style>
|
|
@ -28,7 +28,7 @@
|
|||
<el-input v-model="ruleForm.phoneNumber" placeholder="请输入手机号" />
|
||||
</el-form-item>
|
||||
<el-form-item label="验证码" prop="Code" v-if="activeIndex==1">
|
||||
<el-input style="width:60%" v-model="ruleForm.Code" :disabled="true" placeholder="请输入验证码" />
|
||||
<el-input style="width:60%" v-model="ruleForm.Code" :disabled="false" placeholder="请输入验证码" />
|
||||
<el-button type="primary" style="margin-left:10px" @click="sendcaptchaImg">发送验证码</el-button>
|
||||
</el-form-item>
|
||||
<el-form-item label="设置密码" prop="password" v-if="activeIndex==1">
|
||||
|
@ -409,11 +409,13 @@ const sbmitImg=()=>{
|
|||
type: type.value
|
||||
}
|
||||
sendCode(params).then(res=>{
|
||||
isImg.value=false
|
||||
if(res.code==200){
|
||||
ruleForm.Code=res.data
|
||||
isImg.value=false
|
||||
// ruleForm.Code=res.data
|
||||
ElMessage.success(res.msg||'验证码-已发送')
|
||||
}
|
||||
|
||||
}).catch(err=>{
|
||||
isImg.value=false
|
||||
})
|
||||
}else{
|
||||
ElMessage.error('请根据图片输入验证码')
|
||||
|
|
|
@ -0,0 +1,264 @@
|
|||
<template>
|
||||
<div class="login-container">
|
||||
<div class="box-item desc">
|
||||
<div class="welcome">
|
||||
<p>欢迎登录 {{ homeTitle }}</p>
|
||||
</div>
|
||||
<img class="welcome-img" :src="leftBg2" />
|
||||
</div>
|
||||
<div class="box-item login">
|
||||
<WindowTools :is-has-max="false" />
|
||||
<div class="login-title">账号登录</div>
|
||||
<el-form ref="formRef" class="login-form" :model="loginForm" :rules="rules" size="large">
|
||||
<el-form-item prop="username">
|
||||
<el-input v-model.trim="loginForm.username" placeholder="请输入用户名" />
|
||||
</el-form-item>
|
||||
<el-form-item prop="password" style="margin-bottom: 15px">
|
||||
<el-input v-model="loginForm.password" autocomplete="on" type="password" placeholder="请输入密码" />
|
||||
</el-form-item>
|
||||
<div class="flex mb-5">
|
||||
<el-checkbox v-model="loginForm.rememberMe">记住密码</el-checkbox>
|
||||
<!-- <el-checkbox >阅读并同意《xxx》</el-checkbox> -->
|
||||
</div>
|
||||
|
||||
<el-form-item>
|
||||
<el-button :loading="btnLoading" class="btn" type="primary" @click="submitForm(formRef)">登录</el-button>
|
||||
</el-form-item>
|
||||
<div class="flex mb-4" style="display: flex;justify-content: center;color: #ccc;cursor: pointer;">
|
||||
<a class="hover:text-sky-500" style="margin-right: 10px;" @click="RegisterModel(1)">注册账号</a>
|
||||
|
|
||||
<a class="hover:text-sky-500" style="margin-left: 10px;" @click="RegisterModel(2)">忘记密码</a>
|
||||
</div>
|
||||
</el-form>
|
||||
</div>
|
||||
</div>
|
||||
<el-dialog v-model="showDownLoading" width="500" :show-close="false" :close-on-click-modal="false"
|
||||
:close-on-press-escape="false" align-center>
|
||||
<el-progress :text-inside="true" :stroke-width="22" :percentage="downloadProp" :show-text="false"
|
||||
status="success" />
|
||||
</el-dialog>
|
||||
<!--选择学科-->
|
||||
<SelectSubject v-model="isSubject" :login-data="loginForm" />
|
||||
<!--注册弹框-->
|
||||
<Register ref="RegModel"></Register>
|
||||
</template>
|
||||
<script setup>
|
||||
import { onMounted, reactive, ref } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { encrypt, decrypt } from '@/utils/jsencrypt'
|
||||
import useUserStore from '@/store/modules/user'
|
||||
import leftBg2 from '@/assets/images/login/left-bg2.png'
|
||||
import WindowTools from '@/components/window-tools/index.vue'
|
||||
import SelectSubject from '@/components/select-subject/index.vue'
|
||||
import Register from './components/Register.vue'
|
||||
import { sessionStore } from '@/utils/store'
|
||||
|
||||
const { session } = require('@electron/remote')
|
||||
const downloadProp = ref(0)
|
||||
const showDownLoading = ref(false)
|
||||
const { ipcRenderer } = window.electron || {}
|
||||
const formRef = ref()
|
||||
const userStore = useUserStore()
|
||||
const btnLoading = ref(false)
|
||||
const isSubject = ref(false)
|
||||
const RegModel = ref(false)
|
||||
//表单
|
||||
const loginForm = reactive({
|
||||
username: '',
|
||||
password: '',
|
||||
rememberMe: false
|
||||
})
|
||||
|
||||
//表单规则
|
||||
const rules = reactive({
|
||||
username: [{ required: true, trigger: 'blur', message: '请输入您的账号' }],
|
||||
password: [{ required: true, trigger: 'blur', message: '请输入您的密码' }]
|
||||
})
|
||||
|
||||
let curWinUrl = import.meta.env.VITE_APP_BUILD_BASE_PATH
|
||||
let homeTitle = ref(import.meta.env.VITE_APP_TITLE)
|
||||
ipcRenderer.on('update-app-progress', (e, prop) => {
|
||||
downloadProp.value = prop
|
||||
showDownLoading.value = prop !== 100
|
||||
})
|
||||
// 打开弹窗
|
||||
const RegisterModel = type => {
|
||||
RegModel.value.OpenModel(type)
|
||||
}
|
||||
//登录
|
||||
const submitForm = async (formEl) => {
|
||||
if (!formEl) return
|
||||
await formEl.validate(async (valid) => {
|
||||
if (valid) {
|
||||
btnLoading.value = true
|
||||
|
||||
// 勾选了需要记住密码设置在 cookie 中设置记住用户名和密码
|
||||
if (loginForm.rememberMe) {
|
||||
await setCookie('username', loginForm.username)
|
||||
await setCookie('password', encrypt(loginForm.password))
|
||||
await setCookie('rememberMe', loginForm.rememberMe.toString())
|
||||
} else {
|
||||
// 否则移除
|
||||
await session.defaultSession.clearStorageData({
|
||||
origin: curWinUrl,
|
||||
storages: ['cookies']
|
||||
})
|
||||
}
|
||||
|
||||
try {
|
||||
await userStore.login(loginForm)
|
||||
await userStore.getInfo()
|
||||
if (userStore.user.edustage || userStore.user.edusubject) {
|
||||
ElMessage.success('登录成功')
|
||||
ipcRenderer && ipcRenderer.send('openMainWindow')
|
||||
} else {
|
||||
isSubject.value = true
|
||||
}
|
||||
} finally {
|
||||
btnLoading.value = false
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const getCookie = async () => {
|
||||
const username = (await getCookieDetail('username'))[0]
|
||||
const password = (await getCookieDetail('password'))[0]
|
||||
const rememberMe = (await getCookieDetail('rememberMe'))[0]
|
||||
loginForm.username = username ? username.value : loginForm.username
|
||||
loginForm.password = password ? decrypt(password.value) : loginForm.password
|
||||
loginForm.rememberMe = rememberMe ? Boolean(rememberMe.value) : false
|
||||
}
|
||||
|
||||
// 获取cookie
|
||||
const getCookieDetail = (name) => {
|
||||
return session.defaultSession.cookies.get({ url: curWinUrl, name })
|
||||
}
|
||||
|
||||
// 设置cookie
|
||||
const setCookie = (name, value) => {
|
||||
// 30天过期
|
||||
let Days = 30
|
||||
let times = Math.round(Date.now() / 1000) + Days * 24 * 60 * 60
|
||||
const cookie = {
|
||||
url: curWinUrl,
|
||||
name,
|
||||
value,
|
||||
expirationDate: times
|
||||
}
|
||||
return session.defaultSession.cookies.set(cookie)
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
localStorage.clear()
|
||||
sessionStore.set('subject', {
|
||||
bookList: null,
|
||||
curBook: null,
|
||||
curNode: null,
|
||||
defaultExpandedKeys: [],
|
||||
subjectTree: []
|
||||
})
|
||||
getCookie()
|
||||
})
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.login-container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
-webkit-app-region: drag;
|
||||
|
||||
.box-item {
|
||||
width: 444px;
|
||||
height: 520px;
|
||||
|
||||
&.desc {
|
||||
background: #ffffff;
|
||||
border-radius: 12px 0px 0px 12px;
|
||||
box-shadow: 0px 16px 73px 8px rgba(203, 203, 203, 0.2);
|
||||
padding: 23px 25px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
background-color: #003b94;
|
||||
}
|
||||
|
||||
&.login {
|
||||
background: #ffffff;
|
||||
border-radius: 0px 12px 12px 0px;
|
||||
padding: 34px 42px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.welcome {
|
||||
padding-top: 35px;
|
||||
|
||||
p {
|
||||
color: #ffffff;
|
||||
line-height: 25px;
|
||||
letter-spacing: 0.26px;
|
||||
text-align: center;
|
||||
font-weight: 700;
|
||||
font-size: 26px;
|
||||
}
|
||||
}
|
||||
|
||||
.welcome-img {
|
||||
margin-top: 20px;
|
||||
width: 350px;
|
||||
height: 350px;
|
||||
}
|
||||
|
||||
.login-title {
|
||||
font-size: 20px;
|
||||
text-align: center;
|
||||
color: #1e1e1e;
|
||||
margin-bottom: 35px;
|
||||
margin-top: 50px;
|
||||
}
|
||||
|
||||
.login-form {
|
||||
-webkit-app-region: no-drag;
|
||||
|
||||
.captcha-input {
|
||||
width: 60%;
|
||||
}
|
||||
|
||||
.captcha-img {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.btn {
|
||||
width: 350px;
|
||||
height: 50px;
|
||||
border-radius: 4px;
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
text-align: center;
|
||||
color: #ffffff;
|
||||
line-height: 50px;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.header-tool {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
-webkit-app-region: no-drag;
|
||||
|
||||
span {
|
||||
padding: 5px 10px;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.el-form-item {
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
</style>
|
|
@ -1,264 +1,13 @@
|
|||
<template>
|
||||
<div class="login-container">
|
||||
<div class="box-item desc">
|
||||
<div class="welcome">
|
||||
<p>欢迎登录 {{ homeTitle }}</p>
|
||||
</div>
|
||||
<img class="welcome-img" :src="leftBg2" />
|
||||
</div>
|
||||
<div class="box-item login">
|
||||
<WindowTools :is-has-max="false" />
|
||||
<div class="login-title">账号登录</div>
|
||||
<el-form ref="formRef" class="login-form" :model="loginForm" :rules="rules" size="large">
|
||||
<el-form-item prop="username">
|
||||
<el-input v-model.trim="loginForm.username" placeholder="请输入用户名" />
|
||||
</el-form-item>
|
||||
<el-form-item prop="password" style="margin-bottom: 15px">
|
||||
<el-input v-model="loginForm.password" autocomplete="on" type="password" placeholder="请输入密码" />
|
||||
</el-form-item>
|
||||
<div class="flex mb-5">
|
||||
<el-checkbox v-model="loginForm.rememberMe">记住密码</el-checkbox>
|
||||
<!-- <el-checkbox >阅读并同意《xxx》</el-checkbox> -->
|
||||
</div>
|
||||
|
||||
<el-form-item>
|
||||
<el-button :loading="btnLoading" class="btn" type="primary" @click="submitForm(formRef)">登录</el-button>
|
||||
</el-form-item>
|
||||
<div class="flex mb-4" style="display: flex;justify-content: center;color: #ccc;cursor: pointer;">
|
||||
<a class="hover:text-sky-500" style="margin-right: 10px;" @click="RegisterModel(1)">注册账号</a>
|
||||
|
|
||||
<a class="hover:text-sky-500" style="margin-left: 10px;" @click="RegisterModel(2)">忘记密码</a>
|
||||
</div>
|
||||
</el-form>
|
||||
</div>
|
||||
</div>
|
||||
<el-dialog v-model="showDownLoading" width="500" :show-close="false" :close-on-click-modal="false"
|
||||
:close-on-press-escape="false" align-center>
|
||||
<el-progress :text-inside="true" :stroke-width="22" :percentage="downloadProp" :show-text="false"
|
||||
status="success" />
|
||||
</el-dialog>
|
||||
<!--选择学科-->
|
||||
<SelectSubject v-model="isSubject" :login-data="loginForm" />
|
||||
<!--注册弹框-->
|
||||
<Register ref="RegModel"></Register>
|
||||
<ycLogin v-if="buildMode === 'yc'">
|
||||
</ycLogin>
|
||||
<defultLogin v-else>
|
||||
</defultLogin>
|
||||
</template>
|
||||
<script setup>
|
||||
import { onMounted, reactive, ref } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { encrypt, decrypt } from '@/utils/jsencrypt'
|
||||
import useUserStore from '@/store/modules/user'
|
||||
import leftBg2 from '@/assets/images/login/left-bg2.png'
|
||||
import WindowTools from '@/components/window-tools/index.vue'
|
||||
import SelectSubject from '@/components/select-subject/index.vue'
|
||||
import Register from './components/Register.vue'
|
||||
import { sessionStore } from '@/utils/store'
|
||||
|
||||
const { session } = require('@electron/remote')
|
||||
const downloadProp = ref(0)
|
||||
const showDownLoading = ref(false)
|
||||
const { ipcRenderer } = window.electron || {}
|
||||
const formRef = ref()
|
||||
const userStore = useUserStore()
|
||||
const btnLoading = ref(false)
|
||||
const isSubject = ref(false)
|
||||
const RegModel = ref(false)
|
||||
//表单
|
||||
const loginForm = reactive({
|
||||
username: '',
|
||||
password: '',
|
||||
rememberMe: false
|
||||
})
|
||||
|
||||
//表单规则
|
||||
const rules = reactive({
|
||||
username: [{ required: true, trigger: 'blur', message: '请输入您的账号' }],
|
||||
password: [{ required: true, trigger: 'blur', message: '请输入您的密码' }]
|
||||
})
|
||||
|
||||
let curWinUrl = import.meta.env.VITE_APP_BUILD_BASE_PATH
|
||||
let homeTitle = ref(import.meta.env.VITE_APP_TITLE)
|
||||
ipcRenderer.on('update-app-progress', (e, prop) => {
|
||||
downloadProp.value = prop
|
||||
showDownLoading.value = prop !== 100
|
||||
})
|
||||
// 打开弹窗
|
||||
const RegisterModel = type => {
|
||||
RegModel.value.OpenModel(type)
|
||||
}
|
||||
//登录
|
||||
const submitForm = async (formEl) => {
|
||||
if (!formEl) return
|
||||
await formEl.validate(async (valid) => {
|
||||
if (valid) {
|
||||
btnLoading.value = true
|
||||
|
||||
// 勾选了需要记住密码设置在 cookie 中设置记住用户名和密码
|
||||
if (loginForm.rememberMe) {
|
||||
await setCookie('username', loginForm.username)
|
||||
await setCookie('password', encrypt(loginForm.password))
|
||||
await setCookie('rememberMe', loginForm.rememberMe.toString())
|
||||
} else {
|
||||
// 否则移除
|
||||
await session.defaultSession.clearStorageData({
|
||||
origin: curWinUrl,
|
||||
storages: ['cookies']
|
||||
})
|
||||
}
|
||||
|
||||
try {
|
||||
await userStore.login(loginForm)
|
||||
await userStore.getInfo()
|
||||
if (userStore.user.edustage || userStore.user.edusubject) {
|
||||
ElMessage.success('登录成功')
|
||||
ipcRenderer && ipcRenderer.send('openMainWindow')
|
||||
} else {
|
||||
isSubject.value = true
|
||||
}
|
||||
} finally {
|
||||
btnLoading.value = false
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const getCookie = async () => {
|
||||
const username = (await getCookieDetail('username'))[0]
|
||||
const password = (await getCookieDetail('password'))[0]
|
||||
const rememberMe = (await getCookieDetail('rememberMe'))[0]
|
||||
loginForm.username = username ? username.value : loginForm.username
|
||||
loginForm.password = password ? decrypt(password.value) : loginForm.password
|
||||
loginForm.rememberMe = rememberMe ? Boolean(rememberMe.value) : false
|
||||
}
|
||||
|
||||
// 获取cookie
|
||||
const getCookieDetail = (name) => {
|
||||
return session.defaultSession.cookies.get({ url: curWinUrl, name })
|
||||
}
|
||||
|
||||
// 设置cookie
|
||||
const setCookie = (name, value) => {
|
||||
// 30天过期
|
||||
let Days = 30
|
||||
let times = Math.round(Date.now() / 1000) + Days * 24 * 60 * 60
|
||||
const cookie = {
|
||||
url: curWinUrl,
|
||||
name,
|
||||
value,
|
||||
expirationDate: times
|
||||
}
|
||||
return session.defaultSession.cookies.set(cookie)
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
localStorage.clear()
|
||||
sessionStore.set('subject', {
|
||||
bookList: null,
|
||||
curBook: null,
|
||||
curNode: null,
|
||||
defaultExpandedKeys: [],
|
||||
subjectTree: []
|
||||
})
|
||||
getCookie()
|
||||
})
|
||||
import ycLogin from './yc-login.vue'
|
||||
import defultLogin from './defult-login.vue'
|
||||
const buildMode = import.meta.env.MODE
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.login-container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
-webkit-app-region: drag;
|
||||
|
||||
.box-item {
|
||||
width: 444px;
|
||||
height: 520px;
|
||||
|
||||
&.desc {
|
||||
background: #ffffff;
|
||||
border-radius: 12px 0px 0px 12px;
|
||||
box-shadow: 0px 16px 73px 8px rgba(203, 203, 203, 0.2);
|
||||
padding: 23px 25px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
background-color: #003b94;
|
||||
}
|
||||
|
||||
&.login {
|
||||
background: #ffffff;
|
||||
border-radius: 0px 12px 12px 0px;
|
||||
padding: 34px 42px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.welcome {
|
||||
padding-top: 35px;
|
||||
|
||||
p {
|
||||
color: #ffffff;
|
||||
line-height: 25px;
|
||||
letter-spacing: 0.26px;
|
||||
text-align: center;
|
||||
font-weight: 700;
|
||||
font-size: 26px;
|
||||
}
|
||||
}
|
||||
|
||||
.welcome-img {
|
||||
margin-top: 20px;
|
||||
width: 350px;
|
||||
height: 350px;
|
||||
}
|
||||
|
||||
.login-title {
|
||||
font-size: 20px;
|
||||
text-align: center;
|
||||
color: #1e1e1e;
|
||||
margin-bottom: 35px;
|
||||
margin-top: 50px;
|
||||
}
|
||||
|
||||
.login-form {
|
||||
-webkit-app-region: no-drag;
|
||||
|
||||
.captcha-input {
|
||||
width: 60%;
|
||||
}
|
||||
|
||||
.captcha-img {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.btn {
|
||||
width: 350px;
|
||||
height: 50px;
|
||||
border-radius: 4px;
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
text-align: center;
|
||||
color: #ffffff;
|
||||
line-height: 50px;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.header-tool {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
-webkit-app-region: no-drag;
|
||||
|
||||
span {
|
||||
padding: 5px 10px;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.el-form-item {
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -0,0 +1,287 @@
|
|||
<template>
|
||||
<div class="login-container">
|
||||
<div class="login-yc">
|
||||
<img class="welcome-img" :src="buildMode === 'yc2?'?leftBg2:leftBg1" />
|
||||
</div>
|
||||
<div class="box-item login">
|
||||
<WindowTools :is-has-max="false" />
|
||||
<div style="display: flex;justify-content: center;"><img class="title-logo" :src="yclogo" /></div>
|
||||
<div class="login-title">永川中小学</div>
|
||||
<div class="login-title2">{{buildMode === 'yc2?'?'重庆永川虚拟仿真AI实训教学管理系统':'人工智能赋能科学素养与劳动技能系统'}}</div>
|
||||
<el-form ref="formRef" class="login-form" :model="loginForm" :rules="rules" size="large">
|
||||
<el-form-item prop="username">
|
||||
<el-input v-model.trim="loginForm.username" placeholder="请输入用户名" />
|
||||
</el-form-item>
|
||||
<el-form-item prop="password" style="margin-bottom: 15px">
|
||||
<el-input v-model="loginForm.password" autocomplete="on" type="password" placeholder="请输入密码" />
|
||||
</el-form-item>
|
||||
<div class="flex mb-5">
|
||||
<el-checkbox v-model="loginForm.rememberMe">记住密码</el-checkbox>
|
||||
<!-- <el-checkbox >阅读并同意《xxx》</el-checkbox> -->
|
||||
</div>
|
||||
|
||||
<el-form-item style="margin-bottom: 20px;">
|
||||
<el-button :loading="btnLoading" class="btn" type="primary" @click="submitForm(formRef)">登录</el-button>
|
||||
</el-form-item>
|
||||
<div class="flex mb-4" style="display: flex;justify-content: center;color: #ccc;cursor: pointer;">
|
||||
<a class="hover:text-sky-500" style="margin-right: 10px;" @click="RegisterModel(1)">注册账号</a>
|
||||
|
|
||||
<a class="hover:text-sky-500" style="margin-left: 10px;" @click="RegisterModel(2)">忘记密码</a>
|
||||
</div>
|
||||
<div class="title-bottom">
|
||||
重庆市永川区教育委员会
|
||||
</div>
|
||||
</el-form>
|
||||
</div>
|
||||
</div>
|
||||
<el-dialog v-model="showDownLoading" width="500" :show-close="false" :close-on-click-modal="false"
|
||||
:close-on-press-escape="false" align-center>
|
||||
<el-progress :text-inside="true" :stroke-width="22" :percentage="downloadProp" :show-text="false"
|
||||
status="success" />
|
||||
</el-dialog>
|
||||
<!--选择学科-->
|
||||
<SelectSubject v-model="isSubject" :login-data="loginForm" />
|
||||
<!--注册弹框-->
|
||||
<Register ref="RegModel"></Register>
|
||||
</template>
|
||||
<script setup>
|
||||
import { onMounted, reactive, ref } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { encrypt, decrypt } from '@/utils/jsencrypt'
|
||||
import useUserStore from '@/store/modules/user'
|
||||
import yclogo from '@/assets/images/login/yc-logo.png'
|
||||
import leftBg1 from '@/assets/images/login/ycpeitu.png'
|
||||
import leftBg2 from '@/assets/images/login/ycpeitu2.jpg'
|
||||
import WindowTools from '@/components/window-tools/index.vue'
|
||||
import SelectSubject from '@/components/select-subject/index.vue'
|
||||
import Register from './components/Register.vue'
|
||||
import { sessionStore } from '@/utils/store'
|
||||
const buildMode = import.meta.env.MODE
|
||||
const { session } = require('@electron/remote')
|
||||
const downloadProp = ref(0)
|
||||
const showDownLoading = ref(false)
|
||||
const { ipcRenderer } = window.electron || {}
|
||||
const formRef = ref()
|
||||
const userStore = useUserStore()
|
||||
const btnLoading = ref(false)
|
||||
const isSubject = ref(false)
|
||||
const RegModel = ref(false)
|
||||
//表单
|
||||
const loginForm = reactive({
|
||||
username: '',
|
||||
password: '',
|
||||
rememberMe: false
|
||||
})
|
||||
|
||||
//表单规则
|
||||
const rules = reactive({
|
||||
username: [{ required: true, trigger: 'blur', message: '请输入您的账号' }],
|
||||
password: [{ required: true, trigger: 'blur', message: '请输入您的密码' }]
|
||||
})
|
||||
|
||||
let curWinUrl = import.meta.env.VITE_APP_BUILD_BASE_PATH
|
||||
ipcRenderer.on('update-app-progress', (e, prop) => {
|
||||
downloadProp.value = prop
|
||||
showDownLoading.value = prop !== 100
|
||||
})
|
||||
// 打开弹窗
|
||||
const RegisterModel = type => {
|
||||
RegModel.value.OpenModel(type)
|
||||
}
|
||||
//登录
|
||||
const submitForm = async (formEl) => {
|
||||
if (!formEl) return
|
||||
await formEl.validate(async (valid) => {
|
||||
if (valid) {
|
||||
btnLoading.value = true
|
||||
|
||||
// 勾选了需要记住密码设置在 cookie 中设置记住用户名和密码
|
||||
if (loginForm.rememberMe) {
|
||||
await setCookie('username', loginForm.username)
|
||||
await setCookie('password', encrypt(loginForm.password))
|
||||
await setCookie('rememberMe', loginForm.rememberMe.toString())
|
||||
} else {
|
||||
// 否则移除
|
||||
await session.defaultSession.clearStorageData({
|
||||
origin: curWinUrl,
|
||||
storages: ['cookies']
|
||||
})
|
||||
}
|
||||
|
||||
try {
|
||||
await userStore.login(loginForm)
|
||||
await userStore.getInfo()
|
||||
if (userStore.user.edustage || userStore.user.edusubject) {
|
||||
ElMessage.success('登录成功')
|
||||
ipcRenderer && ipcRenderer.send('openMainWindow')
|
||||
} else {
|
||||
isSubject.value = true
|
||||
}
|
||||
} finally {
|
||||
btnLoading.value = false
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const getCookie = async () => {
|
||||
const username = (await getCookieDetail('username'))[0]
|
||||
const password = (await getCookieDetail('password'))[0]
|
||||
const rememberMe = (await getCookieDetail('rememberMe'))[0]
|
||||
loginForm.username = username ? username.value : loginForm.username
|
||||
loginForm.password = password ? decrypt(password.value) : loginForm.password
|
||||
loginForm.rememberMe = rememberMe ? Boolean(rememberMe.value) : false
|
||||
}
|
||||
|
||||
// 获取cookie
|
||||
const getCookieDetail = (name) => {
|
||||
return session.defaultSession.cookies.get({ url: curWinUrl, name })
|
||||
}
|
||||
|
||||
// 设置cookie
|
||||
const setCookie = (name, value) => {
|
||||
// 30天过期
|
||||
let Days = 30
|
||||
let times = Math.round(Date.now() / 1000) + Days * 24 * 60 * 60
|
||||
const cookie = {
|
||||
url: curWinUrl,
|
||||
name,
|
||||
value,
|
||||
expirationDate: times
|
||||
}
|
||||
return session.defaultSession.cookies.set(cookie)
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
localStorage.clear()
|
||||
sessionStore.set('subject', {
|
||||
bookList: null,
|
||||
curBook: null,
|
||||
curNode: null,
|
||||
defaultExpandedKeys: [],
|
||||
subjectTree: []
|
||||
})
|
||||
getCookie()
|
||||
})
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.login-container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
-webkit-app-region: drag;
|
||||
.login-yc{
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
img{
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
.box-item {
|
||||
width: 444px;
|
||||
height: 520px;
|
||||
|
||||
&.desc {
|
||||
background: #ffffff;
|
||||
border-radius: 12px 0px 0px 12px;
|
||||
box-shadow: 0px 16px 73px 8px rgba(203, 203, 203, 0.2);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
background-color: #003b94;
|
||||
}
|
||||
|
||||
&.login {
|
||||
background: #ffffff;
|
||||
border-radius: 0px 12px 12px 0px;
|
||||
padding: 34px 15px;
|
||||
position: relative;
|
||||
.title-logo{
|
||||
width: 50px;
|
||||
}
|
||||
}
|
||||
|
||||
.welcome {
|
||||
padding-top: 35px;
|
||||
|
||||
p {
|
||||
color: #ffffff;
|
||||
line-height: 25px;
|
||||
letter-spacing: 0.26px;
|
||||
text-align: center;
|
||||
font-weight: 700;
|
||||
font-size: 26px;
|
||||
}
|
||||
}
|
||||
|
||||
/*.welcome-img {
|
||||
margin-top: 20px;
|
||||
width: 350px;
|
||||
height: 350px;
|
||||
}*/
|
||||
|
||||
.login-title {
|
||||
font-size: 20px;
|
||||
text-align: center;
|
||||
color: #1e1e1e;
|
||||
margin-bottom: 10px;
|
||||
margin-top: 5px;
|
||||
font-width: bold;
|
||||
}
|
||||
|
||||
.login-title2 {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.login-form {
|
||||
-webkit-app-region: no-drag;
|
||||
|
||||
.captcha-input {
|
||||
width: 60%;
|
||||
}
|
||||
|
||||
.captcha-img {
|
||||
cursor: pointer;
|
||||
}
|
||||
.title-bottom{
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
font-size: 14px;
|
||||
color: rgba(0, 0, 0, 0.45);
|
||||
}
|
||||
}
|
||||
|
||||
.btn {
|
||||
width: 350px;
|
||||
height: 50px;
|
||||
border-radius: 4px;
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
text-align: center;
|
||||
color: #ffffff;
|
||||
line-height: 50px;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.header-tool {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
-webkit-app-region: no-drag;
|
||||
|
||||
span {
|
||||
padding: 5px 10px;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.el-form-item {
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,389 @@
|
|||
<template>
|
||||
<div class="ai-container">
|
||||
<el-steps style="max-width:100% " :active="activeStep" align-center>
|
||||
<el-step title="生成大纲" />
|
||||
<el-step title="选择模板" />
|
||||
<el-step title="制作PPT" />
|
||||
</el-steps>
|
||||
<div class="card-box">
|
||||
<el-card class="card2" v-if="activeStep === 0">
|
||||
<div class="paragraphs">
|
||||
{{ outputText }}
|
||||
</div>
|
||||
<el-button style="margin-bottom: 5px;" type="primary" @click="addMessage">从新生成</el-button>
|
||||
<el-button style="margin-bottom: 5px;" type="primary" @click="activeStep = 1">下一步</el-button>
|
||||
</el-card>
|
||||
<el-card v-if="activeStep === 1">
|
||||
<div style="padding-bottom: 10px">ppt模板选择</div>
|
||||
<div class="themes">
|
||||
<div v-for="item in backGroundList" :key="item.templateIndexId" :style="{
|
||||
padding: '5px',
|
||||
paddingRight: '5px',
|
||||
paddingLeft: '5px',
|
||||
margin: '5px',
|
||||
backgroundColor: getBackgroundColor(item.templateIndexId),
|
||||
borderRadius: '10px',
|
||||
borderBlock: '10px solid #e6e6e6'
|
||||
}" @click="chooseBackground(item.templateIndexId)" @mouseenter="changeCursor('pointer')" @mouseleave="changeCursor('default')">
|
||||
{{ item.name }}
|
||||
<img style="width: 150px; height: auto" :src="getBackGroundImg(item.detailImage)" alt="" />
|
||||
</div>
|
||||
</div>
|
||||
<el-row class="el-row">
|
||||
<!-- <el-col :span="6" class="el-col">
|
||||
<div class="grid-content-1">
|
||||
<div>演讲备注</div>
|
||||
<el-switch v-model="outlineData.is_card_note" />
|
||||
</div>
|
||||
</el-col>-->
|
||||
<!-- <el-col :span="6" class="el-col">
|
||||
<div class="grid-content-2">
|
||||
<div>生成封面</div>
|
||||
<el-switch v-model="outlineData.is_cover_img" />
|
||||
</div>
|
||||
</el-col>-->
|
||||
<el-col :span="6" class="el-col">
|
||||
<div class="grid-content-1">
|
||||
<div>自动配图</div>
|
||||
<el-switch v-model="outlineData.isFigure" />
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="6" class="el-col">
|
||||
<div class="grid-content-2">
|
||||
<div>PPT作者名:</div>
|
||||
<el-input v-model="outlineData.author" style="width: 50%" />
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<div>
|
||||
<el-button style="margin-bottom: 5px;" type="primary" @click="activeStep = 0">上一步</el-button>
|
||||
<el-button style="margin-bottom: 5px;" type="primary" v-loading="createPPTLoading" @click="outlineCreatePPT()">生成PPT</el-button>
|
||||
</div>
|
||||
</el-card>
|
||||
<el-card v-if="activeStep === 2">
|
||||
<el-progress :percentage="percentage" type="circle"></el-progress>
|
||||
</el-card>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted } from "vue";
|
||||
import { ElMessage } from 'element-plus'
|
||||
import {
|
||||
getBackGroundV2,
|
||||
createPPTV2,
|
||||
getProgressV2,
|
||||
} from "@/utils/ppt-request.js";
|
||||
import CryptoJS from "crypto-js"
|
||||
|
||||
import { getSignature } from "@/utils/index.js";
|
||||
import {sessionStore} from "@/utils/store";
|
||||
|
||||
let appId = "01ec9aa3";
|
||||
let secret = "M2QxMDAxMjYyYTEzODMwMGRkZTQ4NmUy";
|
||||
let apikey = "39d05b269fa229f431a56c21794a8ea5"
|
||||
let timestamp = Math.floor(Date.now() / 1000);
|
||||
let signature = getSignature(appId, secret, timestamp);
|
||||
const { ipcRenderer } = window.electron || {}
|
||||
|
||||
|
||||
const outputText = ref(""); // 用于展示的大纲数据
|
||||
const stagingData = ref([]); //储存的对话数据,用于多轮对话
|
||||
const stagOutputText = ref(""); // 暂存大纲用于拆分
|
||||
let extractedParts = ref([]) // 初步拆分
|
||||
|
||||
let firstArray = ref([]); //大纲的大纲等级数字部分
|
||||
let secondArray = ref([]); //大纲的文字部分
|
||||
|
||||
|
||||
const backGroundList = ref([]);
|
||||
let subjectdata = sessionStore.get('subject.curNode')
|
||||
const inputTheme = ref(subjectdata.edustage + subjectdata.edusubject + "《" + subjectdata.itemtitle + "》的授课课件"); // 输入的主题
|
||||
const inputRequire = ref("") // 输入的需求
|
||||
const activeStep = ref(0); // 上方进度条
|
||||
const combined = ref('') // 修改完毕的大纲数据,准备传入ppt生成模型
|
||||
|
||||
const treeData = ref([]);
|
||||
const status = ref("init");
|
||||
|
||||
const percentage = ref(0);
|
||||
|
||||
const createPPTLoading = ref(false);
|
||||
|
||||
const getBackgrounds = () => {
|
||||
treeData.value = [];
|
||||
getBackGroundV2().then((res) => {
|
||||
console.log(res);
|
||||
backGroundList.value = res.records;
|
||||
});
|
||||
};
|
||||
|
||||
const getBackGroundImg = (imgUrlStr) => {
|
||||
return JSON.parse(imgUrlStr).titleCoverImage
|
||||
};
|
||||
|
||||
const outlineData = ref({
|
||||
query: '', // 用户要求(最多8000字)
|
||||
// templateId: 'auto', // ppt生成主题
|
||||
author: 'AIX平台',
|
||||
isFigure: false, // 是否自动配图
|
||||
search: true,
|
||||
language: "cn"
|
||||
}
|
||||
)
|
||||
|
||||
const emit = defineEmits(['addSuccess'])
|
||||
const props = defineProps({
|
||||
dataList: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
}
|
||||
})
|
||||
|
||||
// 将输入数据或返回数据存入记忆中
|
||||
function updateStagingData(role, newData) {
|
||||
stagingData.value.push({ role: role, content: newData });
|
||||
}
|
||||
//大纲直接生成ppt
|
||||
const outlineCreatePPT = () => {
|
||||
const newOutlineData = { ...outlineData.value, };
|
||||
newOutlineData.query = outputText.value;
|
||||
createPPTLoading.value = true;
|
||||
createPPTV2(newOutlineData).then((res) => {
|
||||
console.log(res, "正在生成中");
|
||||
createPPTLoading.value = false;
|
||||
activeStep.value = 2
|
||||
|
||||
const checkProgress = () => {
|
||||
getProgressV2(res.sid).then(response => {
|
||||
percentage.value = Math.round(response?.donePages*100/response?.totalPages);
|
||||
if (response.pptStatus === "done") {
|
||||
emit('addSuccess',{...res,url:response.pptUrl})
|
||||
ElMessage.success("生成成功");
|
||||
} else {
|
||||
const sleepTime = 2000;
|
||||
let remainingTime = sleepTime;
|
||||
const intervalId = setInterval(() => {
|
||||
remainingTime -= 100;
|
||||
if (remainingTime <= 0) {
|
||||
clearInterval(intervalId);
|
||||
checkProgress();
|
||||
}
|
||||
}, 100);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
checkProgress();
|
||||
})
|
||||
};
|
||||
|
||||
//初次对话
|
||||
const addMessage = () => {
|
||||
const themeValue = inputTheme.value;
|
||||
const requireValue = inputRequire.value;
|
||||
firstArray.value = []
|
||||
secondArray.value = []
|
||||
extractedParts.value = []
|
||||
stagOutputText.value = ''
|
||||
const combinedString = `请帮我生成一个ppt大纲,主题为:${themeValue}。具体内容要求为:${requireValue}。注意,用三个等级大纲展示,如1. 1.1 1.1.2 2. 2.1这种类型,且按照这种顺序,不要有完全相同数字等级的大纲,不要有目录`
|
||||
updateStagingData("user", combinedString);
|
||||
connectWebSocket(stagingData.value);
|
||||
// activeStep.value = 3
|
||||
};
|
||||
|
||||
let ttsWS
|
||||
function connectWebSocket(data) {
|
||||
outputText.value = ""; //清楚展示部分内容
|
||||
status.value = "ttsing";
|
||||
return getWebsocketUrl().then((url) => {
|
||||
ttsWS = new WebSocket(url);
|
||||
ttsWS.onopen = () => {
|
||||
webSocketSend(ttsWS, data);
|
||||
};
|
||||
ttsWS.onmessage = (e) => {
|
||||
result1(e.data);
|
||||
};
|
||||
ttsWS.onerror = (e) => {
|
||||
status.value = "error";
|
||||
console.log("WebSocket error:", e);
|
||||
};
|
||||
ttsWS.onclose = () => {
|
||||
status.value = "init";
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
const getBackgroundColor = (key) => {
|
||||
return outlineData.value.templateId === key ? '#83e2b6' : '#f5f5f5';
|
||||
};
|
||||
|
||||
function getWebsocketUrl() {
|
||||
return new Promise((resolve, reject) => {
|
||||
var apiKey = apikey;
|
||||
var apiSecret = secret;
|
||||
var url = "wss://spark-api.xf-yun.com/v4.0/chat";
|
||||
|
||||
var host = "spark-api.xf-yun.com";
|
||||
var date = new Date().toGMTString();
|
||||
var algorithm = "hmac-sha256";
|
||||
var headers = "host date request-line";
|
||||
var signatureOrigin = `host: ${host}\ndate: ${date}\nGET /v4.0/chat HTTP/1.1`;
|
||||
var signatureSha = CryptoJS.HmacSHA256(signatureOrigin, apiSecret);
|
||||
var signature = CryptoJS.enc.Base64.stringify(signatureSha);
|
||||
|
||||
var authorizationOrigin = `api_key="${apiKey}", algorithm="${algorithm}", headers="${headers}", signature="${signature}"`;
|
||||
var authorization = CryptoJS.enc.Base64.stringify(
|
||||
CryptoJS.enc.Utf8.parse(authorizationOrigin)
|
||||
);
|
||||
|
||||
url = `${url}?authorization=${authorization}&date=${date}&host=${host}`;
|
||||
console.log(url);
|
||||
resolve(url);
|
||||
});
|
||||
}
|
||||
function webSocketSend(ws, data) {
|
||||
const params = {
|
||||
header: {
|
||||
app_id: appId,
|
||||
},
|
||||
parameter: {
|
||||
chat: {
|
||||
domain: "4.0Ultra",
|
||||
temperature: 0.5,
|
||||
max_tokens: 1024,
|
||||
},
|
||||
},
|
||||
payload: {
|
||||
message: {
|
||||
text: data,
|
||||
},
|
||||
},
|
||||
};
|
||||
ws.send(JSON.stringify(params));
|
||||
}
|
||||
|
||||
function result1(resultData) {
|
||||
let jsonData = JSON.parse(resultData);
|
||||
outputText.value += jsonData.payload.choices.text[0].content;
|
||||
const div = document.querySelector('.paragraphs');
|
||||
if (div) {
|
||||
div.scrollTop = div.scrollHeight;
|
||||
}
|
||||
if (jsonData.payload && jsonData.payload.usage) {
|
||||
updateStagingData("assistant", outputText.value) //返回数据存入记忆池
|
||||
}
|
||||
if (jsonData.header.code !== 0) {
|
||||
alert(`提问失败: ${jsonData.header.code}:${jsonData.header.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
const chooseBackground = (data) => {
|
||||
outlineData.value.templateId = data
|
||||
}
|
||||
|
||||
const changeCursor = (cursorStyle) => {
|
||||
document.documentElement.style.cursor = cursorStyle;
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
// let pptUrl = "https://bjcdn.openstorage.cn/xinghuo-privatedata/zhiwen/2024-12-06/23754754-b5bb-494a-b96d-7a5dc78820eb/89c1aefc634a4566a1779b2bc8ffa943.pptx";
|
||||
// emit('addSuccess',{url:pptUrl})
|
||||
props.dataList.filter(item => {
|
||||
inputRequire.value += item.answer
|
||||
})
|
||||
getBackgrounds();
|
||||
// addMessage()
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.ai-container {
|
||||
width: 100%;
|
||||
background-color: #f5f7f6;
|
||||
padding: 20px
|
||||
}
|
||||
|
||||
.card-box {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.card1 {
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.paragraphs {
|
||||
white-space: pre-wrap;
|
||||
text-align: left;
|
||||
max-height: 60vh;
|
||||
overflow-y: auto;
|
||||
border: 1px solid #409EFF;
|
||||
padding: 10px;
|
||||
margin: 5px
|
||||
}
|
||||
|
||||
.themes {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
height: 250px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.outline {
|
||||
white-space: pre-wrap;
|
||||
text-align: left;
|
||||
|
||||
border: 1px solid #409EFF;
|
||||
padding: 10px;
|
||||
outline-style: none;
|
||||
/* margin: 5px */
|
||||
}
|
||||
|
||||
.outline-row {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.outline-row>.el-col {
|
||||
display: flex;
|
||||
flex-direction: column
|
||||
}
|
||||
|
||||
.outline-row>.el-col>div,
|
||||
.outline-row>.el-col>div>.el-input {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 3px;
|
||||
}
|
||||
|
||||
.item-with-dash {
|
||||
margin-left: 100px
|
||||
}
|
||||
|
||||
.item-with-dash::after {
|
||||
content: "";
|
||||
border-bottom: 1px dashed #000;
|
||||
flex-grow: 1;
|
||||
margin-left: 4px;
|
||||
}
|
||||
|
||||
.grid-content-1 {
|
||||
border-radius: 4px;
|
||||
background-color: #c2dbf3;
|
||||
}
|
||||
|
||||
.grid-content-2 {
|
||||
border-radius: 4px;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
.el-row {
|
||||
padding: 20px
|
||||
}
|
||||
:deep(.el-card__body){
|
||||
padding: 10px 15px;
|
||||
}
|
||||
</style>
|
|
@ -280,14 +280,14 @@ export default {
|
|||
cookie,
|
||||
fileType: item.fileType
|
||||
})
|
||||
ipcRenderer.on('listen-file-change-on' + item.fileNewName, () => {
|
||||
/*ipcRenderer.on('listen-file-change-on' + item.fileNewName, () => {
|
||||
items.async = 'on'
|
||||
})
|
||||
ipcRenderer.on('listen-file-change-success' + item.fileNewName, (e, { data, md5 }) => {
|
||||
items.fileSize = data.fileSize
|
||||
items.md5 = md5
|
||||
items.async = true
|
||||
})
|
||||
})*/
|
||||
}
|
||||
})
|
||||
})
|
||||
|
|
|
@ -347,7 +347,7 @@ export default {
|
|||
Object.assign(items, item)
|
||||
asyncLocalFile(items).then(() => {
|
||||
ipcRenderer.send('open-path-app', item.fileNewName)
|
||||
if (this.listenList.indexOf(item.fileNewName) === -1) {
|
||||
if (this.listenList?.indexOf(item.fileNewName) === -1) {
|
||||
this.listenList.push(item.fileNewName)
|
||||
let cookie = localStorage.getItem('Admin-Token')
|
||||
ipcRenderer.send('listen-file-change', {
|
||||
|
@ -357,14 +357,14 @@ export default {
|
|||
cookie,
|
||||
fileType: item.fileType
|
||||
})
|
||||
ipcRenderer.on('listen-file-change-on' + item.fileNewName, () => {
|
||||
/*ipcRenderer.on('listen-file-change-on' + item.fileNewName, () => {
|
||||
items.async = 'on'
|
||||
})
|
||||
ipcRenderer.on('listen-file-change-success' + item.fileNewName, (e, { data, md5 }) => {
|
||||
items.fileSize = data.fileSize
|
||||
items.md5 = md5
|
||||
items.async = true
|
||||
})
|
||||
})*/
|
||||
}
|
||||
})
|
||||
})
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
</template>
|
||||
|
||||
<script setup>
|
||||
import AiPpt from './ai-ppt.vue';
|
||||
import AiPpt from './ai-pptV2.vue';
|
||||
const model = defineModel()
|
||||
const emit = defineEmits(['addSuccess'])
|
||||
const props = defineProps({
|
||||
|
|
|
@ -162,7 +162,7 @@ import KjListItem from '@/views/prepare/container/kj-list-item.vue'
|
|||
import { getSmarttalkPage, moveSmarttalk, creatAPT } from '@/api/file'
|
||||
import { toTimeText } from '@/utils/date'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { parseCataByNode, creatPPT, asyncLocalFile } from '@/utils/talkFile'
|
||||
import { parseCataByNode, creatPPT, asyncLocalFile, creatAIPPT } from '@/utils/talkFile'
|
||||
import FileOperBatch from '@/views/prepare/container/file-oper-batch.vue'
|
||||
import SetHomework from '@/components/set-homework/index.vue'
|
||||
import outLink from '@/utils/linkConfig'
|
||||
|
@ -293,9 +293,12 @@ export default {
|
|||
// },
|
||||
methods: {
|
||||
addAiPPT(item) {
|
||||
this.currentFileList.unshift(item.resData)
|
||||
KjListItem.methods.openFileWin(item.resData);
|
||||
this.pptDialog = false
|
||||
console.log(this.currentNode.itemtitle + '.pptx',item.url, {...this.uploadData,fileShowName: this.currentNode.itemtitle + '.pptx'})
|
||||
creatAIPPT(this.currentNode.itemtitle + '.pptx',item.url, {...this.uploadData,fileShowName: this.currentNode.itemtitle + '.pptx'}).then((res) => {
|
||||
this.currentFileList.unshift(res.resData)
|
||||
KjListItem.methods.openFileWin(res.resData);
|
||||
this.pptDialog = false
|
||||
})
|
||||
},
|
||||
// test() {
|
||||
// toolStore.resetDef() // 重置状态
|
||||
|
@ -537,7 +540,7 @@ export default {
|
|||
}
|
||||
},
|
||||
clickChoose(value) {
|
||||
this.checkFileList = value ? this.currentFileList : []
|
||||
this.checkFileList = value ? this.currentSCFileList : []
|
||||
},
|
||||
deleteTalk(item) {
|
||||
let index = this.currentFileList.indexOf(item)
|
||||
|
|
|
@ -161,7 +161,11 @@ const delRow = (item) => {
|
|||
}
|
||||
|
||||
// 加入备课
|
||||
const addLesson = ({ id }) => {
|
||||
const addLesson = ({ id, fileSize }) => {
|
||||
if (fileSize>1024*1024*150) {
|
||||
ElMessage.warning('文件超过150M,暂停超过150M资源的下载,请重新选择')
|
||||
return
|
||||
}
|
||||
let data = {
|
||||
id,
|
||||
fileRoot: '备课',
|
||||
|
@ -190,6 +194,8 @@ const handleRow = (item) => {
|
|||
})
|
||||
curRow.value = item
|
||||
isShow.value = true
|
||||
console.log(item,'item');
|
||||
|
||||
}
|
||||
</script>
|
||||
|
||||
|
|
|
@ -18,21 +18,17 @@
|
|||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- 第三方资源筛选-->
|
||||
<!-- <el-row class="resoure-btns" v-if="isThird">
|
||||
<el-col :span="24" class="query-row flex">
|
||||
<div class="flex row-left">
|
||||
<el-button v-for="item in coursewareTypeList" :key="item.id"
|
||||
:type="sourceStore.thirdQuery.type == item.value ? 'primary' : ''" round
|
||||
@click="sourceStore.thirdChangeType(item.value)">
|
||||
{{item.label }}
|
||||
</el-button>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row> -->
|
||||
<el-row class="resoure-btns">
|
||||
<el-col :span="24" class="query-row flex">
|
||||
<div class="flex row-left"> <el-select v-model="sourceStore.query.fileSuffix" @change="sourceStore.changeSuffix"
|
||||
<div class="flex row-left">
|
||||
<!-- 第三方资源筛选-->
|
||||
<el-select v-if="isThird" v-model="sourceStore.thirdQuery.type" @change="sourceStore.thirdChangeType"
|
||||
style="width: 110px">
|
||||
<el-option v-for="item in coursewareTypeList" :key="item.value" :label="item.label"
|
||||
:value="item.value" />
|
||||
</el-select>
|
||||
|
||||
<el-select v-else v-model="sourceStore.query.fileSuffix" @change="sourceStore.changeSuffix"
|
||||
style="width: 110px">
|
||||
<el-option v-for="item in sourceStore.resourceFormatList" :key="item.value" :label="item.label"
|
||||
:value="item.value" />
|
||||
|
|
|
@ -121,6 +121,10 @@ const handleRow = (item) => {
|
|||
thirdPreview.value.init(item.itemId)
|
||||
}
|
||||
const openChapter = (item)=>{
|
||||
if (item.size>1024*1024*150) {
|
||||
ElMessage.warning('文件超过150M,暂停超过150M资源的下载,请重新选择')
|
||||
return
|
||||
}
|
||||
currentItem.value = item
|
||||
// 打开弹窗
|
||||
treelogRef.value.openDialog()
|
||||
|
|
|
@ -84,11 +84,11 @@ import PDF from '@/components/PdfJs/index.vue'
|
|||
import { useRoute } from 'vue-router';
|
||||
import useResoureStore from '../../resource/store'
|
||||
import { listEvaluationclue } from '@/api/teaching/classwork'
|
||||
import { uploadServer, getJSONFile } from '@/utils/common'
|
||||
import { ElNotification } from 'element-plus'
|
||||
import ChooseTextbook from "@/components/choose-textbook/index.vue";
|
||||
import { listEvaluation } from '@/api/classManage/index'
|
||||
import useUserStore from '@/store/modules/user'
|
||||
import { pdfCallBack } from '@/utils/pdftools'
|
||||
const userStore = useUserStore()
|
||||
const sourceStore = useResoureStore()
|
||||
// import { getStaticUrl } from '@/utils/tool'
|
||||
|
@ -171,52 +171,6 @@ const selectHandel = (value) => {
|
|||
const filterData = searchInp.value !== '' ? showData.value : standList.value
|
||||
showData.value = filterList(filterData);
|
||||
}
|
||||
//保存json文件
|
||||
const saveJSON = (data) => {
|
||||
|
||||
let filename = ''
|
||||
// const data = {
|
||||
// name: 'txt',
|
||||
// class: '五年级2班',
|
||||
// school: '重庆市酉阳二中',
|
||||
// time: '2024-08-09'
|
||||
// }
|
||||
// saveJSON(jsonStr);
|
||||
if (!data) {
|
||||
console.log('传入的data数据为null');
|
||||
return;
|
||||
}
|
||||
if (!filename) {
|
||||
filename = `json${Date.now()}.json`
|
||||
console.log('未传入文件名,采用默认文件名' + filename);
|
||||
}
|
||||
let newdata = null;
|
||||
if (typeof data === 'object') {
|
||||
newdata = JSON.stringify(data, undefined, 4)
|
||||
}
|
||||
// 创建json文件blob流
|
||||
const blob = new Blob([newdata], { type: 'text/json' });
|
||||
// 创建file文件
|
||||
// const file = new File([blob],filename, {type: blob.type})
|
||||
// 创建上传文件流
|
||||
// const formdata = new FormData();
|
||||
// formdata.append('file', file);
|
||||
//其他参数待添加
|
||||
|
||||
//上传
|
||||
// uploadServer(formdata).then(res => {
|
||||
// console.log('+++++++++++++');
|
||||
// console.log(res.data);
|
||||
// })
|
||||
|
||||
let e = document.createEvent('MouseEvents');
|
||||
let a = document.createElement('a');
|
||||
a.download = filename;
|
||||
a.href = window.URL.createObjectURL(blob);
|
||||
a.dataset.downloadurl = ['text/json', a.download, a.href].join(':');
|
||||
e.initMouseEvent('click', true, false, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
|
||||
a.dispatchEvent(e);
|
||||
}
|
||||
// 查询
|
||||
const getData = (data) => {
|
||||
const { textBook, node } = data
|
||||
|
@ -295,53 +249,9 @@ onMounted(async () => {
|
|||
window.addEventListener('message',(event) => {
|
||||
// console.log('------------');
|
||||
const iframeMes = event.data;
|
||||
if(iframeMes.storageInfo){
|
||||
// saveJSON(iframeMes.storageInfo);
|
||||
// console.log(JSON.stringify(iframeMes.storageInfo));
|
||||
}
|
||||
if(iframeMes.quoteInfo){
|
||||
console.log(iframeMes.quoteInfo);
|
||||
const { textStr, StartStr, EndStr } = iframeMes.quoteInfo;
|
||||
if(textStr === StartStr || textStr === EndStr){
|
||||
ElNotification({
|
||||
title: '引用内容',
|
||||
message: textStr,
|
||||
duration: 0,
|
||||
type: 'info',
|
||||
offset: 120
|
||||
})
|
||||
//如果开头和结尾的文字跟内容相同,那么它要么是开头,要么是整段,不需要替换操作
|
||||
console.log('无需替换------',textStr);
|
||||
return
|
||||
}
|
||||
let midStr = ''
|
||||
if(StartStr === '' && EndStr === '' && textStr === '') return
|
||||
if(StartStr === '' && EndStr !== '') {
|
||||
midStr = textStr.replace(EndStr,'eeeeee').split('eeeeee')[0]
|
||||
}else if(StartStr !== '' && EndStr === ''){
|
||||
midStr = textStr.replace(StartStr,'ssssss').split('ssssss')[1]
|
||||
}else{
|
||||
midStr = textStr.replace(StartStr, 'ssssss').replace(EndStr,'eeeeee').split('ssssss')[1].split('eeeeee')[0];
|
||||
}
|
||||
ElNotification({
|
||||
title: '引用内容',
|
||||
message: StartStr + midStr + EndStr,
|
||||
duration: 0,
|
||||
type: 'info',
|
||||
offset: 120
|
||||
})
|
||||
console.log('中间文字------',midStr);
|
||||
console.log('转换后整体文字------',StartStr + midStr + EndStr);
|
||||
}
|
||||
//监听pdf返回事件,在pdf工具类中统一处理
|
||||
pdfCallBack(iframeMes)
|
||||
})
|
||||
|
||||
// const isDev = process.env.NODE_ENV == 'development'
|
||||
// if (isDev)
|
||||
// pdfUrl.value = '/'+getStaticUrl('aaa.pdf', 'user', 'selfFile', true)
|
||||
// else
|
||||
// pdfUrl.value = getStaticUrl(route.query.path, 'user', 'selfFile', true)
|
||||
|
||||
// console.log('页面',pdfUrl.value);
|
||||
})
|
||||
</script>
|
||||
<style scoped lang="scss">
|
||||
|
|
|
@ -83,17 +83,15 @@ import { onMounted, ref } from 'vue'
|
|||
import PDF from '@/components/PdfJs/index.vue'
|
||||
import { useRoute } from 'vue-router';
|
||||
import useResoureStore from '../resource/store'
|
||||
import { listEvaluationclue } from '@/api/teaching/classwork'
|
||||
// import { uploadServer, getJSONFile } from '@/utils/common'
|
||||
import { ElNotification } from 'element-plus'
|
||||
import ChooseTextbook from "@/components/choose-textbook/index.vue";
|
||||
import { listEvaluation } from '@/api/classManage/index'
|
||||
import useUserStore from '@/store/modules/user'
|
||||
import { sessionStore } from '@/utils/store'
|
||||
import { useGetSubject } from '@/hooks/useGetSubject'
|
||||
import { pdfCallBack, getAnalysisList, addAnalysis, updateAnalusis } from '@/utils/pdftools'
|
||||
const userStore = useUserStore()
|
||||
const sourceStore = useResoureStore()
|
||||
// import { getStaticUrl } from '@/utils/tool'
|
||||
|
||||
const route = useRoute();
|
||||
const pdfUrl = ref('');
|
||||
|
@ -111,7 +109,6 @@ const bookInfo = ref(null);
|
|||
const booksel = ref(0);
|
||||
const bookList = ref([])
|
||||
|
||||
|
||||
const searchOptions = [{
|
||||
value: '0',
|
||||
label: '按时间',
|
||||
|
@ -122,17 +119,6 @@ const searchOptions = [{
|
|||
const standList = ref([]);
|
||||
const showData = ref([]);
|
||||
|
||||
//查询课标分析列表
|
||||
const getlistEvaluationclue = (firstid, levelid) => {
|
||||
const newid = firstid ? firstid : levelid;
|
||||
listEvaluationclue({evalid: newid, parentid: 0, cluegroup: 'teachresource', orderby: "timestamp desc", pageSize: 100}).then((res) => {
|
||||
if(res.code === 200){
|
||||
const newData = formaterTime(res.rows)
|
||||
standList.value = newData
|
||||
showData.value = filterList(newData)
|
||||
}
|
||||
})
|
||||
}
|
||||
const formaterTime = (data) => {
|
||||
return data.map(item => {
|
||||
return {
|
||||
|
@ -173,75 +159,7 @@ const selectHandel = (value) => {
|
|||
const filterData = searchInp.value !== '' ? showData.value : standList.value
|
||||
showData.value = filterList(filterData);
|
||||
}
|
||||
//保存json文件
|
||||
const saveJSON = (data) => {
|
||||
|
||||
let filename = ''
|
||||
// const data = {
|
||||
// name: 'txt',
|
||||
// class: '五年级2班',
|
||||
// school: '重庆市酉阳二中',
|
||||
// time: '2024-08-09'
|
||||
// }
|
||||
// saveJSON(jsonStr);
|
||||
if (!data) {
|
||||
console.log('传入的data数据为null');
|
||||
return;
|
||||
}
|
||||
if (!filename) {
|
||||
filename = `json${Date.now()}.json`
|
||||
console.log('未传入文件名,采用默认文件名' + filename);
|
||||
}
|
||||
let newdata = null;
|
||||
if (typeof data === 'object') {
|
||||
newdata = JSON.stringify(data, undefined, 4)
|
||||
}
|
||||
// 创建json文件blob流
|
||||
const blob = new Blob([newdata], { type: 'text/json' });
|
||||
// 创建file文件
|
||||
// const file = new File([blob],filename, {type: blob.type})
|
||||
// 创建上传文件流
|
||||
// const formdata = new FormData();
|
||||
// formdata.append('file', file);
|
||||
//其他参数待添加
|
||||
|
||||
//上传
|
||||
// uploadServer(formdata).then(res => {
|
||||
// console.log('+++++++++++++');
|
||||
// console.log(res.data);
|
||||
// })
|
||||
|
||||
let e = document.createEvent('MouseEvents');
|
||||
let a = document.createElement('a');
|
||||
a.download = filename;
|
||||
a.href = window.URL.createObjectURL(blob);
|
||||
a.dataset.downloadurl = ['text/json', a.download, a.href].join(':');
|
||||
e.initMouseEvent('click', true, false, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
|
||||
a.dispatchEvent(e);
|
||||
}
|
||||
// 查询
|
||||
const getData = (data) => {
|
||||
const { textBook, node } = data
|
||||
let textbookId = textBook.curBookId
|
||||
let levelSecondId = node.id
|
||||
let levelFirstId
|
||||
if (node.parentNode) {
|
||||
levelFirstId = node.parentNode.id
|
||||
} else {
|
||||
levelFirstId = node.id
|
||||
levelSecondId = ''
|
||||
}
|
||||
sourceStore.query.levelFirstId = levelFirstId
|
||||
sourceStore.query.levelSecondId = levelSecondId
|
||||
sourceStore.query.textbookId = textbookId
|
||||
sourceStore.nodeData = {
|
||||
textbookId, //版本
|
||||
levelFirstId, //单元
|
||||
levelSecondId //单元课程
|
||||
}
|
||||
sourceStore.handleQuery()
|
||||
getlistEvaluationclue(levelFirstId, levelSecondId)
|
||||
}
|
||||
// 获取学科
|
||||
const getAllSubject = async () => {
|
||||
const { edustage, edusubject } = userStore.user;
|
||||
|
@ -259,6 +177,7 @@ const getAllSubject = async () => {
|
|||
bookList.value = dataList
|
||||
const session = sessionStore.get('subject.curNode')
|
||||
console.log('session',session);
|
||||
console.log('datalist',dataList[0]);
|
||||
let filePath = import.meta.env.VITE_APP_RES_FILE_PATH;
|
||||
if(session.rootid){
|
||||
const idx = dataList.findIndex(item => item.id === session.rootid)
|
||||
|
@ -269,11 +188,11 @@ const getAllSubject = async () => {
|
|||
bookInfo.value = {...dataList[0]}
|
||||
filePath += dataList[0].fileurl.replace('.txt','.pdf')
|
||||
}
|
||||
await loadPdfAnimation(filePath)
|
||||
}else{
|
||||
bookInfo.value = {...dataList[0]}
|
||||
filePath += dataList[0].fileurl.replace('.txt','.pdf')
|
||||
}
|
||||
await loadPdfAnimation(filePath)
|
||||
}
|
||||
const bookChange = async (item, idx) => {
|
||||
booksel.value = idx
|
||||
|
@ -293,6 +212,14 @@ const loadPdfAnimation = (path) => {
|
|||
onMounted(async () => {
|
||||
await useGetSubject();
|
||||
await getAllSubject();
|
||||
await getAnalysisList({evalid: bookInfo.value.id, parentid: 0, cluegroup: 'textbookAnalysis', orderby: "timestamp desc", pageSize: 100}).then((res) => {
|
||||
if(res.code === 200){
|
||||
const newData = formaterTime(res.rows)
|
||||
standList.value = newData
|
||||
// showData.value = filterList(newData)
|
||||
showData.value = newData
|
||||
}
|
||||
})
|
||||
if(cardref.value && headref.value){
|
||||
const cardH = cardref.value.offsetHeight;
|
||||
const headh = headref.value.offsetHeight;
|
||||
|
@ -309,46 +236,10 @@ onMounted(async () => {
|
|||
}
|
||||
})
|
||||
window.addEventListener('message',(event) => {
|
||||
// console.log('------------');
|
||||
console.log('------------');
|
||||
const iframeMes = event.data;
|
||||
if(iframeMes.storageInfo){
|
||||
// saveJSON(iframeMes.storageInfo);
|
||||
// console.log(JSON.stringify(iframeMes.storageInfo));
|
||||
}
|
||||
if(iframeMes.quoteInfo){
|
||||
console.log(iframeMes.quoteInfo);
|
||||
const { textStr, StartStr, EndStr } = iframeMes.quoteInfo;
|
||||
if(textStr === StartStr || textStr === EndStr){
|
||||
ElNotification({
|
||||
title: '引用内容',
|
||||
message: textStr,
|
||||
duration: 0,
|
||||
type: 'info',
|
||||
offset: 120
|
||||
})
|
||||
//如果开头和结尾的文字跟内容相同,那么它要么是开头,要么是整段,不需要替换操作
|
||||
console.log('无需替换------',textStr);
|
||||
return
|
||||
}
|
||||
let midStr = ''
|
||||
if(StartStr === '' && EndStr === '' && textStr === '') return
|
||||
if(StartStr === '' && EndStr !== '') {
|
||||
midStr = textStr.replace(EndStr,'eeeeee').split('eeeeee')[0]
|
||||
}else if(StartStr !== '' && EndStr === ''){
|
||||
midStr = textStr.replace(StartStr,'ssssss').split('ssssss')[1]
|
||||
}else{
|
||||
midStr = textStr.replace(StartStr, 'ssssss').replace(EndStr,'eeeeee').split('ssssss')[1].split('eeeeee')[0];
|
||||
}
|
||||
ElNotification({
|
||||
title: '引用内容',
|
||||
message: StartStr + midStr + EndStr,
|
||||
duration: 0,
|
||||
type: 'info',
|
||||
offset: 120
|
||||
})
|
||||
console.log('中间文字------',midStr);
|
||||
console.log('转换后整体文字------',StartStr + midStr + EndStr);
|
||||
}
|
||||
console.log('------------',iframeMes);
|
||||
pdfCallBack(iframeMes)
|
||||
})
|
||||
|
||||
// const isDev = process.env.NODE_ENV == 'development'
|
||||
|
|
Loading…
Reference in New Issue