Merge branch 'main' into cys

This commit is contained in:
cys 2024-07-17 14:20:31 +08:00
commit fbb9fed298
27 changed files with 1387 additions and 373 deletions

View File

@ -22,8 +22,8 @@ export default defineConfig({
server: {
proxy: {
'/dev-api': {
// target: 'http://27.128.240.72:7865',
target: 'http://192.168.2.52:7863',
target: 'http://27.128.240.72:7865',
// target: 'http://192.168.2.52:7863',
changeOrigin: true,
rewrite: (p) => p.replace(/^\/dev-api/, '')
},

View File

@ -1,9 +1,9 @@
const fs = require('fs')
const path = require('path')
import { ElectronDownloadManager } from 'electron-dl-manager';
const manager = new ElectronDownloadManager();
import { ElectronDownloadManager } from 'electron-dl-manager'
import { dialog } from 'electron'
const manager = new ElectronDownloadManager()
export default async function ({ app, shell, BrowserWindow, ipcMain }) {
const userDataPath = app.getPath('userData')
//默认浏览器打开url
ipcMain.on('open-url-browser', (e, url) => {
@ -22,8 +22,8 @@ export default async function ({ app, shell, BrowserWindow, ipcMain }) {
})
//下载文件
ipcMain.on('download-file-default', (e,url) => {
createFolder('selfFile').then(async ()=>{
ipcMain.on('download-file-default', (e, url) => {
createFolder('selfFile').then(async () => {
const browserWindow = BrowserWindow.fromId(e.sender.id)
const id = await manager.download({
window: browserWindow,
@ -38,17 +38,65 @@ export default async function ({ app, shell, BrowserWindow, ipcMain }) {
id,
percentCompleted,
// Get the number of bytes received so far
bytesReceived: item.getReceivedBytes(),
});
bytesReceived: item.getReceivedBytes()
})
},
onDownloadCompleted: async ({ id, item }) => {
console.log(item)
},
onDownloadCancelled: async () => {},
onDownloadInterrupted: async () => {},
onError: (err, data) => {},
onError: (err, data) => {}
}
});
})
})
})
/**...
* 接收渲染进程 保存文件的 的通知
* @param {Object} event
* @param {String} url 下载链接
* @param {String} fileName 文件名称包括后缀名例如图1.png
*/
ipcMain.on('save-as', function (event, url, fileName) {
let win = BrowserWindow.getFocusedWindow();
//通过扩展名识别文件类型
let filters = [{ name: '全部文件', extensions: ['*'] }]
let ext = path.extname(fileName) //获取扩展名
if (ext && ext !== '.') {
const name = ext.slice(1, ext.length)
if (name) {
filters.unshift({
name: '',
extensions: [name]
})
}
}
let filePath = null //用户选择存放文件的路径
//1- 弹出另存为弹框,用于获取保存路径
dialog
.showSaveDialog(win, {
title: '另存为',
filters,
defaultPath: fileName
})
.then((result) => {
//点击保存后开始下载
filePath = result.filePath
if (filePath) {
win.webContents.downloadURL(url) // 触发will-download事件
}
})
.catch(() => {
console.log('另存为--catch')
})
//2- 准备下载的时候触发
win.webContents.session.once('will-download', (event, item, webContents) => {
if (!filePath) return
//设置下载项的保存文件路径
item.setSavePath(filePath)
})
})

View File

@ -8,8 +8,8 @@ File({ app, shell, BrowserWindow, ipcMain })
function createWindow() {
// Create the browser window.
const mainWindow = new BrowserWindow({
width: 888,
height: 520,
width: 1050,
height: 650,
show: false,
frame: false,
autoHideMenuBar: true,

View File

@ -8,3 +8,18 @@ export const getSmarttalkPage = (params) => {
params
})
}
export function deleteSmarttalk(id) {
return request({
url: '/smarttalk/file/' + id,
method: 'delete'
})
}
export const updateSmarttalk = (params) => {
return request({
url: '/smarttalk/file/updateSmarttalk',
method: 'post',
params
})
}

View File

@ -1,9 +1,9 @@
@font-face {
font-family: "iconfont"; /* Project id 2794390 */
src: url('iconfont.woff2?t=1720953486579') format('woff2'),
url('iconfont.woff?t=1720953486579') format('woff'),
url('iconfont.ttf?t=1720953486579') format('truetype'),
url('iconfont.svg?t=1720953486579#iconfont') format('svg');
src: url('iconfont.woff2?t=1721179711733') format('woff2'),
url('iconfont.woff?t=1721179711733') format('woff'),
url('iconfont.ttf?t=1721179711733') format('truetype'),
url('iconfont.svg?t=1721179711733#iconfont') format('svg');
}
.iconfont {
@ -14,6 +14,10 @@
-moz-osx-font-smoothing: grayscale;
}
.icon-yidongdaozu:before {
content: "\e67d";
}
.icon-shanchu:before {
content: "\e852";
}

File diff suppressed because one or more lines are too long

View File

@ -5,6 +5,13 @@
"css_prefix_text": "icon-",
"description": "",
"glyphs": [
{
"icon_id": "1207918",
"name": "移动到组",
"font_class": "yidongdaozu",
"unicode": "e67d",
"unicode_decimal": 59005
},
{
"icon_id": "8288874",
"name": "删除",

View File

@ -14,6 +14,8 @@
/>
<missing-glyph />
<glyph glyph-name="yidongdaozu" unicode="&#59005;" d="M904.448 270.272 119.616 270.272c-23.68 0-44.736-14.656-52.48-36.48C65.024 227.968 64 221.952 64 216c0-16.32 7.616-32.192 21.184-42.688l293.248-225.728c24.128-18.56 59.008-14.464 78.016 9.088 18.944 23.552 14.848 57.664-9.28 76.224L288 156.544l616 0c30.72 0 56 29.44 56 59.456C960 246.016 935.168 270.272 904.448 270.272zM119.552 497.728l784.832 0c23.68 0 44.736 14.656 52.48 36.48C958.976 540.032 960 546.048 960 552c0 16.32-7.616 32.192-21.184 42.688l-293.248 225.728c-24.128 18.56-59.008 14.464-78.016-9.088C548.608 787.776 552.64 753.6 576.832 735.04L736 611.456 120 611.456C89.28 611.456 64 582.016 64 552 64 521.984 88.832 497.728 119.552 497.728z" horiz-adv-x="1024" />
<glyph glyph-name="shanchu" unicode="&#59474;" d="M736.653061-33.959184H287.346939c-45.97551 0-83.591837 37.616327-83.591837 83.591837V540.734694h616.489796v-491.102041c0-45.97551-37.616327-83.591837-83.591837-83.591837zM245.55102 498.938776v-449.306123c0-22.987755 18.808163-41.795918 41.795919-41.795918h449.306122c22.987755 0 41.795918 18.808163 41.795919 41.795918V498.938776H245.55102zM407.510204 101.877551c-11.493878 0-20.897959 9.404082-20.897959 20.897959V384c0 11.493878 9.404082 20.897959 20.897959 20.897959s20.897959-9.404082 20.897959-20.897959v-261.22449c0-11.493878-9.404082-20.897959-20.897959-20.897959zM616.489796 101.877551c-11.493878 0-20.897959 9.404082-20.897959 20.897959V384c0 11.493878 9.404082 20.897959 20.897959 20.897959s20.897959-9.404082 20.897959-20.897959v-261.22449c0-11.493878-9.404082-20.897959-20.897959-20.897959zM846.367347 498.938776H177.632653c-45.97551 0-83.591837 37.616327-83.591837 83.591836v31.346939c0 45.97551 37.616327 83.591837 83.591837 83.591837h668.734694c45.97551 0 83.591837-37.616327 83.591837-83.591837v-31.346939c0-45.97551-37.616327-83.591837-83.591837-83.591836zM177.632653 655.673469c-22.987755 0-41.795918-18.808163-41.795918-41.795918v-31.346939c0-22.987755 18.808163-41.795918 41.795918-41.795918h668.734694c22.987755 0 41.795918 18.808163 41.795918 41.795918v31.346939c0 22.987755-18.808163 41.795918-41.795918 41.795918H177.632653zM650.44898 655.673469h-276.89796c-28.734694 0-52.244898 23.510204-52.244898 52.244898v41.795919c0 28.734694 23.510204 52.244898 52.244898 52.244898h276.89796c28.734694 0 52.244898-23.510204 52.244898-52.244898v-41.795919c0-28.734694-23.510204-52.244898-52.244898-52.244898z m-276.89796 104.489796c-5.746939 0-10.44898-4.702041-10.448979-10.448979v-41.795919c0-5.746939 4.702041-10.44898 10.448979-10.448979h276.89796c5.746939 0 10.44898 4.702041 10.448979 10.448979v41.795919c0 5.746939-4.702041 10.44898-10.448979 10.448979h-276.89796z" horiz-adv-x="1024" />
<glyph glyph-name="xiazai" unicode="&#58973;" d="M795.694545 500.363636a289.047273 289.047273 0 0 1-567.38909 0 197.585455 197.585455 0 0 1 18.385454-395.636363h30.952727a23.272727 23.272727 0 0 1 0 46.545454H246.690909a151.272727 151.272727 0 1 0-2.327273 302.545455l23.272728-5.352727 3.025454 25.6a242.269091 242.269091 0 0 0 480.814546 0l4.654545-25.134546 23.272727 4.887273a151.272727 151.272727 0 1 0-2.327272-302.545455h-34.909091a23.272727 23.272727 0 0 1 0-46.545454h35.141818a197.585455 197.585455 0 0 1 18.385454 395.636363zM628.363636 206.196364l-91.927272-93.090909v286.254545a23.272727 23.272727 0 0 1-46.545455 0v-285.090909l-91.927273 93.090909A23.272727 23.272727 0 1 1 365.149091 174.545455l131.490909-131.723637a23.272727 23.272727 0 0 1 33.047273 0L661.178182 174.545455A23.272727 23.272727 0 1 1 628.363636 206.196364z" horiz-adv-x="1024" />

Before

Width:  |  Height:  |  Size: 48 KiB

After

Width:  |  Height:  |  Size: 49 KiB

View File

@ -32,3 +32,27 @@ html,body{
fill: currentColor;
overflow: hidden;
}
::-webkit-scrollbar {
width:5px;
height:5px;
background-color:#F5F5F5;
}
/* 滚动条上的滚动滑块. */
::-webkit-scrollbar-thumb {
background-color: #a3b7cb;
border-radius: 50px;
}
/* 滚动条轨道. */
::-webkit-scrollbar-track {
-webkit-box-shadow:inset 0 0 6px rgba(0, 142, 255, 1);
box-shadow:inset 0 0 6px rgba(0, 142, 255, 1);
background-color:#E7E3DF;
}
/* 滚动条没有滑块的轨道部分 */
::-webkit-scrollbar-track-piece {
background-color: #E7E3DF;
}
/* 当同时有垂直滚动条和水平滚动条时交汇的部分. */
::-webkit-scrollbar-corner {
background:transparent;
}

View File

@ -1,16 +1,20 @@
<template>
<div class="book-wrap">
<el-scrollbar height="100%">
<div class="book-name flex" @click="dialogVisible = true">
<span>{{ curBookName }}</span>
<i class="iconfont icon-xiangyou"></i>
</div>
<div class="book-list">
<el-tree :data="treeData" :props="defaultProps" highlight-current @node-click="handleNodeClick">
<el-tree ref="refTree" :data="treeData" :props="defaultProps" node-key="id"
:default-expanded-keys="defaultExpandedKeys" :current-node-key="currentNode" 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"
@ -35,12 +39,12 @@
</template>
<script setup>
import { onMounted, ref } from 'vue';
import { onMounted, ref, nextTick, toRaw } from 'vue';
import useUserStore from '@/store/modules/user'
import { listEvaluation } from '@/api/subject'
// emit
const emit = defineEmits(['nodeClick'])
const emit = defineEmits(['nodeClick', 'changeBook'])
// store
const userStore = useUserStore()
const { edustage, edusubject, userId } = userStore.user
@ -63,6 +67,14 @@ const curBookName = ref('')
const volumeOne = ref([])
//
const volumeTwo = ref([])
// ID
const currentNode = ref(0)
//
const currentNodeName = ref('')
//
const defaultExpandedKeys = ref([])
// tree
const refTree = ref(null)
//
@ -73,14 +85,25 @@ const getSubjectContent = async () => {
entpcourseedituserid: userId,
pageSize: 500
}
let data;
if (localStorage.getItem('evaluationList')) {
evaluationList.value = JSON.parse(localStorage.getItem('evaluationList'))
data = evaluationList.value
}
else {
const { rows } = await listEvaluation(params)
localStorage.setItem('evaluationList', JSON.stringify(rows))
evaluationList.value = rows
data = rows
}
//
await getSubject()
//
volumeOne.value = rows.filter(item => item.level == 1 && item.semester == '上册')
volumeOne.value = data.filter(item => item.level == 1 && item.semester == '上册')
//
volumeTwo.value = rows.filter(item => item.level == 1 && item.semester == '下册')
volumeTwo.value = data.filter(item => item.level == 1 && item.semester == '下册')
getTreeData()
}
@ -93,7 +116,6 @@ const changeBook = ({ id, itemtitle }) => {
setTimeout(() => {
dialogVisible.value = false
}, 100);
}
const getTreeData = () => {
@ -101,8 +123,76 @@ const getTreeData = () => {
let upData = transData(volumeOne.value)
let downData = transData(volumeTwo.value)
treeData.value = upData.length ? upData : downData
defaultExpandedKeys.value = [treeData.value[0].id]
nextTick(() => {
currentNode.value = getLastLevelData(treeData.value)[0].id
currentNodeName.value = getLastLevelData(treeData.value)[0].label
emitChangeBook()
})
}
const emitChangeBook = () => {
let curNode = {
id: currentNode.value,
label: currentNodeName.value
}
let parentNode = findParentByChildId(treeData.value, currentNode.value)
curNode.parentNode = toRaw(parentNode)
const data = {
textBook: {
curBookId: curBookId.value,
curBookName: curBookName.value
},
node: curNode
}
emit('changeBook', data)
}
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 transData = (data) => {
let ary = []
@ -133,8 +223,16 @@ const transData = (data) => {
//
const getSubject = async () => {
if (localStorage.getItem('subjectList')) {
subjectList.value = JSON.parse(localStorage.getItem('subjectList'))
}
else {
const { rows } = await listEvaluation({ itemkey: "version", pageSize: 500 })
subjectList.value = rows.filter(item => item.edustage == edustage && item.edusubject == edusubject && isHaveUnit(item.id))
localStorage.setItem('subjectList', JSON.stringify(subjectList.value))
}
//
curBookName.value = subjectList.value[0].itemtitle
curBookId.value = subjectList.value[0].id
}
@ -146,12 +244,32 @@ const isHaveUnit = (id) => {
})
}
const renderContent = (h, { node, data }) => {
console.log(node, data)
}
const handleNodeClick = (data) => {
emit('nodeClick', data)
const handleNodeClick = (data, node) => {
/**
* data : 当前节点数据
* node : 当前节点对象 包含当前节点所有数据 parent属性 指向父节点Node对象
*/
const currentNode = data;
const parentNode = node.parent.data;
if (Array.isArray(parentNode)) {
currentNode.parentNode = null
}
else {
currentNode.parentNode = parentNode
}
let curData = {
textBook: {
curBookId: curBookId.value,
curBookName: curBookName.value
},
node: toRaw(currentNode)
}
emit('nodeClick', curData)
}
onMounted(() => {
@ -161,7 +279,6 @@ onMounted(() => {
</script>
<style lang="scss" scoped>
.book-wrap {
width: 300px;
height: 100%;
@ -170,10 +287,17 @@ onMounted(() => {
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;
@ -184,7 +308,7 @@ onMounted(() => {
}
.book-list {
padding-left: 10px;
padding: 45px 10px 0 10px;
flex: 1;
}
}
@ -240,12 +364,12 @@ onMounted(() => {
:deep(.el-tree-node) {
.el-tree-node__content {
height: 40px;
border-radius: 10px;
&:hover {
background-color: #eaf3ff;
}
}
}
.tree-label {
@ -254,9 +378,8 @@ onMounted(() => {
text-overflow: ellipsis;
}
:deep(.el-tree--highlight-current .el-tree-node.is-current>.el-tree-node__content){
background-color: #d9e8fe !important;
:deep(.el-tree--highlight-current .el-tree-node.is-current>.el-tree-node__content) {
background-color: #eaf3ff !important;
color: #409EFF
}
</style>

View File

@ -0,0 +1,47 @@
<template>
<svg class="icon file-icon" aria-hidden="true" :style="{'font-size': size + 'px'}">
<use :xlink:href="getFileTypeIcon()"></use>
</svg>
</template>
<script setup>
import { defineProps } from 'vue'
const props = defineProps({
fileName: {
type: String,
default: ''
},
size: {
type: Number,
default: 30
}
})
const getFileTypeIcon = () => {
const name = props.fileName.substr(props.fileName.lastIndexOf('.') + 1);
const iconObj = {
pdf: 'icon-pdf',
ppt: 'icon-ppt',
pptx: 'icon-pptx',
doc: 'icon-word',
docx: 'icon-word',
mp4: 'icon-video',
mov: 'icon-mov',
avi: 'icon-avi',
jpeg: 'icon-jpeg',
jpg: 'icon-jpg',
png: 'icon-png',
gif: 'icon-gif',
txt: 'icon-txt',
rar: 'icon-rar',
}
return '#' + iconObj[name]
}
</script>
<style lang="scss" scoped>
</style>

View File

@ -0,0 +1,384 @@
<template>
<el-dialog v-model="dialogVisible" append-to-body :show-close="false" width="630" :before-close="beforeClose"
style="border-radius: 5px;padding-top: 0">
<div class="dialog-title flex">
<span>{{ title }}</span>
<i class="iconfont icon-close" @click="closeDialog"></i>
</div>
<div class="dialog-content">
<div class="book-name flex" @click="bookVisible = true">
<span>{{ curBookName }}</span>
<i class="iconfont icon-yidongdaozu"></i>
</div>
<el-scrollbar height="400px">
<div class="book-data">
<el-tree ref="refTree" :data="treeData" :props="defaultProps" node-key="id"
:default-expanded-keys="defaultExpandedKeys" :current-node-key="currentNodeId" 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>
<template #footer>
<div class="dialog-footer">
<el-button @click="closeDialog">取消</el-button>
<el-button type="primary" @click="onSubmit">
确定
</el-button>
</div>
</template>
</el-dialog>
<!--选择教材弹窗-->
<el-dialog v-model="bookVisible" 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="bookVisible = false"></i>
</div>
</template>
<div class="textbook-container">
<el-scrollbar height="450px">
<div class="textbook-item flex" v-for="item in subjectList" :class="curBookId == item.id ? 'active-item' : ''"
:key="item.id" @click="changeBook(item)">
<img :src="item.avartar" class="textbook-img" alt="">
<span class="book-name">{{ item.itemtitle }}</span>
</div>
</el-scrollbar>
</div>
</el-dialog>
</template>
<script setup>
import { ref, reactive, toRaw, onMounted, nextTick, watch, defineProps, defineEmits } from 'vue'
const props = defineProps({
modelValue: {
type: Boolean,
default: false
},
title: {
type: String,
default: '移动至'
}
})
const dialogVisible = ref(false)
const bookVisible = ref(false)
//
const subjectList = ref([])
const evaluationList = ref([])
const treeData = ref([])
const defaultProps = {
children: 'children',
label: 'label',
class: 'textbook-tree'
}
//ID
const curBookId = ref(-1)
//
const curBookName = ref('')
//
const volumeOne = ref([])
//
const volumeTwo = ref([])
//
const currentNode = reactive({
data: {}
})
// ID
const currentNodeId = ref(0)
//
const currentNodeName = ref('')
//
const defaultExpandedKeys = ref([])
// tree
const refTree = ref(null)
// emit
const emit = defineEmits(['update:modelValue', 'onSubmit'])
watch(() => props.modelValue, (newVal) => {
dialogVisible.value = newVal
})
const getSubjectContent = () => {
evaluationList.value = JSON.parse(localStorage.getItem('evaluationList'))
let data = evaluationList.value
//
getSubject()
//
volumeOne.value = data.filter(item => item.level == 1 && item.semester == '上册')
//
volumeTwo.value = data.filter(item => item.level == 1 && item.semester == '下册')
getTreeData()
}
const getSubject = () => {
subjectList.value = JSON.parse(localStorage.getItem('subjectList'))
//
curBookName.value = subjectList.value[0].itemtitle
curBookId.value = subjectList.value[0].id
}
const getTreeData = () => {
//
let upData = transData(volumeOne.value)
let downData = transData(volumeTwo.value)
treeData.value = upData.length ? upData : downData
defaultExpandedKeys.value = [treeData.value[0].id]
nextTick(() => {
currentNodeId.value = getLastLevelData(treeData.value)[0].id
currentNodeName.value = getLastLevelData(treeData.value)[0].label
emitChangeBook()
})
}
const emitChangeBook = () => {
let curNode = {
id: currentNodeId.value,
label: currentNodeName.value
}
let parentNode = findParentByChildId(treeData.value, currentNodeId.value)
curNode.parentNode = toRaw(parentNode)
const data = {
textBook: {
curBookId: curBookId.value,
curBookName: curBookName.value
},
node: curNode
}
currentNode.data = data
}
// 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, node) => {
/**
* data : 当前节点数据
* node : 当前节点对象 包含当前节点所有数据 parent属性 指向父节点Node对象
*/
const nodeData = data;
const parentNode = node.parent.data;
if (Array.isArray(parentNode)) {
nodeData.parentNode = null
}
else {
nodeData.parentNode = parentNode
}
let curData = {
textBook: {
curBookId: curBookId.value,
curBookName: curBookName.value
},
node: toRaw(nodeData)
}
currentNode.data = curData
// emit('nodeClick', curData)
}
const transData = (data) => {
let ary = []
data.forEach(item => {
let obj = {}
if (item.rootid == curBookId.value) {
obj.label = item.itemtitle
obj.id = item.id
let ary2 = []
evaluationList.value.forEach(el => {
let obj2 = {}
if (item.id == el.parentid) {
obj2 = {
label: el.itemtitle,
id: el.id
}
ary2.push(obj2)
}
obj.children = ary2
})
ary.push(obj)
}
})
return ary
}
//
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;
}
//
const changeBook = ({ id, itemtitle }) => {
curBookId.value = id
curBookName.value = itemtitle
getTreeData()
setTimeout(() => {
bookVisible.value = false
}, 0);
}
const onSubmit = () => {
emit('onSubmit', toRaw(currentNode.data))
closeDialog()
}
const closeDialog = () => {
emit('update:modelValue', false)
}
//
const beforeClose = (done) => {
emit('update:modelValue', false)
done()
}
onMounted(() => {
getSubjectContent()
})
</script>
<style lang="scss" scoped>
.dialog-title {
justify-content: space-between;
font-size: 16px;
font-weight: 600;
color: #000;
.icon-close{
cursor: pointer;
}
}
.dialog-content {
padding: 10px 20px;
.book-name {
justify-content: space-between;
font-size: 14px;
font-weight: 600;
color: #0f0f0f;
margin: 15px 0;
cursor: pointer;
.icon-yidongdaozu {
color: #3a3a3a;
}
}
}
.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;
}
}
:deep(.el-tree-node) {
.el-tree-node__content {
height: 40px;
border-radius: 10px;
&:hover {
background-color: #eaf3ff;
}
}
}
:deep(.el-tree--highlight-current .el-tree-node.is-current>.el-tree-node__content) {
background-color: #eaf3ff !important;
color: #409EFF;
font-weight: bold
}
</style>

View File

@ -1,10 +1,11 @@
<template>
<el-dialog v-model="dialogValue" width="630" :before-close="beforeClose">
<el-dialog v-model="dialogValue" width="630" :before-close="beforeClose" style="border-radius: 5px;">
<div class="file-dialog">
<el-form>
<el-form-item label="文件">
<div class="create-item file-item flex">
<el-upload action="" multiple :before-upload="hanleFileBefore" :auto-upload="true">
<el-upload :file-list="fileList" :show-file-list="false" :auto-upload="false" multiple
:on-change="hanleFileChange">
<el-button slot="trigger">选择文件</el-button>
</el-upload>
<span class="upload-desc">说明一次最多上传5个文件单个文件大小不能大于100M</span>
@ -15,14 +16,13 @@
<div class="file-list-item flex" v-for="(item, index) in fileList" :key="item.uid">
<div class="file-name">
<span class="name">标题</span>
<svg class="icon icon-ppt" aria-hidden="true">
<use :xlink:href="getFileTypeIcon(item.name)"></use>
</svg>
<span class="text">{{ item.name }}</span>
<FileImage :fileName="item.name" size="50"/>
<el-input class="file-input" v-model="item.fileData.name" placeholder="请输入文件名" />
<span>.{{ getFileSuffix(item.name) }}</span>
</div>
<div class="flex-type flex">
<span class="name">类别</span>
<el-select v-model="item.fileType" placeholder="Select" style="width: 100px">
<el-select v-model="item.fileData.fileFlag" placeholder="Select" style="width: 100px">
<el-option v-for="item in resourceType" :key="item.alue" :label="item.label" :value="item.value" />
</el-select>
@ -46,7 +46,11 @@
<script setup>
import { ref, defineProps, defineEmits, watch } from 'vue'
import FileImage from '@/components/file-image/index.vue'
import { ElMessage } from 'element-plus'
import { resourceType } from '@/utils/resourceDict'
import { getFileSuffix, getFileName } from '@/utils/ruoyi'
const props = defineProps({
modelValue: {
@ -60,30 +64,14 @@ const emit = defineEmits(['update:modelValue', 'submitFile'])
//
const fileList = ref([])
const fileType = ref(1)
//
const resourceType = ref([
{
label: '课件',
value: 1
},
{
label: '教案',
value: 2
},
{
label: '素材',
value: 3
}
])
watch(() => props.modelValue, (newVal) => {
dialogValue.value = newVal
})
const hanleFileBefore = (rawFile) => {
//
const hanleFileChange = (file) => {
console.log(file)
//
const audioTypes = ['audio/mpeg', 'audio/wav', 'audio/ogg', 'audio/aac']
//
const videoTypes = ['video/mp4', 'video/webm', 'video/ogg']
@ -93,36 +81,33 @@ const hanleFileBefore = (rawFile) => {
const pptTypes = ['application/vnd.ms-powerpoint', 'application/vnd.openxmlformats-officedocument.presentationml.presentation']
// pdf
const pdfTypes = ['application/pdf']
// zip
const zipTypes = ['application/x-zip-compressed','application/x-compressed']
//
const imgTypes = ['image/jpeg','image/gif', 'image/png']
// text
const textTypes = ['text/plain']
const fileType = rawFile.type
if (!(audioTypes.includes(fileType) || videoTypes.includes(fileType) || wordTypes.includes(fileType) || pptTypes.includes(fileType) || pdfTypes.includes(fileType))) {
ElMessage.error('文件格式错误! 请上传音频、视频、word、ppt、pdf文件!')
const fileType = file.raw.type
if (!(audioTypes.includes(fileType) || videoTypes.includes(fileType) || wordTypes.includes(fileType) || pptTypes.includes(fileType) || pdfTypes.includes(fileType) || zipTypes.includes(fileType) || imgTypes.includes(fileType) || textTypes.includes(fileType))) {
ElMessage.error('文件格式错误! 请上传图片、音频、视频、word、ppt、pdf、txt、zip文件!')
return false
}
//
const fileSize = rawFile.size / 1024 / 1024 > 100
const fileSize = file.raw.size / 1024 / 1024 > 100
if (fileSize) {
ElMessage.error('文件大小错误! 请上传小于100M的文件!')
return false
}
console.log(rawFile)
rawFile.fileType = 1
fileList.value.push(rawFile)
if (file.status === 'ready') {
return false
}
const getFileTypeIcon = (fileName)=>{
const name = fileName.substr(fileName.lastIndexOf('.') + 1);
console.log(name)
const iconObj = {
pdf: 'icon-pdf',
ppt: 'icon-ppt',
doc: 'icon-word',
docx: 'icon-word',
mp4: 'icon-video'
// fileData
file.fileData = {
fileFlag: '课件',
name: getFileName(file.name),
}
fileList.value.push(file)
}
return '#' + iconObj[name]
}
//
@ -143,11 +128,16 @@ const closeDialog = () => {
}
//
const submitFile = () => {
emit('submitFile', fileList.value)
//
fileList.value.forEach((item) => {
let suffix = getFileSuffix(item.name)
item.fileData.fileShowName = item.fileData.name + '.' + suffix
delete item.fileData.name
})
emit('submitFile',fileList.value)
closeDialog()
}
</script>
<style lang="scss" scoped>
@ -172,15 +162,18 @@ const submitFile = () => {
width: 100%;
justify-content: space-between;
margin-bottom: 15px;
.file-name {
width: 50%;
width: 55%;
margin-right: 20px;
display: flex;
align-items: center
}
.icon{
.icon {
font-size: 30px;
}
.flex-type {
align-items: center;
margin-right: 20px;
@ -192,6 +185,7 @@ const submitFile = () => {
.name {
position: relative;
flex-shrink: 0;
&::after {
content: '*';
position: absolute;
@ -210,4 +204,24 @@ const submitFile = () => {
}
}
}
.file-input {
border-bottom: solid #dfdfdf 1px;
&:hover {
border-color: #409EFF;
}
&:focus{
border-color: #409EFF;
}
}
:deep(.el-input__wrapper){
box-shadow: none
}
:deep(.el-input__wrapper.is-focus){
box-shadow: none
}
:deep(.el-input__wrapper:hover){
box-shadow: none
}
</style>

View File

@ -1,5 +1,5 @@
<template>
<el-popover placement="left-end" width="300px" trigger="click">
<el-popover placement="left-end" width="300px" title="文件上传" trigger="click">
<template #default>
<el-upload
ref="talk_uploader_core"
@ -22,7 +22,7 @@
<div class="talk-uploader-body">
<div v-for="(item, index) in uploaderStore.uploadList" :key="index">
<div class="prepare-body-main-item">
<div class="prepare-uploader-progress" :style="{'width': item.percentage+'%'}"></div>
<div class="prepare-uploader-progress" :style="{ width: item.percentage + '%' }"></div>
<div class="prepare-body-main-item-icon">
<svg
class="icon"
@ -35,11 +35,11 @@
</svg>
</div>
<div class="prepare-body-main-item-info">
<div class="prepare-item-info-title">平面向量基本定理及坐标表示</div>
<div class="prepare-item-info-title">{{ item.raw.name }}</div>
<div class="prepare-item-info-message">
<div>1.6MB</div>
&nbsp;&nbsp;|&nbsp;&nbsp;
<div>古诗词诵读 > 静女</div>
<div>{{formatFileSize(item.raw.size)}}</div>
<!-- &nbsp;&nbsp;|&nbsp;&nbsp;-->
<!-- <div>古诗词诵读 > 静女</div>-->
</div>
</div>
<div class="prepare-body-main-item-tool" @click="removeUploadFile(item.uid)">
@ -99,6 +99,23 @@ export default {
}, 1000)
},
methods: {
formatFileSize(fileSize) {
if (fileSize < 1024) {
return fileSize + 'B'
} else if (fileSize < 1024 * 1024) {
let temp = fileSize / 1024
temp = temp.toFixed(2)
return temp + 'KB'
} else if (fileSize < 1024 * 1024 * 1024) {
let temp = fileSize / (1024 * 1024)
temp = temp.toFixed(2)
return temp + 'MB'
} else {
let temp = fileSize / (1024 * 1024 * 1024)
temp = temp.toFixed(2)
return temp + 'GB'
}
},
onSuccess(res, file, files) {
this.removeUploadFile(file.uid)
file.callback(res)
@ -119,7 +136,7 @@ export default {
this.uploadDatas = this.uploadNow.fileData
this.getFileMD5(this.uploadNow.raw).then((md5) => {
this.uploadDatas.md5 = md5
this.$refs.talk_uploader_core.handleStart(this.uploadNow.raw)
// this.$refs.talk_uploader_core.handleStart(this.uploadNow.raw)
this.$refs.talk_uploader_core.submit()
})
}
@ -158,10 +175,15 @@ export default {
.talk-uploader-body {
width: 100%;
height: 300px;
overflow: auto;
}
.prepare-body-main-item {
position: relative;
.prepare-uploader-progress{
display: flex;
align-items: center;
border-bottom: 1px solid rgba(131, 131, 127, 0.17);
padding: 10px 0;
.prepare-uploader-progress {
height: 100%;
position: absolute;
background-color: #83bb67;
@ -171,10 +193,7 @@ export default {
&:hover {
background-color: rgba(144, 147, 153, 0.2);
}
display: flex;
align-items: center;
border-bottom: 1px solid rgba(131, 131, 127, 0.17);
padding: 10px 0;
.prepare-body-main-item-icon {
width: 80px;
}

View File

@ -84,6 +84,7 @@ const useUserStore = defineStore(
this.token = ''
this.roles = []
this.permissions = []
localStorage.clear()
removeToken()
resolve()
}).catch(error => {

View File

@ -0,0 +1,78 @@
// 判断是不是昨天
const isYestday = (date) => {
var yesterday = new Date(new Date() - 1000 * 60 * 60 * 24)
return (
yesterday.getYear() === date.getYear() &&
yesterday.getMonth() === date.getMonth() &&
yesterday.getDate() === date.getDate()
)
}
// 判断是不是今年
const isYear = (date) => {
return date.getYear() === new Date().getYear()
}
//将时间转为 yy/mm/dd hh:mm:ss
const formatDateTime = (date) => {
if (!date) {
return ''
}
var dateObject = new Date(date)
var y = dateObject.getFullYear()
var m = dateObject.getMonth() + 1
m = m < 10 ? '0' + m : m
var d = dateObject.getDate()
d = d < 10 ? '0' + d : d
var h = dateObject.getHours()
h = h < 10 ? '0' + h : h
var minute = dateObject.getMinutes()
minute = minute < 10 ? '0' + minute : minute
var second = dateObject.getSeconds()
second = second < 10 ? '0' + second : second
return y + '/' + m + '/' + d + ' ' + h + ':' + minute + ':' + second
}
/**
* 将时间戳转换为文本描述
*
* 此函数旨在将时间戳转换为更易读的文本格式以便用户可以更直观地理解时间信息
* 通过传入的时间函数将生成对应的时间文本"刚刚""5分钟前""昨天"
* 如果设置了simple参数为true则会返回更简单的23/06/25只显示年月日如果是今年只返回月和日
*
* @param {string} timeStamp - 需要转换的时间
* @param {boolean} simple - 是否使用简单的格式默认为false23/06/2506/25
* @returns {string} - 转换后的时间文本
*/
export const toTimeText = (timeStamp, simple) => {
if (!timeStamp) {
return ''
}
var dateTime = new Date(timeStamp)
var currentTime = Date.parse(new Date()) //当前时间
var timeDiff = currentTime - dateTime //与当前时间误差
var timeText = ''
if (timeDiff <= 60000) {
//一分钟内
timeText = '刚刚'
} else if (timeDiff > 60000 && timeDiff < 3600000) {
//1小时内
timeText = Math.floor(timeDiff / 60000) + '分钟前'
} else if (timeDiff >= 3600000 && timeDiff < 86400000 && !isYestday(dateTime)) {
//今日
timeText = formatDateTime(dateTime).substr(11, 5)
} else if (isYestday(dateTime)) {
//昨天
timeText = '昨天' + formatDateTime(dateTime).substr(11, 5)
} else if (isYear(dateTime)) {
//今年
timeText = formatDateTime(dateTime).substr(5, simple ? 5 : 14)
} else {
//不属于今年
timeText = formatDateTime(dateTime)
if (simple) {
timeText = timeText.substr(2, 8)
}
}
return timeText
}

View File

@ -0,0 +1,65 @@
export const tabs = [
{
label: '平台资源',
value: '平台'
},
{
label: '校本资源',
value: '校本'
}
]
// 资源格式
export const resourceFormat = [
{
label: 'word',
value: 'word'
},
{
label: 'ppt',
value: 'ppt'
},
{
label: 'mp3',
value: 'mp3'
},
{
label: 'mp4',
value: 'mp4'
},
{
label: 'JPG',
value: 'jpg'
},
{
label: 'PNG',
value: 'png'
},
{
label: 'RAR',
value: 'rar'
},
{
label: 'TXT',
value: 'txt'
}
]
// 资源类型
export const resourceType = [
{
label: '素材',
value: '素材'
},
{
label: '课件',
value: '课件'
},
{
label: '教案',
value: '教案'
}
]

View File

@ -245,3 +245,15 @@ export function getNormalPath(p) {
export function blobValidate(data) {
return data.type !== 'application/json'
}
// 获取文件后缀
export const getFileSuffix = (filename) => {
return filename.substring(filename.lastIndexOf('.') + 1);
}
// 获取文件名(不带后缀)
export const getFileName = (filename) => {
// 使用正则表达式匹配文件名部分(不包括后缀)
if(!filename) return
return filename.replace(/\.[^/.]+$/, "");
}

View File

@ -1,6 +1,6 @@
<template>
<div class="page-resource flex">
<ChooseTextbook @node-click="nodeClick" />
<div class="page-resource flex" v-loading="isLoading">
<ChooseTextbook @changeBook="changeBook" @node-click="nodeClick" />
<div class="page-right">
<div class="prepare-body-header">
<div>
@ -8,62 +8,84 @@
<el-popover placement="top-start" :width="250" trigger="hover">
<template #default>
<div>
<el-button type="success" size="small" :icon="Check" circle /> 2024-07-11 16:15
<el-button type="success" size="small" :icon="Check" circle />
2024-07-11 16:15
同步成功
</div>
</template>
<template #reference>
<el-button size="small" text
><el-icon><Refresh /></el-icon></el-button
>
<el-icon>
<Refresh />
</el-icon>
云同步
</el-button
>
</template>
</el-popover>
</div>
<div style="display: flex">
<el-upload
ref="choosefile"
v-model:file-list="fileList"
name="file"
:show-file-list="false"
:auto-upload="false"
:multiple="true"
:on-change="chooseFile"
class="editor-img-uploader"
>
<el-button>上传资料</el-button>
</el-upload>
<el-button type="primary" @click="changeFile">新建课件</el-button>
<el-button type="primary" @click="clearFile">crear</el-button>
<el-button @click="isDialogOpen=true">上传资料</el-button>
<el-button type="primary" style="margin-left: 10px">新建课件</el-button>
</div>
</div>
<div class="prepare-body-main">
<div v-for="index in 10" :key="index" class="prepare-body-main-item">
<div v-for="(item,index) in currentFileList" :key="index" class="prepare-body-main-item">
<div class="prepare-body-main-item-icon">
<svg class="icon" aria-hidden="true" font-size="50px" color="red" style="margin: auto">
<use xlink:href="#icon-ppt"></use>
</svg>
</div>
<div class="prepare-body-main-item-info">
<div class="prepare-item-info-title">平面向量基本定理及坐标表示</div>
<div class="prepare-item-info-title">{{ item.fileShowName }}</div>
<div class="prepare-item-info-message">
<div>
<div style="width: 80px">
<el-icon
style="background-color: green; border-radius: 20px; color: white; top: 2px"
><Check /></el-icon
>已同步
>
<Check />
</el-icon
>
已同步
</div>
|
<div style="width: 80px">{{ formatFileSize(item.fileSize) }}</div>
|
<div style="width: 100px">{{ toTimeText(item.uploadTime, true) }}</div>
|
<div style="white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;" :title="item.levelFirstName + (item.levelSecondName ? ' > ' + item.levelSecondName : '') + (item.levelThirdNmae ? ' > ' + item.levelThirdNmae : '')">
{{ item.levelFirstName + (item.levelSecondName ? ' > ' + item.levelSecondName : '') + (item.levelThirdNmae ? ' > ' + item.levelThirdNmae : '') }}
</div>
&nbsp;&nbsp;|&nbsp;&nbsp;
<div>1.6MB</div>
&nbsp;&nbsp;|&nbsp;&nbsp;
<div>2024-07-10</div>
&nbsp;&nbsp;|&nbsp;&nbsp;
<div>古诗词诵读 > 静女</div>
</div>
</div>
<div class="prepare-body-main-item-tool">
<el-popover placement="left-start" popper-class="prepare-popper" trigger="click">
<el-popover placement="left-start" :hide-after="100" :ref="'popover_'+index" popper-class="prepare-popper" trigger="click">
<template #default>
<div style="width: 100%; height: 100px; background-color: #003b94"></div>
<div style="width: 100%;">
<div class="item-popover">
<div class="item-popover-item">
<el-button text @click="editTalk(item,index)">
<i class="iconfont icon-bianji"></i>
<span>重命名</span>
</el-button>
</div>
<div class="item-popover-item">
<el-button text @click="deleteTalk(item);closePopver(index)">
<i class="iconfont icon-shanchu"></i>
<span>删除</span>
</el-button>
</div>
<div class="item-popover-item">
<el-button text @click="downloadFile(item)">
<i class="iconfont icon-xiazai"></i>
<span>下载</span>
</el-button>
</div>
</div>
</div>
</template>
<template #reference>
<span class="iconfont icon-shenglvehao" style="cursor: pointer"></span>
@ -73,35 +95,47 @@
</div>
</div>
</div>
<uploadDialog v-model="isDialogOpen" @submitFile="submitFile" />
</div>
</template>
<script setup>
import { Check } from '@element-plus/icons-vue'
import ChooseTextbook from '@/components/choose-textbook/index.vue'
import { deleteSmarttalk } from '@/api/file'
</script>
<script>
import FileUpload from '@/components/file-upload/index.vue'
import ChooseTextbook from '@/components/choose-textbook/index.vue'
import ResoureList from '@/views/resource/container/resoure-list.vue'
import ResoureSearch from '@/views/resource/container/resoure-search.vue'
import uploadDialog from '@/components/upload-dialog/index.vue'
import { Refresh } from '@element-plus/icons-vue'
import uploaderState from '@/store/modules/uploader'
import { deleteSmarttalk, getSmarttalkPage, updateSmarttalk } from '@/api/file'
import { toTimeText } from '@/utils/date'
import { ElMessage, ElMessageBox } from 'element-plus'
// import { getSmarttalkPage } from '@/api/file'
const { ipcRenderer } = window.electron || {}
export default {
name: 'Prepare',
components: { ResoureSearch, ResoureList, ChooseTextbook, FileUpload, Refresh },
components: { ResoureSearch, ResoureList, ChooseTextbook, FileUpload, Refresh, uploadDialog },
data() {
return {
fileList:[],
fileUrl:
'https://wzyzoss.eos-chongqing-3.cmecloud.cn/2024/7/10/117cdf208c6b4e58bf2b73369eaf3cb5.pptx',
filePath: 'C:/Users/zhuhao/Desktop/工作文档/0901高一【数学(人教A版)】集合的概念-PPT课件.pptx',
isLoading: false,
isDialogOpen: false,
showToolVisible: false,
fileList: [],
currentNode: {},
currentFileList: [],
// fileUrl:
// 'https://wzyzoss.eos-chongqing-3.cmecloud.cn/2024/7/10/117cdf208c6b4e58bf2b73369eaf3cb5.pptx',
// filePath: 'C:/Users/zhuhao/Desktop//0901(A)-PPT.pptx',
uploadData: {
textbookId: '123',
levelFirstId: '123',
levelSecondId: '123',
fileSource: '平台',
fileFlag: '课件'
textbookId: null,
levelFirstId: 39103,
levelSecondId: null,
fileSource: '个人',
fileRoot: '备课'
}
}
},
@ -112,6 +146,8 @@ export default {
})
},
mounted() {
//
// const destination = '0901(A)-PPT.pptx'
// ipcRenderer.send('open-path-app',this.filePath)
// const source = 'D:\\edufile\\0901(A)-PPT.pptx'
@ -122,6 +158,67 @@ export default {
// })
},
methods: {
formatFileSize(fileSize) {
if (fileSize < 1024) {
return fileSize + 'B'
} else if (fileSize < (1024 * 1024)) {
var temp = fileSize / 1024
temp = temp.toFixed(2)
return temp + 'KB'
} else if (fileSize < (1024 * 1024 * 1024)) {
var temp = fileSize / (1024 * 1024)
temp = temp.toFixed(2)
return temp + 'MB'
} else {
var temp = fileSize / (1024 * 1024 * 1024)
temp = temp.toFixed(2)
return temp + 'GB'
}
},
editTalk(item) {
ElMessageBox.prompt('请输入新的名称', '重命名', {
confirmButtonText: '确认',
cancelButtonText: '取消',
inputValue: item.fileShowName.substring(0,item.fileShowName.lastIndexOf('.')),
})
.then(({ value }) => {
item.fileShowName = value;
updateSmarttalk({id:item.id,fileShowName:item.fileShowName}).then(res=>{
if (res.data===true) {
ElMessage({
type: 'success',
message: `修改成功!`,
})
}
})
})
.catch(() => {
})
},
downloadFile(item) {
ipcRenderer.send('save-as',item.fileFullPath,item.fileShowName)
},
deleteTalk(item) {
deleteSmarttalk(item.id).then(res=>{
let index = this.currentFileList.indexOf(item);
this.currentFileList.splice(index,1)
})
},
closePopver(index){
this.$refs['popover_'+index][0].hide();
},
submitFile(files) {
let _this = this;
files.filter(file => {
file.fileData = Object.assign(this.uploadData, file.fileData)
file.callback = function(res) {
_this.currentFileList.unshift(res.resData);
}
})
console.log(files)
uploaderState().pushFile(files)
this.fileList = []
},
callback({ error, filePath }) {
if (error) {
console.error('An error occurred:', error)
@ -129,27 +226,36 @@ export default {
}
console.log('File copied to:', filePath)
},
changeBook(data) {
this.nodeClick(data)
},
nodeClick(data) {
console.log(data)
let cata = this.parseCataByNode(data.node)
this.currentNode = data.node
this.uploadData.levelFirstId = cata.length > 0 ? cata[0] : null
this.uploadData.levelSecondId = cata.length > 1 ? cata[1] : null
this.uploadData.levelThirdId = cata.length > 2 ? cata[2] : null
this.uploadData.textbookId = data.textBook.curBookId
this.isLoading = true
getSmarttalkPage({
...this.uploadData,
orderByColumn: 'uploadTime',
isAsc: 'desc'
}).then(res => {
this.currentFileList = [...res.rows]
this.isLoading = false
}).catch(res => {
this.isLoading = false
})
},
chooseFile(file) {
file.fileData = {
textbookId: '123',
levelFirstId: '123',
levelSecondId: '123',
fileSource: '平台',
fileFlag: '课件'
parseCataByNode(node) {
if (node.parentNode) {
let arr = this.parseCataByNode(node.parentNode)
arr.push(node.id)
return arr
} else {
return [node.id]
}
file.callback = function(res){
console.log(res)
}
},
changeFile() {
uploaderState().pushFile(this.fileList)
this.fileList = [];
},
clearFile() {
this.fileList = [];
}
}
}
@ -160,6 +266,20 @@ export default {
min-width: 80px !important;
padding: 5px !important;
}
.item-popover-item {
padding: 5px 0;
display: flex;
align-items: center;
justify-content: center;
font-size: 12px;
cursor: pointer;
.iconfont {
margin-right: 5px;
color: #a2a2a2;
}
}
</style>
<style scoped lang="scss">
.page-resource {
@ -167,6 +287,7 @@ export default {
height: 100%;
.page-right {
min-width: 0;
flex: 1;
margin-left: 20px;
height: 100%;
@ -175,6 +296,7 @@ export default {
box-shadow: 0px 0px 20px 0px rgba(99, 99, 99, 0.06);
display: flex;
flex-direction: column;
.prepare-body-header {
height: 60px;
width: 100%;
@ -184,44 +306,54 @@ export default {
justify-content: space-between;
padding: 0 20px;
}
.prepare-body-main {
flex: 1;
width: 100%;
overflow: auto;
padding: 0 30px;
.prepare-body-main-item {
&:hover{
&:hover {
background-color: rgba(144, 147, 153, 0.2);
cursor: pointer;
}
display: flex;
align-items: center;
border-bottom: 1px solid rgba(131, 131, 127, 0.17);
padding: 10px 0;
.prepare-body-main-item-icon {
width: 80px;
}
.prepare-body-main-item-tool {
font-size: 18px !important;
font-weight: bold;
flex: 1;
text-align: right;
padding-right: 30px;
}
.prepare-body-main-item-info {
display: flex;
flex-direction: column;
min-width: 0;
flex: 1;
.prepare-item-info-title {
text-align: left;
font-size: 16px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.prepare-item-info-message {
font-size: 12px;
line-height: 23px;
color: #909399;
display: flex;
flex-wrap: wrap;
justify-content: space-between;
}
}
}

View File

@ -1,80 +0,0 @@
<template>
<div class="create-resoure">
<el-page-header @back="goBack">
<template #content>
<span class="font-600 mr-3 header-name"> 上传备课资源 </span>
</template>
</el-page-header>
<div class="create-main">
<el-form>
<el-form-item label="目录">
<div class="create-item">第二章 地球上的大气 / 大气的组成和垂直分层</div>
</el-form-item>
<el-form-item label="文件">
<div class="create-item file-item flex">
<FileUpload/>
<!-- <el-button round size="small" color="#eeeeee" class="add-btn">
<i class="iconfont icon-jiahao"></i>
添加文件
</el-button> -->
<span class="upload-desc">说明一次最多上传5个文件单个文件大小不能大于100M</span>
</div>
</el-form-item>
</el-form>
</div>
</div>
</template>
<script setup>
import useResoureStore from '../store'
import FileUpload from '@/components/file-upload/index.vue'
const sourceStore = useResoureStore()
const goBack = () => {
sourceStore.isCreate = false
}
</script>
<style lang="scss" scoped>
.create-resoure {
.el-page-header {
padding: 10px 15px;
border-bottom: solid #ececec 1px;
.header-name {
font-size: 16px;
}
}
.create-main{
font-size: 12px;
padding: 40px;
.el-form-item{
margin-bottom: 30px;
}
.create-item{
font-size: 13px;
width: 100%;
text-align: left;
border-bottom: solid #e0e0e0 1px;
.add-btn{
padding: 13px 11px;
}
.icon-jiahao{
font-size: 14px;
margin-right: 5px;
}
.upload-desc{
margin-left: 10px;
font-size: 12px;
color: #b7b7b7;
}
}
.file-item{
padding-bottom: 15px;
}
}
}
</style>

View File

@ -1,43 +1,46 @@
<template>
<div class="resource-list">
<div class="resource-list" v-loading="sourceStore.loading">
<el-scrollbar height="400px">
<el-empty description="暂无数据" v-if="!sourceStore.result.list.length" />
<ul>
<li class="list-item">
<li class="list-item" v-for="item in sourceStore.result.list" :key="item.id">
<div class="item-left flex">
<svg class="icon icon-ppt" aria-hidden="true">
<use xlink:href="#icon-ppt"></use>
</svg>
<FileImage :fileName="item.fileName" :size="50" />
<div class="flex item-left-content">
<div class="name flex">第1课课后巩固练习 2022-2023学年部编版语文七年级上册</div>
<div class="name flex">{{ item.fileShowName }}</div>
<div class="item-tags flex">
<el-tag type="info" class="mr-10">教案</el-tag>
<el-tag type="info" class="mr-10">PPT</el-tag>
<span class="gray-text mr-10">2024-07-13上传</span>
<span class="line mr-10"></span>
<span class="gray-text mr-10">下载3次</span>
<el-tag type="info" class="mr-10">{{ item.fileFlag }}</el-tag>
<el-tag type="info" class="mr-10">{{ getFileSuffix(item.fileName) }}</el-tag>
<span class="gray-text mr-10">{{ item.uploadTime }}上传</span>
<!-- <span class="line mr-10"></span>
<span class="gray-text mr-10">下载3次</span> -->
</div>
</div>
</div>
<div class="item-btns" @click.stop>
<el-popover placement="bottom-end" trigger="click" popper-class="custom-popover">
<el-popover placement="bottom-end" trigger="hover" popper-class="custom-popover"
:visible="item.showPopover">
<template #reference>
<el-button link type="primary"> <i class="iconfont icon-shenglvehao"></i></el-button>
</template>
<template #default>
<div class="item-popover">
<div class="item-popover-item">
<div class="item-popover-item" @click="editRow(item)">
<i class="iconfont icon-bianji"></i>
<span>编辑</span>
</div>
<div class="item-popover-item">
<div class="item-popover-item" @click="delRow(item)">
<i class="iconfont icon-shanchu"></i>
<span>删除</span>
</div>
<div class="item-popover-item">
<div class="item-popover-item" @click="downloadFile(item)">
<i class="iconfont icon-xiazai"></i>
<span>下载</span>
</div>
<div class="item-popover-item" @click="moveFile(item)">
<i class="iconfont icon-xiazai"></i>
<span>移动至</span>
</div>
</div>
</template>
</el-popover>
@ -48,24 +51,73 @@
</div>
</li>
</ul>
<div class="pagination-box">
<el-pagination v-model:current-page="currentPage" v-model:page-size="pageSize"
:page-sizes="[100, 200, 300, 400]" background layout="total, sizes, prev, pager, next, jumper" :total="400"
@size-change="handleSizeChange" @current-change="handleCurrentChange" />
</div>
</el-scrollbar>
<div class="pagination-box">
<el-pagination v-model:current-page="sourceStore.query.pageNum" v-model:page-size="sourceStore.query.pageSize"
:page-sizes="[10, 20, 30, 50]" background layout="total, sizes, prev, pager, next, jumper"
:total="sourceStore.result.total" @size-change="handleSizeChange" @current-change="handleCurrentChange" />
</div>
</div>
</template>
<script setup>
import { ref } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import FileImage from '@/components/file-image/index.vue'
import { deleteSmarttalk, updateSmarttalk } from '@/api/file'
import { getFileSuffix } from '@/utils/ruoyi'
import useResoureStore from '../store'
const currentPage = ref(1)
const pageSize = ref(100)
const { ipcRenderer } = window.electron || {}
const sourceStore = useResoureStore()
const handleSizeChange = () => { }
const handleCurrentChange = () => { }
//
const downloadFile = (item) => {
ipcRenderer.send('save-as', item.fileFullPath, item.fileShowName)
}
const editRow = (item) => {
console.log(item.fileShowName.substring(0, item.fileShowName.lastIndexOf('.')))
ElMessageBox.prompt('请输入新的名称', '重命名', {
confirmButtonText: '确认',
cancelButtonText: '取消',
inputValue: item.fileShowName.substring(0, item.fileShowName.lastIndexOf('.'))
})
.then(({ value }) => {
item.fileShowName = value + '.' + item.fileSuffix
updateSmarttalk({ id: item.id, fileShowName: item.fileShowName }).then((res) => {
if (res.data === true) {
ElMessage({
type: 'success',
message: `修改成功!`
})
sourceStore.handleQuery()
}
})
})
.catch(() => { })
}
//
const delRow = (item) => {
sourceStore.loading = true
try {
deleteSmarttalk(item.id).then(() => {
ElMessage.success('操作成功')
sourceStore.handleQuery()
})
} finally {
sourceStore.loading = false
}
}
//
const moveFile = (item) => {
moveDialogVisible.value = true
}
</script>
@ -82,6 +134,7 @@ const handleCurrentChange = () => { }
justify-content: center;
font-size: 12px;
cursor: pointer;
.iconfont {
margin-right: 5px;
color: #a2a2a2;
@ -97,6 +150,7 @@ const handleCurrentChange = () => { }
.resource-list {
.list-item {
flex: 1;
padding: 10px 20px;
border-bottom: solid #f1f1f1 1px;
display: flex;

View File

@ -3,7 +3,8 @@
<el-row justify="space-between">
<el-col :span="12" class="tab-btns flex">
<el-button text v-for="item in sourceStore.tabs" :key="item.id"
:type="sourceStore.curTab == item.id ? 'primary' : ''" @click="sourceStore.changeTab(item.id)">{{ item.text
:type="sourceStore.query.fileSource == item.value ? 'primary' : ''"
@click="sourceStore.changeTab(item.value)">{{ item.label
}}</el-button>
</el-col>
<el-col :span="12" class="search-box flex">
@ -12,46 +13,30 @@
</el-row>
<el-row class="resoure-btns">
<el-col :span="24" class="query-row flex">
<div class="flex"> <el-select v-model="sourceStore.curFormat" placeholder="Select" size="small"
<div class="flex row-left"> <el-select v-model="sourceStore.query.fileSuffix"
style="width: 100px">
<el-option v-for="item in sourceStore.formatList" :key="item.value" :label="item.label"
<el-option v-for="item in sourceStore.resourceFormatList" :key="item.value" :label="item.label"
:value="item.value" />
</el-select>
<div class="line"></div>
<el-button size="small" v-for="item in sourceStore.typeList" :key="item.id"
:type="sourceStore.curType == item.id ? 'primary' : ''" round @click="sourceStore.changeType(item.id)">{{
item.text }}</el-button>
<el-button v-for="item in sourceStore.resourceTypeList" :key="item.id"
:type="sourceStore.query.fileFlag == item.value ? 'primary' : ''" round
@click="sourceStore.changeType(item.value)">{{
item.label }}</el-button>
</div>
<div>
<el-button type="primary" round size="small" @click="openDialog">
<i class="iconfont icon-jiahao"></i>
新建资源</el-button>
<slot name="add" />
</div>
</el-col>
</el-row>
<uploadDialog v-model="isDialogOpen" @submitFile="submitFile"/>
</div>
</template>
<script setup>
import { ref, toRaw } from 'vue'
import useResoureStore from '../store'
import uploadDialog from '@/components/upload-dialog/index.vue'
import uploaderState from '@/store/modules/uploader'
const sourceStore = useResoureStore()
const isDialogOpen = ref(false)
const openDialog = ()=>{
isDialogOpen.value = true
}
const submitFile = (fileList)=>{
console.log(toRaw(fileList))
uploaderState().pushFile(toRaw(fileList))
}
</script>
<style lang="scss" scoped>
.resoure-search {
@ -74,6 +59,9 @@ const submitFile = (fileList)=>{
.query-row {
justify-content: space-between;
.row-left{
align-items: center;
}
}
.line {
@ -83,12 +71,12 @@ const submitFile = (fileList)=>{
margin: 0 10px;
}
.icon-jiahao {
font-size: 12px;
margin-right: 3px;
font-weight: bold;
}
}
}
.el-button.is-round{
padding: 3px 15px;
font-size: 13px;
}
</style>

View File

@ -1,33 +1,94 @@
<template>
<div class="page-resource flex">
<ChooseTextbook @nodeClick="nodeClick" />
<!--左侧 教材 目录-->
<ChooseTextbook @changeBook="changeBook" @nodeClick="nodeClick" />
<div class="page-right">
<template v-if="!sourceStore.isCreate">
<ResoureSearch />
<!-- 搜索 -->
<ResoureSearch #add>
<el-button type="primary" round @click="openDialog" class="create-btn">
<i class="iconfont icon-jiahao"></i>
新建资源</el-button>
</ResoureSearch>
<!-- 列表 -->
<ResoureList />
</template>
<template v-else>
<CreateResoure/>
</template>
</div>
</div>
<!-- 上传弹窗 -->
<uploadDialog v-model="isDialogOpen" @submitFile="submitFile" />
<!-- <MoveFile v-model="isDialogOpen" @onSubmit="onSubmit" /> -->
</template>
<script setup>
import { toRaw } from 'vue'
import { ref, toRaw } from 'vue'
import useResoureStore from './store'
import ChooseTextbook from '@/components/choose-textbook/index.vue'
import ResoureSearch from './container/resoure-search.vue'
import ResoureList from './container/resoure-list.vue'
import CreateResoure from './container/create-resoure.vue'
import uploadDialog from '@/components/upload-dialog/index.vue'
import MoveFile from '@/components/move-file/index.vue'
import uploaderState from '@/store/modules/uploader'
const sourceStore = useResoureStore()
const isDialogOpen = ref(false)
const { ipcRenderer } = window.electron || {}
// ipcRenderer.send('set-winsize',{x:1100,y: 700})
const openDialog = () => {
isDialogOpen.value = true
}
const onSubmit = (data)=>{
console.log(data)
}
//
const changeBook = (data) => {
getData(data)
}
//
const nodeClick = (data) => {
console.log(toRaw(data))
getData(data)
}
//
const getData = (data) => {
const { textBook, node } = data
let textBookId = textBook.curBookId
let levelFirstId = node.id
let levelSecondId = node.parentNode ? node.parentNode.id : ''
sourceStore.query = {
textBookId,
levelFirstId,
levelSecondId,
...sourceStore.query
}
sourceStore.handleQuery()
}
//
const submitFile = (data) => {
let fileList = toRaw(data)
const { textBookId, levelFirstId, levelSecondId, fileSource, fileRoot } = sourceStore.query
//
let fileData = { textBookId, levelFirstId, levelSecondId, fileSource, fileRoot }
fileList.forEach(item => {
fileData.fileShowName = item.fileData.fileShowName
fileData.fileFlag = item.fileData.fileFlag
item.fileData = fileData
item.callback = fileCallBack
})
// console.log(fileList)
uploaderState().pushFile(fileList)
}
const fileCallBack = (res) => {
console.log(res)
if (res.code == 200) {
sourceStore.handleQuery()
}
}
</script>
@ -38,6 +99,7 @@ const nodeClick = (data) => {
height: 100%;
.page-right {
min-width: 0;
flex: 1;
margin-left: 20px;
height: 100%;
@ -45,5 +107,14 @@ const nodeClick = (data) => {
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;
}
</style>

View File

@ -1,76 +1,82 @@
import { defineStore } from 'pinia'
import { getSmarttalkPage } from '@/api/file/index'
import { tabs, resourceType, resourceFormat } from '@/utils/resourceDict'
import useUserStore from '@/store/modules/user'
const tabs = [
const userStore = useUserStore()
const resourceTypeList = [
{
type: '',
text: '平台资源',
id: 1
label: '全部',
value: ''
},
{
type: '',
text: '校本资源',
id: 2
}
...resourceType
]
const typeList = [
{
type: '',
text: '全部',
id: 1
},
{
type: '',
text: '素材',
id: 2
},
{
type: '',
text: '课件',
id: 4
},
{
type: '',
text: '教案',
id: 6
},
]
// 资源格式
const formatList = [
const resourceFormatList = [
{
label: '资源格式',
value: -1
value: ''
},
{
label: 'word',
value: 1
},
{
label: 'ppt',
value: 2
}
...resourceFormat
]
// 校本资源为学校ID
tabs.forEach(item =>{
if( item.label == "校本资源"){
item.value = userStore.user.deptId
}
})
const structQuery = {
pageNum: 1,
pageSize: 10
}
export default defineStore('resource', {
state: () => ({
tabs,
typeList,
formatList,
curTab: 1,
curType: 1,
resourceTypeList,
resourceFormatList,
curFormat: -1,
searchKey: '',
// 新建资源
isCreate: false,
loading: false,
//查询条件
query: {
fileSource: '平台',
fileSuffix: '',
fileFlag: '',
fileRoot: '资源',
orderByColumn: 'uploadTime',
isAsc: 'desc',
...structQuery
},
result: {
list: [],
total: 0
}
}),
actions: {
handleQuery() {
try {
this.loading = true
getSmarttalkPage(this.query).then((res) => {
this.result.total = res.total
this.result.list = res.rows
})
} finally {
this.loading = false
}
},
changeTab(val) {
this.curTab = val
this.query.fileSource = val
this.handleQuery()
},
changeType(val) {
this.curType = val
this.query.fileFlag = val
this.handleQuery()
}
}
})