|
|
@@ -34,15 +34,38 @@
|
|
|
:value="item.value"
|
|
|
/>
|
|
|
</el-select>
|
|
|
- <el-button
|
|
|
- type="primary"
|
|
|
- size="small"
|
|
|
- plain
|
|
|
- style="margin-left: 10px"
|
|
|
- @click="handleUploadImage"
|
|
|
- >上传图片</el-button
|
|
|
+ <el-upload
|
|
|
+ action="#"
|
|
|
+ class="inline-upload"
|
|
|
+ :show-file-list="false"
|
|
|
+ :disabled="isView"
|
|
|
+ :accept="imageAccept"
|
|
|
+ :before-upload="beforeImageUpload"
|
|
|
+ :http-request="handleUploadImage"
|
|
|
>
|
|
|
+ <el-button
|
|
|
+ type="primary"
|
|
|
+ size="small"
|
|
|
+ plain
|
|
|
+ :loading="questionImageLoading"
|
|
|
+ >上传图片</el-button
|
|
|
+ >
|
|
|
+ </el-upload>
|
|
|
<span class="upload-tip">试题图片上传</span>
|
|
|
+
|
|
|
+ <div v-if="innerValue.questionImage" class="image-preview">
|
|
|
+ <el-image
|
|
|
+ :src="resolveImageUrl(innerValue.questionImage)"
|
|
|
+ :preview-src-list="[resolveImageUrl(innerValue.questionImage)]"
|
|
|
+ fit="cover"
|
|
|
+ class="image-thumb"
|
|
|
+ />
|
|
|
+ <i
|
|
|
+ v-if="!isView"
|
|
|
+ class="el-icon-error image-remove"
|
|
|
+ @click="handleRemoveImage"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
</el-form-item>
|
|
|
|
|
|
<el-form-item label="专业类别:" prop="category">
|
|
|
@@ -98,12 +121,37 @@
|
|
|
placeholder="请输入选项(100字以内)"
|
|
|
class="option-input"
|
|
|
/>
|
|
|
- <el-button
|
|
|
- type="text"
|
|
|
- size="small"
|
|
|
- @click="handleUploadOptionImage(index)"
|
|
|
- >上传图片</el-button
|
|
|
+ <el-upload
|
|
|
+ action="#"
|
|
|
+ class="inline-upload"
|
|
|
+ :show-file-list="false"
|
|
|
+ :disabled="isView"
|
|
|
+ :accept="imageAccept"
|
|
|
+ :before-upload="beforeImageUpload"
|
|
|
+ :http-request="
|
|
|
+ (options) => handleUploadOptionImage(options, index)
|
|
|
+ "
|
|
|
>
|
|
|
+ <el-button
|
|
|
+ type="text"
|
|
|
+ size="small"
|
|
|
+ :loading="optionImageLoading === index"
|
|
|
+ >上传图片</el-button
|
|
|
+ >
|
|
|
+ </el-upload>
|
|
|
+ <span v-if="item.optionImage" class="image-preview option-preview">
|
|
|
+ <el-image
|
|
|
+ :src="resolveImageUrl(item.optionImage)"
|
|
|
+ :preview-src-list="[resolveImageUrl(item.optionImage)]"
|
|
|
+ fit="cover"
|
|
|
+ class="image-thumb small"
|
|
|
+ />
|
|
|
+ <i
|
|
|
+ v-if="!isView"
|
|
|
+ class="el-icon-error image-remove"
|
|
|
+ @click="handleRemoveOptionImage(index)"
|
|
|
+ />
|
|
|
+ </span>
|
|
|
<el-button
|
|
|
v-if="innerValue.type !== '3' && innerValue.options.length > 2"
|
|
|
type="text"
|
|
|
@@ -135,6 +183,10 @@
|
|
|
</template>
|
|
|
|
|
|
<script>
|
|
|
+ import { uploadFile } from '@/api/system/file/index';
|
|
|
+ import { getImageUrl } from '@/utils/file';
|
|
|
+ import { MAX_IMAGE_SIZE } from "@/config/setting.js";
|
|
|
+
|
|
|
const typeOptions = [
|
|
|
{ label: '单选题', value: '1' },
|
|
|
{ label: '多选题', value: '2' },
|
|
|
@@ -164,6 +216,9 @@
|
|
|
return {
|
|
|
typeOptions,
|
|
|
categoryOptions,
|
|
|
+ imageAccept: 'image/png,image/jpeg,image/jpg,image/gif,image/bmp',
|
|
|
+ questionImageLoading: false,
|
|
|
+ optionImageLoading: -1,
|
|
|
rules: {
|
|
|
content: [
|
|
|
{ required: true, message: '请输入试题内容', trigger: 'blur' }
|
|
|
@@ -216,17 +271,17 @@
|
|
|
handleTypeChange(val) {
|
|
|
if (val === '1' || val === '2') {
|
|
|
this.innerValue.options = [
|
|
|
- { key: 'A', content: '' },
|
|
|
- { key: 'B', content: '' },
|
|
|
- { key: 'C', content: '' },
|
|
|
- { key: 'D', content: '' }
|
|
|
+ { key: 'A', content: '', optionImage: '' },
|
|
|
+ { key: 'B', content: '', optionImage: '' },
|
|
|
+ { key: 'C', content: '', optionImage: '' },
|
|
|
+ { key: 'D', content: '', optionImage: '' }
|
|
|
];
|
|
|
this.innerValue.rightAnswer = '';
|
|
|
this.innerValue.rightAnswers = [];
|
|
|
} else if (val === '3') {
|
|
|
this.innerValue.options = [
|
|
|
- { key: 'A', content: '正确' },
|
|
|
- { key: 'B', content: '错误' }
|
|
|
+ { key: 'A', content: '正确', optionImage: '' },
|
|
|
+ { key: 'B', content: '错误', optionImage: '' }
|
|
|
];
|
|
|
this.innerValue.rightAnswer = '';
|
|
|
this.innerValue.rightAnswers = [];
|
|
|
@@ -236,13 +291,75 @@
|
|
|
this.innerValue.rightAnswers = [];
|
|
|
}
|
|
|
},
|
|
|
- handleUploadImage() {
|
|
|
- // TODO: 试题图片上传
|
|
|
- this.$message.info('试题图片上传待接入');
|
|
|
+ async handleUploadImage(options) {
|
|
|
+ this.questionImageLoading = true;
|
|
|
+ try {
|
|
|
+ const storePath = await this.doUpload(options.file);
|
|
|
+ this.$set(this.innerValue, 'questionImage', storePath);
|
|
|
+ this.$message.success('上传成功');
|
|
|
+ } catch (error) {
|
|
|
+ this.$message.error(error.message || '上传失败');
|
|
|
+ } finally {
|
|
|
+ this.questionImageLoading = false;
|
|
|
+ }
|
|
|
+ },
|
|
|
+ handleRemoveImage() {
|
|
|
+ this.$set(this.innerValue, 'questionImage', '');
|
|
|
+ },
|
|
|
+ async handleUploadOptionImage(options, index) {
|
|
|
+ const option = this.innerValue.options[index];
|
|
|
+ if (!option) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ this.optionImageLoading = index;
|
|
|
+ try {
|
|
|
+ const storePath = await this.doUpload(options.file);
|
|
|
+ this.$set(option, 'optionImage', storePath);
|
|
|
+ this.$message.success('上传成功');
|
|
|
+ } catch (error) {
|
|
|
+ this.$message.error(error.message || '上传失败');
|
|
|
+ } finally {
|
|
|
+ this.optionImageLoading = -1;
|
|
|
+ }
|
|
|
+ },
|
|
|
+ handleRemoveOptionImage(index) {
|
|
|
+ const option = this.innerValue.options[index];
|
|
|
+ if (option) {
|
|
|
+ this.$set(option, 'optionImage', '');
|
|
|
+ }
|
|
|
},
|
|
|
- handleUploadOptionImage(index) {
|
|
|
- // TODO: 选项图片上传
|
|
|
- this.$message.info('选项图片上传待接入:' + index);
|
|
|
+ /* 校验图片类型与大小,el-upload 的 before-upload 钩子 */
|
|
|
+ beforeImageUpload(file) {
|
|
|
+ if (!file.type.startsWith('image')) {
|
|
|
+ this.$message.error('只能选择图片');
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ if (file.size / 1024 / 1024 > MAX_IMAGE_SIZE) {
|
|
|
+ this.$message.error(`图片大小不能超过 ${MAX_IMAGE_SIZE}MB`);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ return true;
|
|
|
+ },
|
|
|
+ /* 调用文件上传接口,返回 storePath */
|
|
|
+ async doUpload(file) {
|
|
|
+ const res = await uploadFile({ module: 'main', multiPartFile: file });
|
|
|
+ const storePath = res && res.data && res.data.storePath;
|
|
|
+ if (!storePath) {
|
|
|
+ throw new Error('上传失败');
|
|
|
+ }
|
|
|
+ if(import.meta.env.MODE === 'development') {
|
|
|
+ const url = res && res.data && res.data.url;
|
|
|
+ return url;
|
|
|
+ }
|
|
|
+ return storePath;
|
|
|
+ },
|
|
|
+ /* storePath 拼接为可访问的图片地址 */
|
|
|
+ resolveImageUrl(storePath) {
|
|
|
+ if(storePath.startsWith('http')) {
|
|
|
+ return storePath;
|
|
|
+ }
|
|
|
+ const imageUrl = storePath ? getImageUrl(storePath) : '';
|
|
|
+ return imageUrl;
|
|
|
},
|
|
|
handleAddOption() {
|
|
|
if (this.innerValue.type === '3') {
|
|
|
@@ -252,7 +369,11 @@
|
|
|
const nextKey = String.fromCharCode(
|
|
|
65 + this.innerValue.options.length
|
|
|
);
|
|
|
- this.innerValue.options.push({ key: nextKey, content: '' });
|
|
|
+ this.innerValue.options.push({
|
|
|
+ key: nextKey,
|
|
|
+ content: '',
|
|
|
+ optionImage: ''
|
|
|
+ });
|
|
|
},
|
|
|
handleDeleteOption(index) {
|
|
|
if (this.innerValue.type === '3') {
|
|
|
@@ -294,6 +415,47 @@
|
|
|
margin-left: 10px;
|
|
|
}
|
|
|
|
|
|
+ .inline-upload {
|
|
|
+ display: inline-block;
|
|
|
+ margin-left: 10px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .image-preview {
|
|
|
+ position: relative;
|
|
|
+ display: inline-block;
|
|
|
+ margin-left: 8px;
|
|
|
+ line-height: 1;
|
|
|
+
|
|
|
+ .image-thumb {
|
|
|
+ width: 80px;
|
|
|
+ height: 80px;
|
|
|
+ border: 1px solid #dcdfe6;
|
|
|
+ border-radius: 4px;
|
|
|
+
|
|
|
+ &.small {
|
|
|
+ width: 40px;
|
|
|
+ height: 40px;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .image-remove {
|
|
|
+ position: absolute;
|
|
|
+ top: -6px;
|
|
|
+ right: -6px;
|
|
|
+ color: #f56c6c;
|
|
|
+ background: #fff;
|
|
|
+ border-radius: 50%;
|
|
|
+ cursor: pointer;
|
|
|
+ font-size: 16px;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .option-preview {
|
|
|
+ margin-top: 0;
|
|
|
+ margin-right: 10px;
|
|
|
+ vertical-align: middle;
|
|
|
+ }
|
|
|
+
|
|
|
.option-list {
|
|
|
.option-item {
|
|
|
display: flex;
|
|
|
@@ -320,5 +482,6 @@
|
|
|
|
|
|
.danger-text {
|
|
|
color: #f56c6c;
|
|
|
+ margin-left: 10px;
|
|
|
}
|
|
|
</style>
|