提交 54bb1fc9 authored 作者: 王鹏飞's avatar 王鹏飞

Merge branch 'master' into gdrtvu

...@@ -295,6 +295,13 @@ ...@@ -295,6 +295,13 @@
"watchThrottled": true, "watchThrottled": true,
"watchTriggerable": true, "watchTriggerable": true,
"watchWithFilter": true, "watchWithFilter": true,
"whenever": true "whenever": true,
"DirectiveBinding": true,
"MaybeRef": true,
"MaybeRefOrGetter": true,
"onWatcherCleanup": true,
"useId": true,
"useModel": true,
"useTemplateRef": true
} }
} }
...@@ -67,6 +67,7 @@ declare global { ...@@ -67,6 +67,7 @@ declare global {
const onStartTyping: typeof import('@vueuse/core')['onStartTyping'] const onStartTyping: typeof import('@vueuse/core')['onStartTyping']
const onUnmounted: typeof import('vue')['onUnmounted'] const onUnmounted: typeof import('vue')['onUnmounted']
const onUpdated: typeof import('vue')['onUpdated'] const onUpdated: typeof import('vue')['onUpdated']
const onWatcherCleanup: typeof import('vue')['onWatcherCleanup']
const pausableWatch: typeof import('@vueuse/core')['pausableWatch'] const pausableWatch: typeof import('@vueuse/core')['pausableWatch']
const provide: typeof import('vue')['provide'] const provide: typeof import('vue')['provide']
const reactify: typeof import('@vueuse/core')['reactify'] const reactify: typeof import('@vueuse/core')['reactify']
...@@ -175,6 +176,7 @@ declare global { ...@@ -175,6 +176,7 @@ declare global {
const useFullscreen: typeof import('@vueuse/core')['useFullscreen'] const useFullscreen: typeof import('@vueuse/core')['useFullscreen']
const useGamepad: typeof import('@vueuse/core')['useGamepad'] const useGamepad: typeof import('@vueuse/core')['useGamepad']
const useGeolocation: typeof import('@vueuse/core')['useGeolocation'] const useGeolocation: typeof import('@vueuse/core')['useGeolocation']
const useId: typeof import('vue')['useId']
const useIdle: typeof import('@vueuse/core')['useIdle'] const useIdle: typeof import('@vueuse/core')['useIdle']
const useImage: typeof import('@vueuse/core')['useImage'] const useImage: typeof import('@vueuse/core')['useImage']
const useInfiniteScroll: typeof import('@vueuse/core')['useInfiniteScroll'] const useInfiniteScroll: typeof import('@vueuse/core')['useInfiniteScroll']
...@@ -194,6 +196,7 @@ declare global { ...@@ -194,6 +196,7 @@ declare global {
const useMemoize: typeof import('@vueuse/core')['useMemoize'] const useMemoize: typeof import('@vueuse/core')['useMemoize']
const useMemory: typeof import('@vueuse/core')['useMemory'] const useMemory: typeof import('@vueuse/core')['useMemory']
const useMin: typeof import('@vueuse/math')['useMin'] const useMin: typeof import('@vueuse/math')['useMin']
const useModel: typeof import('vue')['useModel']
const useMounted: typeof import('@vueuse/core')['useMounted'] const useMounted: typeof import('@vueuse/core')['useMounted']
const useMouse: typeof import('@vueuse/core')['useMouse'] const useMouse: typeof import('@vueuse/core')['useMouse']
const useMouseInElement: typeof import('@vueuse/core')['useMouseInElement'] const useMouseInElement: typeof import('@vueuse/core')['useMouseInElement']
...@@ -243,6 +246,7 @@ declare global { ...@@ -243,6 +246,7 @@ declare global {
const useSum: typeof import('@vueuse/math')['useSum'] const useSum: typeof import('@vueuse/math')['useSum']
const useSupported: typeof import('@vueuse/core')['useSupported'] const useSupported: typeof import('@vueuse/core')['useSupported']
const useSwipe: typeof import('@vueuse/core')['useSwipe'] const useSwipe: typeof import('@vueuse/core')['useSwipe']
const useTemplateRef: typeof import('vue')['useTemplateRef']
const useTemplateRefsList: typeof import('@vueuse/core')['useTemplateRefsList'] const useTemplateRefsList: typeof import('@vueuse/core')['useTemplateRefsList']
const useTextDirection: typeof import('@vueuse/core')['useTextDirection'] const useTextDirection: typeof import('@vueuse/core')['useTextDirection']
const useTextSelection: typeof import('@vueuse/core')['useTextSelection'] const useTextSelection: typeof import('@vueuse/core')['useTextSelection']
...@@ -294,6 +298,6 @@ declare global { ...@@ -294,6 +298,6 @@ declare global {
// for type re-export // for type re-export
declare global { declare global {
// @ts-ignore // @ts-ignore
export type { Component, ComponentPublicInstance, ComputedRef, ExtractDefaultPropTypes, ExtractPropTypes, ExtractPublicPropTypes, InjectionKey, PropType, Ref, VNode, WritableComputedRef } from 'vue' export type { Component, ComponentPublicInstance, ComputedRef, DirectiveBinding, ExtractDefaultPropTypes, ExtractPropTypes, ExtractPublicPropTypes, InjectionKey, PropType, Ref, MaybeRef, MaybeRefOrGetter, VNode, WritableComputedRef } from 'vue'
import('vue') import('vue')
} }
This source diff could not be displayed because it is too large. You can view the blob instead.
...@@ -39,8 +39,8 @@ ...@@ -39,8 +39,8 @@
"ua-parser-js": "^1.0.33", "ua-parser-js": "^1.0.33",
"universal-cookie": "^4.0.4", "universal-cookie": "^4.0.4",
"video.js": "^7.21.1", "video.js": "^7.21.1",
"vue": "^3.4.25", "vue": "^3.5.12",
"vue-router": "^4.3.2", "vue-router": "^4.4.5",
"vuedraggable": "^4.1.0" "vuedraggable": "^4.1.0"
}, },
"devDependencies": { "devDependencies": {
...@@ -53,8 +53,8 @@ ...@@ -53,8 +53,8 @@
"@types/node": "^20.3.1", "@types/node": "^20.3.1",
"@types/ua-parser-js": "^0.7.36", "@types/ua-parser-js": "^0.7.36",
"@types/video.js": "^7.3.52", "@types/video.js": "^7.3.52",
"@vitejs/plugin-vue": "^5.0.4", "@vitejs/plugin-vue": "^5.1.4",
"@vue-macros/reactivity-transform": "^0.4.4", "@vue-macros/reactivity-transform": "^1.1.2",
"@vue/eslint-config-typescript": "^12.0.0", "@vue/eslint-config-typescript": "^12.0.0",
"@vue/tsconfig": "^0.5.1", "@vue/tsconfig": "^0.5.1",
"chalk": "^5.2.0", "chalk": "^5.2.0",
...@@ -62,9 +62,10 @@ ...@@ -62,9 +62,10 @@
"eslint-plugin-vue": "^9.25.0", "eslint-plugin-vue": "^9.25.0",
"sass": "^1.58.3", "sass": "^1.58.3",
"typescript": "~5.4.5", "typescript": "~5.4.5",
"unplugin-auto-import": "^0.17.5", "unplugin-auto-import": "^0.17.8",
"vite": "^5.2.10", "vite": "^5.4.9",
"vite-plugin-checker": "^0.6.4", "vite-plugin-checker": "^0.6.4",
"vite-plugin-mkcert": "^1.17.6",
"vue-tsc": "^1.8.27" "vue-tsc": "^1.8.27"
} }
} }
...@@ -64,7 +64,7 @@ function handleClick(path: string) { ...@@ -64,7 +64,7 @@ function handleClick(path: string) {
<div class="logo"> <div class="logo">
<router-link to="/"><img :src="appConfig.logo" /></router-link> <router-link to="/"><img :src="appConfig.logo" /></router-link>
</div> </div>
<div class="line"></div> <div class="line" v-if="appConfig.title"></div>
</template> </template>
<h1 class="app-name"> <h1 class="app-name">
<router-link to="/">{{ appConfig.title }}</router-link> <router-link to="/">{{ appConfig.title }}</router-link>
......
...@@ -46,7 +46,7 @@ const appConfigList = [ ...@@ -46,7 +46,7 @@ const appConfigList = [
}, },
{ {
system: 'dml', system: 'dml',
title: '数字营销实验室', title: '数智营销实践教学平台',
logo: 'https://zws-imgs-pub.ezijing.com/pc/base/ezijing-logo.svg', logo: 'https://zws-imgs-pub.ezijing.com/pc/base/ezijing-logo.svg',
hosts: ['saas-dml-web'], hosts: ['saas-dml-web'],
dmlURL: import.meta.env.VITE_DML_PRO_URL dmlURL: import.meta.env.VITE_DML_PRO_URL
...@@ -67,12 +67,73 @@ const appConfigList = [ ...@@ -67,12 +67,73 @@ const appConfigList = [
xExamLabel: '商务数据分析理论考试', xExamLabel: '商务数据分析理论考试',
labExamLabel: '商务数据分析实操考试', labExamLabel: '商务数据分析实操考试',
loginURL: import.meta.env.VITE_SWSJFXS_LOGIN_URL loginURL: import.meta.env.VITE_SWSJFXS_LOGIN_URL
},
{
system: 'swsjfxs',
title: '商务数据分析师',
logo: 'https://webapp-pub.ezijing.com/website/base/images/logo_swsjfxs.png',
favicon: 'https://webapp-pub.ezijing.com/website/base/images/favicon_swsjfxs.png',
hosts: ['saas-lab-bda'],
studentMenus: [
{ name: '首页', path: '/' },
{ name: '我的大赛', path: '/student/contest' },
{ name: '大赛成绩查询', path: '/student/contest/score' }
],
xTrainLabel: '理论训练',
labTrainLabel: '实操训练',
xExamLabel: '理论考试',
labExamLabel: '实操考试',
loginURL: import.meta.env.VITE_SWSJFXS_LOGIN_URL,
hideAvailableEvents: true, // 隐藏可参与赛项
hidePracticalTestPaper: true, // 隐藏实操试卷
hideContestToolbar: true // 隐藏大赛工具栏
},
{
system: 'amo',
title: '全媒体运营师',
logo: 'https://webapp-pub.ezijing.com/website/base/images/logo_swsjfxs.png',
favicon: 'https://webapp-pub.ezijing.com/website/base/images/favicon_swsjfxs.png',
hosts: ['saas-lab-amo'],
studentMenus: [
{ name: '首页', path: '/' },
{ name: '我的大赛', path: '/student/contest' },
{ name: '大赛成绩查询', path: '/student/contest/score' }
],
xTrainLabel: '理论训练',
labTrainLabel: '实操训练',
xExamLabel: '理论考试',
labExamLabel: '实操考试',
loginURL: import.meta.env.VITE_SWSJFXS_LOGIN_URL,
hideAvailableEvents: true, // 隐藏可参与赛项
hidePracticalTestPaper: true, // 隐藏实操试卷
hideContestToolbar: true // 隐藏大赛工具栏
},
{
system: 'default',
title: '商业数据分析与应用大赛',
logo: 'https://zws-imgs-pub.ezijing.com/pc/base/ezijing-logo.svg',
// logo: 'https://webapp-pub.ezijing.com/website/base/images/logo_swsjfxs.png',
// favicon: 'https://webapp-pub.ezijing.com/website/base/images/favicon_swsjfxs.png',
hosts: ['saas-lab-bda2'],
studentMenus: [
{ name: '首页', path: '/' },
{ name: '我的大赛', path: '/student/contest' }
// { name: '大赛成绩查询', path: '/student/contest/score' }
],
xTrainLabel: '理论训练',
labTrainLabel: '实操训练',
xExamLabel: '理论考试',
labExamLabel: '实操考试',
// loginURL: import.meta.env.VITE_SWSJFXS_LOGIN_URL,
hideAvailableEvents: true, // 隐藏可参与赛项
hidePracticalTestPaper: true, // 隐藏实操试卷
hideContestToolbar: true // 隐藏大赛工具栏
} }
] ]
export function useAppConfig() { export function useAppConfig() {
const found = appConfigList.find(item => { const found = appConfigList.find(item => {
return item.hosts.find(host => location.host.includes(host)) return item.hosts.find(host => location.host.split('.').includes(host))
}) })
const appConfig = found || appConfigList[0] const appConfig = found || appConfigList[0]
......
<script setup lang="ts">
import type { RecordItem, ReportItem } from '../types'
import { getReportList } from '../api'
interface Props {
data: RecordItem
}
const props = defineProps<Props>()
const reportList = ref<ReportItem[]>([])
async function fetchReport() {
const res = await getReportList({ competition_id: props.data.competition_id, student_id: props.data.student_id })
reportList.value = res.data.items
}
onMounted(() => {
fetchReport()
})
</script>
<template>
<el-dialog title="查看报告" width="500px">
<ul>
<li v-for="item in reportList" :key="item.id" style="line-height: 24px">
<a :href="`https://view.officeapps.live.com/op/view.aspx?src=${item.url}`" target="_blank">
{{ item.name }}
<el-button size="small" type="primary" style="margin-left: 10px">查看</el-button>
</a>
</li>
</ul>
<template #footer>
<el-row justify="center">
<el-button round auto-insert-space @click="$emit('update:modelValue', false)">关闭</el-button>
</el-row>
</template>
</el-dialog>
</template>
export interface RecordItem { export interface RecordItem {
check_time: string check_time: string
checked_count: number
checked_flag_name: string
checked_flag: boolean
checker_id?: string checker_id?: string
class_id_name: string
class_id: string class_id: string
class_name: string
commit_time: string commit_time: string
competition_competitor_pictures: any
competition_id_name: string
competition_id: string
competition_is_more_status: string
competition_rule_type: string
competition_uri: string
course_name?: string course_name?: string
experiment_id: string experiment_id: string
experiment_name: string experiment_name: string
file?: string file?: string
gender: string
grade: string
id_number: string
id: string
login_id: string
need_check_count: number
organ_id_name: string
organ_id: string
pictures?: string pictures?: string
score: string publish_status_name: string
publish_status: string
score_details?: string score_details?: string
score_status: string
score: string
sno_number: string sno_number: string
specialty_id_name: string
specialty_id: string specialty_id: string
specialty_name: string
status: 0 | 1 | 2
status_name: string status_name: string
status: 0 | 1 | 2
student_id: string student_id: string
student_name: string student_name: string
competition_competitor_pictures: any train_count: string
updated_time: string
} }
export interface FileItem { export interface FileItem {
......
...@@ -8,6 +8,7 @@ const SyncExamDialog = defineAsyncComponent(() => import('../components/SyncExam ...@@ -8,6 +8,7 @@ const SyncExamDialog = defineAsyncComponent(() => import('../components/SyncExam
const ImportExamDialog = defineAsyncComponent(() => import('../components/ImportExamDialog.vue')) const ImportExamDialog = defineAsyncComponent(() => import('../components/ImportExamDialog.vue'))
const ImportScoreDialog = defineAsyncComponent(() => import('../components/ImportScoreDialog.vue')) const ImportScoreDialog = defineAsyncComponent(() => import('../components/ImportScoreDialog.vue'))
const ScoreViewPicturesDialog = defineAsyncComponent(() => import('../components/ScoreViewPicturesDialog.vue')) const ScoreViewPicturesDialog = defineAsyncComponent(() => import('../components/ScoreViewPicturesDialog.vue'))
const ReportDialog = defineAsyncComponent(() => import('../components/ReportDialog.vue'))
const route = useRoute() const route = useRoute()
...@@ -129,6 +130,7 @@ function onUpdateSuccess() { ...@@ -129,6 +130,7 @@ function onUpdateSuccess() {
appList?.refetch() appList?.refetch()
} }
// 查看截图
const viewPictureVisible = ref(false) const viewPictureVisible = ref(false)
const rowData = ref() const rowData = ref()
function handleViewPicture(row: any) { function handleViewPicture(row: any) {
...@@ -136,6 +138,13 @@ function handleViewPicture(row: any) { ...@@ -136,6 +138,13 @@ function handleViewPicture(row: any) {
viewPictureVisible.value = true viewPictureVisible.value = true
} }
// 查看报告
const viewReportVisible = ref(false)
function handleViewReport(row: any) {
rowData.value = row
viewReportVisible.value = true
}
// function getModuleStatus(row: any, index: number) { // function getModuleStatus(row: any, index: number) {
// const [first] = row.student_module_status_list // const [first] = row.student_module_status_list
// try { // try {
...@@ -155,13 +164,28 @@ function handleViewPicture(row: any) { ...@@ -155,13 +164,28 @@ function handleViewPicture(row: any) {
<AppCard title="大赛评分"> <AppCard title="大赛评分">
<AppList v-bind="listOptions" ref="appList"> <AppList v-bind="listOptions" ref="appList">
<template #header-buttons> <template #header-buttons>
<el-button type="primary" round :icon="Refresh" @click="syncDialogVisible = true" v-permission="'v1-expert-check-sync-exam'" <el-button
type="primary"
round
:icon="Refresh"
@click="syncDialogVisible = true"
v-permission="'v1-expert-check-sync-exam'"
>系统同步考试成绩</el-button >系统同步考试成绩</el-button
> >
<el-button type="primary" round :icon="Upload" @click="importExamVisible = true" v-permission="'v1-expert-check-import-exam'" <el-button
type="primary"
round
:icon="Upload"
@click="importExamVisible = true"
v-permission="'v1-expert-check-import-exam'"
>批量导入考试成绩</el-button >批量导入考试成绩</el-button
> >
<el-button type="primary" round :icon="Upload" @click="importScoreVisible = true" v-permission="'v1-expert-check-import-score'" <el-button
type="primary"
round
:icon="Upload"
@click="importScoreVisible = true"
v-permission="'v1-expert-check-import-score'"
>批量导入完整评分</el-button >批量导入完整评分</el-button
> >
</template> </template>
...@@ -170,9 +194,11 @@ function handleViewPicture(row: any) { ...@@ -170,9 +194,11 @@ function handleViewPicture(row: any) {
<span :class="{ 'is-info': row.score_name !== '--' }">{{ row.score_name }}</span> <span :class="{ 'is-info': row.score_name !== '--' }">{{ row.score_name }}</span>
</template> </template>
<template #table-x="{ row }"> <template #table-x="{ row }">
<!-- <el-button text type="primary" v-if="row.publish_status === '0'" v-permission="'v1-expert-check-set-score'"> <el-button text type="primary" v-if="row.publish_status === '0'" v-permission="'v1-expert-check-set-score'">
<router-link :to="`/admin/contest/check/${row.id}`" target="_blank">评分</router-link> <router-link :to="`/admin/contest/check/${row.id}`" target="_blank">评分</router-link>
</el-button> --> </el-button>
<br />
<el-button text type="primary" @click="handleViewReport(row)">查看报告</el-button><br />
<el-button text type="primary" @click="handleViewPicture(row)">查看截图</el-button> <el-button text type="primary" @click="handleViewPicture(row)">查看截图</el-button>
</template> </template>
</AppList> </AppList>
...@@ -182,9 +208,14 @@ function handleViewPicture(row: any) { ...@@ -182,9 +208,14 @@ function handleViewPicture(row: any) {
<!-- 批量导入考试成绩 --> <!-- 批量导入考试成绩 -->
<ImportExamDialog v-model="importExamVisible" @update="onUpdateSuccess" v-if="importExamVisible"></ImportExamDialog> <ImportExamDialog v-model="importExamVisible" @update="onUpdateSuccess" v-if="importExamVisible"></ImportExamDialog>
<!-- 批量导入完整评分 --> <!-- 批量导入完整评分 -->
<ImportScoreDialog v-model="importScoreVisible" @update="onUpdateSuccess" v-if="importScoreVisible"></ImportScoreDialog> <ImportScoreDialog
v-model="importScoreVisible"
@update="onUpdateSuccess"
v-if="importScoreVisible"></ImportScoreDialog>
<!-- 查看截图 --> <!-- 查看截图 -->
<ScoreViewPicturesDialog v-model="viewPictureVisible" :data="rowData" v-if="rowData"></ScoreViewPicturesDialog> <ScoreViewPicturesDialog v-model="viewPictureVisible" :data="rowData" v-if="rowData"></ScoreViewPicturesDialog>
<!-- 查看报告 -->
<ReportDialog v-model="viewReportVisible" :data="rowData" v-if="rowData && viewReportVisible"></ReportDialog>
</template> </template>
<style lang="scss"> <style lang="scss">
......
...@@ -43,6 +43,7 @@ const formRef = $ref<FormInstance>() ...@@ -43,6 +43,7 @@ const formRef = $ref<FormInstance>()
const form = reactive({ const form = reactive({
id: '', id: '',
name: '', name: '',
client_id: '',
host_unit_id: '', host_unit_id: '',
organizer_ids: [], organizer_ids: [],
technical_support_unit_id: '', technical_support_unit_id: '',
...@@ -197,6 +198,7 @@ function handleSubmit() { ...@@ -197,6 +198,7 @@ function handleSubmit() {
const params: ContestUpdateParams = pick(mergedForm, [ const params: ContestUpdateParams = pick(mergedForm, [
'id', 'id',
'name', 'name',
'client_id',
'host_unit_id', 'host_unit_id',
'organizer_ids', 'organizer_ids',
'technical_support_unit_id', 'technical_support_unit_id',
...@@ -244,11 +246,21 @@ function handleDateRangeChange(value: any) { ...@@ -244,11 +246,21 @@ function handleDateRangeChange(value: any) {
form.date = secondDate form.date = secondDate
} }
} }
const clientList = [
{ label: '商务数据分析师赛项', value: 'business_data_analyst' },
{ label: '全媒体运营师赛项', value: 'all_media_operator' }
]
</script> </script>
<template> <template>
<el-dialog :title="title" :close-on-click-modal="false" align-center width="600px" @update:modelValue="value => $emit('update:modelValue', value)"> <el-dialog :title="title" :close-on-click-modal="false" align-center width="600px" @update:modelValue="value => $emit('update:modelValue', value)">
<el-form ref="formRef" :model="form" :rules="rules" label-width="110px"> <el-form ref="formRef" :model="form" :rules="rules" label-width="110px">
<el-form-item label="客户端标识" prop="client_id">
<el-select v-model="form.client_id" style="width: 100%" clearable>
<el-option v-for="item in clientList" :key="item.value" :label="item.label" :value="item.value"></el-option>
</el-select>
</el-form-item>
<el-form-item label="赛项名称" prop="name"> <el-form-item label="赛项名称" prop="name">
<el-input v-model="form.name" :disabled="isUpdate" /> <el-input v-model="form.name" :disabled="isUpdate" />
</el-form-item> </el-form-item>
...@@ -304,7 +316,8 @@ function handleDateRangeChange(value: any) { ...@@ -304,7 +316,8 @@ function handleDateRangeChange(value: any) {
v-for="item in form.train_platform_configs" v-for="item in form.train_platform_configs"
:key="item.platform_key"> :key="item.platform_key">
<div style="display: flex; align-items: center"> <div style="display: flex; align-items: center">
<span style="margin-right: 10px; width: 180px">{{ item.name }}</span> <!-- <span style="margin-right: 10px; width: 180px">{{ item.name }}</span> -->
<el-input v-model="item.name" style="margin-right: 10px; width: 200px" />
<el-input v-model="item.url" /> <el-input v-model="item.url" />
</div> </div>
</el-checkbox> </el-checkbox>
...@@ -318,7 +331,8 @@ function handleDateRangeChange(value: any) { ...@@ -318,7 +331,8 @@ function handleDateRangeChange(value: any) {
v-for="item in form.competition_platform_configs" v-for="item in form.competition_platform_configs"
:key="item.platform_key"> :key="item.platform_key">
<div style="display: flex; align-items: center"> <div style="display: flex; align-items: center">
<span style="margin-right: 10px; width: 180px">{{ item.name }}</span> <!-- <span style="margin-right: 10px; width: 180px">{{ item.name }}</span> -->
<el-input v-model="item.name" style="margin-right: 10px; width: 200px" />
<el-input v-model="item.url" /> <el-input v-model="item.url" />
</div> </div>
</el-checkbox> </el-checkbox>
......
...@@ -12,6 +12,8 @@ const props = defineProps<Props>() ...@@ -12,6 +12,8 @@ const props = defineProps<Props>()
const appList = $ref<InstanceType<typeof AppList> | null>(null) const appList = $ref<InstanceType<typeof AppList> | null>(null)
const route = useRoute()
// 列表配置 // 列表配置
const listOptions = $computed(() => { const listOptions = $computed(() => {
return { return {
...@@ -52,7 +54,7 @@ function onUpdateSuccess() { ...@@ -52,7 +54,7 @@ function onUpdateSuccess() {
<template> <template>
<AppCard title="发布成绩"> <AppCard title="发布成绩">
<el-descriptions> <el-descriptions>
<el-descriptions-item label="赛项名称:">{{ $route.query.name }}</el-descriptions-item> <el-descriptions-item label="赛项名称:">{{ route.query.name }}</el-descriptions-item>
</el-descriptions> </el-descriptions>
<AppList v-bind="listOptions" ref="appList"> <AppList v-bind="listOptions" ref="appList">
<template #table-count="{ row }"> {{ row.checked_count }}/{{ row.need_check_count }} </template> <template #table-count="{ row }"> {{ row.checked_count }}/{{ row.need_check_count }} </template>
......
...@@ -19,7 +19,8 @@ const formRef = ref<FormInstance>() ...@@ -19,7 +19,8 @@ const formRef = ref<FormInstance>()
const form = reactive({ const form = reactive({
experiment_name: '', experiment_name: '',
organ_id: '', organ_id: '',
sso_id: '' sso_id: '',
is_copy_class: 0
}) })
onMounted(() => { onMounted(() => {
...@@ -72,6 +73,14 @@ async function handleSubmit() { ...@@ -72,6 +73,14 @@ async function handleSubmit() {
<el-option v-for="item in teachers" :key="item.id" :label="item.name" :value="item.sso_id"></el-option> <el-option v-for="item in teachers" :key="item.id" :label="item.name" :value="item.sso_id"></el-option>
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item label="是否复制班级" prop="experiment_name">
<el-switch
v-model="form.is_copy_class"
active-text="是"
inactive-text="否"
:active-value="1"
:inactive-value="0" />
</el-form-item>
</el-form> </el-form>
<template #footer> <template #footer>
<el-row justify="center"> <el-row justify="center">
......
...@@ -4,3 +4,8 @@ import httpRequest from '@/utils/axios' ...@@ -4,3 +4,8 @@ import httpRequest from '@/utils/axios'
export function qwenChat(data: any) { export function qwenChat(data: any) {
return httpRequest.post('/api/lab/v1/experiment/qwen/chat', data, { headers: { 'Content-Type': 'application/json' } }) return httpRequest.post('/api/lab/v1/experiment/qwen/chat', data, { headers: { 'Content-Type': 'application/json' } })
} }
// 聊天(流式响应)
export function aiChat(data: any) {
return httpRequest.post('/api/ai/api/chat/data-analysis/generate-chart', data, { headers: { 'Content-Type': 'application/json' } })
}
...@@ -3,7 +3,7 @@ const emit = defineEmits(['success']) ...@@ -3,7 +3,7 @@ const emit = defineEmits(['success'])
const file = ref() const file = ref()
const onSuccess = res => { const onSuccess = res => {
file.value = res.data.detail file.value = res.data
emit('success', file.value) emit('success', file.value)
} }
</script> </script>
...@@ -12,13 +12,13 @@ const onSuccess = res => { ...@@ -12,13 +12,13 @@ const onSuccess = res => {
<el-upload <el-upload
class="ai-upload" class="ai-upload"
drag drag
action="/api/lab/v1/experiment/qwen/upload-file" action="/api/ai/api/upload-file"
accept=".csv, .xls, .xlsx, text/csv, application/csv,text/comma-separated-values, application/csv, application/excel,application/vnd.msexcel, text/anytext, application/vnd. ms-excel,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" accept=".csv, .xls, .xlsx, text/csv, application/csv,text/comma-separated-values, application/csv, application/excel,application/vnd.msexcel, text/anytext, application/vnd. ms-excel,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
:data="{ purpose: 'file-extract' }" :data="{ purpose: 'file-extract' }"
:show-file-list="false" :show-file-list="false"
:on-success="onSuccess"> :on-success="onSuccess">
<ul class="ai-upload-list" v-if="file"> <ul class="ai-upload-list" v-if="file">
<li>{{ file.filename }}</li> <li>{{ file.file_name }}</li>
</ul> </ul>
<div class="ai-upload-box"> <div class="ai-upload-box">
<img src="@/assets/images/ai_plus.png" height="40" /> <img src="@/assets/images/ai_plus.png" height="40" />
......
import { fetchEventSource } from '@microsoft/fetch-event-source' // import { fetchEventSource } from '@microsoft/fetch-event-source'
import { ElMessage } from 'element-plus' // import { ElMessage } from 'element-plus'
// export function useChat() {
// const messages = ref([])
// const isLoading = ref(false)
// async function post() {
// isLoading.value = true
// await fetchEventSource('/api/lab/v1/experiment/qwen/chat', {
// method: 'POST',
// headers: { 'Content-Type': 'application/json' },
// body: JSON.stringify({ model: 'qwen-long', messages: messages.value }),
// async onopen(response) {
// if (response.ok) {
// return response
// } else {
// throw response
// }
// },
// onmessage(res) {
// console.log(res.data)
// try {
// const message = JSON.parse(res.data)
// if (message.error) {
// ElMessage.error(message.error.message)
// return
// }
// const id = message.id
// const messageIndex = messages.value.findIndex(session => session.id === id)
// let content = message?.choices[0]?.delta.content || ''
// content = content.replaceAll('\n', '<br/>')
// if (messageIndex === -1) {
// messages.value.push({ id, role: 'assistant', content })
// } else {
// messages.value[messageIndex].content = messages.value[messageIndex].content + content
// }
// isLoading.value = false
// } catch (error) {
// console.log(error)
// isLoading.value = false
// }
// },
// onerror(err) {
// isLoading.value = false
// throw err
// }
// })
// }
// return { messages, post, isLoading }
// }
import { isString } from 'lodash-es'
import { aiChat } from '../api'
export function useChat() { export function useChat() {
const messages = ref([]) const messages = ref([])
const isLoading = ref(false) const isLoading = ref(false)
async function post(data) {
async function post() {
isLoading.value = true isLoading.value = true
await fetchEventSource('/api/lab/v1/experiment/qwen/chat', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ model: 'qwen-long', messages: messages.value }),
async onopen(response) {
if (response.ok) {
return response
} else {
throw response
}
},
onmessage(res) {
console.log(res.data)
try { try {
const message = JSON.parse(res.data) const response = await aiChat(data)
if (message.error) { const items = response.data.items || []
ElMessage.error(message.error.message) items
return .filter(item => item.type !== 'flow')
} .forEach(item => {
const id = message.id let content = item.content || ''
const messageIndex = messages.value.findIndex(session => session.id === id) if (item.type === 'text') {
let content = message?.choices[0]?.delta.content || ''
content = content.replaceAll('\n', '<br/>') content = content.replaceAll('\n', '<br/>')
if (messageIndex === -1) {
messages.value.push({ id, role: 'assistant', content })
} else {
messages.value[messageIndex].content = messages.value[messageIndex].content + content
} }
isLoading.value = false if (!isString(content)) {
try {
content = JSON.stringify(content)
} catch (error) { } catch (error) {
console.log(error) console.log(error)
isLoading.value = false
} }
},
onerror(err) {
isLoading.value = false
throw err
} }
console.log(content)
messages.value.push({ role: 'assistant', content, type: item.type })
}) })
} catch (error) {
isLoading.value = false
throw error
} finally {
isLoading.value = false
}
} }
return { messages, post, isLoading } return { messages, post, isLoading }
} }
...@@ -5,16 +5,16 @@ const { messages, post, isLoading } = useChat() ...@@ -5,16 +5,16 @@ const { messages, post, isLoading } = useChat()
const chatInput = ref('') const chatInput = ref('')
const file = ref(null)
const onUploadSuccess = res => { const onUploadSuccess = res => {
const message = { role: 'system', content: `fileid://${res.id}` } file.value = res
messages.value.push(message)
} }
async function postMessage() { async function postMessage() {
if (!chatInput.value) return if (!chatInput.value) return
const message = { role: 'user', content: chatInput.value } const message = { role: 'user', content: chatInput.value }
messages.value.push(message) messages.value.push(message)
post(message) post({ file_path: file.value?.file_path, file_name: file.value?.file_name, chart_content: chatInput.value })
chatInput.value = '' chatInput.value = ''
} }
const chatRef = ref() const chatRef = ref()
...@@ -43,7 +43,10 @@ watch(messages.value, () => nextTick(() => scrollToBottom())) ...@@ -43,7 +43,10 @@ watch(messages.value, () => nextTick(() => scrollToBottom()))
<template v-for="(item, index) in messages" :key="index"> <template v-for="(item, index) in messages" :key="index">
<div class="ai-message-item" :class="item.role" v-if="item.role !== 'system'"> <div class="ai-message-item" :class="item.role" v-if="item.role !== 'system'">
<div class="ai-message__avatar"><img :src="item.role === 'assistant' ? '/images/ai_avatar_bot.png' : '/images/ai_avatar_user.png'" /></div> <div class="ai-message__avatar"><img :src="item.role === 'assistant' ? '/images/ai_avatar_bot.png' : '/images/ai_avatar_user.png'" /></div>
<div class="ai-message__content" v-html="item.content"></div> <div class="ai-message__content">
<iframe :srcdoc="item.content" frameborder="0" v-if="item.type === 'html'" width="900" height="500"></iframe>
<div v-html="item.content" v-else></div>
</div>
</div> </div>
</template> </template>
<div class="ai-message-item" v-if="isLoading"> <div class="ai-message-item" v-if="isLoading">
...@@ -76,7 +79,7 @@ watch(messages.value, () => nextTick(() => scrollToBottom())) ...@@ -76,7 +79,7 @@ watch(messages.value, () => nextTick(() => scrollToBottom()))
flex: none; flex: none;
} }
.el-upload-dragger { .el-upload-dragger {
padding: 80px 0; padding: 80px 20px;
} }
} }
} }
......
...@@ -68,6 +68,19 @@ const learnURL = import.meta.env.VITE_SAAS_LEARN_URL ...@@ -68,6 +68,19 @@ const learnURL = import.meta.env.VITE_SAAS_LEARN_URL
margin-left: 0 !important; margin-left: 0 !important;
} }
} }
.system-amo {
.bg {
background: url(@/assets/images/amo_home_student_bg.png) no-repeat center center;
background-size: contain;
}
.link1,
.select1 {
display: none;
}
.select2 {
margin-left: 0 !important;
}
}
.system-x { .system-x {
.bg { .bg {
background: url(@/assets/images/x_home_student_bg.png) no-repeat center center; background: url(@/assets/images/x_home_student_bg.png) no-repeat center center;
......
...@@ -2,6 +2,9 @@ ...@@ -2,6 +2,9 @@
import type { Contest } from '../types' import type { Contest } from '../types'
import ContestItem from '../components/ContestItem.vue' import ContestItem from '../components/ContestItem.vue'
import { getMyContestList, getContestList } from '../api' import { getMyContestList, getContestList } from '../api'
import { useAppConfig } from '@/composables/useAppConfig'
const appConfig = useAppConfig()
let myContestList = $ref<Contest[]>([]) let myContestList = $ref<Contest[]>([])
function fetchMyList() { function fetchMyList() {
getMyContestList().then(res => { getMyContestList().then(res => {
...@@ -27,7 +30,7 @@ onMounted(() => { ...@@ -27,7 +30,7 @@ onMounted(() => {
</div> </div>
<el-empty description="暂无数据" v-else /> <el-empty description="暂无数据" v-else />
</AppCard> </AppCard>
<AppCard title="可参与赛项"> <AppCard title="可参与赛项" v-if="appConfig.hideAvailableEvents !== true">
<div class="contest-list" v-if="contestList.length"> <div class="contest-list" v-if="contestList.length">
<ContestItem :data="item" v-for="item in contestList" :key="item.id"></ContestItem> <ContestItem :data="item" v-for="item in contestList" :key="item.id"></ContestItem>
</div> </div>
......
...@@ -7,6 +7,8 @@ import { upload } from '@/utils/upload' ...@@ -7,6 +7,8 @@ import { upload } from '@/utils/upload'
import { getContest, getExperimentRecord, uploadExperimentPicture } from '../api' import { getContest, getExperimentRecord, uploadExperimentPicture } from '../api'
import dayjs from 'dayjs' import dayjs from 'dayjs'
import { useCookies } from '@vueuse/integrations/useCookies' import { useCookies } from '@vueuse/integrations/useCookies'
import { useAppConfig } from '@/composables/useAppConfig'
const appConfig = useAppConfig()
const Book = defineAsyncComponent(() => import('../components/Book.vue')) const Book = defineAsyncComponent(() => import('../components/Book.vue'))
const Video = defineAsyncComponent(() => import('../components/Video.vue')) const Video = defineAsyncComponent(() => import('../components/Video.vue'))
...@@ -117,7 +119,7 @@ const competitionUrl = computed(() => { ...@@ -117,7 +119,7 @@ const competitionUrl = computed(() => {
return url.includes('?') ? `${url}&token=${cookies.get('TGC')}` : `${url}?token=${cookies.get('TGC')}` return url.includes('?') ? `${url}&token=${cookies.get('TGC')}` : `${url}?token=${cookies.get('TGC')}`
}) })
let isHeadShow = $ref(true) let isHeadShow = $ref(!appConfig.hideContestToolbar)
const handleShowHead = function () { const handleShowHead = function () {
isHeadShow = !isHeadShow isHeadShow = !isHeadShow
} }
...@@ -144,7 +146,7 @@ const reportDialogVisible = $ref(false) ...@@ -144,7 +146,7 @@ const reportDialogVisible = $ref(false)
<!-- <Book :competition_id="id" :key="resizeKey"></Book> --> <!-- <Book :competition_id="id" :key="resizeKey"></Book> -->
<Book :competition_id="id" :experiment_id="getExperimentId"></Book> <Book :competition_id="id" :experiment_id="getExperimentId"></Book>
</el-tab-pane> </el-tab-pane>
<el-tab-pane label="操作视频" lazy v-if="false"> <el-tab-pane label="操作视频" lazy>
<Video :competition_id="id"></Video> <Video :competition_id="id"></Video>
</el-tab-pane> </el-tab-pane>
<el-tab-pane label="讨论交流" lazy> <el-tab-pane label="讨论交流" lazy>
...@@ -153,14 +155,14 @@ const reportDialogVisible = $ref(false) ...@@ -153,14 +155,14 @@ const reportDialogVisible = $ref(false)
<el-tab-pane label="过程与结果" lazy> <el-tab-pane label="过程与结果" lazy>
<Result :competition_id="id" @update="fetchInfo"></Result> <Result :competition_id="id" @update="fetchInfo"></Result>
</el-tab-pane> </el-tab-pane>
<el-tab-pane label="实操试卷" lazy> <el-tab-pane label="实操试卷" lazy v-if="appConfig.hidePracticalTestPaper !== true">
<Exam :competition_id="id"></Exam> <Exam :competition_id="id"></Exam>
</el-tab-pane> </el-tab-pane>
</el-tabs> </el-tabs>
</div> </div>
</template> </template>
<template #right> <template #right>
<AppCard v-if="isHeadShow"> <AppCard v-if="isHeadShow" style="margin-bottom: 20px">
<el-row justify="space-between"> <el-row justify="space-between">
<el-button type="primary" :icon="HomeFilled" @click="handleBackHome">返回首页</el-button> <el-button type="primary" :icon="HomeFilled" @click="handleBackHome">返回首页</el-button>
<div> <div>
...@@ -235,7 +237,6 @@ const reportDialogVisible = $ref(false) ...@@ -235,7 +237,6 @@ const reportDialogVisible = $ref(false)
position: relative; position: relative;
flex: 1; flex: 1;
width: 100%; width: 100%;
margin-top: 20px;
background: #f8f9fa; background: #f8f9fa;
} }
.iframe { .iframe {
......
...@@ -27,7 +27,7 @@ const selectChange = function () { ...@@ -27,7 +27,7 @@ const selectChange = function () {
</script> </script>
<template> <template>
<AppCard> <AppCard>
<h1>2023年全国大学生商业数据分析与应用大赛成绩查询结果</h1> <h1>2024 年湖北工匠杯成绩查询</h1>
<div style="display: flex; align-items: center; padding-top: 30px"> <div style="display: flex; align-items: center; padding-top: 30px">
<span style="margin-right: 10px">赛项</span> <span style="margin-right: 10px">赛项</span>
<el-select style="width: 320px" v-model="eventValue" placeholder="请选择" @change="selectChange"> <el-select style="width: 320px" v-model="eventValue" placeholder="请选择" @change="selectChange">
......
...@@ -5,7 +5,6 @@ import { useAppConfig } from '@/composables/useAppConfig' ...@@ -5,7 +5,6 @@ import { useAppConfig } from '@/composables/useAppConfig'
const appConfig = useAppConfig() const appConfig = useAppConfig()
const httpRequest = axios.create({ const httpRequest = axios.create({
timeout: 60000,
withCredentials: true, withCredentials: true,
headers: { headers: {
'Content-Type': 'application/x-www-form-urlencoded' 'Content-Type': 'application/x-www-form-urlencoded'
......
import fs from 'node:fs' // import fs from 'node:fs'
import path from 'node:path' // import path from 'node:path'
import { fileURLToPath, URL } from 'node:url' import { fileURLToPath, URL } from 'node:url'
import { defineConfig } from 'vite' import { defineConfig } from 'vite'
...@@ -7,6 +7,7 @@ import vue from '@vitejs/plugin-vue' ...@@ -7,6 +7,7 @@ import vue from '@vitejs/plugin-vue'
// import checker from 'vite-plugin-checker' // import checker from 'vite-plugin-checker'
import AutoImport from 'unplugin-auto-import/vite' import AutoImport from 'unplugin-auto-import/vite'
import ReactivityTransform from '@vue-macros/reactivity-transform/vite' import ReactivityTransform from '@vue-macros/reactivity-transform/vite'
import mkcert from 'vite-plugin-mkcert'
export default defineConfig(() => ({ export default defineConfig(() => ({
// base: mode === 'prod' ? 'https://webapp-pub.ezijing.com/website/prod/saas-lab/' : '/', // base: mode === 'prod' ? 'https://webapp-pub.ezijing.com/website/prod/saas-lab/' : '/',
...@@ -17,16 +18,17 @@ export default defineConfig(() => ({ ...@@ -17,16 +18,17 @@ export default defineConfig(() => ({
dts: true, dts: true,
eslintrc: { enabled: true } eslintrc: { enabled: true }
}), }),
ReactivityTransform() ReactivityTransform(),
mkcert()
// checker({ vueTsc: true, eslint: { lintCommand: 'eslint "./src/**/*.{vue,js,jsx,ts,tsx}"' } }) // checker({ vueTsc: true, eslint: { lintCommand: 'eslint "./src/**/*.{vue,js,jsx,ts,tsx}"' } })
], ],
server: { server: {
open: true, open: true,
host: 'dev.ezijing.com', host: 'dev.ezijing.com',
https: { // https: {
key: fs.readFileSync(path.join(__dirname, './https/ezijing.com.key')), // key: fs.readFileSync(path.join(__dirname, './https/ezijing.com.key')),
cert: fs.readFileSync(path.join(__dirname, './https/ezijing.com.pem')) // cert: fs.readFileSync(path.join(__dirname, './https/ezijing.com.pem'))
}, // },
proxy: { proxy: {
'/api': 'https://saas-lab.ezijing.com' '/api': 'https://saas-lab.ezijing.com'
// '/api/resource': { // '/api/resource': {
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论