Commit ee8a8474 authored by liujiangnan's avatar liujiangnan

feat

parent 9d944887
.edit-data{ .edit-data{
padding: 10px; padding: 10px;
}
.el-card {
display: inline-block;
margin: 3px;
}
.data-card {
position: relative;
width: 90%;
vertical-align: top;
}
.data-card .el-card__body {
padding: 15px 20px 0px;
}
.audio-uploader {
text-align: left;
margin-bottom: 10px;
}
audio {
margin: 10px 0;
}
.el-form-item {
margin-bottom: 15px;
}
.delete-btn-wrapper {
position: absolute;
bottom: 10px;
left: 0;
right: 0;
text-align: center;
}
/* 音频信息显示样式 */
.audio-info {
display: flex;
align-items: center;
justify-content: space-between;
margin-top: 10px;
padding: 8px 12px;
background: #f5f7fa;
border-radius: 4px;
}
.audio-file-info {
flex: 1;
overflow: hidden;
margin-right: 10px;
}
.audio-file-info .filename {
display: block;
font-size: 14px;
color: #606266;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.audio-file-info .file-meta {
font-size: 12px;
color: #909399;
}
.audio-play-btn {
font-size: 30px;
color: #409EFF;
cursor: pointer;
transition: all 0.3s;
}
.audio-play-btn:hover {
color: #66b1ff;
}
/* 图片上传区域样式 */
.image-uploader {
width: 120px;
height: 120px;
line-height: 120px;
border: 1px dashed #d9d9d9;
border-radius: 6px;
cursor: pointer;
position: relative;
overflow: hidden;
}
.image-uploader .el-upload {
width: 120px;
height: 120px;
}
.image-uploader:hover {
border-color: #409EFF;
}
.upload-icon {
font-size: 28px;
color: #8c939d;
width: 120px;
height: 120px;
line-height: 120px;
text-align: center;
}
.image-preview {
width: 100%;
height: 100%;
position: relative;
}
.uploaded-image {
width: 100%;
height: 100%;
object-fit: cover;
}
.image-actions {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.5);
display: flex;
justify-content: center;
align-items: center;
opacity: 0;
transition: opacity 0.3s;
}
.image-preview:hover .image-actions {
opacity: 1;
}
.image-actions i {
color: #fff;
font-size: 20px;
margin: 0 10px;
cursor: pointer;
}
.image-actions i:hover {
transform: scale(1.2);
} }
\ No newline at end of file
...@@ -15,10 +15,107 @@ ...@@ -15,10 +15,107 @@
<script src="//teach.cdn.ireadabc.com/h5template/h5-static-lib/js/element-ui/index.js"></script> <script src="//teach.cdn.ireadabc.com/h5template/h5-static-lib/js/element-ui/index.js"></script>
</head> </head>
<body> <body>
<div class="edit-data"> <div class="edit-data">
<!-- 简单的例子,一个输入框+一个按钮,保存输入框的信息 --> <el-card class="data-card" v-for="item, index in data" :key="index">
test: <input type="text" name="test" /> <div slot="header" class="clearfix">
<input id="save" type="button" value="保存" > <span>题目{{index + 1}}</span>
<el-button style="float: right; padding: 3px 0" type="text" @click="deleteConf(index)">删除</el-button>
</div>
<div>
<el-form label-width="90px">
<el-form-item label="题干图片">
<div class="image-uploader">
<el-upload
:action="uploadUrl"
:data="uploadData"
:show-file-list="false"
:on-success="(res, file) => handleImageSuccess(res, file, index)"
:before-upload="beforeImageUpload">
<div v-if="data[index].image_url" class="image-preview">
<img :src="data[index].image_url" class="uploaded-image">
<div class="image-actions">
<i class="el-icon-zoom-in" @click.stop="previewImage(data[index].image_url)"></i>
<i class="el-icon-delete" @click.stop="removeImage(index)"></i>
</div>
</div>
<i v-else class="el-icon-plus upload-icon"></i>
</el-upload>
</div>
</el-form-item>
<el-form-item label="题干">
<el-input type="textarea" v-model="data[index].question" placeholder="请输入题干英文" @change="save"></el-input>
</el-form-item>
<!-- 新增音频上传和预览部分 -->
<el-form-item label="题干音频">
<div class="audio-uploader">
<el-upload
:action="uploadUrl"
:data="uploadData"
:on-success="(res, file) => handleAudioSuccess(res, file, index)"
:before-upload="beforeAudioUpload"
:show-file-list="false">
<el-button size="small" type="primary">{{ data[index].audio_url ? '重新上传' : '上传' }}</el-button>
&nbsp;&nbsp;&nbsp;&nbsp;
<span v-if="data[index].audio_url" class="filename" @click.stop="togglePlay(index)">{{ data[index].audio_name }}(点击播放)</span>
<audio :ref="'audio' + index" :src="data[index].audio_url" @ended.stop="audioEnded(index)" style="display: none;"></audio>
</el-upload>
</div>
</el-form-item>
<el-form-item v-for="(option, optionIndex) in data[index].options" :key="optionIndex" :label="'选项' + (optionIndex + 1)">
<el-row style="border: 1px solid #E4E7ED; padding: 10px;">
<el-col :span="6">
<div class="image-uploader" style="display: inline-block;">
<el-upload
:action="uploadUrl"
:data="uploadData"
:show-file-list="false"
:on-success="(res, file) => handleOptionImageSuccess(res, file, index, optionIndex)"
:before-upload="beforeImageUpload">
<div v-if="data[index].options[optionIndex].image_url" class="image-preview">
<img :src="data[index].options[optionIndex].image_url" class="uploaded-image">
<div class="image-actions">
<i class="el-icon-zoom-in" @click.stop="previewImage(data[index].options[optionIndex].image_url)"></i>
<i class="el-icon-delete" @click.stop="removeOptionImage(index, optionIndex)"></i>
</div>
</div>
<i v-else class="el-icon-plus upload-icon"></i>
</el-upload>
</div>
</el-col>
<el-col :span="18">
<el-input style="width: 60%;" v-model="data[index].options[optionIndex].text" placeholder="请输入选项英文" @change="save">
</el-input><br>
<div class="audio-uploader">
<el-upload
:action="uploadUrl"
:data="uploadData"
:on-success="(res, file) => handleOptionAudioSuccess(res, file, index, optionIndex)"
:before-upload="beforeAudioUpload"
:show-file-list="false">
<el-button size="small" type="primary">{{ data[index].options[optionIndex].audio_url ? '重新上传' : '上传' }}音频</el-button>
&nbsp;&nbsp;&nbsp;&nbsp;
<span v-if="data[index].options[optionIndex].audio_url" class="filename" @click.stop="toggleOptionPlay(index, optionIndex)">{{ data[index].options[optionIndex].audio_name }}(点击播放)</span>
<audio :ref="'audio' + index + '-' + optionIndex" :src="data[index].options[optionIndex].audio_url" @ended.stop="audioOptionEnded(index, optionIndex)" style="display: none;"></audio>
</el-upload>
</div>
<el-checkbox v-model="data[index].options[optionIndex].is_correct" @change="save">正确</el-checkbox>
<el-button style="float: right;" icon="el-icon-delete" size="small" type="primary" @click.stop="deleteOption(index, optionIndex)">删除选项</el-button>
</el-col>
</el-row>
</el-form-item>
<el-form-item>
<el-button size="small" type="primary" @click.stop="addOption(index)">增加选项</el-button>
</el-form-item>
</el-form>
</div>
</el-card>
<el-card shadow="hover" class="data-card">
<div style="cursor: pointer; display: flex; justify-content: center; align-items: center; height: 80px;"
@click="addConfig">
<el-button class="btn" type="text" size="mini" icon="el-icon-plus">新加</el-button>
</div>
</el-card>
</div> </div>
</body> </body>
<script src="./index.js"></script> <script src="./index.js"></script>
......
...@@ -10,26 +10,242 @@ var uploadData = window.courseware.uploadData(); //上传文件必须要的参 ...@@ -10,26 +10,242 @@ var uploadData = window.courseware.uploadData(); //上传文件必须要的参
*/ */
//对应模板的名称,这个可以不写,但是最好是能写上,同时开发多个模板的情况下如果不写这个,本地缓存可能会存在混乱 //对应模板的名称,这个可以不写,但是最好是能写上,同时开发多个模板的情况下如果不写这个,本地缓存可能会存在混乱
var key = "h5_test"; var key = "lexile_question";
$(function(){ new Vue({
window.courseware.getData(function(data){ el: '.edit-data',
//数据加载完,才算页面加载完成 created() {
//获取数据 window.courseware.getData((data) => {
if(data&&data.test){ this.data = data || [];
$("[name='test']").val(data.test); }, key);
} window['air'].getUploadCallback = (url, data) => {
this.uploadUrl = url;
//绑定点击事件 this.uploadData = data;
$('#save').on('click',function(){ };
var test = $("[name='test']").val(); },
data() {
//保存新增或修改的数据 return {
window.courseware.setData({test: test},function(){ data: [],
uploadUrl: window.courseware.uploadUrl(),
uploadData: window.courseware.uploadData(),
}
},
methods: {
handleAudioSuccess(res, file, index) {
// 假设上传接口返回的音频URL在 res.url 中
this.data[index].audio_url = res.url;
this.data[index].audio_name = file.name;
// 计算文件大小
const size = file.size;
if (size < 1024) {
this.data[index].audio_size = size + 'B';
} else if (size < 1024 * 1024) {
this.data[index].audio_size = (size / 1024).toFixed(2) + 'KB';
} else {
this.data[index].audio_size = (size / (1024 * 1024)).toFixed(2) + 'MB';
}
// 获取音频时长
const audio = new Audio(res.url);
audio.addEventListener('loadedmetadata', () => {
const duration = audio.duration;
const minutes = Math.floor(duration / 60);
const seconds = Math.floor(duration % 60);
this.data[index].audio_duration = `${minutes}:${seconds.toString().padStart(2, '0')}`;
});
this.save();
},
handleOptionAudioSuccess(res, file, index, optionIndex) {
// 假设上传接口返回的音频URL在 res.url 中
this.data[index].options[optionIndex].audio_url = res.url;
this.data[index].options[optionIndex].audio_name = file.name;
// 计算文件大小
const size = file.size;
if (size < 1024) {
this.data[index].options[optionIndex].audio_size = size + 'B';
} else if (size < 1024 * 1024) {
this.data[index].options[optionIndex].audio_size = (size / 1024).toFixed(2) + 'KB';
} else {
this.data[index].options[optionIndex].audio_size = (size / (1024 * 1024)).toFixed(2) + 'MB';
}
// 获取音频时长
const audio = new Audio(res.url);
audio.addEventListener('loadedmetadata', () => {
const duration = audio.duration;
const minutes = Math.floor(duration / 60);
const seconds = Math.floor(duration % 60);
this.data[index].options[optionIndex].audio_duration = `${minutes}:${seconds.toString().padStart(2, '0')}`;
});
this.save();
},
// 播放/暂停音频
togglePlay(index) {
const audio = this.$refs['audio' + index][0];
if (this.data[index].isPlaying) {
audio.pause();
this.data[index].isPlaying = false;
} else {
// 先停止其他正在播放的音频
this.data.forEach((item, i) => {
if (i !== index && item.isPlaying) {
this.$refs['audio' + i][0].pause();
item.isPlaying = false;
}
});
audio.play();
this.data[index].isPlaying = true;
}
},
toggleOptionPlay(index, optionIndex) {
const audio = this.$refs['audio' + index + '-' + optionIndex][0];
if (this.data[index].options[optionIndex].isPlaying) {
audio.pause();
this.data[index].options[optionIndex].isPlaying = false;
} else {
// 先停止其他正在播放的音频
this.data[index].options.forEach((item, i) => {
if (i !== optionIndex && item.isPlaying) {
this.$refs['audio' + index + '-' + i][0].pause();
item.isPlaying = false;
}
});
audio.play();
this.data[index].options[optionIndex].isPlaying = true;
}
},
// 音频播放结束
audioEnded(index) {
this.data[index].isPlaying = false;
},
audioOptionEnded(index, optionIndex) {
this.data[index].options[optionIndex].isPlaying = false;
},
beforeAudioUpload(file) {
const isAudio = file.type.startsWith('audio/');
const isLt20M = file.size / 1024 / 1024 < 20;
if (!isAudio) {
this.$message.error('只能上传音频文件!');
return false;
}
if (!isLt20M) {
this.$message.error('音频大小不能超过 20MB!');
return false;
}
return true;
},
save() {
// 保存新增或修改的数据
window.courseware.setData(this.data, () => {
//保存数据也是异步的 //保存数据也是异步的
alert("保存成功!"); this.$message.success("实时保存成功");
},key); }, key);
},
}) deleteConf(index) {
},key); this.data.splice(index, 1);
}) this.save();
\ No newline at end of file },
addConfig() {
this.data.push({
question: '',
audio_url: '',
image_url: '',
audio_name: '',
audio_size: '',
audio_duration: '',
isPlaying: false,
options: [],
});
this.save();
},
addOption(index) {
this.data[index].options.push({
image_url: '',
audio_url: '',
audio_name: '',
text: '',
is_correct: false,
});
this.save();
},
deleteOption(index, optionIndex) {
this.data[index].options.splice(optionIndex, 1);
this.save();
},
// 图片上传成功的回调
handleImageSuccess(res, file, index) {
// 假设上传接口返回的图片URL在 res.url 中
console.log(res, file, index);
this.data[index].image_url = res.url;
this.save();
},
handleOptionImageSuccess(res, file, index, optionIndex) {
this.data[index].options[optionIndex].image_url = res.url;
this.save();
},
// 图片上传前的验证
beforeImageUpload(file) {
const isImage = file.type.startsWith('image/');
const isLt2M = file.size / 1024 / 1024 < 5;
if (!isImage) {
this.$message.error('只能上传图片文件!');
return false;
}
if (!isLt2M) {
this.$message.error('图片大小不能超过 2MB!');
return false;
}
return true;
},
// 预览图片
previewImage(url) {
// 使用 Element UI 的 Image 组件预览
const h = this.$createElement;
this.$msgbox({
title: '图片预览',
message: h('img', {
attrs: {
src: url,
style: 'max-width: 100%; max-height: 500px;'
}
}),
showCancelButton: false,
confirmButtonText: '关闭'
});
},
removeImage(index) {
this.$confirm('确定要删除这张图片吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
this.data[index].image_url = '';
this.save();
this.$message({
type: 'success',
message: '删除成功!'
});
}).catch(() => { });
},
removeOptionImage(index, optionIndex) {
this.data[index].options[optionIndex].image_url = '';
this.save();
}
},
computed: {
}
});
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment