update:打包配置&页面ui&ai运动站

This commit is contained in:
ltx 2026-07-06 17:27:36 +08:00
parent c8ffd7d3b1
commit 17b973805e
26 changed files with 1319 additions and 119 deletions

View File

@ -1,4 +1,4 @@
# base目录
# base目录
VITE_BASE_URL=/
VITE_API_BASE=http://localhost:14102
VITE_IMAGE_BASE_URL=http://localhost:14102
VITE_API_BASE=http://localhost:52356
VITE_IMAGE_BASE_URL=http://localhost:52356

1
.gitignore vendored
View File

@ -1,6 +1,7 @@
.DS_Store
node_modules/
/dist
/.oss-build
# local env files

Binary file not shown.

View File

@ -15,6 +15,49 @@ npm run serve
npm run build
```
`npm run build` 会自动生成当前时间目录,并上传到阿里云 OSS。默认规则:
- 本地产物目录:`.oss-build/<YYYYMMDDHHmm>`
- OSS 目录:`static/web/samart-static/<YYYYMMDDHHmm>/`
- 资源访问前缀:`https://yuedong-wechatapplet.oss-cn-shanghai.aliyuncs.com/static/web/samart-static/<YYYYMMDDHHmm>/`
上传前需要配置环境变量:
```
OSS_ACCESS_KEY_ID=你的AccessKeyId
OSS_ACCESS_KEY_SECRET=你的AccessKeySecret
```
可选环境变量:
```
OSS_BUCKET=yuedong-wechatapplet
OSS_REGION=oss-cn-shanghai
OSS_PREFIX=static/web/samart-static
OSS_ENDPOINT=yuedong-wechatapplet.oss-cn-shanghai.aliyuncs.com
OSS_PUBLIC_BASE=https://yuedong-wechatapplet.oss-cn-shanghai.aliyuncs.com/static/web/samart-static/自定义目录/
BUILD_TIMESTAMP=202607061710
BUILD_OUT_DIR=.oss-build/202607061710
```
只验证打包、不上传:
```
npm run build -- --skip-upload
```
也可以使用:
```
npm run build:local
```
只做原始 Vite 打包:
```
npm run build:vite
```
### Run your unit tests
```
npm run test:unit

View File

@ -5,7 +5,10 @@
"scripts": {
"dev": "vite",
"serve": "vite preview",
"build": "vite build"
"build": "node scripts/build-oss.mjs",
"build:local": "node scripts/build-oss.mjs --skip-upload",
"build:vite": "vite build",
"build:oss": "node scripts/build-oss.mjs"
},
"dependencies": {
"@icon-park/svg": "^1.4.0",

201
scripts/build-oss.mjs Normal file
View File

@ -0,0 +1,201 @@
import crypto from 'node:crypto'
import fs from 'node:fs/promises'
import https from 'node:https'
import path from 'node:path'
import { spawn } from 'node:child_process'
import { fileURLToPath } from 'node:url'
const __dirname = path.dirname(fileURLToPath(import.meta.url))
const root = path.resolve(__dirname, '..')
const args = new Set(process.argv.slice(2))
const skipUpload = args.has('--skip-upload')
const timestamp = process.env.BUILD_TIMESTAMP || formatTimestamp(new Date())
const bucket = process.env.OSS_BUCKET || 'yuedong-wechatapplet'
const region = process.env.OSS_REGION || 'oss-cn-shanghai'
const endpoint = normalizeEndpoint(process.env.OSS_ENDPOINT || `${bucket}.${region}.aliyuncs.com`)
const ossPrefix = normalizeObjectPrefix(process.env.OSS_PREFIX || 'static/web/samart-static')
const uploadPrefix = normalizeObjectPrefix(`${ossPrefix}/${timestamp}`)
const publicBase = normalizePublicBase(
process.env.OSS_PUBLIC_BASE || `https://${endpoint}/${uploadPrefix}/`
)
const outDir = path.resolve(root, process.env.BUILD_OUT_DIR || path.join('.oss-build', timestamp))
console.log(`[build] timestamp: ${timestamp}`)
console.log(`[build] output: ${path.relative(root, outDir)}`)
console.log(`[build] public base: ${publicBase}`)
await runViteBuild()
if (skipUpload) {
console.log('[oss] skip upload by --skip-upload')
process.exit(0)
}
const accessKeyId = 'LTAI5tAUZ6o8r8K2vkZX4shA'
const accessKeySecret = 'Mz898g6O3OQ8Kzsi7WaK2XPwZFKoRk'
if (!accessKeyId || !accessKeySecret) {
throw new Error(
'Missing OSS credentials. Please set OSS_ACCESS_KEY_ID and OSS_ACCESS_KEY_SECRET, or use `npm run build -- --skip-upload`.'
)
}
const files = await listFiles(outDir)
console.log(`[oss] uploading ${files.length} files to oss://${bucket}/${uploadPrefix}/`)
for (const file of files) {
const relativePath = toPosixPath(path.relative(outDir, file))
const objectKey = `${uploadPrefix}/${relativePath}`
await uploadObject({
file,
objectKey,
bucket,
endpoint,
accessKeyId,
accessKeySecret
})
console.log(`[oss] uploaded ${objectKey}`)
}
console.log(`[oss] done: ${publicBase}`)
function formatTimestamp(date) {
const parts = new Intl.DateTimeFormat('zh-CN', {
timeZone: process.env.BUILD_TIMEZONE || 'Asia/Shanghai',
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
hour12: false
}).formatToParts(date)
const get = (type) => parts.find((item) => item.type === type)?.value || ''
return `${get('year')}${get('month')}${get('day')}${get('hour')}${get('minute')}`
}
function runViteBuild() {
const viteBin = path.resolve(root, 'node_modules/vite/bin/vite.js')
const env = {
...process.env,
BUILD_TIMESTAMP: timestamp,
VITE_BUILD_OUT_DIR: outDir,
VITE_APP_PUBLIC_BASE: publicBase
}
return new Promise((resolve, reject) => {
const child = spawn(process.execPath, [viteBin, 'build'], {
cwd: root,
env,
stdio: 'inherit'
})
child.on('error', reject)
child.on('close', (code) => {
if (code === 0) {
resolve()
} else {
reject(new Error(`vite build failed with exit code ${code}`))
}
})
})
}
async function listFiles(dir) {
const entries = await fs.readdir(dir, { withFileTypes: true })
const files = []
for (const entry of entries) {
const fullPath = path.join(dir, entry.name)
if (entry.isDirectory()) {
files.push(...await listFiles(fullPath))
} else if (entry.isFile()) {
files.push(fullPath)
}
}
return files
}
async function uploadObject({ file, objectKey, bucket, endpoint, accessKeyId, accessKeySecret }) {
const body = await fs.readFile(file)
const contentType = getContentType(file)
const date = new Date().toUTCString()
const resource = `/${bucket}/${objectKey}`
const signature = crypto
.createHmac('sha1', accessKeySecret)
.update(`PUT\n\n${contentType}\n${date}\n${resource}`)
.digest('base64')
const requestPath = `/${objectKey.split('/').map(encodeURIComponent).join('/')}`
await new Promise((resolve, reject) => {
const req = https.request({
method: 'PUT',
hostname: endpoint,
path: requestPath,
headers: {
Authorization: `OSS ${accessKeyId}:${signature}`,
Date: date,
'Content-Type': contentType,
'Content-Length': body.length
}
}, (res) => {
const chunks = []
res.on('data', (chunk) => chunks.push(chunk))
res.on('end', () => {
if (res.statusCode >= 200 && res.statusCode < 300) {
resolve()
} else {
const message = Buffer.concat(chunks).toString('utf8')
reject(new Error(`OSS upload failed: ${res.statusCode} ${res.statusMessage}\n${message}`))
}
})
})
req.on('error', reject)
req.end(body)
})
}
function getContentType(file) {
const ext = path.extname(file).toLowerCase()
const map = {
'.html': 'text/html; charset=utf-8',
'.js': 'application/javascript; charset=utf-8',
'.mjs': 'application/javascript; charset=utf-8',
'.css': 'text/css; charset=utf-8',
'.json': 'application/json; charset=utf-8',
'.svg': 'image/svg+xml',
'.png': 'image/png',
'.jpg': 'image/jpeg',
'.jpeg': 'image/jpeg',
'.gif': 'image/gif',
'.webp': 'image/webp',
'.ico': 'image/x-icon',
'.woff': 'font/woff',
'.woff2': 'font/woff2',
'.ttf': 'font/ttf',
'.otf': 'font/otf',
'.map': 'application/json; charset=utf-8',
'.txt': 'text/plain; charset=utf-8'
}
return map[ext] || 'application/octet-stream'
}
function normalizeEndpoint(value) {
return value.replace(/^https?:\/\//, '').replace(/\/+$/, '')
}
function normalizeObjectPrefix(value) {
return toPosixPath(value).replace(/^\/+|\/+$/g, '')
}
function normalizePublicBase(value) {
return value.endsWith('/') ? value : `${value}/`
}
function toPosixPath(value) {
return value.split(path.sep).join('/')
}

View File

@ -1,6 +1,16 @@
import http from './http.js'
import http from './http.js'
// 获取AI运动站列表
export function GetAiSportsStationList(a, b, c, d, e, f, g, h) {
return http.get(`/api/AiSportsStation/GetAiSportsStationList?StudentName=${a ?? ''}&Sex=${b ?? ''}&ClassId=${c ?? 0}&ProjectName=${d ?? ''}&StartTime=${e ?? ''}&EndTime=${f ?? ''}&PageIndex=${g}&PageSize=${h}`)
export function GetAiSportsStationList(params) {
const query = new URLSearchParams({
StudentName: params?.StudentName ?? '',
Sex: params?.Sex ?? '',
ClassId: params?.ClassId ?? 0,
ProjectName: params?.ProjectName ?? '',
StartTime: params?.StartTime ?? '',
EndTime: params?.EndTime ?? '',
PageIndex: params?.PageIndex ?? 1,
PageSize: params?.PageSize ?? 10
})
return http.get(`/api/AiSportsStation/GetAiSportsStationList?${query.toString()}`)
}

View File

@ -1,7 +1,7 @@
<template>
<div class="commonTable">
<el-table :data="propsChannel" highlight-current-row stripe
style="width: calc(100% - 40px) ;height: calc(100vh - 250px); margin:0 20px ;" fit="true"
style="width: calc(100% - 40px) ;height: calc(100vh - 330px); margin:0 20px ;" fit="true"
@selection-change="handleSelectionChange"
:header-cell-style="{ 'text-align': 'center', 'font-size': '12px', 'color': '#3F404A', padding: '3px', background: '#F8F8FA', height: '50px' }"
:cell-style="getCellStyles" :row-style="{ height: '0px' }">
@ -94,11 +94,12 @@ function goUp(row) {
width: 100%;
padding-bottom: 16px;
box-sizing: border-box;
background-color: #fff;
}
:deep(.el-table) {
width: calc(100% - 36px) !important;
height: calc(100vh - 270px) !important;
height: calc(100vh - 330px) ;
margin: 16px 18px 0 !important;
overflow: hidden;
border-radius: 10px;

View File

@ -1,4 +1,4 @@
<template>
<template>
<commonManage>
<div class="head">
<div class="left">
@ -151,16 +151,16 @@
startTime = val.timeRange[0];
endTime = val.timeRange[1];
}
const res = await _api.GetAiSportsStationList(
val.StudentName,
val.Sex,
val.ClassId,
val.ProjectName,
startTime,
endTime,
val.pageIndex,
val.pageSize
);
const res = await _api.GetAiSportsStationList({
StudentName: val.StudentName,
Sex: val.Sex,
ClassId: val.ClassId,
ProjectName: val.ProjectName,
StartTime: startTime,
EndTime: endTime,
PageIndex: val.pageIndex,
PageSize: val.pageSize,
});
if (res.code == 200) {
data.value.tableNeed.tableData = res.data.datas;
data.value.tableNeed.total = res.data.total;
@ -291,3 +291,4 @@
}
}
</style>

View File

@ -80,6 +80,8 @@ const upStudy = (val) => {
<style lang="less" scoped>
.commonTable {
width: 100%;
background-color: #fff;
}
:deep(.el-table) {

View File

@ -31,7 +31,8 @@
</div>
</div>
<!-- 导出报告弹出框 -->
<el-dialog v-model="exportDialogVisible" title="导出报告" width="520px" :close-on-click-modal="false">
<el-dialog v-model="exportDialogVisible" title="导出报告" width="520px" class="classroom-report-export"
align-center :close-on-click-modal="false">
<div class="export-dialog-body">
<span class="export-label">选择时间段:</span>
<el-date-picker v-model="exportTimeRange" type="datetimerange" range-separator="至"
@ -105,8 +106,9 @@ const data = ref({
pageIndex: 1,
pageSize: 10,
total: 0,
height: 'calc(100vh - 210px)',
height: 'calc(100vh - 280px)',
tdHeight: '56px',
loading: false,
tableLabel: [
{
label: "序号",
@ -672,28 +674,28 @@ function confirm() {
// 点击重置
function reset() {
data.value.classroomModeData.refreshTable = false
data.value.classroomModeData.tableNeed.loading = true
data.value.classroomModeData.gradeAclass = ''
data.value.classroomModeData.teacherName = ''
GetActivitiesPageListApi()
}
// 点击查询
function search() {
data.value.classroomModeData.refreshTable = false
data.value.classroomModeData.tableNeed.loading = true
GetActivitiesPageListApi()
}
// 切换课堂模式每页个数
function handleSizeChange(e) {
console.log(e, 'handleSizeChange')
data.value.classroomModeData.tableNeed.pageSize = e
data.value.classroomModeData.refreshTable = false
data.value.classroomModeData.tableNeed.loading = true
GetActivitiesPageListApi()
}
// 切换课堂模式页数
function changePage(e) {
console.log(e, 'changePage')
data.value.classroomModeData.tableNeed.pageIndex = e
data.value.classroomModeData.refreshTable = false
data.value.classroomModeData.tableNeed.loading = true
GetActivitiesPageListApi()
}
onMounted(() => {
@ -707,6 +709,7 @@ onMounted(() => {
// 分页
async function GetActivitiesPageListApi() {
let k = data.value.classroomModeData
k.tableNeed.loading = true
try {
const res = await _api.GetClassReportList(k.gradeAclass ? k.gradeAclass[0] : '', k.gradeAclass ? k.gradeAclass[1] : '', k.teacherName, k.tableNeed.pageIndex, k.tableNeed.pageSize);
if (res.code == 200) {
@ -720,14 +723,21 @@ async function GetActivitiesPageListApi() {
k.tableNeed.tableData = res.data.datas
k.refreshTable = true
k.tableNeed.loading = false
}
} else {
ElMessage.error(res.message)
k.refreshTable = true
k.tableNeed.loading = false
}
} catch (error) {
console.error('Error occurred while calling API:', error);
k.refreshTable = true
k.tableNeed.loading = false
} finally {
k.refreshTable = true
k.tableNeed.loading = false
}
}
//课堂报告详情
@ -1062,4 +1072,5 @@ function processChartData(rawData) {
}
}
</style>
</style>
<style lang="less" scoped src="../motionData/motion-data-theme.less"></style>

View File

@ -41,12 +41,21 @@
</el-input>
</el-form-item>
</el-form>
<div class="submit-btn" @click="submitForm(ruleFormRef)">
<span>登录</span>
<div class="submit-btn" :class="{ 'is-loading': loginLoading }" @click="submitForm(ruleFormRef)">
<span>{{ loginLoading ? '登录中' : '登录' }}</span>
</div>
<!-- <p class="card-footer">加油,同学!运动让你更棒 ⭐</p> -->
</div>
</div>
<div class="login-loading-mask" v-show="loginLoading">
<div class="login-loading-panel" aria-label="登录中">
<div class="login-loader">
<span></span>
</div>
<p>正在登录</p>
<em>请稍候</em>
</div>
</div>
<!-- <div class="registerDialog" v-if="loginAllData.registerData.registerDialog">
<div class="head">
<p class="register">注册</p>
@ -166,6 +175,7 @@ let $message = appContext.config.globalProperties.$message;
let countdown = ref(60); // 倒计时秒数
let timer = ref(null); // 定时器
let isEmial = ref(false)
const loginLoading = ref(false)
const getCodeColor = computed(() => countdown.value === 60 ? 'gray' : '#4188FE');
const codeText = computed(() => countdown.value === 60 ? '获取验证码' : `${countdown.value} 秒后重试`);
// 图片预加载函数
@ -372,6 +382,7 @@ let loginAllData = ref({
})
//点击登录
const submitForm = (formEl) => {
if (loginLoading.value) return
if (!formEl) return
formEl.validate((valid) => {
console.log(valid, 'valid')
@ -513,6 +524,7 @@ function encryptAES(encryptString, encryptKey) {
}
//登录接口
async function loginApi(val) {
loginLoading.value = true
let encryptAESData = encryptAES(val.password, 'C5ABA9E202D94C43A3CA66002BF77FAF')
let data = {
username: val.userName,
@ -525,7 +537,7 @@ async function loginApi(val) {
if (res1.code == 310) {
$message.success('登录成功,正在跳转!');
store.commit('setUserInfo', res1.data);
router.push({ path: '/' });
await router.push({ path: '/' });
console.log(data.username, 'datadatadata')
localStorage.setItem('username', data.username)
} else {
@ -533,6 +545,8 @@ async function loginApi(val) {
}
} catch (error) {
console.error('Error occurred while calling API:', error);
} finally {
loginLoading.value = false
}
}
//注册接口
@ -636,6 +650,34 @@ async function ModifyPwdApi(val) {
}
}
@keyframes loginPanelIn {
from {
opacity: 0;
transform: translate3d(0, 8px, 0);
}
to {
opacity: 1;
transform: translate3d(0, 0, 0);
}
}
@keyframes loginLoaderSpin {
to {
transform: rotate(360deg);
}
}
@keyframes loginProgressMove {
0% {
background-position: 100% 0;
}
100% {
background-position: -100% 0;
}
}
.login {
width: 100%;
height: 100%;
@ -918,6 +960,14 @@ async function ModifyPwdApi(val) {
transform: translateY(1px) scale(0.99);
box-shadow: 0 4px 14px rgba(0, 120, 255, 0.3);
}
&.is-loading {
cursor: wait;
pointer-events: none;
opacity: 0.88;
box-shadow: 0 6px 18px rgba(0, 120, 255, 0.28);
transform: none;
}
}
.card-footer {
@ -928,5 +978,76 @@ async function ModifyPwdApi(val) {
}
}
}
.login-loading-mask {
position: absolute;
inset: 0;
z-index: 12;
display: flex;
align-items: center;
justify-content: center;
background: rgba(245, 249, 255, 0.46);
}
.login-loading-panel {
width: 156px;
min-height: 136px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
box-sizing: border-box;
padding: 20px 22px 18px;
border: 1px solid rgba(225, 235, 248, 0.95);
border-radius: 12px;
background: rgba(255, 255, 255, 0.96);
box-shadow: 0 16px 36px rgba(25, 78, 148, 0.16);
animation: loginPanelIn 0.22s ease-out both;
p {
margin: 14px 0 0;
color: #17233d;
font-size: 14px;
font-weight: 600;
line-height: 20px;
}
em {
margin-top: 3px;
color: #8a96a8;
font-size: 12px;
font-style: normal;
line-height: 17px;
}
&::after {
content: '';
width: 92px;
height: 3px;
margin-top: 14px;
border-radius: 999px;
background: linear-gradient(90deg, #2487f0 38%, #edf3fb 38%);
background-size: 260% 100%;
animation: loginProgressMove 1.05s ease-in-out infinite;
}
}
.login-loader {
width: 44px;
height: 44px;
position: relative;
border-radius: 50%;
border: 3px solid #dce9fb;
border-top-color: #2487f0;
animation: loginLoaderSpin 0.78s linear infinite;
span {
position: absolute;
inset: 8px;
border-radius: inherit;
border: 2px solid #eef5ff;
border-bottom-color: #7db5f5;
}
}
}
</style>
</style>

View File

@ -0,0 +1,324 @@
.trainingData,
.eventRelease {
width: 100% !important;
min-height: calc(100vh - 108px);
height: auto !important;
display: flex;
gap: 14px;
padding: 12px;
box-sizing: border-box;
background: linear-gradient(180deg, #f4f8ff 0%, #eef4fb 100%);
.left {
flex: 0 0 204px;
width: 204px !important;
height: calc(100vh - 132px) !important;
margin: 0 !important;
overflow: hidden;
align-items: stretch !important;
border: 1px solid #e7eef8;
border-radius: 10px !important;
background: #ffffff !important;
box-shadow: 0 8px 22px rgba(29, 68, 130, 0.06);
.head {
width: 100%;
height: 56px !important;
padding: 8px;
box-sizing: border-box;
gap: 6px;
justify-content: center !important;
border-bottom: 1px solid #edf2f8;
background: linear-gradient(180deg, #ffffff 0%, #f8fbff 100%);
p {
width: auto !important;
min-width: 74px;
height: 34px;
margin: 0;
padding: 0 12px;
border-radius: 8px;
color: #4e5969 !important;
font-size: 14px !important;
font-weight: 600 !important;
line-height: 34px;
transition: all 0.18s ease;
}
.active {
color: #176be7 !important;
background: #eaf3ff;
}
}
.cont {
flex: 1;
width: 100%;
height: auto !important;
padding: 10px 8px 14px;
box-sizing: border-box;
overflow: auto;
.tabOne,
.leftOneActive {
width: 100% !important;
height: 38px !important;
margin: 0 0 6px !important;
border-radius: 8px;
justify-content: flex-start !important;
padding: 0 12px;
box-sizing: border-box;
transition: all 0.18s ease;
p {
margin: 0;
color: #4e5969 !important;
font-size: 14px !important;
font-weight: 500 !important;
line-height: 38px;
}
}
.tabOne:hover {
background: #f2f7ff;
p {
color: #176be7 !important;
}
}
.leftOneActive {
background: linear-gradient(135deg, #2f8dff 0%, #176be7 100%) !important;
box-shadow: 0 7px 16px rgba(47, 141, 255, 0.22);
p {
color: #ffffff !important;
font-weight: 600 !important;
}
}
}
}
.right {
flex: 1 1 auto;
width: auto !important;
min-width: 0;
height: auto !important;
> .head,
> .head1 {
width: 100% !important;
min-height: 72px;
height: auto !important;
margin: 0 0 12px !important;
padding: 16px 18px;
box-sizing: border-box;
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 12px 16px;
justify-content: flex-start !important;
border: 1px solid #e7eef8;
border-radius: 10px !important;
background: #ffffff !important;
box-shadow: 0 8px 22px rgba(29, 68, 130, 0.06);
> div[style*="display: flex"] {
display: flex !important;
flex-wrap: wrap;
gap: 12px 16px;
}
.one {
width: auto !important;
height: 36px !important;
margin: 0 !important;
display: flex;
align-items: center;
justify-content: flex-start !important;
gap: 8px;
p {
width: auto !important;
min-width: 68px;
margin: 0;
color: #4e5969 !important;
font-size: 13px !important;
font-weight: 500 !important;
line-height: 36px;
text-align: right !important;
white-space: nowrap;
}
:deep(.el-cascader),
:deep(.el-select) {
width: 210px !important;
height: 36px !important;
}
:deep(.el-input),
:deep(.el-date-editor) {
width: 210px !important;
height: 36px !important;
}
:deep(.el-date-editor) {
width: 260px !important;
}
:deep(.el-input__wrapper),
:deep(.el-select__wrapper) {
min-height: 36px;
border-radius: 8px;
background: #f8fbff;
box-shadow: 0 0 0 1px #dfe8f5 inset;
transition: all 0.18s ease;
}
:deep(.el-input__wrapper:hover),
:deep(.el-select__wrapper:hover) {
background: #ffffff;
box-shadow: 0 0 0 1px #9fc6ff inset;
}
:deep(.el-input__wrapper.is-focus),
:deep(.el-select__wrapper.is-focused) {
background: #ffffff;
box-shadow: 0 0 0 1px #2f8dff inset, 0 0 0 3px rgba(47, 141, 255, 0.12);
}
}
.btn {
width: auto !important;
height: 36px !important;
margin: 0 0 0 auto !important;
display: flex;
align-items: center;
justify-content: flex-end !important;
gap: 10px;
.reset,
.search,
.export,
.export-btn {
width: auto !important;
min-width: 78px;
height: 36px !important;
margin: 0 !important;
padding: 0 16px;
box-sizing: border-box;
border-radius: 8px !important;
font-size: 14px !important;
font-weight: 500 !important;
line-height: 34px !important;
text-align: center;
cursor: pointer;
user-select: none;
transition: all 0.18s ease;
}
.reset {
color: #4e5969 !important;
border: 1px solid #d9e3f0 !important;
background: #ffffff !important;
}
.search,
.export,
.export-btn {
color: #ffffff !important;
border: 1px solid #176be7 !important;
background: linear-gradient(135deg, #2f8dff 0%, #176be7 100%) !important;
box-shadow: 0 7px 16px rgba(47, 141, 255, 0.2);
}
.reset:hover,
.search:hover,
.export:hover,
.export-btn:hover {
transform: translateY(-1px);
}
}
}
> .classroomMode,
> .trainingDetail {
width: 100%;
height: auto !important;
> .table {
width: 100% !important;
margin: 0 !important;
padding: 0 !important;
overflow: hidden;
border: 1px solid #e7eef8;
border-radius: 10px !important;
background: #ffffff !important;
box-shadow: 0 8px 22px rgba(29, 68, 130, 0.06);
.tableExport {
height: 0 !important;
min-height: 0;
padding: 0;
overflow: hidden;
}
}
}
> .commonTable,
> .table {
overflow: hidden;
border: 1px solid #e7eef8;
border-radius: 10px;
background: #ffffff;
box-shadow: 0 8px 22px rgba(29, 68, 130, 0.06);
}
}
}
:global(.motion-detail-placeholder) {
min-height: 480px;
border: 1px solid #e7eef8;
border-radius: 10px;
background: linear-gradient(180deg, #ffffff 0%, #f8fbff 100%);
}
:global(.classroom-report-export) {
border-radius: 10px;
overflow: hidden;
.el-dialog__header {
margin: 0;
padding: 18px 22px;
border-bottom: 1px solid #edf2f8;
background: linear-gradient(180deg, #ffffff 0%, #f8fbff 100%);
}
.el-dialog__body {
padding: 22px;
}
.el-dialog__footer {
padding: 14px 22px 18px;
border-top: 1px solid #edf2f8;
}
}
@media (max-width: 1280px) {
.trainingData {
.left {
flex-basis: 180px;
width: 180px !important;
}
.right {
> .head,
> .head1 {
.btn {
width: 100% !important;
margin-left: 0 !important;
}
}
}
}
}

View File

@ -0,0 +1,144 @@
.trainingDetail {
width: 100% !important;
min-height: calc(100vh - 108px);
height: auto !important;
padding: 12px;
box-sizing: border-box;
background: linear-gradient(180deg, #f4f8ff 0%, #eef4fb 100%);
> .head {
width: 100% !important;
min-height: 72px;
height: auto !important;
margin: 0 0 12px !important;
padding: 16px 18px;
box-sizing: border-box;
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 12px 16px;
justify-content: flex-start !important;
border: 1px solid #e7eef8;
border-radius: 10px !important;
background: #ffffff !important;
box-shadow: 0 8px 22px rgba(29, 68, 130, 0.06);
.one {
width: auto !important;
height: 36px !important;
margin: 0 !important;
display: flex;
align-items: center;
gap: 8px;
p {
width: auto !important;
min-width: 68px;
margin: 0;
color: #4e5969 !important;
font-size: 13px !important;
font-weight: 500 !important;
line-height: 36px;
text-align: right !important;
white-space: nowrap;
}
:deep(.el-select),
:deep(.el-input),
:deep(.el-date-editor) {
width: 210px !important;
height: 36px !important;
}
:deep(.el-input__wrapper),
:deep(.el-select__wrapper) {
min-height: 36px;
border-radius: 8px;
background: #f8fbff;
box-shadow: 0 0 0 1px #dfe8f5 inset;
transition: all 0.18s ease;
}
:deep(.el-input__wrapper:hover),
:deep(.el-select__wrapper:hover) {
background: #ffffff;
box-shadow: 0 0 0 1px #9fc6ff inset;
}
}
.btn {
width: auto !important;
height: 36px !important;
margin: 0 0 0 auto !important;
display: flex;
align-items: center;
justify-content: flex-end !important;
gap: 10px;
.reset,
.search {
width: auto !important;
min-width: 78px;
height: 36px !important;
margin: 0 !important;
padding: 0 16px;
box-sizing: border-box;
border-radius: 8px !important;
font-size: 14px !important;
font-weight: 500 !important;
line-height: 34px !important;
text-align: center;
cursor: pointer;
user-select: none;
transition: all 0.18s ease;
}
.reset {
color: #4e5969 !important;
border: 1px solid #d9e3f0 !important;
background: #ffffff !important;
}
.search {
color: #ffffff !important;
border: 1px solid #176be7 !important;
background: linear-gradient(135deg, #2f8dff 0%, #176be7 100%) !important;
box-shadow: 0 7px 16px rgba(47, 141, 255, 0.2);
}
}
}
> .table {
width: 100% !important;
margin: 0 !important;
overflow: hidden;
border: 1px solid #e7eef8;
border-radius: 10px !important;
background: #ffffff !important;
box-shadow: 0 8px 22px rgba(29, 68, 130, 0.06);
.tableExport {
height: 58px !important;
padding: 0 18px;
box-sizing: border-box;
justify-content: flex-end !important;
border-bottom: 1px solid #edf2f8;
p {
width: auto !important;
min-width: 78px;
height: 36px !important;
margin: 0 !important;
padding: 0 16px;
border: 1px solid #176be7 !important;
border-radius: 8px !important;
background: linear-gradient(135deg, #2f8dff 0%, #176be7 100%) !important;
box-shadow: 0 7px 16px rgba(47, 141, 255, 0.2);
color: #ffffff !important;
font-size: 14px !important;
font-weight: 500 !important;
line-height: 34px !important;
}
}
}
}

View File

@ -16,9 +16,8 @@ import * as _api from '@/api/grade';
import * as _api1 from '@/api/TrainingData';
import { ElMessage } from 'element-plus';
import commonTable from '@/views/motionData/trainingData/components/commonTbale/index.vue';
import eventBus from '@/eventBus.js';
import axios from 'axios'
const emits = defineEmits(['exportData','handleSizeChange','changePage','video'])
const emits = defineEmits(['exportData','handleSizeChange','changePage','video','openDetail'])
const props = defineProps<{
data: any
}>()
@ -35,7 +34,7 @@ function changePage(e) {
function toView(e) {
console.log(e, 'toView')
// return
eventBus.emit('phyClassRoomDetail', e);
emits('openDetail', e)
}
function video(e) {
console.log(e, 'videoclass')
@ -55,8 +54,8 @@ function exportData() {
.classroomMode {
width: 100%;
height: auto;
background-color: #fff;
border-radius: 10px;
.table {
width: calc(100% - 24px);
@ -66,7 +65,7 @@ function exportData() {
.tableExport {
width: 100%;
height: 60px;
height: 30px;
display: flex;
align-items: center;
justify-content: right;
@ -90,4 +89,4 @@ function exportData() {
}
}
}
</style>
</style>

View File

@ -53,6 +53,9 @@ import commonTable from '@/views/motionData/trainingData/components/commonTbale/
import { useRouter, useRoute } from "vue-router";
import axios from 'axios'
const route = useRoute();
const props = defineProps<{
detailData?: any
}>()
const DataSourceData = ref([{
value: 1,
label: '物联网',
@ -120,7 +123,7 @@ const data = ref({
})
onMounted(() => {
console.log(route.params, 'route')
data.value.paramsData = route.params ? route.params : JSON.parse(localStorage.getItem('physicalDetail'))
data.value.paramsData = props.detailData || (Object.keys(route.params || {}).length ? route.params : JSON.parse(localStorage.getItem('physicalDetail') || '{}'))
console.log(data.value.paramsData, 'routerouterouteroute')
if (Number(data.value.paramsData.itemType) == 1) {
data.value.tableNeed.tableLabel = [
@ -206,17 +209,17 @@ function reset() {
data.value.gender = ''
data.value.level = ''
data.value.DataSource = 1
data.value.dataShow = false
data.value.tableNeed.loading = true
GetSportsTestDetailsApi(data.value)
}
// 点击查询
function search() {
data.value.dataShow = false
data.value.tableNeed.loading = true
GetSportsTestDetailsApi(data.value)
}
function changePage(e) {
data.value.tableNeed.pageIndex = e
data.value.dataShow = false
data.value.tableNeed.loading = true
GetSportsTestDetailsApi(data.value)
console.log(e, 'changePage')
}
@ -226,12 +229,13 @@ function toView(e) {
}
function handleSizeChange(e) {
data.value.tableNeed.pageSize = e
data.value.dataShow = false
data.value.tableNeed.loading = true
GetSportsTestDetailsApi(data.value)
console.log(e, 'handleSizeChange')
}
// 课堂模式详情接口
async function GetSportsTestDetailsApi(val) {
val.tableNeed.loading = true
try {
const cub=val.paramsData.startTime+''
console.log(cub,'val.paramsData.startTime');
@ -248,11 +252,19 @@ async function GetSportsTestDetailsApi(val) {
val.tableNeed.total = res.data.total
val.tableNeed.tableData = res.data.datas
data.value.dataShow = true
val.tableNeed.loading = false
}else{
ElMessage.error(res.message)
data.value.dataShow = true
val.tableNeed.loading = false
}
} catch (error) {
console.error('Error occurred while calling API:', error);
data.value.dataShow = true
val.tableNeed.loading = false
} finally {
data.value.dataShow = true
val.tableNeed.loading = false
}
}
// 导出
@ -403,4 +415,5 @@ function exportData() {
}
}
}
</style>
</style>
<style lang="less" scoped src="../../../motion-detail-theme.less"></style>

View File

@ -97,19 +97,27 @@
</div>
</div>
<classroomMode :data="data.classroomModeData" v-if="data.active == 0 && data.classroomModeData.refreshTable"
@exportData="exportData" @handle-size-change="handleSizeChange" @change-page="changePage">
@exportData="exportData" @handle-size-change="handleSizeChange" @change-page="changePage"
@open-detail="openDetailDialog">
</classroomMode>
<freeMode :data="data.freeModeData" v-if="data.active == 1 && data.freeModeData.refreshTable"
@handleExport="handleExport" @handle-size-change="handleSizeChangeF" @change-page="changePageF">
</freeMode>
</div>
<el-dialog v-model="detailDialog.visible" title="体测数据详情" width="86%" class="motion-detail-dialog"
append-to-body align-center destroy-on-close :close-on-click-modal="false" @opened="handleDetailOpened"
@closed="handleDetailClosed">
<div v-if="!detailDialog.contentReady" class="motion-detail-placeholder" v-loading="true"></div>
<physicalDetail v-else :key="detailDialog.key" :detail-data="detailDialog.row" />
</el-dialog>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue';
import classroomMode from '@/views/motionData/physicalTestData/components/physicalClass/index.vue';
import freeMode from '@/views/motionData/physicalTestData/components/physicalFreeMode/index.vue';
import physicalDetail from '@/views/motionData/physicalTestData/components/physicalDetail/index.vue';
import * as _api from '@/api/TrainingData';
import * as _api1 from '@/api/grade';
import { ElMessage } from 'element-plus';
@ -582,6 +590,34 @@ const data = ref({
})
const detailDialog = ref({
visible: false,
contentReady: false,
key: 0,
row: null as any
})
let detailContentTimer: number | undefined
function openDetailDialog(row) {
if (detailContentTimer) {
window.clearTimeout(detailContentTimer)
}
detailDialog.value.row = row
detailDialog.value.key += 1
detailDialog.value.contentReady = false
detailDialog.value.visible = true
}
function handleDetailOpened() {
detailContentTimer = window.setTimeout(() => {
detailDialog.value.contentReady = true
}, 80)
}
function handleDetailClosed() {
if (detailContentTimer) {
window.clearTimeout(detailContentTimer)
}
detailDialog.value.contentReady = false
detailDialog.value.row = null
}
const changeTab = (e) => {
data.value.active = e
data.value.classroomModeData.currentActive.currentTopTab = e
@ -633,13 +669,13 @@ const leftOneTabClick = (e, k) => {
data.value.classroomModeData.tableNeed.tableLabel = data.value.tableLabel7
break
}
data.value.classroomModeData.refreshTable = false
data.value.classroomModeData.tableNeed.loading = true
data.value.classroomModeData.currentActive.currentLeftActive = e
reset(0)
// GetClassRoomModeDataListApi()
} else {
data.value.leftTwoActive = e
data.value.freeModeData.refreshTable = false
data.value.freeModeData.tableNeed.loading = true
data.value.freeModeData.currentActive.currentLeftActive = e
reset(1)
// setTimeout(() => {
@ -652,14 +688,14 @@ const leftOneTabClick = (e, k) => {
// 点击重置
function reset(val) {
if (val == 0) {
data.value.classroomModeData.refreshTable = false
data.value.classroomModeData.tableNeed.loading = true
data.value.classroomModeData.startTime = ''
data.value.classroomModeData.gradeAclass = ''
data.value.classroomModeData.teacherName = ''
data.value.classroomModeData.DataSource = ''
GetClassRoomModeDataListApi()
} else {
data.value.freeModeData.refreshTable = false
data.value.freeModeData.tableNeed.loading = true
data.value.freeModeData.gradeAclass = ''
data.value.freeModeData.studentName = ''
data.value.freeModeData.studentNo = ''
@ -674,10 +710,10 @@ function reset(val) {
// 点击查询
function search(val) {
if (val == 0) {
data.value.classroomModeData.refreshTable = false
data.value.classroomModeData.tableNeed.loading = true
GetClassRoomModeDataListApi()
} else {
data.value.freeModeData.refreshTable = false
data.value.freeModeData.tableNeed.loading = true
GetFreeModeDataListApi()
}
@ -699,28 +735,28 @@ function handleExport(e) {
function handleSizeChange(e) {
console.log(e, 'handleSizeChange')
data.value.classroomModeData.tableNeed.pageSize = e
data.value.classroomModeData.refreshTable = false
data.value.classroomModeData.tableNeed.loading = true
GetClassRoomModeDataListApi()
}
// 切换课堂模式页数
function changePage(e) {
console.log(e, 'changePage')
data.value.classroomModeData.tableNeed.pageIndex = e
data.value.classroomModeData.refreshTable = false
data.value.classroomModeData.tableNeed.loading = true
GetClassRoomModeDataListApi()
}
// 切换自由模式每页个数
function handleSizeChangeF(e) {
console.log(e, 'handleSizeChangeF')
data.value.freeModeData.tableNeed.pageSize = e
data.value.freeModeData.refreshTable = false
data.value.freeModeData.tableNeed.loading = true
GetFreeModeDataListApi()
}
// 切换自由模式页数
function changePageF(e) {
console.log(e, 'changePageF')
data.value.freeModeData.tableNeed.pageIndex = e
data.value.freeModeData.refreshTable = false
data.value.freeModeData.tableNeed.loading = true
GetFreeModeDataListApi()
}
onMounted(() => {
@ -779,6 +815,7 @@ async function initList() {
// 课堂模式分页
async function GetClassRoomModeDataListApi() {
let k = data.value.classroomModeData
k.tableNeed.loading = true
try {
const res = await _api.GetSportsTestDataList(k.currentActive.currentLeftActive, k.startTime ? k.startTime[0] : '', k.startTime ? k.startTime[1] : '', k.gradeAclass ? k.gradeAclass[1] : '', k.teacherName,data.value.classroomModeData.DataSource,k.tableNeed.pageIndex, k.tableNeed.pageSize);
if (res.code == 200) {
@ -800,18 +837,26 @@ async function GetClassRoomModeDataListApi() {
k.tableNeed.total = res.data.total
k.tableNeed.tableData = res.data.datas
k.refreshTable = true
k.tableNeed.loading = false
}
} else {
ElMessage.error(res.message)
k.refreshTable = true
k.tableNeed.loading = false
}
} catch (error) {
console.error('Error occurred while calling API:', error);
k.refreshTable = true
k.tableNeed.loading = false
} finally {
k.refreshTable = true
k.tableNeed.loading = false
}
}
// 自由模式分页
async function GetFreeModeDataListApi() {
let k = data.value.freeModeData
k.tableNeed.loading = true
try {
const res = await _api.GetFreeModeDataList('', k.currentActive.currentLeftActive, k.gradeAclass ? k.gradeAclass[1] : "", k.studentNo, k.studentName, k.gender, k.startTime ? k.startTime[0] : '', k.startTime ? k.startTime[1] : '', k.tableNeed.pageIndex, k.tableNeed.pageSize);
if (res.code == 200) {
@ -829,13 +874,20 @@ async function GetFreeModeDataListApi() {
k.tableNeed.total = res.data.total
k.tableNeed.tableData = res.data.datas
k.refreshTable = true
k.tableNeed.loading = false
}
} else {
ElMessage.error(res.message)
k.refreshTable = true
k.tableNeed.loading = false
}
} catch (error) {
console.error('Error occurred while calling API:', error);
k.refreshTable = true
k.tableNeed.loading = false
} finally {
k.refreshTable = true
k.tableNeed.loading = false
}
}
// daochu
@ -1143,4 +1195,5 @@ function exportF() {
}
}
</style>
</style>
<style lang="less" scoped src="../motion-data-theme.less"></style>

View File

@ -15,8 +15,7 @@ import { onMounted, ref } from 'vue';
import * as _api from '@/api/grade';
import { ElMessage } from 'element-plus';
import commonTable from '@/views/motionData/trainingData/components/commonTbale/index.vue';
import eventBus from '@/eventBus.js';
const emits = defineEmits(['changePage', 'handleSizeChange','exportData'])
const emits = defineEmits(['changePage', 'handleSizeChange','exportData','openDetail'])
const props = defineProps<{
data: any
}>()
@ -35,7 +34,7 @@ function changePage(e) {
// 查看详情
function toView(e) {
console.log(e, 'toView')
eventBus.emit('classRoomDetail', e);
emits('openDetail', e)
}
function handleSizeChange(e) {
// console.log(e, 'handleSizeChange')
@ -50,10 +49,9 @@ function exportData() {
<style lang="less" scoped>
.classroomMode {
width: 100%;
height: auto;
height: 100%;
background-color: #fff;
border-radius: 10px;
.table {
width: calc(100% - 24px);
background-color: #fff;
@ -62,7 +60,7 @@ function exportData() {
.tableExport {
width: 100%;
height: 60px;
height: 30px;
display: flex;
align-items: center;
justify-content: right;
@ -86,4 +84,4 @@ function exportData() {
}
}
}
</style>
</style>

View File

@ -67,10 +67,17 @@
</div>
</div>
<gradeList :data="data.classroomModeData" v-if="data.classroomModeData.refreshTable"
@change-page="changePage" @handle-size-change="handleSizeChange" @export-data="exportData">
@change-page="changePage" @handle-size-change="handleSizeChange" @export-data="exportData"
@open-detail="openDetailDialog">
</gradeList>
</div>
</div>
<el-dialog v-model="scoreDetailDialog.visible" title="体测成绩详情" width="86%" class="motion-detail-dialog"
append-to-body align-center destroy-on-close :close-on-click-modal="false" @opened="handleScoreDetailOpened"
@closed="handleScoreDetailClosed">
<div v-if="!scoreDetailDialog.contentReady" class="motion-detail-placeholder" v-loading="true"></div>
<trainingDetail v-else :key="scoreDetailDialog.key" :detail-data="scoreDetailDialog.row" />
</el-dialog>
<commonDialog :list="data.detailDialogData" @cancle="cancle" @confirm="confirm">
<template #cont>
<div class="dialogManage">
@ -93,6 +100,7 @@
<script setup lang="ts">
import { ref, onMounted } from 'vue';
import gradeList from '@/views/motionData/summaryOfGrades/components/gradeList/index.vue';
import trainingDetail from '@/views/motionData/trainingData/components/trainingDetail/index.vue';
import commonDialog from '@/components/commonDialog/index.vue';
import * as _api from '@/api/grade';
import * as _api1 from '@/api/TrainingData';
@ -236,6 +244,34 @@ const data = ref({
}
})
const scoreDetailDialog = ref({
visible: false,
contentReady: false,
key: 0,
row: null as any
})
let scoreDetailContentTimer: number | undefined
function openDetailDialog(row) {
if (scoreDetailContentTimer) {
window.clearTimeout(scoreDetailContentTimer)
}
scoreDetailDialog.value.row = row
scoreDetailDialog.value.key += 1
scoreDetailDialog.value.contentReady = false
scoreDetailDialog.value.visible = true
}
function handleScoreDetailOpened() {
scoreDetailContentTimer = window.setTimeout(() => {
scoreDetailDialog.value.contentReady = true
}, 80)
}
function handleScoreDetailClosed() {
if (scoreDetailContentTimer) {
window.clearTimeout(scoreDetailContentTimer)
}
scoreDetailDialog.value.contentReady = false
scoreDetailDialog.value.row = null
}
function changeSwitch(e, k) {
if (e == 0) {
data.value.detailDialogData.switch2 = false
@ -256,12 +292,12 @@ function changeClassroomMode(e) {
}
function changePage(e) {
data.value.classroomModeData.tableNeed.pageIndex = e
data.value.classroomModeData.refreshTable = false
data.value.classroomModeData.tableNeed.loading = true
GetOverallDataPageListApi(data.value.classroomModeData)
}
function handleSizeChange(e) {
data.value.classroomModeData.tableNeed.pageSize = e
data.value.classroomModeData.refreshTable = false
data.value.classroomModeData.tableNeed.loading = true
GetOverallDataPageListApi(data.value.classroomModeData)
}
// 导出
@ -343,13 +379,13 @@ function reset() {
data.value.classroomModeData.dataSource = ''
data.value.classroomModeData.tableNeed.pageIndex = 1
data.value.classroomModeData.tableNeed.pageSize = 10
data.value.classroomModeData.refreshTable = false
data.value.classroomModeData.tableNeed.loading = true
// GetOverallDataPageListApi(data.value.classroomModeData)
GetTableLabel()
}
// 点击查询
function search() {
data.value.classroomModeData.refreshTable = false
data.value.classroomModeData.tableNeed.loading = true
GetOverallDataPageListApi(data.value.classroomModeData)
}
// 格式转换
@ -365,7 +401,7 @@ function transformData(data) {
}
const leftOneTabClick = (e) => {
data.value.leftOneActive = e
data.value.classroomModeData.refreshTable = false
data.value.classroomModeData.tableNeed.loading = true
reset()
}
// 获取所有年级
@ -479,6 +515,7 @@ async function GetTableLabel() {
}
// 体测成绩汇总列表
async function GetOverallDataPageListApi(val) {
val.tableNeed.loading = true
try {
const res = await _api1.GetOverallDataPageList(data.value.leftOneActive, val.gradeAclass, val.studentNo, val.studentName, val.gender, val.achievement, val.tableNeed.pageIndex, val.tableNeed.pageSize);
if (res.code == 200) {
@ -502,12 +539,20 @@ async function GetOverallDataPageListApi(val) {
// }
})
val.refreshTable = true
val.tableNeed.loading = false
}
} else {
ElMessage.error(res.message)
val.refreshTable = true
val.tableNeed.loading = false
}
} catch (error) {
console.error('Error occurred while calling API:', error);
val.refreshTable = true
val.tableNeed.loading = false
} finally {
val.refreshTable = true
val.tableNeed.loading = false
}
}
</script>
@ -701,4 +746,5 @@ async function GetOverallDataPageListApi(val) {
}
}
}
</style>
</style>
<style lang="less" scoped src="../motion-data-theme.less"></style>

View File

@ -16,9 +16,8 @@ import * as _api from '@/api/grade';
import * as _api1 from '@/api/TrainingData';
import { ElMessage } from 'element-plus';
import commonTable from '@/views/motionData/trainingData/components/commonTbale/index.vue';
import eventBus from '@/eventBus.js';
import axios from 'axios'
const emits = defineEmits(['exportData','handleSizeChange','changePage'])
const emits = defineEmits(['exportData','handleSizeChange','changePage','openDetail'])
const props = defineProps<{
data: any
}>()
@ -34,7 +33,7 @@ function changePage(e) {
// 查看详情
function toView(e) {
console.log(e, 'toView')
eventBus.emit('classRoomDetail', e);
emits('openDetail', e)
}
function handleSizeChange(e) {
// console.log(e, 'handleSizeChange')
@ -49,9 +48,8 @@ function exportData() {
.classroomMode {
width: 100%;
height: auto;
border-radius: 10px;
background-color: #fff;
.table {
width: calc(100% - 24px);
background-color: #fff;
@ -60,7 +58,7 @@ function exportData() {
.tableExport {
width: 100%;
height: 60px;
height: 30px;
display: flex;
align-items: center;
justify-content: right;
@ -84,4 +82,4 @@ function exportData() {
}
}
}
</style>
</style>

View File

@ -1,6 +1,6 @@
<template>
<div class="commonTable">
<el-table :data="propsTableNeed.tableData" highlight-current-row style="width: calc(100% - 20px);height: calc(100% - 220px); margin:0 auto;"
<div class="commonTable" :class="{ 'is-switching': switching }">
<el-table v-loading="switching || propsTableNeed.loading" :data="propsTableNeed.tableData" highlight-current-row style="width: 100%; margin: 0;"
tooltip-effect="dark" :height="propsTableNeed.height" fit="true" @selection-change="handleSelectionChange"
:header-cell-style="{ 'text-align': 'center', 'font-size': '12px', 'color': '#3F404A', padding: '3px', background: '#F8F8FA', height: '50px' }"
:cell-style="getCellStyles" :row-style="{ height: '0px' }">
@ -49,10 +49,27 @@ let tableLabel = ref(propsTableNeed.value.tableLabel)
let pageIndex = ref(propsTableNeed.value.pageIndex)
let pageSize = ref(propsTableNeed.value.pageSize)
let total = ref(propsTableNeed.value.total)
let switchTimer: any = null
const switching = ref(false)
const emits = defineEmits(['changeSize', 'handleSelectionChange', 'toView', 'handleSizeChange', 'edit', 'upStudy','video','report'])
onMounted(() => {
})
onBeforeUnmount(() => {
if (switchTimer) {
clearTimeout(switchTimer)
}
})
const startSwitching = () => {
switching.value = true
if (switchTimer) {
clearTimeout(switchTimer)
}
switchTimer = setTimeout(() => {
switching.value = false
switchTimer = null
}, 360)
}
function getCellStyles({ row, column, rowIndex, columnIndex }) {
const prop = column.property;
return {
@ -62,6 +79,7 @@ function getCellStyles({ row, column, rowIndex, columnIndex }) {
}
const changeSize = (e) => {
pageIndex.value = e
startSwitching()
emits("changeSize", e)
}
const handleSelectionChange = (val) => {
@ -75,6 +93,8 @@ const video=(val)=>{
emits("video", val)
}
const handleSizeChange = (val) => {
pageSize.value = val
startSwitching()
emits("handleSizeChange", val)
}
const edit = (val) => {
@ -124,6 +144,7 @@ const report = (val) => {
height: 60px;
margin-top: 5px;
margin-left: 20px;
margin-right: 20px;
}
:deep(.defaultData) {
@ -211,4 +232,127 @@ const report = (val) => {
margin: 0 auto;
}
}
</style>
.commonTable {
width: 100%;
padding: 0;
box-sizing: border-box;
background: #ffffff;
border-radius: 10px;
transition: opacity 0.18s ease;
}
.commonTable.is-switching {
opacity: 0.88;
}
:deep(.el-table) {
width: 100% !important;
margin: 0 !important;
overflow: hidden;
border: 1px solid #e7eef8;
border-radius: 10px;
color: #344054;
box-shadow: none;
}
:deep(.el-table::before),
:deep(.el-table__inner-wrapper::before) {
display: none;
}
:deep(.el-table__header-wrapper th.el-table__cell) {
height: 48px !important;
padding: 0 !important;
background: #f5f8fc !important;
border-bottom: 1px solid #e7eef8 !important;
color: #344054 !important;
font-size: 13px !important;
font-weight: 600;
}
:deep(.el-table__body td.el-table__cell) {
padding: 0 !important;
border-bottom: 1px solid #edf2f8;
color: #4e5969;
font-size: 13px;
}
:deep(.el-table__body tr.el-table__row:hover > td.el-table__cell) {
background: #f7fbff !important;
}
:deep(.el-table__body tr.current-row > td.el-table__cell) {
background: #eef6ff !important;
}
:deep(.el-table .cell) {
padding: 0 10px;
line-height: 22px;
}
:deep(.defaultData) {
color: #4e5969;
}
.operate {
gap: 8px;
min-height: 28px;
}
.toView,
.needUploadTask,
.edit,
.editPassWord {
width: auto;
min-width: 58px;
height: 26px;
padding: 0 8px;
border-radius: 7px;
background: #edf5ff;
color: #176be7;
font-size: 13px;
line-height: 26px;
transition: all 0.18s ease;
}
.toView:hover,
.needUploadTask:hover,
.edit:hover,
.editPassWord:hover {
background: #dcecff;
}
.delete {
width: auto;
min-width: 58px;
height: 26px;
padding: 0 8px;
border-radius: 7px;
background: #fff1f0;
color: #e5484d;
font-size: 13px;
line-height: 26px;
}
:deep(.el-pagination) {
height: 56px;
margin: 0 20px !important;
padding: 0 4px;
display: flex;
align-items: center;
justify-content: flex-end;
}
:deep(.el-pagination .btn-prev),
:deep(.el-pagination .btn-next),
:deep(.el-pagination .el-pager li),
:deep(.el-pagination .el-input__wrapper) {
border-radius: 7px;
}
:deep(.el-pagination .el-pager li.is-active) {
color: #ffffff;
background: #176be7;
}
</style>

View File

@ -51,6 +51,9 @@ import * as _api from '@/api/TrainingData';
import { useRouter, useRoute } from "vue-router";
import axios from 'axios'
const route = useRoute();
const props = defineProps<{
detailData?: any
}>()
const data = ref({
studentName: '',
studentNo: '',
@ -155,7 +158,7 @@ const data = ref({
})
onMounted(() => {
console.log(route, 'route')
data.value.paramsData = route.params.value ? route.params.value : JSON.parse(localStorage.getItem('classRoomDetail'))
data.value.paramsData = props.detailData || (route.params.value ? route.params.value : JSON.parse(localStorage.getItem('classRoomDetail') || '{}'))
console.log(data.value.paramsData, 'routerouterouteroute')
GetClassRoomModeDataDetailsApi(data.value)
})
@ -166,17 +169,18 @@ function reset() {
data.value.studentNo = ''
data.value.gender = ''
data.value.level = ''
data.value.tableNeed.refresh = false
data.value.tableNeed.loading = true
GetClassRoomModeDataDetailsApi(data.value)
}
// 点击查询
function search() {
data.value.tableNeed.refresh = false
data.value.tableNeed.loading = true
GetClassRoomModeDataDetailsApi(data.value)
}
function changePage(e) {
console.log(e, 'changePage')
data.value.tableNeed.pageIndex = e
data.value.tableNeed.loading = true
GetClassRoomModeDataDetailsApi(data.value)
}
// 查看详情
@ -186,10 +190,12 @@ function toView(e) {
function handleSizeChange(e) {
console.log(e, 'handleSizeChange')
data.value.tableNeed.pageSize = e
data.value.tableNeed.loading = true
GetClassRoomModeDataDetailsApi(data.value)
}
// 课堂模式详情接口
async function GetClassRoomModeDataDetailsApi(val) {
val.tableNeed.loading = true
try {
const res = await _api.GetClassRoomModeDataDetails(val.paramsData.id,'',data.value.paramsData.itemType, val.studentNo, val.studentName, val.gender, val.level, val.tableNeed.pageIndex, val.tableNeed.pageSize);
if (res.code == 200) {
@ -201,9 +207,19 @@ async function GetClassRoomModeDataDetailsApi(val) {
val.tableNeed.total = res.data.total
val.tableNeed.tableData = res.data.datas
data.value.tableNeed.refresh = true
val.tableNeed.loading = false
} else {
ElMessage.error(res.message)
data.value.tableNeed.refresh = true
val.tableNeed.loading = false
}
} catch (error) {
console.error('Error occurred while calling API:', error);
data.value.tableNeed.refresh = true
val.tableNeed.loading = false
} finally {
data.value.tableNeed.refresh = true
val.tableNeed.loading = false
}
}
// 导出
@ -355,4 +371,5 @@ function exportData() {
}
}
}
</style>
</style>
<style lang="less" scoped src="../../../motion-detail-theme.less"></style>

View File

@ -96,19 +96,27 @@
</div>
</div>
<classroomMode :data="data.classroomModeData" v-if="data.active == 0 && data.classroomModeData.refreshTable"
@exportData="exportData" @handle-size-change="handleSizeChange" @change-page="changePage">
@exportData="exportData" @handle-size-change="handleSizeChange" @change-page="changePage"
@open-detail="openDetailDialog">
</classroomMode>
<freeMode :data="data.freeModeData" v-if="data.active == 1 && data.freeModeData.refreshTable"
@handleExport="handleExport" @handle-size-change="handleSizeChangeF" @change-page="changePageF">
</freeMode>
</div>
<el-dialog v-model="detailDialog.visible" title="训练记录详情" width="86%" class="motion-detail-dialog"
append-to-body align-center destroy-on-close :close-on-click-modal="false" @opened="handleDetailOpened"
@closed="handleDetailClosed">
<div v-if="!detailDialog.contentReady" class="motion-detail-placeholder" v-loading="true"></div>
<trainingDetail v-else :key="detailDialog.key" :detail-data="detailDialog.row" />
</el-dialog>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue';
import classroomMode from '@/views/motionData/trainingData/components/classroomMode/index.vue';
import freeMode from '@/views/motionData/trainingData/components/freeMode/index.vue';
import trainingDetail from '@/views/motionData/trainingData/components/trainingDetail/index.vue';
import * as _api from '@/api/TrainingData';
import * as _api1 from '@/api/grade';
import { ElMessage } from 'element-plus';
@ -514,6 +522,34 @@ const data = ref({
],
tabDataApiData: {}
})
const detailDialog = ref({
visible: false,
contentReady: false,
key: 0,
row: null as any
})
let detailContentTimer: number | undefined
function openDetailDialog(row) {
if (detailContentTimer) {
window.clearTimeout(detailContentTimer)
}
detailDialog.value.row = row
detailDialog.value.key += 1
detailDialog.value.contentReady = false
detailDialog.value.visible = true
}
function handleDetailOpened() {
detailContentTimer = window.setTimeout(() => {
detailDialog.value.contentReady = true
}, 80)
}
function handleDetailClosed() {
if (detailContentTimer) {
window.clearTimeout(detailContentTimer)
}
detailDialog.value.contentReady = false
detailDialog.value.row = null
}
const changeTab = (e) => {
data.value.active = e
data.value.classroomModeData.currentActive.currentTopTab = e
@ -551,12 +587,12 @@ const leftOneTabClick = (e, k) => {
data.value.classroomModeData.tableNeed.tableLabel = data.value.tableLabel4
break
}
data.value.classroomModeData.refreshTable = false
data.value.classroomModeData.tableNeed.loading = true
data.value.classroomModeData.currentActive.currentLeftActive = e
GetClassRoomModeDataListApi()
} else {
data.value.leftTwoActive = e
data.value.freeModeData.refreshTable = false
data.value.freeModeData.tableNeed.loading = true
data.value.freeModeData.currentActive.currentLeftActive = e
// setTimeout(() => {
// data.value.freeModeData.refreshTable = true
@ -568,14 +604,14 @@ const leftOneTabClick = (e, k) => {
// 点击重置
function reset(val) {
if (val == 0) {
data.value.classroomModeData.refreshTable = false
data.value.classroomModeData.tableNeed.loading = true
data.value.classroomModeData.startTime = ''
data.value.classroomModeData.gradeAclass = ''
data.value.classroomModeData.teacherName = ''
data.value.classroomModeData.DataSource = 1
GetClassRoomModeDataListApi()
} else {
data.value.freeModeData.refreshTable = false
data.value.freeModeData.tableNeed.loading = true
data.value.freeModeData.gradeAclass = ''
data.value.freeModeData.studentName = ''
data.value.freeModeData.studentNo = ''
@ -589,10 +625,10 @@ function reset(val) {
// 点击查询
function search(val) {
if (val == 0) {
data.value.classroomModeData.refreshTable = false
data.value.classroomModeData.tableNeed.loading = true
GetClassRoomModeDataListApi()
} else {
data.value.freeModeData.refreshTable = false
data.value.freeModeData.tableNeed.loading = true
GetFreeModeDataListApi()
}
@ -614,28 +650,28 @@ function handleExport(e) {
function handleSizeChange(e) {
console.log(e, 'handleSizeChange')
data.value.classroomModeData.tableNeed.pageSize = e
data.value.classroomModeData.refreshTable = false
data.value.classroomModeData.tableNeed.loading = true
GetClassRoomModeDataListApi()
}
// 切换课堂模式页数
function changePage(e) {
console.log(e, 'changePage')
data.value.classroomModeData.tableNeed.pageIndex = e
data.value.classroomModeData.refreshTable = false
data.value.classroomModeData.tableNeed.loading = true
GetClassRoomModeDataListApi()
}
// 切换自由模式每页个数
function handleSizeChangeF(e) {
console.log(e, 'handleSizeChangeF')
data.value.freeModeData.tableNeed.pageSize = e
data.value.freeModeData.refreshTable = false
data.value.freeModeData.tableNeed.loading = true
GetFreeModeDataListApi()
}
// 切换自由模式页数
function changePageF(e) {
console.log(e, 'changePageF')
data.value.freeModeData.tableNeed.pageIndex = e
data.value.freeModeData.refreshTable = false
data.value.freeModeData.tableNeed.loading = true
GetFreeModeDataListApi()
}
onMounted(() => {
@ -694,6 +730,7 @@ async function initList() {
// 课堂模式分页
async function GetClassRoomModeDataListApi() {
let k = data.value.classroomModeData
k.tableNeed.loading = true
try {
const res = await _api.GetClassRoomModeDataList(k.currentActive.currentLeftActive, k.startTime ? k.startTime[0] : '', k.startTime ? k.startTime[1] : '', k.gradeAclass ? k.gradeAclass[1] : '', k.teacherName, '', k.tableNeed.pageIndex, k.tableNeed.pageSize);
if (res.code == 200) {
@ -710,18 +747,26 @@ async function GetClassRoomModeDataListApi() {
k.tableNeed.total = res.data.total
k.tableNeed.tableData = res.data.datas
k.refreshTable = true
k.tableNeed.loading = false
}
} else {
ElMessage.error(res.message)
k.refreshTable = true
k.tableNeed.loading = false
}
} catch (error) {
console.error('Error occurred while calling API:', error);
k.refreshTable = true
k.tableNeed.loading = false
} finally {
k.refreshTable = true
k.tableNeed.loading = false
}
}
// 自由模式分页
async function GetFreeModeDataListApi() {
let k = data.value.freeModeData
k.tableNeed.loading = true
try {
const res = await _api.GetFreeModeDataList('', k.currentActive.currentLeftActive, k.gradeAclass ? k.gradeAclass[1] : "", k.studentNo, k.studentName, k.gender, k.startTime ? k.startTime[0] : '', k.startTime ? k.startTime[1] : '', k.tableNeed.pageIndex, k.tableNeed.pageSize);
if (res.code == 200) {
@ -738,13 +783,20 @@ async function GetFreeModeDataListApi() {
k.tableNeed.total = res.data.total
k.tableNeed.tableData = res.data.datas
k.refreshTable = true
k.tableNeed.loading = false
}
} else {
ElMessage.error(res.message)
k.refreshTable = true
k.tableNeed.loading = false
}
} catch (error) {
console.error('Error occurred while calling API:', error);
k.refreshTable = true
k.tableNeed.loading = false
} finally {
k.refreshTable = true
k.tableNeed.loading = false
}
}
// daochu
@ -1051,4 +1103,5 @@ function exportF() {
}
}
</style>
</style>
<style lang="less" scoped src="../motion-data-theme.less"></style>

View File

@ -95,7 +95,7 @@ const data = ref({
// 点击重置
function reset(val) {
data.value.classroomModeData.refreshTable = false
data.value.classroomModeData.tableNeed.loading = true
data.value.classroomModeData.gradeAclass = ''
data.value.classroomModeData.studentName = ''
GetVisionDataListApi()
@ -103,7 +103,7 @@ function reset(val) {
}
// 点击查询
function search(val) {
data.value.classroomModeData.refreshTable = false
data.value.classroomModeData.tableNeed.loading = true
GetVisionDataListApi()
}
function exportExcel(){
@ -113,14 +113,14 @@ function exportExcel(){
function handleSizeChange(e) {
console.log(e, 'handleSizeChange')
data.value.classroomModeData.tableNeed.pageSize = e
data.value.classroomModeData.refreshTable = false
data.value.classroomModeData.tableNeed.loading = true
GetVisionDataListApi()
}
// 切换课堂模式页数
function changePage(e) {
console.log(e, 'changePage')
data.value.classroomModeData.tableNeed.pageIndex = e
data.value.classroomModeData.refreshTable = false
data.value.classroomModeData.tableNeed.loading = true
GetVisionDataListApi()
}
@ -163,6 +163,7 @@ async function GetGradeNames() {
// 课堂模式分页
async function GetVisionDataListApi() {
let k = data.value.classroomModeData
k.tableNeed.loading = true
try {
const res = await _api.GetVisionDataList( k.gradeAclass ? k.gradeAclass[0] : '',k.gradeAclass ? k.gradeAclass[1] : '', k.studentName, k.tableNeed.pageIndex, k.tableNeed.pageSize);
if (res.code == 200) {
@ -174,13 +175,20 @@ async function GetVisionDataListApi() {
k.tableNeed.total = res.data.total
k.tableNeed.tableData = res.data.datas
k.refreshTable = true
k.tableNeed.loading = false
}
} else {
ElMessage.error(res.message)
k.refreshTable = true
k.tableNeed.loading = false
}
} catch (error) {
console.error('Error occurred while calling API:', error);
k.refreshTable = true
k.tableNeed.loading = false
} finally {
k.refreshTable = true
k.tableNeed.loading = false
}
}
@ -373,4 +381,5 @@ async function GetVisionDataListApi() {
}
}
</style>
</style>
<style lang="less" scoped src="../motion-data-theme.less"></style>

View File

@ -26,14 +26,16 @@
<el-option v-for="item in data.options1" :key="item.id" :label="item.label" :value="item.id" />
</el-select>
</div>
<div class="rightBtn">
<p v-for="(item, index) in handelData" :key="index" :class="item.className"
@click="btnClick(item.engName)">
{{ item.chinName }}</p>
</div>
</div>
</div>
<div class="rightBtn">
<p v-for="(item, index) in handelData" :key="index" :class="item.className" @click="btnClick(item.engName)">
{{ item.chinName }}</p>
</div>
<div class="cont">
<el-table :data="data.tableNeed.tableData" style="width: calc(100% - 40px) ;height: calc(100vh - 323px); margin:0 20px ; margin-top: 20px;"
<el-table :data="data.tableNeed.tableData"
style="width: calc(100% - 40px) ;height: calc(100vh - 323px); margin:0 20px ; margin-top: 20px;"
fit="true" v-if="data.tableNeed.refresh"
:header-cell-style="{ 'text-align': 'center', 'font-size': '12px', 'color': '#3F404A', padding: '3px', background: '#F8F8FA', height: '50px' }"
:cell-style="getCellStyles" :row-style="{ height: '0px' }">
@ -116,7 +118,8 @@
<el-upload class="dl-avatar-uploader-min square" ref="uploadRef" action=""
:show-file-list="false" :before-upload="beforeImageUpload" :on-exceed="handleExceed"
list-type="picture" accept=".jpg,.jpeg,.png">
<el-image v-if="ruleForm.photo" :src="ruleForm.photo" class="avatar" style="width: 100px;height: 100px;"></el-image>
<el-image v-if="ruleForm.photo" :src="ruleForm.photo" class="avatar"
style="width: 100px;height: 100px;"></el-image>
<p v-else style="color:#0486FE">点击上传照片</p>
</el-upload>
</el-form-item>
@ -187,7 +190,8 @@
<el-upload class="dl-avatar-uploader-min square" action="" ref="uploadRef"
:show-file-list="false" :before-upload="beforeImageUpload1" :on-exceed="handleExceed"
list-type="picture" accept=".jpg,.jpeg,.png">
<el-image v-if="ruleForm1.photo" :src="ruleForm1.photo" class="avatar" style="width: 100px;height: 100px;"></el-image>
<el-image v-if="ruleForm1.photo" :src="ruleForm1.photo" class="avatar"
style="width: 100px;height: 100px;"></el-image>
<p v-else style="color:#0486FE">点击上传照片</p>
</el-upload>
</el-form-item>
@ -672,7 +676,7 @@ const deleteFile3 = () => {
const beforeImageUpload = (file) => {
console.log(file, 'fileeeee')
console.log(ruleForm.value, 'ruleForm.value')
if (!ruleForm.value.studentNo&&!ruleForm.value.studentName&&!ruleForm.value.gradeId&&!ruleForm.value.classId) {
if (!ruleForm.value.studentNo && !ruleForm.value.studentName && !ruleForm.value.gradeId && !ruleForm.value.classId) {
return ElMessage.error('请选填写信息再上传头像')
}
return new Promise((resolve, reject) => {
@ -694,7 +698,7 @@ const beforeImageUpload = (file) => {
const beforeImageUpload1 = (file) => {
console.log(file, 'fileeeee')
console.log(ruleForm1.value, 'ruleForm.value')
if (!ruleForm1.value.studentNo&&!ruleForm1.value.studentName&&!ruleForm1.value.gradeId&&!ruleForm1.value.classId) {
if (!ruleForm1.value.studentNo && !ruleForm1.value.studentName && !ruleForm1.value.gradeId && !ruleForm1.value.classId) {
return ElMessage.error('请选填写信息再上传头像')
}
return new Promise((resolve, reject) => {
@ -1251,7 +1255,7 @@ async function AddList3() {
async function AddPhoto(val) {
console.log(photoForm, 'photoForm')
const datas = {
gradeId:val.classId[0],
gradeId: val.classId[0],
classId: val.classId[1],
studentNo: val.studentNo,
}
@ -1365,6 +1369,7 @@ function exportData(val) {
}
.rightBtn {
flex:1;
width: 100%;
// height: 100%;
display: flex;

View File

@ -10,12 +10,14 @@ import type { ConfigEnv } from 'vite'
export default ({ mode }: ConfigEnv) => {
const root = process.cwd()
const ENV = loadEnv(mode, root)
const publicBase = process.env.VITE_APP_PUBLIC_BASE || ENV.VITE_APP_PUBLIC_BASE || '/'
const buildOutDir = process.env.VITE_BUILD_OUT_DIR || path.join('./.oss-build', process.env.BUILD_TIMESTAMP || 'latest')
console.log(`当前环境信息:`, mode)
console.log(`ENV:`, ENV)
return defineConfig({
base: 'https://yuedong-wechatapplet.oss-cn-shanghai.aliyuncs.com/static/web/samart-static/202607021745/',
base: mode === 'development' ? '/' : publicBase,
server: {
host: '0.0.0.0',
// https: false,
@ -24,7 +26,7 @@ export default ({ mode }: ConfigEnv) => {
port: 9990,
proxy: {
'/dev-api': {
target: 'http://8.153.108.90:9991',
target: ENV.VITE_API_BASE || 'http://localhost:52356',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/dev-api/, '')
}
@ -76,7 +78,7 @@ export default ({ mode }: ConfigEnv) => {
}
},
build: {
outDir: path.join('./dist', ENV.VITE_BASE_URL),
outDir: buildOutDir,
assetsInlineLimit: 4096,
cssCodeSplit: true,
sourcemap: false,
@ -111,4 +113,5 @@ export default ({ mode }: ConfigEnv) => {
})
]
})
}
}