Compare commits

..

17 Commits
2.5.20 ... main

Author SHA1 Message Date
lyc 1bafc24025 Merge pull request 'lyc-dev' (#242) from lyc-dev into main
Reviewed-on: #242
2025-02-21 17:30:29 +08:00
lyc 2797313612 冲突 2025-02-21 17:29:40 +08:00
lyc e007c30235 修复一些问题 2025-02-21 17:27:39 +08:00
lyc 3675de66f5 处理冲突 2025-02-21 16:55:53 +08:00
朱浩 a679a8fc44 Merge remote-tracking branch 'origin/main' 2025-02-21 16:42:12 +08:00
朱浩 5d7dc6fc80 文件上传插件开发 2025-02-21 16:42:01 +08:00
lyc c812eaee5f 修复一些问题 2025-02-21 16:38:41 +08:00
yangws 084da1f68d Merge pull request 'fix:作业管理分值问题;' (#241) from yws_dev into main
Reviewed-on: #241
2025-02-21 16:01:01 +08:00
小杨 7c377e0eaa fix:作业管理分值问题; 2025-02-21 16:00:30 +08:00
lyc d954c88ec5 edit 2025-02-21 00:15:20 +08:00
lyc a708eb59b5 edit 2025-02-20 17:27:59 +08:00
lyc f02f6a8384 edit:模型模板 2025-02-19 17:28:59 +08:00
lyc 870ffb167a edit bookld 2025-02-19 16:18:36 +08:00
lyc e27f7e0018 edit 2025-02-18 17:32:10 +08:00
lyc 327e7d78ad Merge branch 'main' into lyc-dev 2025-02-17 09:30:08 +08:00
lyc e97ff8e147 修复一些问题 2025-02-16 23:13:30 +08:00
lyc ba1a4a3abe Merge branch 'lyc-dev' of http://27.128.240.72:3000/zhuhao/AIx_Smarttalk_WS into lyc-dev 2025-01-25 02:50:30 +08:00
18 changed files with 592 additions and 267 deletions

6
src/renderer/src/api/file/index.d.ts vendored Normal file
View File

@ -0,0 +1,6 @@
declare module '@/api/file' {
// 定义 uploadFile 函数的类型
// export function uploadFile(file: File): Promise<any>;
export function sessionToken(): Promise<any>;
export function uploadSingleFileToEos(data: SingleUploadData): Promise<any>;
}

View File

@ -110,3 +110,11 @@ export const sessionToken = () => {
method: 'get'
})
}
export const uploadSingleFileToEos = (params) => {
return request({
url: '/common/uploadSingleFileToEos',
method: 'post',
params
})
}

View File

@ -1,8 +1,8 @@
@font-face {
font-family: "iconfont"; /* Project id 4723712 */
src: url('iconfont.woff2?t=1737434703828') format('woff2'),
url('iconfont.woff?t=1737434703828') format('woff'),
url('iconfont.ttf?t=1737434703828') format('truetype');
src: url('iconfont.woff2?t=1739948469020') format('woff2'),
url('iconfont.woff?t=1739948469020') format('woff'),
url('iconfont.ttf?t=1739948469020') format('truetype');
}
.iconfont {
@ -13,6 +13,10 @@
-moz-osx-font-smoothing: grayscale;
}
.icon-zhongxinshiyang:before {
content: "\e67b";
}
.icon-siweidaotu:before {
content: "\e606";
}

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": "4320365",
"name": "重新试样",
"font_class": "zhongxinshiyang",
"unicode": "e67b",
"unicode_decimal": 59003
},
{
"icon_id": "11685410",
"name": "思维导图",

View File

@ -5,7 +5,8 @@
<script setup>
import {ref, onMounted} from "vue"
import {createSignature, sessionToken} from "@/api/file";
import {sessionToken} from "@/api/file";
import {uploadSingleToEos} from '@/utils/fileUpload'
const S3Data = {
apiVersion: "2006-03-01",
@ -25,30 +26,24 @@ const handleFileChange = (event)=> {
const uploadMessage = ref(null)
const callUploadSuccess = (e)=> {
console.log(e)
}
const callProgress = (e,p)=> {
console.log(e,p)
}
const uploadFile = ()=>{
if (selectedFile) {
console.log(S3Data)
// AWS.S3
const s3 = new AWS.S3(S3Data);
let params = {
Key: selectedFile.name,
Bucket: "wzyzoss",
ContentType: selectedFile.type,
Body: selectedFile
}
console.log(params)
console.log(s3)
s3.putObject(params, function (err, data) {
console.log(err,data)
});
let res = uploadSingleToEos(selectedFile, null,{callUploadSuccess,callProgress})
console.log(res)
}
}
onMounted(()=>{
console.log(AWS)
sessionToken().then(res=>{
uploadMessage.value = res.data
console.log(res.data)
S3Data.accessKeyId = res.data.accessKeyId
// S3Data.accessKeyId = "kzOm2cc7nT12ao907Tc"
S3Data.secretAccessKey = res.data.secretAccessKey

View File

@ -1,9 +1,9 @@
<template>
<el-dialog v-model="isDialog" :show-close="false" width="800" append-to-body destroy-on-close>
<el-dialog v-model="model" :show-close="false" width="800" append-to-body destroy-on-close>
<template #header>
<div class="custom-header flex">
<span>{{ item.name }}</span>
<i class="iconfont icon-guanbi" @click="isDialog = false"></i>
<i class="iconfont icon-guanbi" @click="model = false"></i>
</div>
</template>
<div class="dialog-content">
@ -15,7 +15,9 @@
</div>
<div class="flex-start flex" v-else>
<div class="flex" v-loading="!item.msg">
<div class="chart-item robot">{{ item.msg }}</div>
<div class="chart-item robot">
<v-md-editor v-model="item.msg" mode="preview" />
</div>
</div>
<div class="flex flex-end replace-item">
<span @click="saveAdjust(item)"><i class="iconfont icon-tihuan"></i>替换分析结果</span>
@ -54,7 +56,7 @@
</template>
<script setup>
import { ref, reactive, onMounted, onUnmounted } from 'vue'
import { ref, reactive, onMounted, watch, onUnmounted } from 'vue'
import { completion, docList } from '@/api/mode/index'
import { sessionStore } from '@/utils/store'
import { dataSetJson } from '@/utils/comm.js'
@ -66,7 +68,7 @@ const userInfo = useUserStore().user
const textarea = ref('')
const isDialog = defineModel()
const model = defineModel()
const props = defineProps({
item: {
@ -89,6 +91,12 @@ const props = defineProps({
}
})
watch(model, (newVal) =>{
if(newVal){
msgList.value.length = 0
}
})
const emit = defineEmits(['saveEdit'])
const loaded = ref(false)
@ -148,7 +156,7 @@ const getCompletion = async (val) => {
}
const saveAdjust = (item) =>{
isDialog.value = false
model.value = false
emitter.emit('onSaveAdjust', item.msg)
}
@ -158,12 +166,17 @@ const dataset_id = ref('')
const fileList = ref([])
const getList = () =>{
docList({
userId: userInfo.userId,
dataset_id: dataset_id.value
createUser: userInfo.userId,
datasetId: dataset_id.value,
edustage: curNode.edustage,
edusubject: curNode.edusubject
}).then( res =>{
fileList.value = res.rows
Object.assign(curFile, fileList.value[0])
params.document_ids = fileList.value[0].docId
if(res.rows.length){
fileList.value = res.rows
Object.assign(curFile, fileList.value[0])
params.document_ids = fileList.value[0].docId
}
})
}
emitter.on('changeCurFile', (item) =>{
@ -239,6 +252,7 @@ onUnmounted(() => {
.robot {
background: #409EFF;
color: #FFF;
width: 100%;
}
.replace-item{
font-size: 12px;
@ -332,4 +346,13 @@ onUnmounted(() => {
display: flex;
margin-bottom: 10px;
}
:deep(.v-md-editor){
background-color: #409EFF;
}
:deep(.github-markdown-body) {
font-size: 14px;
padding: 10px;
}
</style>

View File

@ -13,7 +13,7 @@
<el-input v-model="form.name" />
</el-form-item>
<el-form-item label="提示词" v-if="item.ex3 == '1' ? false : true">
<el-input v-model="form.prompt" type="textarea" />
<el-input v-model="form.prompt" type="textarea" autosize :rows="2" />
</el-form-item>
</el-form>
</div>

View File

@ -137,7 +137,9 @@ const curFile = reactive({})
const getList = () => {
docList({
createUser: userInfo.userId,
datasetId: dataset_id.value
datasetId: dataset_id.value,
edustage: curNode.edustage,
edusubject: curNode.edusubject
}).then((res) => {
fileList.value = [...res.rows]
if(res.rows.length){

View File

@ -4,12 +4,12 @@
<div class="container-right-header flex">
<el-dropdown @command="changeTemplate" :hide-on-click="false">
<span class="el-dropdown-link">
{{ curTemplate.name }}
{{ curTemplate.ex3 ? `${curTemplate.name}(平台)` : `${curTemplate.name}(个人)` }}
<i class="iconfont icon-xiangxia"></i>
</span>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item v-for="item in templateList" :command="item" :key="item.id">{{ item.name
<el-dropdown-item v-for="item in templateList" :command="item" :key="item.id">{{ item.ex3 ? `${item.name}(平台)` : `${item.name}(个人)`
}}</el-dropdown-item>
</el-dropdown-menu>
</template>
@ -18,20 +18,20 @@
<el-select v-model="curMode" placeholder="Select" class="mr-4 w-30">
<el-option v-for="item in modeOptions" :key="item.value" :disabled="item.disabled" :label="item.label" :value="item.value" />
</el-select>
<el-button type="danger" link :disabled="!(templateList.length)" @click="removeItem(curTemplate, false)">
<el-button type="danger" link :disabled="!(templateList.length)" @click="removeItem(curTemplate, false)" v-if="curTemplate.ex3 != 1">
删除
</el-button>
<el-button type="primary" link :disabled="!(templateList.length)" @click="onAdd">
<i class="iconfont icon-jiahao"></i>
{{curTemplate.ex3 === '1' ? '复制并创建个人模板' : '添加提示词'}}
{{ curTemplate.ex3 == 1 ? '复制并创建个人模板' : '添加提示词' }}
</el-button>
<el-button type="primary" :disabled="!(childTempList.length)" @click="getCompletion">一键研读</el-button>
<el-button type="primary" :disabled="!(childTempList.length)" @click="getModelResult('')">一键研读</el-button>
</div>
</div>
<!--List-->
<div class="container-right-list" ref="listRef">
<template v-for="(item, index) in childTempList">
<div class="template-item" v-loading="item.loading">
<div class="container-right-list" ref="outerContainer">
<template v-for="(item, index) in childTempList" >
<div class="template-item" v-loading="item.loading" ref="messageElements">
<div class="item-header">
<div>
<span class="blue">#</span>{{ item.name }}
@ -50,18 +50,26 @@
<div class="item-text">
{{ item.prompt }}
</div>
<div class="item-text text-answer" v-if="item.answer">
<div class="item-text text-answer">
<div class="item-icon">
<i class="iconfont icon-ai"></i>
</div>
<div class="item-answer">
<TypingEffect v-if="isStarted[index]" :text="item.answer" :delay="10" :aiShow="item.aiShow"
@complete="handleCompleteText($event, index)" @updateScroll="scrollToBottom($event, index)" />
<!-- <TypingEffect v-if="isStarted[index]" :text="item.answer" :delay="10" :aiShow="item.aiShow"
@complete="handleCompleteText($event, index)" @updateScroll="scrollToBottom($event, index)" /> -->
<TypingEffect
:text="item.answer"
:showTypewriter="item.showTypewriter"
:speed="30"
:charsPerFrame="15"
@scroll="handleScroll(index)"
@done="handleDone(index)"
/>
</div>
</div>
<div class="ai-btn" v-if="item.answer">
<el-button type="primary" link @click="againResult(index, item)">
<i class="iconfont icon-ai1"></i>
<el-button type="primary" link @click="getModelResult(index, item)">
<i class="iconfont icon-zhongxinshiyang"></i>
重新研读
</el-button>
<el-button type="primary" link @click="onAdjust(index, item)">
@ -99,7 +107,6 @@ import TypingEffect from '@/components/typing-effect/index.vue'
import useUserStore from '@/store/modules/user'
import emitter from '@/utils/mitt';
import { dataSetJson } from '@/utils/comm.js'
import { cloneDeep } from 'lodash'
const props = defineProps(['type'])
const { user } = useUserStore()
@ -123,7 +130,7 @@ const modeOptions = ref([
},
{
label: 'deepseek模型',
value: 3
value: 3,
}
])
@ -138,7 +145,7 @@ const modeOptions = ref([
const isWordDialog = ref(false)
const editItem = reactive({})
const onAdd = () => {
console.log(curTemplate)
Object.assign(editItem, curTemplate)
editItem.isAdd = true
isWordDialog.value = true
@ -176,61 +183,253 @@ const getTemplateList = () => {
}
})
}
const getChildTemplate = () => {
const getChildTemplate = (val) => {
tempLoading.value = true
modelList({ model: props.type, type: 2, parentId: curTemplate.id, ex1: curNode.edustage, ex2: curNode.edusubject }).then(res => {
childTempList.value = res.rows
if (childTempList.value.length) {
childTempList.value.forEach(item => item.answer = '')
childTempList.value.forEach(item => {
item.answer = '';
item.showTypewriter = false
})
}
getTempResult()
//
getTempResult(val)
}).finally(() => {
tempLoading.value = false
})
}
const isStarted = ref([]);
const listRef = ref()
//
const getTempResult = () => {
tempResult({ mainModelId: curTemplate.id, pageNum: 1, pageSize: 10000, ex1: curNode.id }).then(res => {
const getTempResult = (val) => {
tempResult({ mainModelId: curTemplate.id, pageNum: 1, pageSize: 10000, ex1: curNode.id }).then(async res => {
let rows = res.rows
childTempList.value.forEach(item => {
rows.forEach(el => {
if (item.id == el.modelId) {
item.answer = getResult(el.content)
item.answer = el.content
item.resultId = el.id
}
})
})
if (rows.length > 0) {
isStarted.value = new Array(rows.length).fill(true)
}
// val : true
if(val){
// dom
await nextTick()
//
let lastIndex = childTempList.value.length - 1
getModelResult(lastIndex, childTempList.value[lastIndex], true)
}
})
}
const scrollToBottom = (height, index) => {
const prompt = ref('')
if (listRef.value) {
let sum = 0
let listDom = listRef.value.children
//
const isAgain = ref(false)
if (index == 0) {
// 220
let screenHeight = window.innerHeight - 220
if (height > screenHeight) {
listRef.value.scrollTop = (height - screenHeight + 50)
}
//
const getModelResult = async (index, item, isLast) => {
/**
*
* index: 当前项索引
* item: 当前项
* isLast: 是否最后一项 用于添加子模板后研读
*/
let str = prompt.value
//
if(index !== ''){
isAgain.value = true
//
childTempList.value[index].answer = ''
//
childTempList.value[index].showTypewriter = true
//
if (messageElements.value[index]) {
messageElements.value[index].scrollIntoView({
behavior: 'smooth',
block: 'start',
});
}
else {
for (let i = 0; i < index; i++) {
sum += listDom[i].clientHeight
// prompt
str = str.replace('{模板标题}',item.name)
str = str.replace('{模板内容}',item.prompt)
params.prompt = str
params.template = item.prompt
//
try {
childTempList.value[index].loading = true
const data = await requestModelData()
childTempList.value[index].answer = data.answer;
if(isLast){
await onSaveTemp(childTempList.value[index])
}else{
//
onEditResult(data.answer, index)
}
listRef.value.scrollTop = sum + height
} finally {
childTempList.value[index].loading = false
}
}
//
else{
//
childTempList.value.forEach(item => {
if (item.answer) {
item.answer = ''
}
})
//
messageElements.value[0].scrollIntoView({
behavior: 'smooth',
block: 'start',
})
processNext(0)
}
}
//
const processNext = async (index) =>{
let str = prompt.value
//
const item = childTempList.value[index]
item.loading = true
str = str.replace('{模板标题}',item.name)
str = str.replace('{模板内容}',item.prompt)
params.prompt = str
params.template = item.prompt
//
try {
const data = await requestModelData()
item.answer = data.answer
item.showTypewriter = true
//
await onSaveTemp(item)
} finally {
item.loading = false
}
}
//
const handleDone = (index) =>{
if(isAgain.value){
isAgain.value = false
return
}
else{
if(index == childTempList.value.length - 1) return
outerContainer.value.scrollTo({
top: outerContainer.value.scrollHeight,
behavior: 'smooth',
});
processNext(index + 1)
}
}
// ms:
const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms));
// or
const requestModelData = async () =>{
//
let data = null
if (curMode.value == 1) {
const res = await sendChart({
content: params.prompt,
conversationId: conversation_id.value,
stream: false
})
// 1s
if(!isAdjust.value){
await delay(1000); // 1
}
data = res.data
}
//
else {
if (curMode.value == 3) {
params.llm = 'deepseek-r1:8b'
}
const res = await completion(params)
data = res.data
}
return data
}
const messageElements = ref([]); //
//
// const animationId = ref(null)
const animationIDs = ref([])
const outerContainer = ref()
let lastScrollTime = 0;
const scrollInterval = 200
const handleScroll = (index) => {
const now = Date.now();
if (now - lastScrollTime >= scrollInterval) {
const container = document.querySelector('.container-right-list');
if(isAgain.value){
scrollToTmp(index)
}
else{
if (container) {
if(index == 0){
const item = messageElements.value[index]
if(item.clientHeight > outerContainer.value.clientHeight ){
item.scrollIntoView({ behavior: 'smooth', block: 'end' })
}
}
else{
//
const id = requestAnimationFrame(() => {
container.scrollTo({
top: container.scrollHeight,
behavior: 'smooth',
});
});
animationIDs.value.push(id)
}
}
}
lastScrollTime = now;
}
}
//
const scrollToTmp = (index) =>{
//
if(index == childTempList.value.length - 1){
const id = requestAnimationFrame(() => {
outerContainer.value.scrollTo({
top: outerContainer.value.scrollHeight,
behavior: 'smooth',
});
});
animationIDs.value.push(id)
}
else{
const item = messageElements.value[index]
if(item.clientHeight > outerContainer.value.clientHeight ){
item.scrollIntoView({ behavior: 'smooth', block: 'end' })
}
}
}
//
const changeTemplate = (val) => {
ElMessageBox.confirm(
@ -298,122 +497,10 @@ const onEdit = (index, item) => {
isEdit.value = true
}
//
//
const onEditResult = async (answer, index) => {
const prompt = ref('')
//
const isAgain = ref(false)
const againResult = async (index, item) => {
isAgain.value = true
isStarted.value[index] = false
childTempList.value[index].answer = ''
if (index == 0) {
listRef.value.scrollTop = 0
} else {
scrollToBottom(50, index)
}
try {
await nextTick()
childTempList.value[index].loading = true
item.aiShow = true
let str = cloneDeep(prompt.value)
str = str.replace('{模板标题}',item.name)
str = str.replace('{模板内容}',item.prompt)
params.prompt = str
params.template = item.prompt
let data = null;
//
if (curMode.value == 1) {
const res = await sendChart({
content: params.prompt,
conversationId: conversation_id.value,
stream: false
})
data = res.data
} else {
if (curMode.value == 3) {
params.llm = 'deepseek-r1:8b'
}
//
const res = await completion(params)
data = res.data
}
childTempList.value[index].answer = getResult(data.answer);
isStarted.value[index] = true
} finally {
childTempList.value[index].loading = false
}
}
const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms));
//
const getCompletion = async () => {
isStarted.value = new Array(childTempList.length).fill(false)
isStarted.value[0] = true
childTempList.value.forEach(item => {
if (item.answer) {
item.answer = ''
}
})
for (let item of childTempList.value) {
try {
item.loading = true
item.aiShow = true
let str = cloneDeep(prompt.value)
str = str.replace('{模板标题}',item.name)
str = str.replace('{模板内容}',item.prompt)
params.prompt = str
params.template = item.prompt
//
let data = null
if (curMode.value == 1) {
const res = await sendChart({
content: params.prompt,
conversationId: conversation_id.value,
stream: false
})
await delay(1000); // 1
data = res.data
}
//
else {
if (curMode.value == 3) {
params.llm = 'deepseek-r1:8b'
}
const res = await completion(params)
data = res.data
}
item.answer = getResult(data.answer)
onSaveTemp(item)
} finally {
item.loading = false
}
}
}
const handleCompleteText = async (answer, index) => {
if (index < childTempList.value.length - 1) {
isStarted.value[index + 1] = true; //
}
if (isAgain.value) {
try {
await editTempResult({ id: childTempList.value[index].resultId, content: answer })
} finally {
isAgain.value = false
}
}
await editTempResult({ id: childTempList.value[index].resultId, content: answer })
}
//
@ -446,19 +533,13 @@ const onSaveTemp = async (item) => {
}
}
// ### **
let getResult = (str) => {
let newStr = str.replace(/#+|(\*\*)/g, '');
newStr = newStr.replace(/<think>[\s\S]*?<\/think>/g, '');
return newStr
}
//
//
emitter.on('onGetChild', () => {
getChildTemplate()
getChildTemplate(true)
})
//
emitter.on('onGetMain', () => {
emitter.on('onGetMain====》', () => {
getTemplateList()
})
@ -496,8 +577,8 @@ onMounted(() => {
getTemplateList()
let jsonKey = `${modeType.value}-${data.edustage}-${data.edusubject}`
params.dataset_id = dataSetJson[jsonKey]
if(!params.dataset_id){
curMode.value = 1
@ -524,7 +605,11 @@ onUnmounted(() => {
emitter.off('onGetMain');
emitter.off('onGetChild');
emitter.off('onSaveAdjust');
//
animationIDs.value.forEach(id => cancelAnimationFrame(id))
})
</script>
<style lang="scss" scoped>
@ -616,14 +701,15 @@ onUnmounted(() => {
.iconfont {
margin-right: 3px;
font-size: 18px;
}
:deep(.el-button) {
font-size: 13px;
font-size: 14px;
}
.icon-ai1 {
font-size: 18px;
.icon-zhongxinshiyang {
font-size: 20px;
}
}
}

View File

@ -1,85 +1,109 @@
<template>
<div class="typing-effect" ref="typingEffectRef">
<!-- <span v-html="displayedText"></span> -->
<el-input
v-model="displayedText"
:autosize="{ minRows: 2 }"
type="textarea"
readonly
resize="none"
style="width: 100%;"
input-style="border:none;outline: none;box-shadow:none;color:000;fontSize:14px"
/>
<!-- <el-input
v-model="currentText"
:autosize="{ minRows: 2 }"
type="textarea"
readonly
resize="none"
style="width: 100%;"
input-style="border:none;outline: none;box-shadow:none;color:000;fontSize:14px"
/> -->
<v-md-editor v-model="currentText" mode="preview" />
</div>
</template>
<script setup>
import { ref, onMounted, watch, nextTick } from 'vue';
import { ref, onMounted, watch, onUnmounted } from 'vue';
const props = defineProps({
text: {
type: [String, Object],
required: true
type: String,
required: true,
},
delay: {
speed: {
type: Number,
default: 100 //
default: 50, //
},
charsPerFrame: {
type: Number,
default: 1, //
},
showTypewriter: {
type: Boolean,
default: true, //
},
aiShow: {
type: [Boolean] // true
}
});
const typingEffectRef = ref(null);
const emit = defineEmits(['complete', 'updateScroll']);
const displayedText = ref('');
const index = ref(0);
const type = async () => {
await nextTick()
if(!props.aiShow) {
displayedText.value = props.text
return
}
//ms
let allLength = props.text.length
let allTime = 3000
let addIndex = allLength/(allTime/props.delay);
//5
for (let i = 0; i < addIndex; i++) {
if (index.value <= allLength) {
displayedText.value += props.text.charAt(index.value);
index.value++;
} else {
break;
}
}
if (index.value <= props.text.length) {
// displayedText.value += props.text.charAt(index.value);
// index.value++;
setTimeout(() => {
type();
emit('updateScroll', typingEffectRef.value.clientHeight); //
}, props.delay);
// emits
const emit = defineEmits(['scroll','done']);
//
const currentText = ref(''); //
const currentCharIndex = ref(0); //
const isTyping = ref(false); //
const animationFrameId = ref(null); // requestAnimationFrame ID
//
const typeText = () => {
if (currentCharIndex.value < props.text.length) {
// charsPerFrame
currentText.value += props.text.slice(
currentCharIndex.value,
currentCharIndex.value + props.charsPerFrame
);
currentCharIndex.value += props.charsPerFrame;
emit('scroll'); //
// 使 requestAnimationFrame
animationFrameId.value = requestAnimationFrame(() => {
setTimeout(() => {
typeText();
}, props.speed);
});
} else {
// complete
emit('complete',displayedText.value);
isTyping.value = false;
emit('done')
}
};
onMounted(() => {
resetAndType();
});
const resetAndType = () =>{
displayedText.value = '';
index.value = 0;
type();
//
const showFullText = () =>{
currentText.value = props.text
}
// props 便 text delay
watch([() => props.text, () => props.delay], resetAndType);
//
const startTypeWriter = () => {
isTyping.value = true;
currentText.value = '';
currentCharIndex.value = 0;
typeText();
};
// text
watch(
() => props.text,
() => {
if(props.showTypewriter && props.text){
startTypeWriter();
} else {
showFullText();
}
}
);
//
onMounted(() => {
// startTypeWriter();
});
//
onUnmounted(() => {
if (animationFrameId.value) {
cancelAnimationFrame(animationFrameId.value);
}
});
</script>
@ -87,4 +111,12 @@ watch([() => props.text, () => props.delay], resetAndType);
:deep(.el-textarea__inner:hover){
box-shadow: none;
}
.typing-effect{
:deep(.github-markdown-body){
padding: 0;
font-size: 14px;
}
}
</style>

View File

@ -86,7 +86,7 @@ const defaultImg = ['/img/avatar-default.jpg','/images/img-avatar.png','/src/ass
//
const isStadium = () => {
console.log('isStadium',userStore.roles )
// console.log('isStadium',userStore.roles )
let user = userStore.user
let roles = user.roles
return roles.some(item => item.roleKey === 'stadium')

View File

@ -0,0 +1,160 @@
import {sessionToken, uploadSingleFileToEos} from "@/api/file";
import CryptoJS from 'crypto-js'
import {ElMessageBox} from "element-plus";
/**
* @description
* @param {Function} callGetToken - token回调
* @param {Function} callUpload - EOS回调
* @param {Function} callProgress -
* @param {Function} callUploadSuccess -
*/
type CallBack = {
callGetToken?: (e:any)=>any;
callUploadFile?: (e:any)=>any;
callProgress?: (e:any,p:number)=>any;
callUploadSuccess?: (e:any)=>any;
}
/**
* @description
* @param {string} _bucket -
* @param {string} id - id
* @param {string} filePath -
* @param {string} fileMd5 - MD5
* @param {string} fileNewName -
* @param {string} fileName -
* @param {string} fileSize -
* @param {string} fileSuffix -
* @param {string} fileType -
*/
type SingleUploadData = {
_bucket?: string,
id?: string,
filePath?: string,
fileMd5?: string,
fileNewName?: string,
fileName?: string,
fileSize?: number,
fileSuffix?: string,
fileType?: string,
}
const S3Data = {
apiVersion: "2006-03-01",
accessKeyId: "", // 服务端获取到的 access key ID
secretAccessKey: "", // 服务端获取到的 secret access key
endpoint: "",
sessionToken: '',
signatureVersion: "v2",
sslEnabled: true // 是否启用 HTTPS 连接
}
/**
* @description
* @param file
* @param {SingleUploadData} uploadData
* @param {CallBack} callBack -
*/
const uploadSingleToEos = async (file:File, uploadData: SingleUploadData, callBack?: CallBack) => {
console.log(file)
//去服务端拿授权
let res = await sessionToken().catch((err:any)=>{
ElMessageBox.alert(err)
});
S3Data.accessKeyId = res.data.accessKeyId
S3Data.secretAccessKey = res.data.secretAccessKey
S3Data.endpoint = res.data.endPoint
S3Data.sessionToken = res.data.sessionToken
uploadData = uploadData?uploadData:{}
uploadData._bucket = res.data.bucket
//如果文件名不存在,则使用时间戳为名字
uploadData.id = crypto.randomUUID();
uploadData.fileMd5 = await getFileMD5(file);
uploadData.fileName = file.name?file.name:(new Date().getTime()+"");
if (uploadData.fileName.lastIndexOf(".") === -1) {
uploadData.fileSuffix = ""
uploadData.fileNewName = uploadData.id
}else {
uploadData.fileSuffix = uploadData.fileName.substring(uploadData.fileName.lastIndexOf(".")+1)
uploadData.fileNewName = uploadData.id + "." + uploadData.fileSuffix
}
uploadData.filePath = getEosFileKey(uploadData.id, uploadData.fileSuffix);
uploadData.fileSize = file.size
uploadData.fileType = file.type
console.log(uploadData)
//回调获取token
callBack?.callGetToken && callBack.callGetToken(res.data)
//将文件上传到EOS
let uploadRes = await uploadFile(file,uploadData,callBack).catch((err)=>{
ElMessageBox.alert(err)
});
if (!uploadRes) return
//回调成功上传EOS
callBack?.callGetToken && callBack.callGetToken(uploadRes)
//去服务端确认是否已经完成上传,并保存文件上传信息
let saveUploadRes = await uploadSingleFileToEos(uploadData)
//回调整个文件上传完成
callBack?.callUploadSuccess && callBack.callUploadSuccess(saveUploadRes)
return saveUploadRes
}
/**
* @description
* @param file
* @param uploadData
* @param callBack
*/
const uploadFile = (file:File, uploadData: SingleUploadData,callBack?:CallBack) =>{
return new Promise((resolve, reject) => {
const s3 = new AWS.S3(S3Data);
let params = {
Key: uploadData.filePath,
Bucket: uploadData._bucket,
ContentType: file.type,
Body: file
}
s3.putObject(params, function (err:any, data:any) {
if (err) {
reject(data)
}else {
resolve(data)
}
}).on('httpUploadProgress', function(evt:any) {
//回调上传进度
callBack?.callProgress && callBack.callProgress(evt,Math.round((evt.loaded / evt.total) * 100));
})
})
}
/**
* @description EOS文件key
* @param uuid id
* @param suffix
*/
const getEosFileKey = (uuid:string, suffix:string) => {
let date = new Date()
let year = date.getFullYear()
let month = date.getMonth() + 1
let day = date.getDate()
return year + "/" + month + "/" + day + "/" + uuid + "." + suffix;
}
/**
* @description MD5
* @param file
*/
const getFileMD5 = (file:File):Promise<string> => {
return new Promise((resolve, reject) => {
let reader = new FileReader();
reader.readAsArrayBuffer(file);
reader.onload = function () {
let buffer = reader.result as ArrayBuffer;
let md5 = CryptoJS.MD5(buffer).toString();
resolve(md5)
}
})
}
export {uploadSingleToEos}

View File

@ -4,7 +4,7 @@
<el-tab-pane :label="item.label" style="text-align:left" stretch="true">
<template v-if="item.stuList.length > 0">
<template v-for="(stuItem,stuIndex) in item.stuList" :key="stuIndex">
<el-tag style="margin:5px 10px 0 0" type="primary">{{ stuItem.studentname }}:{{ stuItem.getScore }}</el-tag>
<el-tag style="margin:5px 10px 0 0" type="primary">{{ stuItem.studentname }}:{{ stuItem.point }}</el-tag>
</template>
</template>
<template v-else>
@ -93,6 +93,8 @@ watch(() => useOverview.tableList, () => {
}else{
hasStudents.value = useOverview.tableList.filter(item => useOverview.allData[0].hasAnswers.includes(item.studentid)).map(item => item);
}
console.log(hasStudents.value,'hasStudents.value');
showStudents(0)
},{deep: true})
</script>

View File

@ -376,12 +376,12 @@ const getClassWorkStudentList = (rowId) => {
}
}
}
const allScore = evalarray.reduce((acc, cur) => acc + cur.score, 0)
const allScore = evalarray.reduce((acc, cur) => acc + cur.teacherRating, 0)
//console.log(evalarray, 'evalarray------------------------------------')
if (feedcount > 0) {
// : /*100
response.rows[i].scoingRate = ((score / allScore) * 100).toFixed(0) + '%'
response.rows[i].getScore = score
response.rows[i].getScore = allScore
} else {
response.rows[i].scoingRate = '0%'
response.rows[i].getScore = 0