734 lines
18 KiB
Vue
734 lines
18 KiB
Vue
<template>
|
|
<div>
|
|
<div class="action-bar">
|
|
<input
|
|
ref="excel-upload-input"
|
|
class="excel-upload-input"
|
|
type="file"
|
|
accept=".xlsx, .xls"
|
|
@change="handleClick"
|
|
>
|
|
<el-button
|
|
type="primary"
|
|
icon="el-icon-edit"
|
|
@click="handleAddQuestion"
|
|
>
|
|
添加
|
|
</el-button>
|
|
<el-button
|
|
type="success"
|
|
icon="el-icon-s-promotion"
|
|
@click="importFromOther"
|
|
>
|
|
从其他挑战导入
|
|
</el-button>
|
|
<el-button
|
|
type="success"
|
|
icon="el-icon-upload2"
|
|
@click="handleImport"
|
|
>
|
|
导入Excel
|
|
</el-button>
|
|
<el-button
|
|
type="warning"
|
|
icon="el-icon-download"
|
|
:loading="downloadLoading"
|
|
@click="handleExport"
|
|
>
|
|
{{exportBtnName}}
|
|
</el-button>
|
|
<el-button
|
|
type="danger"
|
|
icon="el-icon-delete-solid"
|
|
@click="handleRemoveAll"
|
|
>
|
|
{{deleteBtnName}}
|
|
</el-button>
|
|
</div>
|
|
<el-table
|
|
:data="tableData"
|
|
border
|
|
fit
|
|
stripe
|
|
row-key="question"
|
|
ref="question_table"
|
|
highlight-current-row
|
|
@selection-change="handleSelectionChange"
|
|
>
|
|
<el-table-column
|
|
type="selection"
|
|
:reserve-selection="true"
|
|
width="55">
|
|
</el-table-column>
|
|
<el-table-column
|
|
type="index"
|
|
:index="computeTableIndex"
|
|
width="50">
|
|
</el-table-column>
|
|
<el-table-column
|
|
label="题目"
|
|
prop="question"
|
|
>
|
|
</el-table-column>
|
|
<el-table-column
|
|
label="正确答案"
|
|
prop="a1"
|
|
>
|
|
</el-table-column>
|
|
<el-table-column
|
|
label="混淆答案1"
|
|
prop="a2"
|
|
>
|
|
</el-table-column>
|
|
<el-table-column
|
|
label="混淆答案2"
|
|
prop="a3"
|
|
>
|
|
</el-table-column>
|
|
<el-table-column
|
|
label="混淆答案3"
|
|
prop="a4"
|
|
>
|
|
</el-table-column>
|
|
<el-table-column
|
|
label="题目类型"
|
|
>
|
|
<template slot-scope="{row}">
|
|
<span>{{ row.type === 3 ? '问卷' : '普通' }}</span>
|
|
</template>
|
|
</el-table-column>
|
|
<el-table-column
|
|
align="center"
|
|
width="180"
|
|
label="操作"
|
|
fixed="right"
|
|
>
|
|
<template slot-scope="scope">
|
|
<el-button
|
|
type="primary"
|
|
size="small"
|
|
icon="el-icon-edit"
|
|
v-permission="['shopexam:edit']"
|
|
@click="handleEditQuestion(scope)"
|
|
>
|
|
编辑
|
|
</el-button>
|
|
<el-button
|
|
type="danger"
|
|
size="small"
|
|
style="margin-left: 10px"
|
|
v-permission="['shopexam:delete']"
|
|
@click="handleDelete(scope)"
|
|
>
|
|
删除
|
|
</el-button>
|
|
</template>
|
|
</el-table-column>
|
|
</el-table>
|
|
<!-- pagination -->
|
|
<el-pagination
|
|
:hide-on-single-page="false"
|
|
:current-page="currentPage"
|
|
:page-sizes="[5, 10, 20, 50, 100]"
|
|
:page-size="pageSize"
|
|
layout="total, sizes, prev, pager, next, jumper"
|
|
:total="dataCount"
|
|
class="al-r"
|
|
@size-change="sizeChange"
|
|
@current-change="pageChange"
|
|
/>
|
|
<el-dialog
|
|
:visible.sync="dialogVisible"
|
|
:title="dialogType==='edit'?'Edit Question':'New Question'"
|
|
>
|
|
<el-form
|
|
:model="record"
|
|
ref="modalForm"
|
|
:rules="modalRules"
|
|
label-width="120px"
|
|
label-position="left"
|
|
>
|
|
<el-form-item label="题目" prop="question">
|
|
<el-input
|
|
v-model="record.question"
|
|
placeholder="题目"
|
|
clearable
|
|
/>
|
|
</el-form-item>
|
|
<el-form-item label="正确答案" prop="a1">
|
|
<el-input
|
|
v-model="record.a1"
|
|
placeholder="正确答案"
|
|
clearable
|
|
/>
|
|
</el-form-item>
|
|
<el-form-item label="混淆答案1" prop="a2">
|
|
<el-input
|
|
v-model="record.a2"
|
|
placeholder="混淆答案1"
|
|
clearable
|
|
/>
|
|
</el-form-item>
|
|
<el-form-item label="混淆答案2" prop="a3">
|
|
<el-input
|
|
v-model="record.a3"
|
|
placeholder="混淆答案2"
|
|
clearable
|
|
/>
|
|
</el-form-item>
|
|
<el-form-item label="混淆答案3" prop="a4">
|
|
<el-input
|
|
v-model="record.a4"
|
|
placeholder="混淆答案3"
|
|
clearable
|
|
/>
|
|
</el-form-item>
|
|
<el-form-item label="题目类型" prop="type">
|
|
<el-select
|
|
v-model="record.type"
|
|
placeholder="选择类型"
|
|
name="type"
|
|
required
|
|
class="w100"
|
|
>
|
|
<el-option
|
|
label="普通"
|
|
:value="1"
|
|
/>
|
|
<el-option
|
|
label="问卷"
|
|
:value="3"
|
|
/>
|
|
</el-select>
|
|
</el-form-item>
|
|
</el-form>
|
|
<div style="text-align:right;">
|
|
<el-button
|
|
type="danger"
|
|
@click="closeModal"
|
|
>
|
|
{{$t('permission.cancel')}}
|
|
</el-button>
|
|
<el-button
|
|
type="primary"
|
|
@click="saveModalData"
|
|
>
|
|
{{$t('permission.confirm')}}
|
|
</el-button>
|
|
</div>
|
|
</el-dialog>
|
|
|
|
<el-dialog
|
|
:visible.sync="listVisible"
|
|
title="选择活动"
|
|
>
|
|
<div class="action-bar">
|
|
<el-select
|
|
v-model="shop"
|
|
:placeholder="'选择'+$t('main.shop')"
|
|
name="shop"
|
|
required
|
|
class="w100"
|
|
v-if="userLevel === 1"
|
|
>
|
|
<el-option
|
|
v-for="item in allDepts"
|
|
:key="item._id"
|
|
:label="item.name"
|
|
:value="item._id"
|
|
/>
|
|
</el-select>
|
|
</div>
|
|
<el-table
|
|
v-loading="listLoading"
|
|
:data="shopExamList"
|
|
border
|
|
fit
|
|
stripe
|
|
highlight-current-row
|
|
@current-change="clickChange"
|
|
style="width: 100%;margin-top:30px;"
|
|
>
|
|
<el-table-column label="选择" width="55">
|
|
<template slot-scope="scope">
|
|
<el-radio v-model="tableRadio" :label="scope.row"><i></i></el-radio>
|
|
</template>
|
|
</el-table-column>
|
|
<el-table-column
|
|
:label="$t('main.shop')"
|
|
prop="shop"
|
|
v-if="userLevel === 1"
|
|
:formatter = "formatDept"
|
|
>
|
|
</el-table-column>
|
|
<el-table-column
|
|
min-width="200px"
|
|
label="名称"
|
|
>
|
|
<template slot-scope="{row}">
|
|
<span>{{ row.name }}</span>
|
|
</template>
|
|
</el-table-column>
|
|
<el-table-column
|
|
min-width="200px"
|
|
label="题目数量"
|
|
>
|
|
<template slot-scope="{row}">
|
|
<span>{{ row.questions.length }}</span>
|
|
</template>
|
|
</el-table-column>
|
|
</el-table>
|
|
<div style="text-align:right;">
|
|
<el-button
|
|
type="danger"
|
|
@click="closeListModal"
|
|
>
|
|
{{$t('permission.cancel')}}
|
|
</el-button>
|
|
<el-button
|
|
type="primary"
|
|
@click="importQuestions"
|
|
>
|
|
{{$t('permission.confirm')}}
|
|
</el-button>
|
|
</div>
|
|
</el-dialog>
|
|
</div>
|
|
</template>
|
|
<script lang="ts">
|
|
import { Component, Vue, Watch } from 'vue-property-decorator'
|
|
import XLSX from 'xlsx'
|
|
import { getExams, IExamData, IExamQuerstion } from '@/api/exam'
|
|
import { exportJson2Excel } from '@/utils/excel'
|
|
import { formatJson, parseTime } from '@/utils'
|
|
import { cloneDeep } from 'lodash'
|
|
import { UserModule } from '@/store/modules/user'
|
|
import { IShopData } from '@/api/types'
|
|
import { getShops } from '@/api/shop'
|
|
|
|
declare module 'vue/types/vue' {
|
|
interface Vue {
|
|
questions: IExamQuerstion[]
|
|
examid?: string
|
|
}
|
|
}
|
|
|
|
@Component({
|
|
name: 'PuzzleList',
|
|
props: ['questions', 'examid'],
|
|
model: {
|
|
prop: 'questions',
|
|
event: 'update'
|
|
}
|
|
})
|
|
export default class extends Vue {
|
|
private excelData = {
|
|
header: null,
|
|
results: null
|
|
}
|
|
|
|
// pagination
|
|
private currentPage = 1
|
|
private pageSize = 5
|
|
private dataCount = 0
|
|
private tableData: IExamQuerstion[] = []
|
|
private loading = false
|
|
private downloadLoading = false
|
|
private filename : string | null= ''
|
|
private multipleSelection : IExamQuerstion[] = []
|
|
private deleteBtnName = '删除所有'
|
|
private exportBtnName = '导出所有'
|
|
private dialogVisible = false
|
|
private listVisible = false
|
|
private dialogType = 'new'
|
|
private record = Object.assign({}, this.defaultPuzzle())
|
|
private shop = UserModule.department || ''
|
|
private allDepts: IShopData[] = []
|
|
private listLoading = false
|
|
private shopExamList: IExamData[] = []
|
|
private tableRadio: any = {}
|
|
|
|
$refs!: {
|
|
modalForm: HTMLFormElement
|
|
'question_table': HTMLTableElement
|
|
'excel-upload-input': HTMLFormElement
|
|
}
|
|
|
|
get userLevel() {
|
|
return UserModule.level
|
|
}
|
|
|
|
async created() {
|
|
if (UserModule.level === 1) {
|
|
await this.getRemoteDeptList()
|
|
} else if (UserModule.department) {
|
|
this.shop = UserModule.department
|
|
}
|
|
}
|
|
|
|
private modalRules = {
|
|
question: [{ required: true, message: '请输入题目', trigger: 'blur' },
|
|
{ min: 2, max: 35, message: '长度在 2 到 35 个字符', trigger: 'blur' }],
|
|
a1: [{ required: true, message: '请输入正确答案', trigger: 'blur' }],
|
|
a2: [{ required: true, message: '至少输入一个错误答案', trigger: 'blur' }]
|
|
}
|
|
|
|
@Watch('questions')
|
|
private initDataChange() {
|
|
this.sliceData()
|
|
}
|
|
|
|
@Watch('multipleSelection')
|
|
private selectChange() {
|
|
this.deleteBtnName = this.multipleSelection.length > 0 ? '删除选中项' : '删除所有'
|
|
this.exportBtnName = this.multipleSelection.length > 0 ? '导出选中项' : '导出所有'
|
|
}
|
|
|
|
@Watch('shop')
|
|
private shopChange() {
|
|
console.log(`shop change: ${this.shop}`)
|
|
this.getList()
|
|
}
|
|
|
|
private defaultPuzzle() {
|
|
return {
|
|
_id: undefined,
|
|
question: '',
|
|
a1: '',
|
|
a2: '',
|
|
a3: '',
|
|
a4: '',
|
|
type: 1
|
|
}
|
|
}
|
|
|
|
// begin of 题库编辑
|
|
private handleClick(e: MouseEvent) {
|
|
const files = (e.target as HTMLInputElement).files
|
|
if (files) {
|
|
const rawFile = files[0] // only use files[0]
|
|
this.upload(rawFile)
|
|
}
|
|
}
|
|
|
|
private handleAddQuestion() {
|
|
this.dialogType = 'new'
|
|
this.dialogVisible = true
|
|
this.record = Object.assign({}, this.defaultPuzzle())
|
|
}
|
|
|
|
private handleEditQuestion(scope: any) {
|
|
this.dialogType = 'edit'
|
|
this.dialogVisible = true
|
|
this.record = cloneDeep(scope.row)
|
|
}
|
|
|
|
private handleImport() {
|
|
(this.$refs['excel-upload-input']).click()
|
|
}
|
|
|
|
private async handleRemoveAll() {
|
|
try {
|
|
const str = this.multipleSelection.length > 0 ? '确定删除选中的题目' : '确定删除所有题目'
|
|
await this.$confirm(str, 'Warning', {
|
|
confirmButtonText: '确定',
|
|
cancelButtonText: '取消',
|
|
type: 'warning'
|
|
})
|
|
if (this.multipleSelection.length > 0) {
|
|
for (const s of this.multipleSelection) {
|
|
this.questions.splice(this.questions.indexOf(s), 1)
|
|
}
|
|
(this.$refs.question_table as any).clearSelection()
|
|
} else {
|
|
this.questions.length = 0
|
|
}
|
|
this.sliceData()
|
|
this.$message({
|
|
type: 'success',
|
|
message: 'Deleted!'
|
|
})
|
|
} catch (err) {
|
|
console.log(err)
|
|
}
|
|
}
|
|
|
|
private handleExport() {
|
|
this.handleDownload()
|
|
}
|
|
|
|
private async handleDelete(scope: any) {
|
|
const { $index, row } = scope
|
|
console.log($index, row)
|
|
try {
|
|
await this.$confirm('确定删除当前记录?', 'Warning', {
|
|
confirmButtonText: '确定',
|
|
cancelButtonText: '取消',
|
|
type: 'warning'
|
|
})
|
|
this.questions.splice(this.questions.indexOf(row), 1)
|
|
this.sliceData()
|
|
this.$message({
|
|
type: 'success',
|
|
message: 'Deleted!'
|
|
})
|
|
} catch (err) {
|
|
console.log(err)
|
|
}
|
|
}
|
|
|
|
private handleSelectionChange(val: any) {
|
|
this.multipleSelection = val
|
|
console.log(this.multipleSelection)
|
|
}
|
|
|
|
private upload(rawFile: File) {
|
|
this.$refs['excel-upload-input'].value = '' // Fixes can't select the same excel
|
|
|
|
this.readerData(rawFile)
|
|
}
|
|
|
|
private readerData(rawFile: File) {
|
|
this.loading = true
|
|
const reader = new FileReader()
|
|
reader.onload = e => {
|
|
const data = (e.target as FileReader).result
|
|
const workbook = XLSX.read(data, { type: 'array' })
|
|
const firstSheetName = workbook.SheetNames[0]
|
|
const worksheet = workbook.Sheets[firstSheetName]
|
|
const header = this.getHeaderRow(worksheet)
|
|
const results = XLSX.utils.sheet_to_json(worksheet)
|
|
this.generateData(header, results)
|
|
this.loading = false
|
|
}
|
|
reader.readAsArrayBuffer(rawFile)
|
|
}
|
|
|
|
private getHeaderRow(sheet: { [key: string]: any }) {
|
|
const headers: string[] = []
|
|
const range = XLSX.utils.decode_range(sheet['!ref'])
|
|
const R = range.s.r
|
|
// start in the first row
|
|
for (let C = range.s.c; C <= range.e.c; ++C) { // walk every column in the range
|
|
const cell = sheet[XLSX.utils.encode_cell({ c: C, r: R })]
|
|
// find the cell in the first row
|
|
let hdr = ''
|
|
if (cell && cell.t) hdr = XLSX.utils.format_cell(cell)
|
|
if (hdr === '') {
|
|
hdr = 'UNKNOWN ' + C // replace with your desired default
|
|
}
|
|
headers.push(hdr)
|
|
}
|
|
return headers
|
|
}
|
|
|
|
private generateData(header: any, results: any) {
|
|
this.excelData.header = header
|
|
this.excelData.results = results
|
|
const questionSet = new Set()
|
|
for (const _q of this.questions) {
|
|
questionSet.add(_q.question)
|
|
}
|
|
let count = 0
|
|
for (const _q of results) {
|
|
if (!questionSet.has(_q.question)) {
|
|
this.questions.push(_q)
|
|
questionSet.add(_q.question)
|
|
count += 1
|
|
}
|
|
}
|
|
this.sliceData()
|
|
console.log(this.excelData)
|
|
const msg = `操作成功, 共导入${count}个题目`
|
|
this.$message({
|
|
type: 'success',
|
|
message: msg
|
|
})
|
|
}
|
|
|
|
// pagination
|
|
private sizeChange(val: number) {
|
|
this.pageSize = val
|
|
this.sliceData()
|
|
}
|
|
|
|
private pageChange(val: number) {
|
|
this.currentPage = val
|
|
this.sliceData()
|
|
}
|
|
|
|
private computeTableIndex(index: number) {
|
|
return (this.currentPage - 1) * this.pageSize + index + 1
|
|
}
|
|
|
|
private sliceData() {
|
|
// 满足过滤条件 -> 分页
|
|
const data = this.filterData()
|
|
this.tableData = data.slice(
|
|
(this.currentPage - 1) * this.pageSize,
|
|
this.currentPage * this.pageSize
|
|
)
|
|
if (this.tableData.length === 0 && this.currentPage > 1) {
|
|
this.currentPage -= 1
|
|
this.sliceData()
|
|
}
|
|
}
|
|
|
|
private filterData() {
|
|
const result: IExamQuerstion[] = []
|
|
for (const _d of this.questions) {
|
|
result.push(_d)
|
|
}
|
|
this.dataCount = result.length
|
|
return result
|
|
}
|
|
|
|
private valchange(val: any) {
|
|
console.log('valchange', val)
|
|
this.$emit('update', val)
|
|
}
|
|
|
|
private handleDownload() {
|
|
this.downloadLoading = true
|
|
const tHeader = ['question', 'a1', 'a2', 'a3', 'a4', 'type']
|
|
const list = this.multipleSelection.length > 0 ? this.multipleSelection.slice(0) : this.questions.slice(0)
|
|
this.filename = parseTime(new Date(), '{y}{m}{d}{h}{i}{s}')
|
|
const data = formatJson(tHeader, list)
|
|
console.log('begin generate excel')
|
|
exportJson2Excel(tHeader, data, this.filename ? this.filename : undefined, undefined, undefined, true, 'xlsx')
|
|
this.downloadLoading = false
|
|
}
|
|
|
|
private async importFromOther() {
|
|
console.log('importFromOther')
|
|
if (UserModule.level === 9 && UserModule.department) {
|
|
await this.getList()
|
|
}
|
|
this.listVisible = true
|
|
}
|
|
|
|
private closeModal() {
|
|
this.dialogVisible = false
|
|
this.$refs.modalForm.clearValidate()
|
|
}
|
|
|
|
private closeListModal() {
|
|
this.listVisible = false
|
|
}
|
|
|
|
private saveModalData() {
|
|
const isEdit = this.dialogType === 'edit'
|
|
this.$refs.modalForm.validate(async(valid: boolean) => {
|
|
if (!valid) {
|
|
this.$message.error('请按要求填写表单')
|
|
return false
|
|
}
|
|
if (isEdit) {
|
|
for (let index = 0; index < this.questions.length; index++) {
|
|
if (this.questions[index]._id === this.record._id) {
|
|
this.questions.splice(index, 1, Object.assign({}, this.record))
|
|
break
|
|
}
|
|
}
|
|
} else {
|
|
this.questions.push(this.record)
|
|
}
|
|
this.dialogVisible = false
|
|
this.$notify({
|
|
title: 'Success',
|
|
dangerouslyUseHTMLString: true,
|
|
message: `
|
|
题目编辑成功, 请点击保存
|
|
`,
|
|
type: 'success'
|
|
})
|
|
})
|
|
}
|
|
|
|
// begin of 活动选择
|
|
private async getRemoteDeptList() {
|
|
const { data } = await getShops({ })
|
|
if (!data.records) return
|
|
this.allDepts = data.records
|
|
}
|
|
|
|
private formatDept(row: number, column: number, cellValue: string) {
|
|
let result = '未指定'
|
|
for (const dep of this.allDepts) {
|
|
if (dep._id === cellValue) {
|
|
result = dep.name
|
|
break
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
|
|
private async getList() {
|
|
this.listLoading = true
|
|
const { data } = await getExams({
|
|
page: 1,
|
|
limit: 20,
|
|
key: '',
|
|
shop: this.shop
|
|
})
|
|
this.listLoading = false
|
|
const records: IExamData[] = data.records
|
|
this.shopExamList = records.filter(o => o._id !== this.examid)
|
|
}
|
|
|
|
private clickChange(item: any) {
|
|
this.tableRadio = item
|
|
console.log(this.tableRadio)
|
|
}
|
|
|
|
private async importQuestions() {
|
|
if (!(this.tableRadio?.questions?.length > 0)) {
|
|
this.$message.error('请先选择一个有题目的活动')
|
|
return
|
|
}
|
|
try {
|
|
await this.$confirm('确定导入当前挑战活动的所有题目?', 'Warning', {
|
|
confirmButtonText: '确定',
|
|
cancelButtonText: '取消',
|
|
type: 'warning'
|
|
})
|
|
const questionSet = new Set()
|
|
for (const _q of this.questions) {
|
|
questionSet.add(_q.question)
|
|
}
|
|
let count = 0
|
|
for (const _q of this.tableRadio.questions) {
|
|
if (!questionSet.has(_q.question)) {
|
|
this.questions.push(_q)
|
|
questionSet.add(_q.question)
|
|
count += 1
|
|
}
|
|
}
|
|
this.sliceData()
|
|
console.log(this.excelData)
|
|
this.closeListModal()
|
|
const msg = `操作成功, 共导入${count}个题目`
|
|
this.$message({
|
|
type: 'success',
|
|
message: msg
|
|
})
|
|
} catch (err) {
|
|
console.log(err)
|
|
}
|
|
}
|
|
}
|
|
</script>
|
|
<style lang="scss" scoped>
|
|
.action-bar {
|
|
margin-bottom: 15px;
|
|
}
|
|
|
|
.excel-upload-input {
|
|
display: none;
|
|
z-index: -9999;
|
|
}
|
|
.el-form-item{
|
|
margin-bottom: 22px;
|
|
}
|
|
</style>
|