<template>
|
<el-dialog
|
v-model="visible"
|
:title="isEdit ? '编辑评委' : '新增评委'"
|
width="600px"
|
:before-close="handleClose"
|
>
|
<el-form
|
ref="formRef"
|
:model="form"
|
:rules="rules"
|
label-width="100px"
|
label-position="left"
|
>
|
<!-- 头像上传 -->
|
<el-form-item label="头像">
|
<el-upload
|
class="avatar-uploader"
|
:show-file-list="false"
|
:before-upload="beforeAvatarUpload"
|
:http-request="handleAvatarUpload"
|
accept="image/*"
|
>
|
<img v-if="form.avatarUrl" :src="form.avatarUrl" class="avatar" />
|
<el-icon v-else class="avatar-uploader-icon"><Plus /></el-icon>
|
</el-upload>
|
</el-form-item>
|
|
<!-- 基本信息 -->
|
<el-form-item label="姓名" prop="name">
|
<el-input v-model="form.name" placeholder="请输入评委姓名" />
|
</el-form-item>
|
|
<el-form-item label="联系电话" prop="phone">
|
<el-input v-model="form.phone" placeholder="请输入联系电话" />
|
</el-form-item>
|
|
<el-form-item label="性别" prop="gender">
|
<el-radio-group v-model="form.gender">
|
<el-radio :label="1">男</el-radio>
|
<el-radio :label="0">女</el-radio>
|
</el-radio-group>
|
</el-form-item>
|
|
<!-- 专业标签 -->
|
<el-form-item label="专业标签" prop="specialtyIds">
|
<el-select
|
v-model="form.specialtyIds"
|
multiple
|
placeholder="请选择专业标签"
|
style="width: 100%"
|
>
|
<el-option
|
v-for="tag in availableTags"
|
:key="tag.id"
|
:label="tag.name"
|
:value="tag.id"
|
/>
|
</el-select>
|
</el-form-item>
|
|
<!-- 描述 -->
|
<el-form-item label="个人描述">
|
<el-input
|
v-model="form.description"
|
type="textarea"
|
:rows="4"
|
placeholder="请输入个人描述"
|
/>
|
</el-form-item>
|
</el-form>
|
|
<template #footer>
|
<div class="dialog-footer">
|
<el-button @click="handleClose">取消</el-button>
|
<el-button type="primary" :loading="loading" @click="handleSubmit">
|
{{ isEdit ? '更新' : '创建' }}
|
</el-button>
|
</div>
|
</template>
|
</el-dialog>
|
</template>
|
|
<script setup lang="ts">
|
import { ref, reactive, computed, watch } from 'vue'
|
import { ElMessage, type FormInstance, type UploadRequestOptions } from 'element-plus'
|
import { JudgeApi, CosUploadService } from '@/api/judge'
|
import type { Judge, JudgeInput, Tag } from '@/api/graphql'
|
|
interface Props {
|
modelValue: boolean
|
judgeData?: Judge | null
|
}
|
|
interface Emits {
|
(e: 'update:modelValue', value: boolean): void
|
(e: 'success'): void
|
}
|
|
const props = withDefaults(defineProps<Props>(), {
|
judgeData: null
|
})
|
|
const emit = defineEmits<Emits>()
|
|
const formRef = ref<FormInstance>()
|
const loading = ref(false)
|
const uploading = ref(false)
|
|
// 控制弹窗显示
|
const visible = computed({
|
get: () => props.modelValue,
|
set: (value) => emit('update:modelValue', value)
|
})
|
|
// 是否为编辑模式
|
const isEdit = computed(() => !!props.judgeData?.id)
|
|
// 表单数据
|
const form = reactive<JudgeInput & { avatarUrl?: string }>({
|
name: '',
|
phone: '',
|
gender: undefined,
|
description: '',
|
avatarMediaId: '',
|
specialtyIds: [],
|
avatarUrl: ''
|
})
|
|
// 可选的专业标签(模拟数据,实际应该从后端获取)
|
const availableTags = ref<Tag[]>([
|
{ id: '1', name: '软件工程', code: 'software' },
|
{ id: '2', name: '人工智能', code: 'ai' },
|
{ id: '3', name: '机械设计', code: 'mechanical' },
|
{ id: '4', name: '工业设计', code: 'industrial' },
|
{ id: '5', name: '创新创业', code: 'innovation' },
|
{ id: '6', name: '项目管理', code: 'management' }
|
])
|
|
// 表单验证规则
|
const rules = {
|
name: [
|
{ required: true, message: '请输入评委姓名', trigger: 'blur' },
|
{ min: 2, max: 20, message: '姓名长度在 2 到 20 个字符', trigger: 'blur' }
|
],
|
phone: [
|
{ pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号码', trigger: 'blur' }
|
]
|
}
|
|
// 监听评委数据变化,填充表单
|
watch(() => props.judgeData, (data) => {
|
try {
|
if (data) {
|
form.id = data.id
|
form.name = data.name || ''
|
form.phone = data.phone || ''
|
form.gender = data.gender
|
form.description = data.description || ''
|
form.avatarUrl = data.avatarUrl || ''
|
form.specialtyIds = data.specialties?.map(tag => tag.id) || []
|
} else {
|
resetForm()
|
}
|
} catch (error) {
|
console.error('Error in watch:', error)
|
resetForm()
|
}
|
}, { immediate: true })
|
|
// 重置表单
|
const resetForm = () => {
|
Object.assign(form, {
|
id: undefined,
|
name: '',
|
phone: '',
|
gender: undefined,
|
description: '',
|
avatarMediaId: '',
|
specialtyIds: [],
|
avatarUrl: ''
|
})
|
formRef.value?.clearValidate()
|
}
|
|
// 头像上传前验证
|
const beforeAvatarUpload = (file: File) => {
|
const isImage = file.type.startsWith('image/')
|
const isLt2M = file.size / 1024 / 1024 < 2
|
|
if (!isImage) {
|
ElMessage.error('只能上传图片文件!')
|
return false
|
}
|
if (!isLt2M) {
|
ElMessage.error('图片大小不能超过 2MB!')
|
return false
|
}
|
return true
|
}
|
|
// 处理头像上传
|
const handleAvatarUpload = async (options: UploadRequestOptions) => {
|
try {
|
uploading.value = true
|
const file = options.file as File
|
|
// 上传到腾讯云COS
|
const avatarUrl = await CosUploadService.uploadFile(file)
|
|
// 更新表单数据
|
form.avatarUrl = avatarUrl
|
form.avatarMediaId = avatarUrl // 这里简化处理,实际应该是媒体ID
|
|
ElMessage.success('头像上传成功')
|
} catch (error) {
|
console.error('头像上传失败:', error)
|
ElMessage.error('头像上传失败')
|
} finally {
|
uploading.value = false
|
}
|
}
|
|
// 提交表单
|
const handleSubmit = async () => {
|
if (!formRef.value) return
|
|
try {
|
await formRef.value.validate()
|
loading.value = true
|
|
const submitData: JudgeInput = {
|
id: form.id,
|
name: form.name,
|
phone: form.phone || undefined,
|
gender: form.gender,
|
description: form.description || undefined,
|
avatarMediaId: form.avatarMediaId || undefined,
|
specialtyIds: form.specialtyIds?.length ? form.specialtyIds : undefined
|
}
|
|
await JudgeApi.saveJudge(submitData)
|
|
ElMessage.success(isEdit.value ? '更新成功' : '创建成功')
|
emit('success')
|
handleClose()
|
} catch (error) {
|
console.error('保存失败:', error)
|
ElMessage.error('保存失败')
|
} finally {
|
loading.value = false
|
}
|
}
|
|
// 关闭弹窗
|
const handleClose = () => {
|
visible.value = false
|
resetForm()
|
}
|
</script>
|
|
<style lang="scss" scoped>
|
.avatar-uploader {
|
.avatar {
|
width: 80px;
|
height: 80px;
|
border-radius: 6px;
|
display: block;
|
}
|
}
|
|
:deep(.avatar-uploader .el-upload) {
|
border: 1px dashed var(--el-border-color);
|
border-radius: 6px;
|
cursor: pointer;
|
position: relative;
|
overflow: hidden;
|
transition: var(--el-transition-duration-fast);
|
|
&:hover {
|
border-color: var(--el-color-primary);
|
}
|
}
|
|
.avatar-uploader-icon {
|
font-size: 28px;
|
color: #8c939d;
|
width: 80px;
|
height: 80px;
|
text-align: center;
|
line-height: 80px;
|
}
|
|
.dialog-footer {
|
text-align: right;
|
}
|
</style>
|