|
|
|
|
@ -1,209 +1,434 @@
|
|
|
|
|
<template>
|
|
|
|
|
<div class="course-qr-admin">
|
|
|
|
|
<!-- 顶部标题 -->
|
|
|
|
|
<div class="admin-header">
|
|
|
|
|
<h1 class="admin-title">
|
|
|
|
|
<i class="el-icon-qr-code"></i>
|
|
|
|
|
课程二维码管理
|
|
|
|
|
</h1>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="admin-main">
|
|
|
|
|
<!-- 课程列表面板 -->
|
|
|
|
|
<div class="course-list-panel">
|
|
|
|
|
<div class="panel-title">
|
|
|
|
|
<i class="el-icon-menu"></i>
|
|
|
|
|
选择课程
|
|
|
|
|
</div>
|
|
|
|
|
<template>
|
|
|
|
|
<div class="course-qr-admin">
|
|
|
|
|
<!-- 顶部标题 -->
|
|
|
|
|
<!-- <div class="admin-header">
|
|
|
|
|
<h1 class="admin-title">
|
|
|
|
|
<i class="el-icon-qr-code"></i>
|
|
|
|
|
课表二维码管理
|
|
|
|
|
</h1>
|
|
|
|
|
</div> -->
|
|
|
|
|
<div class="admin-main">
|
|
|
|
|
<!-- 课程列表面板 -->
|
|
|
|
|
<div class="course-list-panel">
|
|
|
|
|
<div class="panel-title">
|
|
|
|
|
<i class="el-icon-menu"></i>
|
|
|
|
|
选择课表
|
|
|
|
|
</div>
|
|
|
|
|
<div class="course-list-select">
|
|
|
|
|
<el-select style="margin:0 10px 0 0" v-model="course_id" @change="changeCourse" placeholder="请选择课程" clearable>
|
|
|
|
|
<el-option v-for="item in courseList" :key="item.id" :label="item.name" :value="item.id"></el-option>
|
|
|
|
|
</el-select>
|
|
|
|
|
|
|
|
|
|
<el-date-picker @change="changeDate" format="yyyy-MM-dd" value-format="yyyy-MM-dd" v-model="course_date" type="date" placeholder="选择上课日期">
|
|
|
|
|
</el-date-picker>
|
|
|
|
|
</div>
|
|
|
|
|
<el-scrollbar style="height: 600px;">
|
|
|
|
|
<div v-for="course in courses" :key="course.id" :class="['course-item', {active: selectedCourse && selectedCourse.id === course.id}]" @click="selectCourse(course)">
|
|
|
|
|
<div class="course-name">{{ course.name }}</div>
|
|
|
|
|
<div class="course-info">
|
|
|
|
|
<span><i class="el-icon-date"></i> {{ course.time }}</span>
|
|
|
|
|
<span><i class="el-icon-location"></i> {{ course.location }}</span>
|
|
|
|
|
<span><i class="el-icon-user"></i> {{ course.teacher }}</span>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</el-scrollbar>
|
|
|
|
|
</div>
|
|
|
|
|
<!-- 二维码展示面板 -->
|
|
|
|
|
<div class="qr-display-panel">
|
|
|
|
|
<div class="panel-title">
|
|
|
|
|
<i class="el-icon-qr-code"></i>
|
|
|
|
|
课程签到二维码
|
|
|
|
|
</div>
|
|
|
|
|
<div v-if="!selectedCourse" class="empty-state">
|
|
|
|
|
<i class="el-icon-qr-code" style="font-size:64px;opacity:0.5;"></i>
|
|
|
|
|
<h5>请选择课程生成二维码</h5>
|
|
|
|
|
<p class="text-muted">选择左侧课程后,将自动生成对应的签到二维码</p>
|
|
|
|
|
</div>
|
|
|
|
|
<div v-else>
|
|
|
|
|
<div class="qr-container">
|
|
|
|
|
<h4 style="margin-bottom: 20px; color: #2c3e50;">{{ selectedCourse.name }}</h4>
|
|
|
|
|
<div class="qr-code">
|
|
|
|
|
<img :src="qrImgUrl" alt="二维码" style="width:200px;height:200px;" />
|
|
|
|
|
</div>
|
|
|
|
|
<div class="qr-info">
|
|
|
|
|
<h6><i class="el-icon-info"></i> 课程信息</h6>
|
|
|
|
|
<div class="qr-info-grid">
|
|
|
|
|
<div><strong>时间:</strong>{{ selectedCourse.time }}</div>
|
|
|
|
|
<div><strong>地点:</strong>{{ selectedCourse.location }}</div>
|
|
|
|
|
<div><strong>主讲:</strong>{{ selectedCourse.teacher }}</div>
|
|
|
|
|
<div><strong>类型:</strong>{{ getTypeName(selectedCourse.type) }}</div>
|
|
|
|
|
<template v-if="courseContentList.length>0">
|
|
|
|
|
<div v-for="course in courseContentList" :key="course.id"
|
|
|
|
|
:class="['course-item', {active: selectedCourse && selectedCourse.id === course.id}]"
|
|
|
|
|
@click="selectCourse(course)">
|
|
|
|
|
<div class="course-name">{{ course.theme }}</div>
|
|
|
|
|
<div class="course-info">
|
|
|
|
|
<span><i class="el-icon-date"></i> {{ course.date }} - {{course.period}}</span>
|
|
|
|
|
<span><i class="el-icon-location"></i> {{ course.address }}</span>
|
|
|
|
|
<span><i class="el-icon-user"></i> {{ course.teacher.name }}</span>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="qr-actions">
|
|
|
|
|
<el-button type="primary" icon="el-icon-download" class="btn-qr" @click="downloadQR">下载二维码</el-button>
|
|
|
|
|
<el-button type="success" icon="el-icon-view" class="btn-qr" @click="previewCheckin">预览签到页</el-button>
|
|
|
|
|
<el-button type="info" icon="el-icon-document-copy" class="btn-qr" @click="copyUrl">复制链接</el-button>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="usage-tips">
|
|
|
|
|
<h6><i class="el-icon-lightning"></i> 使用说明</h6>
|
|
|
|
|
<ul>
|
|
|
|
|
<li>学员扫描此二维码可直接进入课程签到页面</li>
|
|
|
|
|
<li>二维码包含课程信息,自动识别对应课程</li>
|
|
|
|
|
<li>支持微信扫一扫</li>
|
|
|
|
|
<li>建议在课程开始前30分钟展示二维码</li>
|
|
|
|
|
<li>可下载二维码图片用于打印或分享</li>
|
|
|
|
|
</ul>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
<script>
|
|
|
|
|
export default {
|
|
|
|
|
name: 'CourseQr',
|
|
|
|
|
data() {
|
|
|
|
|
return {
|
|
|
|
|
courses: [
|
|
|
|
|
{
|
|
|
|
|
id: 1,
|
|
|
|
|
name: '2025产业加速营 | 智能制造专题',
|
|
|
|
|
time: '2025-06-04 09:00-17:00',
|
|
|
|
|
location: '商学院A101',
|
|
|
|
|
teacher: '王教授',
|
|
|
|
|
type: 'course'
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
id: 2,
|
|
|
|
|
name: '校友企业参访 | 新能源企业',
|
|
|
|
|
time: '2025-06-10 13:30-17:00',
|
|
|
|
|
location: '苏州高新区',
|
|
|
|
|
teacher: '李总监',
|
|
|
|
|
type: 'activity'
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
id: 3,
|
|
|
|
|
name: '移动课堂 | AI商业落地',
|
|
|
|
|
time: '2025-06-15 09:00-16:00',
|
|
|
|
|
location: '创新实验室B202',
|
|
|
|
|
teacher: '张教授',
|
|
|
|
|
type: 'workshop'
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
id: 4,
|
|
|
|
|
name: '2025校友论坛 | 数字经济新机遇',
|
|
|
|
|
time: '2025-06-20 14:00-17:30',
|
|
|
|
|
location: '报告厅',
|
|
|
|
|
teacher: '特邀嘉宾',
|
|
|
|
|
type: 'activity'
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
id: 5,
|
|
|
|
|
name: '2025暑期创新营 | 项目路演',
|
|
|
|
|
time: '2025-06-27 09:00-12:00',
|
|
|
|
|
location: '多功能厅',
|
|
|
|
|
teacher: '创业导师团',
|
|
|
|
|
type: 'course'
|
|
|
|
|
}
|
|
|
|
|
],
|
|
|
|
|
selectedCourse: null
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
watch: {
|
|
|
|
|
selectedCourse(val) {
|
|
|
|
|
if (val) this.$nextTick(this.renderQRCode)
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
methods: {
|
|
|
|
|
selectCourse(course) {
|
|
|
|
|
this.selectedCourse = course
|
|
|
|
|
},
|
|
|
|
|
renderQRCode() {
|
|
|
|
|
const checkinUrl = `${window.location.origin}/course-checkin.html?courseId=${this.selectedCourse.id}&courseName=${encodeURIComponent(this.selectedCourse.name)}`
|
|
|
|
|
const qrDiv = this.$refs.qrcode
|
|
|
|
|
qrDiv.innerHTML = ''
|
|
|
|
|
// 这里需要根据实际情况生成二维码图片
|
|
|
|
|
// 这里只是占位,实际实现需要根据后端接口生成二维码图片
|
|
|
|
|
this.qrImgUrl = checkinUrl
|
|
|
|
|
},
|
|
|
|
|
getTypeName(type) {
|
|
|
|
|
const types = { course: '课程', activity: '活动', workshop: '移动课堂' }
|
|
|
|
|
return types[type] || type
|
|
|
|
|
},
|
|
|
|
|
downloadQR() {
|
|
|
|
|
if (!this.selectedCourse) return
|
|
|
|
|
// 直接下载图片
|
|
|
|
|
const link = document.createElement('a')
|
|
|
|
|
link.download = `${this.selectedCourse.name}-签到二维码.png`
|
|
|
|
|
link.href = this.qrImgUrl
|
|
|
|
|
link.click()
|
|
|
|
|
},
|
|
|
|
|
previewCheckin() {
|
|
|
|
|
if (!this.selectedCourse) return
|
|
|
|
|
const checkinUrl = `${window.location.origin}/course-checkin.html?courseId=${this.selectedCourse.id}&courseName=${encodeURIComponent(this.selectedCourse.name)}`
|
|
|
|
|
window.open(checkinUrl, '_blank')
|
|
|
|
|
},
|
|
|
|
|
copyUrl() {
|
|
|
|
|
if (!this.selectedCourse) return
|
|
|
|
|
const checkinUrl = `${window.location.origin}/course-checkin.html?courseId=${this.selectedCourse.id}&courseName=${encodeURIComponent(this.selectedCourse.name)}`
|
|
|
|
|
if (navigator.clipboard) {
|
|
|
|
|
navigator.clipboard.writeText(checkinUrl).then(() => {
|
|
|
|
|
this.$message.success('签到链接已复制到剪贴板')
|
|
|
|
|
}).catch(() => {
|
|
|
|
|
this.$prompt('请复制以下链接:', '复制签到链接', { inputValue: checkinUrl })
|
|
|
|
|
})
|
|
|
|
|
} else {
|
|
|
|
|
this.$prompt('请复制以下链接:', '复制签到链接', { inputValue: checkinUrl })
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
computed: {
|
|
|
|
|
qrImgUrl() {
|
|
|
|
|
if (!this.selectedCourse) return ''
|
|
|
|
|
// 假设后端二维码图片接口为 /api/qr/course?id=xxx
|
|
|
|
|
return `/api/qr/course?id=${this.selectedCourse.id}`
|
|
|
|
|
}
|
|
|
|
|
</template>
|
|
|
|
|
<div v-else>
|
|
|
|
|
<el-empty description="暂无课表"></el-empty>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
</el-scrollbar>
|
|
|
|
|
</div>
|
|
|
|
|
<!-- 二维码展示面板 -->
|
|
|
|
|
<div class="qr-display-panel">
|
|
|
|
|
<div class="panel-title">
|
|
|
|
|
<i class="el-icon-qr-code"></i>
|
|
|
|
|
课表签到二维码
|
|
|
|
|
</div>
|
|
|
|
|
<div v-if="!selectedCourse" class="empty-state">
|
|
|
|
|
<i class="el-icon-qr-code" style="font-size:64px;opacity:0.5;"></i>
|
|
|
|
|
<h2>请选择课表生成二维码</h2>
|
|
|
|
|
<p class="text-muted">选择左侧课表后,将自动生成对应的签到二维码</p>
|
|
|
|
|
</div>
|
|
|
|
|
<div v-else>
|
|
|
|
|
<div class="qr-container">
|
|
|
|
|
<h4 style="margin-bottom: 20px; color: #2c3e50;">{{ selectedCourse.course.name }}</h4>
|
|
|
|
|
<div class="qr-code" v-if="qrImgUrl">
|
|
|
|
|
<img :src="qrImgUrl" alt="二维码" style="width:200px;height:200px;" />
|
|
|
|
|
</div>
|
|
|
|
|
<div class="qr-info">
|
|
|
|
|
<h3><i class="el-icon-info"></i> 课表信息</h3>
|
|
|
|
|
<div class="qr-info-grid">
|
|
|
|
|
<div><strong>主题:</strong>{{ selectedCourse.theme }}</div>
|
|
|
|
|
<div><strong>时间:</strong>{{ selectedCourse.date }} - {{selectedCourse.period}}</div>
|
|
|
|
|
<div><strong>地点:</strong>{{ selectedCourse.address }}</div>
|
|
|
|
|
<div><strong>主讲:</strong>{{ selectedCourse.teacher.name }}</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="qr-actions">
|
|
|
|
|
<el-button type="primary" icon="el-icon-download" class="btn-qr" @click="downloadQR">下载二维码</el-button>
|
|
|
|
|
<!-- <el-button type="success" icon="el-icon-view" class="btn-qr" @click="previewCheckin">预览签到页</el-button> -->
|
|
|
|
|
<!-- <el-button type="info" icon="el-icon-document-copy" class="btn-qr" @click="copyUrl">复制链接</el-button> -->
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="usage-tips">
|
|
|
|
|
<h3><i class="el-icon-lightning"></i> 使用说明</h3>
|
|
|
|
|
<ul>
|
|
|
|
|
<li>学员扫描此二维码可直接进入课程签到页面</li>
|
|
|
|
|
<li>二维码包含课程信息,自动识别对应课程</li>
|
|
|
|
|
<li>支持微信扫一扫</li>
|
|
|
|
|
<li>建议在课程开始前30分钟展示二维码</li>
|
|
|
|
|
<li>可下载二维码图片用于打印或分享</li>
|
|
|
|
|
</ul>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
<script>
|
|
|
|
|
import {
|
|
|
|
|
index as courseGet
|
|
|
|
|
} from "@/api/course/index.js"
|
|
|
|
|
import {
|
|
|
|
|
index as courseContentGet,
|
|
|
|
|
qrcodeGet
|
|
|
|
|
} from '@/api/course/courseContent.js'
|
|
|
|
|
export default {
|
|
|
|
|
name: 'CourseQr',
|
|
|
|
|
data() {
|
|
|
|
|
return {
|
|
|
|
|
courseContentList: [],
|
|
|
|
|
courseList: [],
|
|
|
|
|
course_id: '',
|
|
|
|
|
course_date: '',
|
|
|
|
|
selectedCourse: null,
|
|
|
|
|
qrImgUrl:''
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
watch: {
|
|
|
|
|
selectedCourse(val) {
|
|
|
|
|
if (val) {
|
|
|
|
|
this.$nextTick(function(){
|
|
|
|
|
this.renderQRCode(val.id)
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
created() {
|
|
|
|
|
this.getCourse()
|
|
|
|
|
},
|
|
|
|
|
methods: {
|
|
|
|
|
// 获取进行中的 课程
|
|
|
|
|
changeCourse(e) {
|
|
|
|
|
console.log("e", e)
|
|
|
|
|
if (e) {
|
|
|
|
|
this.getCourseContent(e)
|
|
|
|
|
} else {
|
|
|
|
|
if (this.course_date) {
|
|
|
|
|
this.getCourseContent('', this.course_date)
|
|
|
|
|
} else {
|
|
|
|
|
this.courseContentList = []
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
changeDate(e) {
|
|
|
|
|
if (e) {
|
|
|
|
|
this.getCourseContent(this.course_id, e)
|
|
|
|
|
} else {
|
|
|
|
|
if (this.course_id) {
|
|
|
|
|
this.getCourseContent(this.course_id)
|
|
|
|
|
} else {
|
|
|
|
|
this.courseContentList = []
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
async getCourse() {
|
|
|
|
|
const res = await courseGet({
|
|
|
|
|
page: 1,
|
|
|
|
|
page_size: 999,
|
|
|
|
|
filter: [{
|
|
|
|
|
key: 'course_status',
|
|
|
|
|
op: 'eq',
|
|
|
|
|
value: 10
|
|
|
|
|
}]
|
|
|
|
|
})
|
|
|
|
|
this.courseList = res.data
|
|
|
|
|
},
|
|
|
|
|
async getCourseContent(course_id, date) {
|
|
|
|
|
const res = await courseContentGet({
|
|
|
|
|
page: 1,
|
|
|
|
|
page_size: 999,
|
|
|
|
|
show_relation: ['teacher','course'],
|
|
|
|
|
sort_name: 'date',
|
|
|
|
|
sort_type: 'DESC',
|
|
|
|
|
filter: [{
|
|
|
|
|
key: 'course_id',
|
|
|
|
|
op: 'eq',
|
|
|
|
|
value: course_id ? course_id : ''
|
|
|
|
|
}, {
|
|
|
|
|
key: 'date',
|
|
|
|
|
op: 'eq',
|
|
|
|
|
value: date ? date : ''
|
|
|
|
|
}]
|
|
|
|
|
})
|
|
|
|
|
this.courseContentList = res.data
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
selectCourse(course) {
|
|
|
|
|
this.selectedCourse = course
|
|
|
|
|
},
|
|
|
|
|
async renderQRCode(id) {
|
|
|
|
|
const res = await qrcodeGet({
|
|
|
|
|
id:id
|
|
|
|
|
})
|
|
|
|
|
this.qrImgUrl = res.msg
|
|
|
|
|
},
|
|
|
|
|
downloadQR() {
|
|
|
|
|
if (!this.selectedCourse) return
|
|
|
|
|
// 直接下载图片
|
|
|
|
|
const link = document.createElement('a')
|
|
|
|
|
link.href = this.qrImgUrl
|
|
|
|
|
link.download = `${this.selectedCourse.theme}-签到二维码.png`
|
|
|
|
|
link.target = '_blank'
|
|
|
|
|
link.click()
|
|
|
|
|
link.remove(); // 清理临时元素
|
|
|
|
|
},
|
|
|
|
|
previewCheckin() {
|
|
|
|
|
if (!this.selectedCourse) return
|
|
|
|
|
const checkinUrl =
|
|
|
|
|
`${window.location.origin}/course-checkin.html?courseId=${this.selectedCourse.id}&courseName=${encodeURIComponent(this.selectedCourse.name)}`
|
|
|
|
|
window.open(checkinUrl, '_blank')
|
|
|
|
|
},
|
|
|
|
|
copyUrl() {
|
|
|
|
|
if (!this.selectedCourse) return
|
|
|
|
|
const checkinUrl =
|
|
|
|
|
`${window.location.origin}/course-checkin.html?courseId=${this.selectedCourse.id}&courseName=${encodeURIComponent(this.selectedCourse.name)}`
|
|
|
|
|
if (navigator.clipboard) {
|
|
|
|
|
navigator.clipboard.writeText(checkinUrl).then(() => {
|
|
|
|
|
this.$message.success('签到链接已复制到剪贴板')
|
|
|
|
|
}).catch(() => {
|
|
|
|
|
this.$prompt('请复制以下链接:', '复制签到链接', {
|
|
|
|
|
inputValue: checkinUrl
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
} else {
|
|
|
|
|
this.$prompt('请复制以下链接:', '复制签到链接', {
|
|
|
|
|
inputValue: checkinUrl
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
computed: {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
<style scoped>
|
|
|
|
|
.course-qr-admin {
|
|
|
|
|
background: #f8f9fa;
|
|
|
|
|
min-height: 100vh;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.admin-header {
|
|
|
|
|
background: white;
|
|
|
|
|
padding: 20px 30px;
|
|
|
|
|
border-bottom: 1px solid #e9ecef;
|
|
|
|
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.admin-title {
|
|
|
|
|
font-size: 24px;
|
|
|
|
|
font-weight: 700;
|
|
|
|
|
color: #2c3e50;
|
|
|
|
|
margin: 0;
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
gap: 12px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.admin-main {
|
|
|
|
|
display: flex;
|
|
|
|
|
gap: 24px;
|
|
|
|
|
padding: 30px;
|
|
|
|
|
min-height: calc(100vh - 100px);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.course-list-panel {
|
|
|
|
|
flex: 1;
|
|
|
|
|
background: white;
|
|
|
|
|
border-radius: 12px;
|
|
|
|
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
|
|
|
|
padding: 24px;
|
|
|
|
|
max-width: 400px;
|
|
|
|
|
}
|
|
|
|
|
.course-list-select{
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
margin-bottom:10px;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
<style scoped>
|
|
|
|
|
.course-qr-admin { background: #f8f9fa; min-height: 100vh; }
|
|
|
|
|
.admin-header { background: white; padding: 20px 30px; border-bottom: 1px solid #e9ecef; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
|
|
|
|
|
.admin-title { font-size: 24px; font-weight: 700; color: #2c3e50; margin: 0; display: flex; align-items: center; gap: 12px; }
|
|
|
|
|
.admin-main { display: flex; gap: 24px; padding: 30px; min-height: calc(100vh - 100px); }
|
|
|
|
|
.course-list-panel { flex: 1; background: white; border-radius: 12px; box-shadow: 0 2px 8px rgba(0,0,0,0.1); padding: 24px; max-width: 400px; }
|
|
|
|
|
.qr-display-panel { flex: 2; background: white; border-radius: 12px; box-shadow: 0 2px 8px rgba(0,0,0,0.1); padding: 24px; display: flex; flex-direction: column; align-items: center; text-align: center; }
|
|
|
|
|
.panel-title { font-size: 18px; font-weight: 600; margin-bottom: 20px; color: #3498db; display: flex; align-items: center; gap: 8px; }
|
|
|
|
|
.course-item { border: 1px solid #e9ecef; border-radius: 8px; padding: 16px; margin-bottom: 12px; cursor: pointer; transition: all 0.3s ease; }
|
|
|
|
|
.course-item:hover { border-color: #3498db; box-shadow: 0 2px 8px rgba(52, 152, 219, 0.2); }
|
|
|
|
|
.course-item.active { border-color: #3498db; background-color: #e3f2fd; }
|
|
|
|
|
.course-name { font-weight: 600; font-size: 16px; margin-bottom: 8px; color: #2c3e50; }
|
|
|
|
|
.course-info { font-size: 14px; color: #6c757d; display: flex; flex-direction: column; gap: 4px; }
|
|
|
|
|
.qr-container { background: white; border: 2px solid #e9ecef; border-radius: 16px; padding: 30px; margin: 20px 0; box-shadow: 0 4px 12px rgba(0,0,0,0.1); }
|
|
|
|
|
.qr-code { margin: 0 auto; }
|
|
|
|
|
.qr-info { margin-top: 20px; padding: 20px; background: #f8f9fa; border-radius: 12px; text-align: left; }
|
|
|
|
|
.qr-info-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 12px; margin-top: 12px; }
|
|
|
|
|
.qr-actions { display: flex; gap: 12px; margin-top: 20px; justify-content: center; }
|
|
|
|
|
.btn-qr { padding: 12px 24px; border-radius: 8px; font-weight: 500; display: flex; align-items: center; gap: 8px; transition: all 0.3s ease; }
|
|
|
|
|
.btn-qr:hover { transform: translateY(-2px); box-shadow: 0 4px 8px rgba(0,0,0,0.2); }
|
|
|
|
|
.empty-state { display: flex; flex-direction: column; align-items: center; justify-content: center; height: 400px; color: #6c757d; }
|
|
|
|
|
.usage-tips { background: #e8f5e9; border: 1px solid #c8e6c9; border-radius: 8px; padding: 16px; margin-top: 20px; text-align: left; }
|
|
|
|
|
.usage-tips h6 { color: #2e7d32; margin-bottom: 12px; }
|
|
|
|
|
.usage-tips ul { margin: 0; padding-left: 20px; color: #2e7d32; }
|
|
|
|
|
@media (max-width: 768px) { .admin-main { flex-direction: column; padding: 15px; gap: 15px; } .course-list-panel { max-width: none; } .qr-container { padding: 20px; } .qr-actions { flex-direction: column; } .btn-qr { width: 100%; justify-content: center; } }
|
|
|
|
|
|
|
|
|
|
.qr-display-panel {
|
|
|
|
|
flex: 2;
|
|
|
|
|
background: white;
|
|
|
|
|
border-radius: 12px;
|
|
|
|
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
|
|
|
|
padding: 24px;
|
|
|
|
|
display: flex;
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
align-items: center;
|
|
|
|
|
text-align: center;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.panel-title {
|
|
|
|
|
font-size: 18px;
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
margin-bottom: 20px;
|
|
|
|
|
color: #3498db;
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
gap: 8px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.course-item {
|
|
|
|
|
border: 1px solid #e9ecef;
|
|
|
|
|
border-radius: 8px;
|
|
|
|
|
padding: 16px;
|
|
|
|
|
margin-bottom: 12px;
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
transition: all 0.3s ease;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.course-item:hover {
|
|
|
|
|
border-color: #3498db;
|
|
|
|
|
box-shadow: 0 2px 8px rgba(52, 152, 219, 0.2);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.course-item.active {
|
|
|
|
|
border-color: #3498db;
|
|
|
|
|
background-color: #e3f2fd;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.course-name {
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
font-size: 16px;
|
|
|
|
|
margin-bottom: 8px;
|
|
|
|
|
color: #2c3e50;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.course-info {
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
color: #6c757d;
|
|
|
|
|
display: flex;
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
gap: 4px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.qr-container {
|
|
|
|
|
background: white;
|
|
|
|
|
border: 2px solid #e9ecef;
|
|
|
|
|
border-radius: 16px;
|
|
|
|
|
padding: 30px;
|
|
|
|
|
margin: 20px 0;
|
|
|
|
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.qr-code {
|
|
|
|
|
margin: 0 auto;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.qr-info {
|
|
|
|
|
margin-top: 20px;
|
|
|
|
|
padding: 20px;
|
|
|
|
|
background: #f8f9fa;
|
|
|
|
|
border-radius: 12px;
|
|
|
|
|
text-align: left;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.qr-info-grid {
|
|
|
|
|
display: grid;
|
|
|
|
|
grid-template-columns: repeat(auto-fit, minmax(600px, 1fr));
|
|
|
|
|
gap: 12px;
|
|
|
|
|
margin-top: 12px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.qr-actions {
|
|
|
|
|
display: flex;
|
|
|
|
|
gap: 12px;
|
|
|
|
|
margin-top: 20px;
|
|
|
|
|
justify-content: center;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.btn-qr {
|
|
|
|
|
padding: 12px 24px;
|
|
|
|
|
border-radius: 8px;
|
|
|
|
|
font-weight: 500;
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
gap: 8px;
|
|
|
|
|
transition: all 0.3s ease;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.btn-qr:hover {
|
|
|
|
|
transform: translateY(-2px);
|
|
|
|
|
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.empty-state {
|
|
|
|
|
display: flex;
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
align-items: center;
|
|
|
|
|
justify-content: center;
|
|
|
|
|
height: 400px;
|
|
|
|
|
color: #6c757d;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.usage-tips {
|
|
|
|
|
background: #e8f5e9;
|
|
|
|
|
border: 1px solid #c8e6c9;
|
|
|
|
|
border-radius: 8px;
|
|
|
|
|
padding: 16px;
|
|
|
|
|
margin-top: 20px;
|
|
|
|
|
text-align: left;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.usage-tips h6 {
|
|
|
|
|
color: #2e7d32;
|
|
|
|
|
margin-bottom: 12px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.usage-tips ul {
|
|
|
|
|
margin: 0;
|
|
|
|
|
padding-left: 20px;
|
|
|
|
|
color: #2e7d32;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@media (max-width: 768px) {
|
|
|
|
|
.admin-main {
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
padding: 15px;
|
|
|
|
|
gap: 15px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.course-list-panel {
|
|
|
|
|
max-width: none;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.qr-container {
|
|
|
|
|
padding: 20px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.qr-actions {
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.btn-qr {
|
|
|
|
|
width: 100%;
|
|
|
|
|
justify-content: center;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
</style>
|
|
|
|
|
|