716 lines
20 KiB
Vue
716 lines
20 KiB
Vue
<template>
|
||
<!-- 独立窗口模式 -->
|
||
<div v-if="isIndependent" class="independent-form">
|
||
<div class="independent-header">
|
||
<h2>{{ dialogTitle }}</h2>
|
||
</div>
|
||
<div class="independent-content">
|
||
<div class="program-edit">
|
||
<el-scrollbar height="calc(100vh - 150px)">
|
||
<div class="main">
|
||
<div class="tabsTip">
|
||
<el-icon><InfoFilled /></el-icon>邮件题目编辑
|
||
</div>
|
||
<el-tabs v-model="activeName" class="demo-tabs" @tab-click="handleClick">
|
||
<el-tab-pane label="基本内容" name="common">
|
||
<div class="line">
|
||
<div class="title-text">试题题目</div>
|
||
<div class="buttons">
|
||
<ElButton type="primary" plain>
|
||
<el-icon><Document /></el-icon>
|
||
更换题目模板
|
||
</ElButton>
|
||
<ElButton type="primary" plain>
|
||
<el-icon><Search /></el-icon>
|
||
预览
|
||
</ElButton>
|
||
</div>
|
||
<div class="block">
|
||
<Editor
|
||
v-model="formData.content"
|
||
:height="150"
|
||
:key="`editor-content-${formType}`"
|
||
/>
|
||
</div>
|
||
<div class="buttons">
|
||
<ElButton type="primary" plain>
|
||
<el-icon><Paperclip /></el-icon>
|
||
添加附件
|
||
</ElButton>
|
||
</div>
|
||
</div>
|
||
<div class="line">
|
||
<div class="title-text">基本信息</div>
|
||
<el-form
|
||
label-width="80px"
|
||
:inline="true"
|
||
label-position="right"
|
||
class="formList"
|
||
>
|
||
<el-form-item label="专业">
|
||
<el-input v-model="formData.specialtyName" readonly />
|
||
</el-form-item>
|
||
<el-form-item label="课程">
|
||
<el-input v-model="formData.courseName" readonly />
|
||
</el-form-item>
|
||
<el-form-item label="章节">
|
||
<el-input v-model="formData.chapteridDictText" readonly />
|
||
</el-form-item>
|
||
<el-form-item label="科目">
|
||
<el-input v-model="formData.subjectName" readonly />
|
||
</el-form-item>
|
||
<el-form-item label="知识点">
|
||
<el-input v-model="formData.pointNames" readonly />
|
||
</el-form-item>
|
||
<el-form-item label="题目类型">
|
||
<el-input v-model="formData.quType" placeholder="邮件题目" readonly />
|
||
</el-form-item>
|
||
<el-form-item label="难度">
|
||
<el-select
|
||
v-model="formData.quLevel"
|
||
placeholder="请选择难度"
|
||
style="width: 200px"
|
||
>
|
||
<el-option
|
||
v-for="dict in getIntDictOptions(DICT_TYPE.EXAM_QUE_DIFF)"
|
||
:key="dict.value"
|
||
:label="dict.label"
|
||
:value="dict.value"
|
||
/>
|
||
</el-select>
|
||
</el-form-item>
|
||
<el-form-item label="启用状态">
|
||
<el-radio-group v-model="formData.status">
|
||
<el-radio :label="'0'">启用</el-radio>
|
||
<el-radio :label="'1'">禁用</el-radio>
|
||
</el-radio-group>
|
||
</el-form-item>
|
||
</el-form>
|
||
</div>
|
||
</el-tab-pane>
|
||
</el-tabs>
|
||
</div>
|
||
</el-scrollbar>
|
||
</div>
|
||
</div>
|
||
<div class="independent-footer">
|
||
<el-button @click="submitForm" :loading="formLoading" :disabled="formLoading">
|
||
{{ formLoading ? '提交中...' : '确 定' }}
|
||
</el-button>
|
||
<el-button @click="handleCancel">取 消</el-button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 对话框模式 -->
|
||
<div v-else class="edit-dialog">
|
||
<Dialog v-model="dialogVisible" :title="dialogTitle" width="85%" top="10vh">
|
||
<el-scrollbar>
|
||
<div class="main">
|
||
<el-form
|
||
ref="formRef"
|
||
v-loading="formLoading"
|
||
:model="formData"
|
||
:rules="formRules"
|
||
label-width="80px"
|
||
>
|
||
<el-row>
|
||
<el-col :span="12">
|
||
<el-form-item label="专业" prop="specialtyName">
|
||
<el-input v-model="formData.specialtyName" placeholder="请输入专业" disabled />
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="12">
|
||
<el-form-item label="章节名称" prop="chapteridDictText">
|
||
<el-input
|
||
v-model="formData.chapteridDictText"
|
||
placeholder="请输入章节名称"
|
||
disabled
|
||
/>
|
||
</el-form-item>
|
||
</el-col>
|
||
</el-row>
|
||
<el-row>
|
||
<el-col :span="12">
|
||
<el-form-item label="课程" prop="courseName">
|
||
<el-input v-model="formData.courseName" placeholder="请输入课程" disabled />
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="12">
|
||
<el-form-item label="题型难度" prop="quLevel">
|
||
<el-select v-model="formData.quLevel" placeholder="请选择题型难度" clearable>
|
||
<el-option
|
||
v-for="dict in getIntDictOptions(DICT_TYPE.EXAM_QUE_DIFF)"
|
||
:key="dict.value"
|
||
:label="dict.label"
|
||
:value="dict.value"
|
||
/>
|
||
</el-select>
|
||
</el-form-item>
|
||
</el-col>
|
||
</el-row>
|
||
<el-row>
|
||
<el-col :span="12">
|
||
<el-form-item label="题型" prop="subjectName">
|
||
<el-input v-model="formData.subjectName" placeholder="请输入题型" disabled />
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="12">
|
||
<el-form-item label="知识点" prop="pointNames">
|
||
<el-input v-model="formData.pointNames" placeholder="请选择知识点" readonly />
|
||
</el-form-item>
|
||
</el-col>
|
||
</el-row>
|
||
<el-row>
|
||
<el-col :span="12">
|
||
<el-form-item label="启用状态" prop="status">
|
||
<el-radio-group v-model="formData.status">
|
||
<el-radio :label="'0'">启用</el-radio>
|
||
<el-radio :label="'1'">禁用</el-radio>
|
||
</el-radio-group>
|
||
</el-form-item>
|
||
</el-col>
|
||
</el-row>
|
||
<el-row>
|
||
<el-col :span="24">
|
||
<el-form-item label="试题内容" prop="content">
|
||
<Editor v-model="formData.content" :height="150" />
|
||
</el-form-item>
|
||
</el-col>
|
||
</el-row>
|
||
<el-row>
|
||
<el-col :span="24">
|
||
<el-form-item label="试题解析" prop="analysis">
|
||
<Editor v-model="formData.analysis" :height="150" />
|
||
</el-form-item>
|
||
</el-col>
|
||
</el-row>
|
||
</el-form>
|
||
</div>
|
||
</el-scrollbar>
|
||
<template #footer>
|
||
<div class="dialog-footer">
|
||
<el-button @click="dialogVisible = false">取 消</el-button>
|
||
<el-button type="primary" @click="submitForm" :loading="formLoading"> 确 定 </el-button>
|
||
</div>
|
||
</template>
|
||
</Dialog>
|
||
</div>
|
||
</template>
|
||
|
||
<script lang="ts" setup>
|
||
import { ElMessage } from 'element-plus'
|
||
import { useI18n } from '@/hooks/web/useI18n'
|
||
import { ref, reactive, computed, onMounted, nextTick } from 'vue'
|
||
import { getAccessToken } from '@/utils/auth'
|
||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||
import * as QuestionApi from '@/api/paper/question'
|
||
import { Editor } from '@/components/Editor'
|
||
import { InfoFilled, Document, Search, Paperclip } from '@element-plus/icons-vue'
|
||
import { Dialog } from '@/components/Dialog'
|
||
|
||
defineOptions({ name: 'EmailForm' })
|
||
|
||
const { t } = useI18n()
|
||
|
||
// 组件参数
|
||
interface Props {
|
||
isIndependent?: boolean
|
||
}
|
||
|
||
const props = withDefaults(defineProps<Props>(), {
|
||
isIndependent: false
|
||
})
|
||
|
||
// Tauri 独立窗口相关
|
||
let WebviewWindow: any = null
|
||
const isDesktop = ref(false)
|
||
|
||
// 基础数据
|
||
const activeName = ref('common')
|
||
const dialogVisible = ref(false)
|
||
const dialogTitle = ref('')
|
||
const formLoading = ref(false)
|
||
const formType = ref('')
|
||
|
||
// 表单数据
|
||
const formData = ref({
|
||
content: '',
|
||
specialtyName: '',
|
||
courseName: '',
|
||
chapteridDictText: '',
|
||
subjectName: '',
|
||
pointNames: '',
|
||
quType: '邮件题目',
|
||
quLevel: 0,
|
||
status: '0',
|
||
analysis: ''
|
||
})
|
||
|
||
// 表单引用和规则
|
||
const formRef = ref()
|
||
const formRules = reactive({
|
||
content: [{ required: true, message: '请输入题目内容', trigger: 'blur' }],
|
||
quLevel: [{ required: true, message: '请选择难度', trigger: 'change' }]
|
||
})
|
||
|
||
// 检测是否为桌面环境
|
||
onMounted(async () => {
|
||
try {
|
||
if (typeof window !== 'undefined' && (window as any).__TAURI__) {
|
||
const { WebviewWindow: TauriWebviewWindow } = await import('@tauri-apps/api/webviewWindow')
|
||
WebviewWindow = TauriWebviewWindow
|
||
isDesktop.value = true
|
||
}
|
||
} catch (error) {
|
||
console.log('非桌面环境')
|
||
}
|
||
})
|
||
|
||
// 计算属性
|
||
const uploadAction = computed(
|
||
() => import.meta.env.VITE_BASE_URL + import.meta.env.VITE_API_URL + '/infra/file/upload'
|
||
)
|
||
const uploadHeaders = computed(() => ({ Authorization: 'Bearer ' + getAccessToken() }))
|
||
|
||
// Tab 切换
|
||
const handleClick = (tab: any) => {
|
||
console.log('切换到tab:', tab.name)
|
||
}
|
||
|
||
// 表单提交
|
||
const submitForm = async () => {
|
||
// 独立窗口模式下,基于表单数据进行简单验证
|
||
if (props.isIndependent) {
|
||
// 基本必填项检查
|
||
if (!formData.value.content) {
|
||
ElMessage.warning('请填写题目内容')
|
||
return
|
||
}
|
||
} else {
|
||
// 对话框模式下,使用formRef验证
|
||
if (!formRef.value) return
|
||
const valid = await formRef.value.validate()
|
||
if (!valid) return
|
||
}
|
||
|
||
formLoading.value = true
|
||
try {
|
||
const dataToSubmit = JSON.parse(JSON.stringify(formData.value))
|
||
|
||
// 确保基础必需字段
|
||
dataToSubmit.quLevel = dataToSubmit.quLevel || 0
|
||
dataToSubmit.status = dataToSubmit.status || '0'
|
||
|
||
// 确保字符串字段不为undefined
|
||
dataToSubmit.specialtyName = dataToSubmit.specialtyName || ''
|
||
dataToSubmit.courseName = dataToSubmit.courseName || ''
|
||
dataToSubmit.subjectName = dataToSubmit.subjectName || ''
|
||
dataToSubmit.pointNames = dataToSubmit.pointNames || ''
|
||
dataToSubmit.chapteridDictText = dataToSubmit.chapteridDictText || ''
|
||
dataToSubmit.analysis = dataToSubmit.analysis || ''
|
||
dataToSubmit.resourceValue = dataToSubmit.resourceValue || ''
|
||
|
||
console.log('Submitting Email data:', dataToSubmit)
|
||
|
||
if (formType.value === 'create') {
|
||
await QuestionApi.addQuestion(dataToSubmit)
|
||
ElMessage.success(t('common.createSuccess'))
|
||
} else {
|
||
await QuestionApi.editQuestion(dataToSubmit)
|
||
ElMessage.success(t('common.updateSuccess'))
|
||
}
|
||
|
||
if (props.isIndependent) {
|
||
// 独立窗口模式:发送成功事件并关闭窗口
|
||
try {
|
||
const { emit: tauriEmit } = await import('@tauri-apps/api/event')
|
||
const { getCurrentWindow } = await import('@tauri-apps/api/window')
|
||
|
||
await tauriEmit('email-form-success')
|
||
const appWindow = getCurrentWindow()
|
||
await appWindow.close()
|
||
} catch (error) {
|
||
console.error('Failed to close independent window:', error)
|
||
}
|
||
} else {
|
||
// 对话框模式
|
||
dialogVisible.value = false
|
||
emit('success')
|
||
}
|
||
} catch (error: any) {
|
||
console.error('Submit error:', error)
|
||
|
||
// 更详细的错误处理
|
||
let errorMessage = '提交失败'
|
||
if (error?.response?.data?.msg) {
|
||
errorMessage += ':' + error.response.data.msg
|
||
} else if (error?.message) {
|
||
errorMessage += ':' + error.message
|
||
} else if (typeof error === 'string') {
|
||
errorMessage += ':' + error
|
||
} else {
|
||
errorMessage += ':未知错误'
|
||
}
|
||
|
||
ElMessage.error(errorMessage)
|
||
} finally {
|
||
formLoading.value = false
|
||
}
|
||
}
|
||
|
||
// 取消操作
|
||
const handleCancel = async () => {
|
||
if (props.isIndependent) {
|
||
try {
|
||
if (isDesktop.value && WebviewWindow) {
|
||
const currentWindow = WebviewWindow.getCurrent()
|
||
await currentWindow.close()
|
||
}
|
||
} catch (error) {
|
||
console.error('关闭窗口失败:', error)
|
||
}
|
||
} else {
|
||
dialogVisible.value = false
|
||
}
|
||
}
|
||
|
||
// 事件定义
|
||
const emit = defineEmits<{
|
||
success: []
|
||
}>()
|
||
|
||
// 打开表单
|
||
const open = async (type: string, id?: number, rowData?: any) => {
|
||
if (!props.isIndependent) {
|
||
dialogVisible.value = true
|
||
}
|
||
|
||
formType.value = type
|
||
dialogTitle.value = type === 'create' ? '新增邮件题目' : '修改邮件题目'
|
||
|
||
// 重置表单
|
||
await resetForm()
|
||
|
||
if (rowData) {
|
||
// 从父组件传入的数据
|
||
Object.assign(formData.value, {
|
||
specialtyName: rowData.specialtyName || '',
|
||
courseName: rowData.courseName || '',
|
||
chapteridDictText: rowData.chapteridDictText || '',
|
||
subjectName: rowData.subjectName || '',
|
||
pointNames: rowData.pointNames || ''
|
||
})
|
||
}
|
||
|
||
if (id) {
|
||
formLoading.value = true
|
||
try {
|
||
const res = await QuestionApi.getQuestion(id)
|
||
Object.assign(formData.value, res)
|
||
} finally {
|
||
formLoading.value = false
|
||
}
|
||
}
|
||
}
|
||
|
||
// 重置表单
|
||
const resetForm = async () => {
|
||
formData.value = {
|
||
content: '',
|
||
specialtyName: '',
|
||
courseName: '',
|
||
chapteridDictText: '',
|
||
subjectName: '',
|
||
pointNames: '',
|
||
quType: '邮件题目',
|
||
quLevel: 0,
|
||
status: '0',
|
||
analysis: ''
|
||
}
|
||
|
||
await nextTick()
|
||
formRef.value?.resetFields()
|
||
}
|
||
|
||
// 暴露方法
|
||
defineExpose({ open })
|
||
</script>
|
||
|
||
<style lang="scss" scoped>
|
||
// 独立窗口模式样式
|
||
.independent-form {
|
||
width: 100vw;
|
||
height: 100vh;
|
||
display: flex;
|
||
flex-direction: column;
|
||
background-color: var(--app-content-bg-color);
|
||
overflow: hidden;
|
||
|
||
.independent-header {
|
||
background: var(--el-bg-color);
|
||
border-bottom: 1px solid var(--el-border-color);
|
||
padding: 16px 24px;
|
||
box-shadow: var(--el-box-shadow-light);
|
||
|
||
h2 {
|
||
margin: 0;
|
||
font-size: 18px;
|
||
font-weight: 600;
|
||
color: var(--el-text-color-primary);
|
||
}
|
||
}
|
||
|
||
.independent-content {
|
||
flex: 1;
|
||
overflow: hidden;
|
||
background: var(--el-bg-color);
|
||
margin: 16px 16px 0 16px;
|
||
border-radius: 8px;
|
||
box-shadow: var(--el-box-shadow);
|
||
|
||
.program-edit {
|
||
height: 100%;
|
||
padding: 20px;
|
||
|
||
.main {
|
||
height: 100%;
|
||
position: relative;
|
||
|
||
.tabsTip {
|
||
position: absolute;
|
||
top: 0;
|
||
right: 0;
|
||
color: #4f9dfd;
|
||
line-height: 100%;
|
||
text-align: center;
|
||
align-items: center;
|
||
display: flex;
|
||
background: rgba(79, 157, 253, 0.1);
|
||
padding: 8px 12px;
|
||
border-radius: 4px;
|
||
font-size: 14px;
|
||
z-index: 10;
|
||
}
|
||
|
||
.el-tabs {
|
||
height: 100%;
|
||
padding-top: 40px; // 为tabsTip预留空间
|
||
|
||
:deep(.el-tabs__header) {
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
:deep(.el-tabs__content) {
|
||
height: calc(100% - 60px);
|
||
overflow-y: auto;
|
||
padding-right: 8px;
|
||
|
||
.line {
|
||
margin-bottom: 24px;
|
||
|
||
.title-text {
|
||
font-size: 16px;
|
||
font-weight: 600;
|
||
margin-bottom: 12px;
|
||
color: var(--el-text-color-primary);
|
||
padding-bottom: 8px;
|
||
border-bottom: 2px solid var(--el-border-color);
|
||
}
|
||
|
||
.buttons {
|
||
margin: 16px 0;
|
||
display: flex;
|
||
gap: 12px;
|
||
flex-wrap: wrap;
|
||
}
|
||
|
||
.block {
|
||
padding: 16px;
|
||
background: var(--el-fill-color-lighter);
|
||
border-radius: 6px;
|
||
border: 1px solid var(--el-border-color);
|
||
}
|
||
|
||
.formList {
|
||
.el-form-item {
|
||
margin-bottom: 20px !important;
|
||
|
||
:deep(.el-form-item__label) {
|
||
font-weight: 600 !important;
|
||
color: var(--el-text-color-primary) !important;
|
||
}
|
||
|
||
:deep(.el-input__wrapper) {
|
||
border-radius: 6px !important;
|
||
}
|
||
|
||
:deep(.el-select) {
|
||
width: 200px !important;
|
||
|
||
.el-input__wrapper {
|
||
border-radius: 6px !important;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
.independent-footer {
|
||
background: #ffffff !important;
|
||
border-top: 1px solid #e4e7ed !important;
|
||
margin: 0 16px 16px 16px !important;
|
||
padding: 16px 24px !important;
|
||
display: flex !important;
|
||
justify-content: flex-end !important;
|
||
gap: 12px !important;
|
||
box-shadow: 0 -2px 4px rgba(0, 0, 0, 0.1) !important;
|
||
z-index: 1000 !important;
|
||
border-radius: 0 0 8px 8px !important;
|
||
|
||
.el-button {
|
||
border-radius: 6px !important;
|
||
font-weight: 500 !important;
|
||
padding: 10px 20px !important;
|
||
font-size: 14px !important;
|
||
transition: all 0.3s !important;
|
||
min-width: 80px !important;
|
||
height: 36px !important;
|
||
|
||
&:first-child {
|
||
background: #409eff !important;
|
||
border-color: #409eff !important;
|
||
color: #ffffff !important;
|
||
|
||
&:hover {
|
||
background: #66b1ff !important;
|
||
border-color: #66b1ff !important;
|
||
color: #ffffff !important;
|
||
}
|
||
|
||
&:active {
|
||
background: #3a8ee6 !important;
|
||
border-color: #3a8ee6 !important;
|
||
}
|
||
}
|
||
|
||
&:last-child {
|
||
background: #ffffff !important;
|
||
border: 1px solid #dcdfe6 !important;
|
||
color: #606266 !important;
|
||
|
||
&:hover {
|
||
background: #ecf5ff !important;
|
||
border-color: #409eff !important;
|
||
color: #409eff !important;
|
||
}
|
||
|
||
&:active {
|
||
background: #e6f7ff !important;
|
||
border-color: #3a8ee6 !important;
|
||
color: #3a8ee6 !important;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// 对话框模式样式
|
||
.edit-dialog {
|
||
.main {
|
||
padding: 20px;
|
||
|
||
.el-form {
|
||
.el-form-item {
|
||
margin-bottom: 20px;
|
||
|
||
:deep(.el-form-item__label) {
|
||
font-weight: 600;
|
||
color: var(--el-text-color-primary);
|
||
}
|
||
|
||
:deep(.el-input__wrapper) {
|
||
border-radius: 6px;
|
||
}
|
||
|
||
:deep(.el-select) {
|
||
width: 100%;
|
||
|
||
.el-input__wrapper {
|
||
border-radius: 6px;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
:deep(.el-dialog) {
|
||
border-radius: 8px;
|
||
|
||
.el-dialog__header {
|
||
padding: 20px 24px 16px;
|
||
border-bottom: 1px solid var(--el-border-color);
|
||
|
||
.el-dialog__title {
|
||
font-size: 18px;
|
||
font-weight: 600;
|
||
color: var(--el-text-color-primary);
|
||
}
|
||
}
|
||
|
||
.el-dialog__body {
|
||
padding: 24px;
|
||
}
|
||
|
||
.el-dialog__footer {
|
||
padding: 16px 24px 20px;
|
||
border-top: 1px solid var(--el-border-color);
|
||
|
||
.dialog-footer {
|
||
display: flex;
|
||
justify-content: flex-end;
|
||
gap: 12px;
|
||
|
||
.el-button {
|
||
height: 36px;
|
||
padding: 0 20px;
|
||
border-radius: 6px;
|
||
font-weight: 500;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// Tab样式
|
||
:deep(.demo-tabs) {
|
||
.el-tabs__header {
|
||
margin-bottom: 20px;
|
||
|
||
.el-tabs__nav-wrap {
|
||
&::after {
|
||
background-color: var(--el-border-color);
|
||
}
|
||
}
|
||
|
||
.el-tabs__item {
|
||
font-weight: 500;
|
||
color: var(--el-text-color-secondary);
|
||
|
||
&.is-active {
|
||
color: var(--el-color-primary);
|
||
font-weight: 600;
|
||
}
|
||
}
|
||
|
||
.el-tabs__active-bar {
|
||
background-color: var(--el-color-primary);
|
||
}
|
||
}
|
||
}
|
||
</style>
|