2026-01-08 09:17:46 +08:00
|
|
|
|
<template>
|
|
|
|
|
|
<div class="create-contest-page">
|
2026-01-15 16:35:00 +08:00
|
|
|
|
<a-spin :spinning="pageLoading">
|
2026-01-16 16:35:43 +08:00
|
|
|
|
<a-card>
|
|
|
|
|
|
<template #title>
|
|
|
|
|
|
<a-space>
|
|
|
|
|
|
<a-button
|
|
|
|
|
|
type="text"
|
|
|
|
|
|
@click="handleCancel"
|
|
|
|
|
|
style="padding: 0; margin-right: 8px"
|
2026-01-15 16:35:00 +08:00
|
|
|
|
>
|
2026-01-16 16:35:43 +08:00
|
|
|
|
<template #icon><ArrowLeftOutlined /></template>
|
|
|
|
|
|
</a-button>
|
|
|
|
|
|
<a-breadcrumb>
|
|
|
|
|
|
<a-breadcrumb-item>
|
|
|
|
|
|
<router-link :to="`/${tenantCode}/contests`"
|
2026-03-27 22:20:25 +08:00
|
|
|
|
>活动管理</router-link
|
2026-01-16 16:35:43 +08:00
|
|
|
|
>
|
|
|
|
|
|
</a-breadcrumb-item>
|
|
|
|
|
|
<a-breadcrumb-item>{{
|
2026-03-27 22:20:25 +08:00
|
|
|
|
isEdit ? "编辑活动" : "创建活动"
|
2026-01-16 16:35:43 +08:00
|
|
|
|
}}</a-breadcrumb-item>
|
|
|
|
|
|
</a-breadcrumb>
|
|
|
|
|
|
</a-space>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<a-form
|
|
|
|
|
|
ref="formRef"
|
|
|
|
|
|
:model="form"
|
|
|
|
|
|
:rules="rules"
|
|
|
|
|
|
:label-col="{ span: 5 }"
|
|
|
|
|
|
:wrapper-col="{ span: 19 }"
|
|
|
|
|
|
layout="vertical"
|
|
|
|
|
|
>
|
|
|
|
|
|
<!-- 主办信息 -->
|
|
|
|
|
|
<a-card title="主办信息" size="small" class="section-card">
|
|
|
|
|
|
<a-form-item label="主办单位" name="organizers" required>
|
|
|
|
|
|
<a-input
|
|
|
|
|
|
v-model:value="form.organizers"
|
|
|
|
|
|
placeholder="请输入主办单位"
|
|
|
|
|
|
:maxlength="200"
|
|
|
|
|
|
style="width: 600px"
|
2026-01-08 09:17:46 +08:00
|
|
|
|
/>
|
2026-01-16 16:35:43 +08:00
|
|
|
|
</a-form-item>
|
|
|
|
|
|
<a-form-item label="协办单位" name="coOrganizers">
|
|
|
|
|
|
<a-input
|
|
|
|
|
|
v-model:value="form.coOrganizers"
|
|
|
|
|
|
placeholder="请输入协办单位"
|
|
|
|
|
|
:maxlength="200"
|
|
|
|
|
|
style="width: 600px"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</a-form-item>
|
|
|
|
|
|
<a-form-item label="赞助单位" name="sponsors">
|
|
|
|
|
|
<a-input
|
|
|
|
|
|
v-model:value="form.sponsors"
|
|
|
|
|
|
placeholder="请输入赞助单位"
|
|
|
|
|
|
:maxlength="200"
|
|
|
|
|
|
style="width: 600px"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</a-form-item>
|
|
|
|
|
|
</a-card>
|
|
|
|
|
|
|
2026-03-27 22:20:25 +08:00
|
|
|
|
<!-- 活动信息 -->
|
|
|
|
|
|
<a-card title="活动信息" size="small" class="section-card">
|
|
|
|
|
|
<a-form-item label="活动名称" name="contestName" required>
|
2026-01-16 16:35:43 +08:00
|
|
|
|
<a-input
|
|
|
|
|
|
v-model:value="form.contestName"
|
2026-03-27 22:20:25 +08:00
|
|
|
|
placeholder="请输入活动名称"
|
2026-01-16 16:35:43 +08:00
|
|
|
|
:maxlength="200"
|
|
|
|
|
|
style="width: 600px"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</a-form-item>
|
2026-03-27 22:20:25 +08:00
|
|
|
|
<a-form-item label="活动时间" name="timeRange" required>
|
2026-01-16 16:35:43 +08:00
|
|
|
|
<a-range-picker
|
|
|
|
|
|
v-model:value="timeRange"
|
|
|
|
|
|
show-time
|
|
|
|
|
|
format="YYYY-MM-DD HH:mm"
|
|
|
|
|
|
style="width: 600px"
|
|
|
|
|
|
@change="handleTimeRangeChange"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</a-form-item>
|
2026-03-27 22:20:25 +08:00
|
|
|
|
<a-form-item label="活动类型" name="contestType" required>
|
2026-01-16 16:35:43 +08:00
|
|
|
|
<a-select
|
|
|
|
|
|
v-model:value="form.contestType"
|
2026-03-27 22:20:25 +08:00
|
|
|
|
placeholder="请选择活动类型"
|
2026-01-16 16:35:43 +08:00
|
|
|
|
style="width: 200px"
|
|
|
|
|
|
>
|
2026-03-27 22:20:25 +08:00
|
|
|
|
<a-select-option value="individual">个人参与</a-select-option>
|
|
|
|
|
|
<a-select-option value="team">团队参与</a-select-option>
|
2026-01-16 16:35:43 +08:00
|
|
|
|
</a-select>
|
|
|
|
|
|
</a-form-item>
|
2026-03-27 22:20:25 +08:00
|
|
|
|
<a-form-item label="可见范围" name="visibility">
|
|
|
|
|
|
<a-radio-group v-model:value="form.visibility">
|
|
|
|
|
|
<a-radio value="public">公开(所有公众用户可见)</a-radio>
|
|
|
|
|
|
<a-radio value="targeted">定向推送(按城市/年龄筛选)</a-radio>
|
|
|
|
|
|
<a-radio value="designated">指定机构</a-radio>
|
|
|
|
|
|
<a-radio value="internal">仅内部</a-radio>
|
|
|
|
|
|
</a-radio-group>
|
|
|
|
|
|
</a-form-item>
|
|
|
|
|
|
<!-- 定向推送条件 -->
|
|
|
|
|
|
<template v-if="form.visibility === 'targeted'">
|
|
|
|
|
|
<a-form-item label="目标城市">
|
|
|
|
|
|
<a-select
|
|
|
|
|
|
v-model:value="form.targetCities"
|
|
|
|
|
|
mode="tags"
|
|
|
|
|
|
placeholder="输入城市名称后按回车添加,如:广州、深圳"
|
|
|
|
|
|
style="width: 500px"
|
|
|
|
|
|
/>
|
|
|
|
|
|
<template #extra>
|
|
|
|
|
|
<span style="font-size: 12px; color: #9ca3af">留空表示不限城市。用户或子女的城市匹配任一目标城市即可看到活动。</span>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
</a-form-item>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
<!-- 年龄限制(公开和定向都可设置) -->
|
|
|
|
|
|
<a-form-item v-if="form.visibility === 'public' || form.visibility === 'targeted'" label="年龄限制">
|
|
|
|
|
|
<a-space>
|
|
|
|
|
|
<a-input-number v-model:value="form.ageMin" :min="1" :max="99" placeholder="最小" style="width: 100px" />
|
|
|
|
|
|
<span>至</span>
|
|
|
|
|
|
<a-input-number v-model:value="form.ageMax" :min="1" :max="99" placeholder="最大" style="width: 100px" />
|
|
|
|
|
|
<span style="font-size: 12px; color: #9ca3af">岁(留空表示不限年龄,报名时根据参与者生日校验)</span>
|
|
|
|
|
|
</a-space>
|
|
|
|
|
|
</a-form-item>
|
|
|
|
|
|
<a-form-item label="活动详情" name="content" required>
|
2026-01-16 16:35:43 +08:00
|
|
|
|
<RichTextEditor
|
|
|
|
|
|
v-model="form.content"
|
2026-03-27 22:20:25 +08:00
|
|
|
|
placeholder="请输入活动详细说明"
|
2026-01-16 16:35:43 +08:00
|
|
|
|
:height="300"
|
|
|
|
|
|
style="width: 800px"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</a-form-item>
|
2026-03-27 22:20:25 +08:00
|
|
|
|
<a-form-item label="活动封面" name="coverUrl" required>
|
2026-01-16 16:35:43 +08:00
|
|
|
|
<a-upload
|
|
|
|
|
|
v-model:file-list="coverFileList"
|
|
|
|
|
|
list-type="picture-card"
|
|
|
|
|
|
:before-upload="beforeUpload"
|
|
|
|
|
|
:custom-request="handleCoverUpload"
|
|
|
|
|
|
accept="image/*"
|
|
|
|
|
|
:max-count="1"
|
|
|
|
|
|
>
|
|
|
|
|
|
<div v-if="coverFileList.length < 1">
|
|
|
|
|
|
<plus-outlined />
|
|
|
|
|
|
<div style="margin-top: 8px">上传封面</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</a-upload>
|
|
|
|
|
|
<div class="upload-tip">
|
|
|
|
|
|
<a-alert
|
|
|
|
|
|
message="图片要求:尺寸16:9,文件大小小于30M"
|
|
|
|
|
|
type="info"
|
|
|
|
|
|
show-icon
|
|
|
|
|
|
style="margin-top: 8px; width: fit-content"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</a-form-item>
|
2026-03-27 22:20:25 +08:00
|
|
|
|
<a-form-item label="活动海报" name="posterUrl" required>
|
2026-01-16 16:35:43 +08:00
|
|
|
|
<a-upload
|
|
|
|
|
|
v-model:file-list="posterFileList"
|
|
|
|
|
|
list-type="picture-card"
|
|
|
|
|
|
:before-upload="beforeUpload"
|
|
|
|
|
|
:custom-request="handlePosterUpload"
|
|
|
|
|
|
accept="image/*"
|
|
|
|
|
|
:max-count="1"
|
|
|
|
|
|
>
|
|
|
|
|
|
<div v-if="posterFileList.length < 1">
|
|
|
|
|
|
<plus-outlined />
|
|
|
|
|
|
<div style="margin-top: 8px">上传海报</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</a-upload>
|
|
|
|
|
|
<div class="upload-tip">
|
|
|
|
|
|
<a-alert
|
|
|
|
|
|
message="图片要求:尺寸16:9,文件大小小于30M"
|
|
|
|
|
|
type="info"
|
|
|
|
|
|
show-icon
|
|
|
|
|
|
style="margin-top: 8px; width: fit-content"
|
|
|
|
|
|
/>
|
2026-01-15 16:35:00 +08:00
|
|
|
|
</div>
|
2026-01-16 16:35:43 +08:00
|
|
|
|
</a-form-item>
|
2026-03-27 22:20:25 +08:00
|
|
|
|
<a-form-item label="活动附件" name="attachments">
|
2026-01-16 16:35:43 +08:00
|
|
|
|
<a-upload
|
|
|
|
|
|
v-model:file-list="attachmentFileList"
|
|
|
|
|
|
:before-upload="beforeFileUpload"
|
|
|
|
|
|
:custom-request="handleAttachmentUpload"
|
|
|
|
|
|
:max-count="10"
|
|
|
|
|
|
>
|
|
|
|
|
|
<a-button>
|
|
|
|
|
|
<upload-outlined />
|
|
|
|
|
|
上传附件
|
|
|
|
|
|
</a-button>
|
|
|
|
|
|
</a-upload>
|
|
|
|
|
|
</a-form-item>
|
|
|
|
|
|
</a-card>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 报名设置 -->
|
|
|
|
|
|
<a-card title="报名设置" size="small" class="section-card">
|
|
|
|
|
|
<a-form-item label="报名时间" name="registerTimeRange" required>
|
|
|
|
|
|
<a-range-picker
|
|
|
|
|
|
v-model:value="registerTimeRange"
|
|
|
|
|
|
show-time
|
|
|
|
|
|
format="YYYY-MM-DD HH:mm"
|
|
|
|
|
|
style="width: 600px"
|
|
|
|
|
|
:disabled-date="disabledRegisterDate"
|
|
|
|
|
|
@change="handleRegisterTimeRangeChange"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</a-form-item>
|
|
|
|
|
|
</a-card>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 作品提交设置 -->
|
|
|
|
|
|
<a-card title="作品提交设置" size="small" class="section-card">
|
|
|
|
|
|
<a-form-item label="提交规则" name="submitRule" required>
|
|
|
|
|
|
<a-radio-group v-model:value="form.submitRule">
|
|
|
|
|
|
<a-radio value="once">仅允许提交一次</a-radio>
|
|
|
|
|
|
<a-radio value="resubmit">时间范围内允许重复提交</a-radio>
|
|
|
|
|
|
</a-radio-group>
|
|
|
|
|
|
</a-form-item>
|
|
|
|
|
|
<a-form-item label="提交时间" name="submitTimeRange" required>
|
|
|
|
|
|
<a-range-picker
|
|
|
|
|
|
v-model:value="submitTimeRange"
|
|
|
|
|
|
show-time
|
|
|
|
|
|
format="YYYY-MM-DD HH:mm"
|
|
|
|
|
|
style="width: 600px"
|
|
|
|
|
|
:disabled-date="disabledSubmitDate"
|
|
|
|
|
|
@change="handleSubmitTimeRangeChange"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</a-form-item>
|
|
|
|
|
|
</a-card>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 评审规则 -->
|
|
|
|
|
|
<a-card title="评审规则" size="small" class="section-card">
|
|
|
|
|
|
<a-form-item label="评审规则" name="reviewRuleId">
|
|
|
|
|
|
<a-select
|
|
|
|
|
|
v-model:value="form.reviewRuleId"
|
|
|
|
|
|
placeholder="请选择评审规则(可选)"
|
|
|
|
|
|
style="width: 600px"
|
|
|
|
|
|
:options="reviewRuleOptions"
|
|
|
|
|
|
allow-clear
|
|
|
|
|
|
/>
|
|
|
|
|
|
</a-form-item>
|
|
|
|
|
|
<a-form-item label="评审时间" name="reviewTimeRange" required>
|
|
|
|
|
|
<a-range-picker
|
|
|
|
|
|
v-model:value="reviewTimeRange"
|
|
|
|
|
|
show-time
|
|
|
|
|
|
format="YYYY-MM-DD HH:mm"
|
|
|
|
|
|
style="width: 600px"
|
|
|
|
|
|
:disabled-date="disabledReviewDate"
|
|
|
|
|
|
@change="handleReviewTimeRangeChange"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</a-form-item>
|
|
|
|
|
|
</a-card>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 结果公布 -->
|
|
|
|
|
|
<a-card title="结果公布" size="small" class="section-card">
|
|
|
|
|
|
<a-form-item label="公布时间" name="resultPublishTime">
|
|
|
|
|
|
<a-date-picker
|
|
|
|
|
|
v-model:value="resultPublishTime"
|
|
|
|
|
|
show-time
|
|
|
|
|
|
format="YYYY-MM-DD HH:mm"
|
|
|
|
|
|
style="width: 600px"
|
|
|
|
|
|
:disabled-date="disabledPublishDate"
|
|
|
|
|
|
placeholder="请选择结果公布时间"
|
|
|
|
|
|
@change="handleResultPublishTimeChange"
|
2026-01-08 09:17:46 +08:00
|
|
|
|
/>
|
2026-01-16 16:35:43 +08:00
|
|
|
|
</a-form-item>
|
|
|
|
|
|
</a-card>
|
|
|
|
|
|
</a-form>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 底部操作按钮 -->
|
|
|
|
|
|
<div class="form-actions">
|
|
|
|
|
|
<a-space>
|
|
|
|
|
|
<a-button @click="handleCancel">取消</a-button>
|
|
|
|
|
|
<a-button
|
|
|
|
|
|
type="primary"
|
|
|
|
|
|
:loading="submitLoading"
|
|
|
|
|
|
@click="handleSubmit"
|
2026-01-08 09:17:46 +08:00
|
|
|
|
>
|
2026-01-16 16:35:43 +08:00
|
|
|
|
保存
|
|
|
|
|
|
</a-button>
|
|
|
|
|
|
</a-space>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</a-card>
|
2026-01-08 09:17:46 +08:00
|
|
|
|
</a-spin>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<script setup lang="ts">
|
2026-01-15 16:35:00 +08:00
|
|
|
|
import { ref, reactive, nextTick, onMounted, computed } from "vue"
|
2026-01-08 09:17:46 +08:00
|
|
|
|
import { useRouter, useRoute } from "vue-router"
|
|
|
|
|
|
import { message } from "ant-design-vue"
|
|
|
|
|
|
import type { FormInstance, UploadFile } from "ant-design-vue"
|
|
|
|
|
|
import type { Dayjs } from "dayjs"
|
2026-01-15 16:35:00 +08:00
|
|
|
|
import dayjs from "dayjs"
|
2026-01-08 09:17:46 +08:00
|
|
|
|
import {
|
|
|
|
|
|
PlusOutlined,
|
|
|
|
|
|
UploadOutlined,
|
|
|
|
|
|
ArrowLeftOutlined,
|
|
|
|
|
|
} from "@ant-design/icons-vue"
|
2026-01-15 16:35:00 +08:00
|
|
|
|
import RichTextEditor from "@/components/RichTextEditor.vue"
|
2026-01-08 09:17:46 +08:00
|
|
|
|
import {
|
|
|
|
|
|
contestsApi,
|
2026-01-15 16:35:00 +08:00
|
|
|
|
attachmentsApi,
|
2026-01-08 09:17:46 +08:00
|
|
|
|
reviewRulesApi,
|
|
|
|
|
|
type CreateContestForm,
|
2026-01-15 16:35:00 +08:00
|
|
|
|
type Contest,
|
2026-01-08 09:17:46 +08:00
|
|
|
|
} from "@/api/contests"
|
2026-01-15 16:35:00 +08:00
|
|
|
|
import { uploadFile } from "@/api/upload"
|
2026-01-08 09:17:46 +08:00
|
|
|
|
|
|
|
|
|
|
const router = useRouter()
|
|
|
|
|
|
const route = useRoute()
|
|
|
|
|
|
const tenantCode = route.params.tenantCode as string
|
2026-01-15 16:35:00 +08:00
|
|
|
|
|
|
|
|
|
|
// 编辑模式
|
2026-01-16 16:35:43 +08:00
|
|
|
|
const contestId = computed(() =>
|
|
|
|
|
|
route.params.id ? Number(route.params.id) : null
|
|
|
|
|
|
)
|
2026-01-15 16:35:00 +08:00
|
|
|
|
const isEdit = computed(() => !!contestId.value)
|
|
|
|
|
|
const pageLoading = ref(false)
|
2026-01-08 09:17:46 +08:00
|
|
|
|
|
|
|
|
|
|
const formRef = ref<FormInstance>()
|
|
|
|
|
|
const submitLoading = ref(false)
|
|
|
|
|
|
|
|
|
|
|
|
// 表单数据
|
|
|
|
|
|
const form = reactive<
|
|
|
|
|
|
CreateContestForm & {
|
|
|
|
|
|
reviewRuleId?: number
|
|
|
|
|
|
}
|
|
|
|
|
|
>({
|
|
|
|
|
|
contestName: "",
|
|
|
|
|
|
contestType: "individual",
|
2026-03-27 22:20:25 +08:00
|
|
|
|
visibility: "designated",
|
|
|
|
|
|
targetCities: [] as string[],
|
|
|
|
|
|
ageMin: undefined as number | undefined,
|
|
|
|
|
|
ageMax: undefined as number | undefined,
|
2026-01-08 09:17:46 +08:00
|
|
|
|
startTime: "",
|
|
|
|
|
|
endTime: "",
|
|
|
|
|
|
content: "",
|
|
|
|
|
|
coverUrl: "",
|
|
|
|
|
|
posterUrl: "",
|
|
|
|
|
|
organizers: "",
|
|
|
|
|
|
coOrganizers: "",
|
|
|
|
|
|
sponsors: "",
|
|
|
|
|
|
registerStartTime: "",
|
|
|
|
|
|
registerEndTime: "",
|
|
|
|
|
|
submitRule: "once",
|
|
|
|
|
|
submitStartTime: "",
|
|
|
|
|
|
submitEndTime: "",
|
|
|
|
|
|
reviewStartTime: "",
|
|
|
|
|
|
reviewEndTime: "",
|
|
|
|
|
|
resultPublishTime: "",
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
// 时间范围
|
|
|
|
|
|
const timeRange = ref<[Dayjs, Dayjs] | null>(null)
|
|
|
|
|
|
const registerTimeRange = ref<[Dayjs, Dayjs] | null>(null)
|
|
|
|
|
|
const submitTimeRange = ref<[Dayjs, Dayjs] | null>(null)
|
|
|
|
|
|
const reviewTimeRange = ref<[Dayjs, Dayjs] | null>(null)
|
|
|
|
|
|
const resultPublishTime = ref<Dayjs | null>(null)
|
|
|
|
|
|
|
|
|
|
|
|
// 文件列表
|
|
|
|
|
|
const coverFileList = ref<UploadFile[]>([])
|
|
|
|
|
|
const posterFileList = ref<UploadFile[]>([])
|
|
|
|
|
|
const attachmentFileList = ref<UploadFile[]>([])
|
|
|
|
|
|
|
|
|
|
|
|
// 评审规则选项
|
|
|
|
|
|
const reviewRuleOptions = ref<{ value: number; label: string }[]>([])
|
2026-01-15 16:35:00 +08:00
|
|
|
|
|
|
|
|
|
|
// 获取评审规则列表
|
|
|
|
|
|
const fetchReviewRules = async () => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const rules = await reviewRulesApi.getForSelect()
|
2026-01-16 16:35:43 +08:00
|
|
|
|
reviewRuleOptions.value = rules.map((rule) => ({
|
2026-01-15 16:35:00 +08:00
|
|
|
|
value: rule.id,
|
|
|
|
|
|
label: rule.ruleName,
|
|
|
|
|
|
}))
|
|
|
|
|
|
} catch (error) {
|
2026-01-16 16:35:43 +08:00
|
|
|
|
console.error("获取评审规则列表失败:", error)
|
2026-01-15 16:35:00 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-01-08 09:17:46 +08:00
|
|
|
|
|
|
|
|
|
|
// 表单验证规则
|
|
|
|
|
|
const rules = {
|
2026-03-27 22:20:25 +08:00
|
|
|
|
contestName: [{ required: true, message: "请输入活动名称", trigger: "blur" }],
|
2026-01-08 09:17:46 +08:00
|
|
|
|
contestType: [
|
2026-03-27 22:20:25 +08:00
|
|
|
|
{ required: true, message: "请选择活动类型", trigger: "change" },
|
2026-01-08 09:17:46 +08:00
|
|
|
|
],
|
|
|
|
|
|
organizers: [
|
|
|
|
|
|
{ required: true, message: "请输入主办单位", trigger: "change" },
|
|
|
|
|
|
],
|
2026-03-27 22:20:25 +08:00
|
|
|
|
content: [{ required: true, message: "请输入活动详情", trigger: "blur" }],
|
|
|
|
|
|
coverUrl: [{ required: true, message: "请上传活动封面", trigger: "change" }],
|
|
|
|
|
|
posterUrl: [{ required: true, message: "请上传活动海报", trigger: "change" }],
|
2026-01-08 09:17:46 +08:00
|
|
|
|
timeRange: [
|
|
|
|
|
|
{
|
|
|
|
|
|
required: true,
|
|
|
|
|
|
validator: () => {
|
|
|
|
|
|
if (!timeRange.value || !form.startTime || !form.endTime) {
|
2026-03-27 22:20:25 +08:00
|
|
|
|
return Promise.reject(new Error("请选择活动时间"))
|
2026-01-08 09:17:46 +08:00
|
|
|
|
}
|
|
|
|
|
|
return Promise.resolve()
|
|
|
|
|
|
},
|
|
|
|
|
|
trigger: "change",
|
|
|
|
|
|
},
|
|
|
|
|
|
],
|
|
|
|
|
|
registerTimeRange: [
|
|
|
|
|
|
{
|
|
|
|
|
|
required: true,
|
|
|
|
|
|
validator: () => {
|
|
|
|
|
|
if (
|
|
|
|
|
|
!registerTimeRange.value ||
|
|
|
|
|
|
!form.registerStartTime ||
|
|
|
|
|
|
!form.registerEndTime
|
|
|
|
|
|
) {
|
|
|
|
|
|
return Promise.reject(new Error("请选择报名时间"))
|
|
|
|
|
|
}
|
|
|
|
|
|
return Promise.resolve()
|
|
|
|
|
|
},
|
|
|
|
|
|
trigger: "change",
|
|
|
|
|
|
},
|
|
|
|
|
|
],
|
|
|
|
|
|
submitRule: [
|
|
|
|
|
|
{ required: true, message: "请选择提交规则", trigger: "change" },
|
|
|
|
|
|
],
|
|
|
|
|
|
submitTimeRange: [
|
|
|
|
|
|
{
|
|
|
|
|
|
required: true,
|
|
|
|
|
|
validator: () => {
|
|
|
|
|
|
if (
|
|
|
|
|
|
!submitTimeRange.value ||
|
|
|
|
|
|
!form.submitStartTime ||
|
|
|
|
|
|
!form.submitEndTime
|
|
|
|
|
|
) {
|
|
|
|
|
|
return Promise.reject(new Error("请选择提交时间"))
|
|
|
|
|
|
}
|
|
|
|
|
|
return Promise.resolve()
|
|
|
|
|
|
},
|
|
|
|
|
|
trigger: "change",
|
|
|
|
|
|
},
|
|
|
|
|
|
],
|
|
|
|
|
|
reviewTimeRange: [
|
|
|
|
|
|
{
|
|
|
|
|
|
required: true,
|
|
|
|
|
|
validator: () => {
|
|
|
|
|
|
if (
|
|
|
|
|
|
!reviewTimeRange.value ||
|
|
|
|
|
|
!form.reviewStartTime ||
|
|
|
|
|
|
!form.reviewEndTime
|
|
|
|
|
|
) {
|
|
|
|
|
|
return Promise.reject(new Error("请选择评审时间"))
|
|
|
|
|
|
}
|
|
|
|
|
|
return Promise.resolve()
|
|
|
|
|
|
},
|
|
|
|
|
|
trigger: "change",
|
|
|
|
|
|
},
|
|
|
|
|
|
],
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 图片上传前验证
|
|
|
|
|
|
const beforeUpload = (file: File) => {
|
|
|
|
|
|
const isImage = file.type.startsWith("image/")
|
|
|
|
|
|
if (!isImage) {
|
|
|
|
|
|
message.error("只能上传图片文件!")
|
|
|
|
|
|
return false
|
|
|
|
|
|
}
|
|
|
|
|
|
const isLt30M = file.size / 1024 / 1024 < 30
|
|
|
|
|
|
if (!isLt30M) {
|
|
|
|
|
|
message.error("图片大小不能超过30M!")
|
|
|
|
|
|
return false
|
|
|
|
|
|
}
|
|
|
|
|
|
// 验证图片尺寸(16:9)
|
|
|
|
|
|
return new Promise((resolve) => {
|
|
|
|
|
|
const reader = new FileReader()
|
|
|
|
|
|
reader.onload = (e) => {
|
|
|
|
|
|
const img = new Image()
|
|
|
|
|
|
img.onload = () => {
|
|
|
|
|
|
const aspectRatio = img.width / img.height
|
|
|
|
|
|
const targetRatio = 16 / 9
|
|
|
|
|
|
const tolerance = 0.1 // 允许10%的误差
|
|
|
|
|
|
if (Math.abs(aspectRatio - targetRatio) > tolerance) {
|
|
|
|
|
|
message.warning("图片尺寸应为16:9,当前尺寸可能不符合要求")
|
|
|
|
|
|
}
|
|
|
|
|
|
resolve(true)
|
|
|
|
|
|
}
|
|
|
|
|
|
img.src = e.target?.result as string
|
|
|
|
|
|
}
|
|
|
|
|
|
reader.readAsDataURL(file)
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 文件上传前验证
|
|
|
|
|
|
const beforeFileUpload = (file: File) => {
|
|
|
|
|
|
const isLt30M = file.size / 1024 / 1024 < 30
|
|
|
|
|
|
if (!isLt30M) {
|
|
|
|
|
|
message.error("文件大小不能超过30M!")
|
|
|
|
|
|
return false
|
|
|
|
|
|
}
|
|
|
|
|
|
return true
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 封面图片上传
|
|
|
|
|
|
const handleCoverUpload = async (options: any) => {
|
|
|
|
|
|
const { file, onSuccess, onError } = options
|
|
|
|
|
|
try {
|
2026-01-15 16:35:00 +08:00
|
|
|
|
const result: any = await uploadFile(file)
|
|
|
|
|
|
// 兼容不同的响应格式
|
|
|
|
|
|
const url = result.data?.url || result.url
|
|
|
|
|
|
if (url) {
|
|
|
|
|
|
form.coverUrl = url
|
|
|
|
|
|
onSuccess()
|
|
|
|
|
|
message.success("封面上传成功")
|
|
|
|
|
|
} else {
|
|
|
|
|
|
throw new Error("无法获取图片地址")
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (error: any) {
|
|
|
|
|
|
console.error("封面上传失败:", error)
|
2026-01-08 09:17:46 +08:00
|
|
|
|
onError(error)
|
2026-01-15 16:35:00 +08:00
|
|
|
|
message.error(error?.response?.data?.message || "封面上传失败")
|
2026-01-08 09:17:46 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 海报图片上传
|
|
|
|
|
|
const handlePosterUpload = async (options: any) => {
|
|
|
|
|
|
const { file, onSuccess, onError } = options
|
|
|
|
|
|
try {
|
2026-01-15 16:35:00 +08:00
|
|
|
|
const result: any = await uploadFile(file)
|
|
|
|
|
|
// 兼容不同的响应格式
|
|
|
|
|
|
const url = result.data?.url || result.url
|
|
|
|
|
|
if (url) {
|
|
|
|
|
|
form.posterUrl = url
|
|
|
|
|
|
onSuccess()
|
|
|
|
|
|
message.success("海报上传成功")
|
|
|
|
|
|
} else {
|
|
|
|
|
|
throw new Error("无法获取图片地址")
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (error: any) {
|
|
|
|
|
|
console.error("海报上传失败:", error)
|
2026-01-08 09:17:46 +08:00
|
|
|
|
onError(error)
|
2026-01-15 16:35:00 +08:00
|
|
|
|
message.error(error?.response?.data?.message || "海报上传失败")
|
2026-01-08 09:17:46 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 附件上传
|
|
|
|
|
|
const handleAttachmentUpload = async (options: any) => {
|
2026-01-15 16:35:00 +08:00
|
|
|
|
const { file, onSuccess, onError } = options
|
2026-01-08 09:17:46 +08:00
|
|
|
|
try {
|
2026-01-15 16:35:00 +08:00
|
|
|
|
const result: any = await uploadFile(file)
|
|
|
|
|
|
// 兼容不同的响应格式
|
|
|
|
|
|
const url = result.data?.url || result.url
|
|
|
|
|
|
if (url) {
|
|
|
|
|
|
// 更新文件列表中的文件信息,保存上传后的URL
|
|
|
|
|
|
// 使用 nextTick 确保文件已添加到列表中
|
|
|
|
|
|
await nextTick()
|
|
|
|
|
|
const fileIndex = attachmentFileList.value.findIndex(
|
|
|
|
|
|
(f) => f.uid === file.uid || f.name === file.name
|
|
|
|
|
|
)
|
|
|
|
|
|
if (fileIndex !== -1) {
|
|
|
|
|
|
attachmentFileList.value[fileIndex] = {
|
|
|
|
|
|
...attachmentFileList.value[fileIndex],
|
|
|
|
|
|
url,
|
|
|
|
|
|
response: result,
|
|
|
|
|
|
status: "done",
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 如果找不到,手动添加到列表
|
|
|
|
|
|
attachmentFileList.value.push({
|
|
|
|
|
|
uid: file.uid,
|
|
|
|
|
|
name: file.name,
|
|
|
|
|
|
status: "done",
|
|
|
|
|
|
url,
|
|
|
|
|
|
response: result,
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
onSuccess()
|
|
|
|
|
|
message.success("附件上传成功")
|
|
|
|
|
|
} else {
|
|
|
|
|
|
throw new Error("无法获取文件地址")
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (error: any) {
|
|
|
|
|
|
console.error("附件上传失败:", error)
|
|
|
|
|
|
// 标记文件为错误状态
|
|
|
|
|
|
const fileIndex = attachmentFileList.value.findIndex(
|
|
|
|
|
|
(f) => f.uid === file.uid || f.name === file.name
|
|
|
|
|
|
)
|
|
|
|
|
|
if (fileIndex !== -1) {
|
|
|
|
|
|
attachmentFileList.value[fileIndex].status = "error"
|
|
|
|
|
|
}
|
2026-01-08 09:17:46 +08:00
|
|
|
|
onError(error)
|
2026-01-15 16:35:00 +08:00
|
|
|
|
message.error(error?.response?.data?.message || "附件上传失败")
|
2026-01-08 09:17:46 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 时间范围变化处理
|
|
|
|
|
|
const handleTimeRangeChange = (dates: [Dayjs, Dayjs] | null) => {
|
|
|
|
|
|
if (dates && dates.length === 2) {
|
|
|
|
|
|
form.startTime = dates[0].format("YYYY-MM-DD HH:mm:ss")
|
|
|
|
|
|
form.endTime = dates[1].format("YYYY-MM-DD HH:mm:ss")
|
|
|
|
|
|
} else {
|
|
|
|
|
|
form.startTime = ""
|
|
|
|
|
|
form.endTime = ""
|
|
|
|
|
|
}
|
|
|
|
|
|
// 触发验证
|
|
|
|
|
|
formRef.value?.validateFields(["timeRange"])
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const handleRegisterTimeRangeChange = (dates: [Dayjs, Dayjs] | null) => {
|
|
|
|
|
|
if (dates && dates.length === 2) {
|
|
|
|
|
|
form.registerStartTime = dates[0].format("YYYY-MM-DD HH:mm:ss")
|
|
|
|
|
|
form.registerEndTime = dates[1].format("YYYY-MM-DD HH:mm:ss")
|
|
|
|
|
|
} else {
|
|
|
|
|
|
form.registerStartTime = ""
|
|
|
|
|
|
form.registerEndTime = ""
|
|
|
|
|
|
}
|
|
|
|
|
|
// 触发验证
|
|
|
|
|
|
formRef.value?.validateFields(["registerTimeRange"])
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const handleSubmitTimeRangeChange = (dates: [Dayjs, Dayjs] | null) => {
|
|
|
|
|
|
if (dates && dates.length === 2) {
|
|
|
|
|
|
form.submitStartTime = dates[0].format("YYYY-MM-DD HH:mm:ss")
|
|
|
|
|
|
form.submitEndTime = dates[1].format("YYYY-MM-DD HH:mm:ss")
|
|
|
|
|
|
} else {
|
|
|
|
|
|
form.submitStartTime = ""
|
|
|
|
|
|
form.submitEndTime = ""
|
|
|
|
|
|
}
|
|
|
|
|
|
// 触发验证
|
|
|
|
|
|
formRef.value?.validateFields(["submitTimeRange"])
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const handleReviewTimeRangeChange = (dates: [Dayjs, Dayjs] | null) => {
|
|
|
|
|
|
if (dates && dates.length === 2) {
|
|
|
|
|
|
form.reviewStartTime = dates[0].format("YYYY-MM-DD HH:mm:ss")
|
|
|
|
|
|
form.reviewEndTime = dates[1].format("YYYY-MM-DD HH:mm:ss")
|
|
|
|
|
|
} else {
|
|
|
|
|
|
form.reviewStartTime = ""
|
|
|
|
|
|
form.reviewEndTime = ""
|
|
|
|
|
|
}
|
|
|
|
|
|
// 触发验证
|
|
|
|
|
|
formRef.value?.validateFields(["reviewTimeRange"])
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const handleResultPublishTimeChange = (date: Dayjs | null) => {
|
|
|
|
|
|
if (date) {
|
|
|
|
|
|
form.resultPublishTime = date.format("YYYY-MM-DD HH:mm:ss")
|
|
|
|
|
|
} else {
|
|
|
|
|
|
form.resultPublishTime = ""
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-27 22:20:25 +08:00
|
|
|
|
// 禁用报名日期(不早于活动开始时间,不晚于活动结束时间)
|
2026-01-08 09:17:46 +08:00
|
|
|
|
const disabledRegisterDate = (current: Dayjs | null) => {
|
|
|
|
|
|
if (!timeRange.value || !current) return false
|
|
|
|
|
|
const [startTime, endTime] = timeRange.value
|
|
|
|
|
|
return current < startTime.startOf("day") || current > endTime.endOf("day")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-27 22:20:25 +08:00
|
|
|
|
// 禁用提交日期(不早于报名结束时间,不晚于活动结束时间)
|
2026-01-08 09:17:46 +08:00
|
|
|
|
const disabledSubmitDate = (current: Dayjs | null) => {
|
|
|
|
|
|
if (!registerTimeRange.value || !timeRange.value || !current) return false
|
|
|
|
|
|
const registerEndTime = registerTimeRange.value[1]
|
|
|
|
|
|
const [, endTime] = timeRange.value
|
|
|
|
|
|
return (
|
|
|
|
|
|
current < registerEndTime.startOf("day") || current > endTime.endOf("day")
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-27 22:20:25 +08:00
|
|
|
|
// 禁用评审日期(不早于报名结束时间,不晚于活动结束时间)
|
2026-01-08 09:17:46 +08:00
|
|
|
|
const disabledReviewDate = (current: Dayjs | null) => {
|
|
|
|
|
|
if (!registerTimeRange.value || !timeRange.value || !current) return false
|
|
|
|
|
|
const registerEndTime = registerTimeRange.value[1]
|
|
|
|
|
|
const [, endTime] = timeRange.value
|
|
|
|
|
|
return (
|
|
|
|
|
|
current < registerEndTime.startOf("day") || current > endTime.endOf("day")
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-27 22:20:25 +08:00
|
|
|
|
// 禁用公布日期(不早于评审结束时间,不晚于活动结束时间)
|
2026-01-08 09:17:46 +08:00
|
|
|
|
const disabledPublishDate = (current: Dayjs | null) => {
|
|
|
|
|
|
if (!reviewTimeRange.value || !timeRange.value || !current) return false
|
|
|
|
|
|
const reviewEndTime = reviewTimeRange.value[1]
|
|
|
|
|
|
const [, endTime] = timeRange.value
|
|
|
|
|
|
return (
|
|
|
|
|
|
current < reviewEndTime.startOf("day") || current > endTime.endOf("day")
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-27 22:20:25 +08:00
|
|
|
|
// 加载活动数据(编辑模式)
|
2026-01-15 16:35:00 +08:00
|
|
|
|
const loadContestData = async () => {
|
|
|
|
|
|
if (!contestId.value) return
|
2026-01-08 09:17:46 +08:00
|
|
|
|
|
2026-01-15 16:35:00 +08:00
|
|
|
|
pageLoading.value = true
|
2026-01-08 09:17:46 +08:00
|
|
|
|
try {
|
2026-01-15 16:35:00 +08:00
|
|
|
|
const contest = await contestsApi.getDetail(contestId.value)
|
2026-01-08 09:17:46 +08:00
|
|
|
|
|
|
|
|
|
|
// 填充表单数据
|
2026-01-16 16:35:43 +08:00
|
|
|
|
form.contestName = contest.contestName || ""
|
|
|
|
|
|
form.contestType = contest.contestType || "individual"
|
|
|
|
|
|
form.startTime = contest.startTime || ""
|
|
|
|
|
|
form.endTime = contest.endTime || ""
|
|
|
|
|
|
form.content = contest.content || ""
|
|
|
|
|
|
form.coverUrl = contest.coverUrl || ""
|
|
|
|
|
|
form.posterUrl = contest.posterUrl || ""
|
2026-01-15 16:35:00 +08:00
|
|
|
|
// 处理主办/协办/赞助单位(后端返回数组,表单需要字符串)
|
2026-01-08 09:17:46 +08:00
|
|
|
|
form.organizers = Array.isArray(contest.organizers)
|
2026-01-16 16:35:43 +08:00
|
|
|
|
? contest.organizers.join("、")
|
|
|
|
|
|
: contest.organizers || ""
|
2026-01-08 09:17:46 +08:00
|
|
|
|
form.coOrganizers = Array.isArray(contest.coOrganizers)
|
2026-01-16 16:35:43 +08:00
|
|
|
|
? contest.coOrganizers.join("、")
|
|
|
|
|
|
: contest.coOrganizers || ""
|
2026-01-08 09:17:46 +08:00
|
|
|
|
form.sponsors = Array.isArray(contest.sponsors)
|
2026-01-16 16:35:43 +08:00
|
|
|
|
? contest.sponsors.join("、")
|
|
|
|
|
|
: contest.sponsors || ""
|
|
|
|
|
|
form.registerStartTime = contest.registerStartTime || ""
|
|
|
|
|
|
form.registerEndTime = contest.registerEndTime || ""
|
|
|
|
|
|
form.submitRule = contest.submitRule || "once"
|
|
|
|
|
|
form.submitStartTime = contest.submitStartTime || ""
|
|
|
|
|
|
form.submitEndTime = contest.submitEndTime || ""
|
2026-01-15 16:35:00 +08:00
|
|
|
|
form.reviewRuleId = contest.reviewRuleId || undefined
|
2026-01-16 16:35:43 +08:00
|
|
|
|
form.reviewStartTime = contest.reviewStartTime || ""
|
|
|
|
|
|
form.reviewEndTime = contest.reviewEndTime || ""
|
|
|
|
|
|
form.resultPublishTime = contest.resultPublishTime || ""
|
2026-01-08 09:17:46 +08:00
|
|
|
|
|
|
|
|
|
|
// 设置时间范围
|
|
|
|
|
|
if (contest.startTime && contest.endTime) {
|
|
|
|
|
|
timeRange.value = [dayjs(contest.startTime), dayjs(contest.endTime)]
|
|
|
|
|
|
}
|
|
|
|
|
|
if (contest.registerStartTime && contest.registerEndTime) {
|
2026-01-16 16:35:43 +08:00
|
|
|
|
registerTimeRange.value = [
|
|
|
|
|
|
dayjs(contest.registerStartTime),
|
|
|
|
|
|
dayjs(contest.registerEndTime),
|
|
|
|
|
|
]
|
2026-01-08 09:17:46 +08:00
|
|
|
|
}
|
|
|
|
|
|
if (contest.submitStartTime && contest.submitEndTime) {
|
2026-01-16 16:35:43 +08:00
|
|
|
|
submitTimeRange.value = [
|
|
|
|
|
|
dayjs(contest.submitStartTime),
|
|
|
|
|
|
dayjs(contest.submitEndTime),
|
|
|
|
|
|
]
|
2026-01-08 09:17:46 +08:00
|
|
|
|
}
|
|
|
|
|
|
if (contest.reviewStartTime && contest.reviewEndTime) {
|
2026-01-16 16:35:43 +08:00
|
|
|
|
reviewTimeRange.value = [
|
|
|
|
|
|
dayjs(contest.reviewStartTime),
|
|
|
|
|
|
dayjs(contest.reviewEndTime),
|
|
|
|
|
|
]
|
2026-01-08 09:17:46 +08:00
|
|
|
|
}
|
|
|
|
|
|
if (contest.resultPublishTime) {
|
|
|
|
|
|
resultPublishTime.value = dayjs(contest.resultPublishTime)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-15 16:35:00 +08:00
|
|
|
|
// 设置封面图片
|
2026-01-08 09:17:46 +08:00
|
|
|
|
if (contest.coverUrl) {
|
2026-01-16 16:35:43 +08:00
|
|
|
|
coverFileList.value = [
|
|
|
|
|
|
{
|
|
|
|
|
|
uid: "-1",
|
|
|
|
|
|
name: "cover",
|
|
|
|
|
|
status: "done",
|
|
|
|
|
|
url: contest.coverUrl,
|
|
|
|
|
|
},
|
|
|
|
|
|
]
|
2026-01-08 09:17:46 +08:00
|
|
|
|
}
|
2026-01-15 16:35:00 +08:00
|
|
|
|
|
|
|
|
|
|
// 设置海报图片
|
2026-01-08 09:17:46 +08:00
|
|
|
|
if (contest.posterUrl) {
|
2026-01-16 16:35:43 +08:00
|
|
|
|
posterFileList.value = [
|
|
|
|
|
|
{
|
|
|
|
|
|
uid: "-2",
|
|
|
|
|
|
name: "poster",
|
|
|
|
|
|
status: "done",
|
|
|
|
|
|
url: contest.posterUrl,
|
|
|
|
|
|
},
|
|
|
|
|
|
]
|
2026-01-15 16:35:00 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 加载附件
|
|
|
|
|
|
if (contest.attachments && contest.attachments.length > 0) {
|
2026-01-16 16:35:43 +08:00
|
|
|
|
attachmentFileList.value = contest.attachments.map(
|
|
|
|
|
|
(att: any, index: number) => ({
|
|
|
|
|
|
uid: `-${index + 3}`,
|
|
|
|
|
|
name: att.fileName,
|
|
|
|
|
|
status: "done",
|
|
|
|
|
|
url: att.fileUrl,
|
|
|
|
|
|
})
|
|
|
|
|
|
)
|
2026-01-08 09:17:46 +08:00
|
|
|
|
}
|
|
|
|
|
|
} catch (error: any) {
|
2026-03-27 22:20:25 +08:00
|
|
|
|
message.error(error?.response?.data?.message || "加载活动数据失败")
|
2026-01-15 16:35:00 +08:00
|
|
|
|
router.back()
|
2026-01-08 09:17:46 +08:00
|
|
|
|
} finally {
|
2026-01-15 16:35:00 +08:00
|
|
|
|
pageLoading.value = false
|
2026-01-08 09:17:46 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 提交表单
|
|
|
|
|
|
const handleSubmit = async () => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
await formRef.value?.validate()
|
|
|
|
|
|
submitLoading.value = true
|
|
|
|
|
|
|
2026-01-15 16:35:00 +08:00
|
|
|
|
// 构建提交数据,确保所有字符串字段不为 null/undefined
|
|
|
|
|
|
const submitData: CreateContestForm = {
|
2026-01-16 16:35:43 +08:00
|
|
|
|
contestName: form.contestName || "",
|
|
|
|
|
|
contestType: form.contestType || "individual",
|
|
|
|
|
|
startTime: form.startTime || "",
|
|
|
|
|
|
endTime: form.endTime || "",
|
|
|
|
|
|
content: form.content || "",
|
|
|
|
|
|
coverUrl: form.coverUrl || "",
|
|
|
|
|
|
posterUrl: form.posterUrl || "",
|
|
|
|
|
|
organizers: form.organizers || "",
|
|
|
|
|
|
coOrganizers: form.coOrganizers || "",
|
|
|
|
|
|
sponsors: form.sponsors || "",
|
|
|
|
|
|
registerStartTime: form.registerStartTime || "",
|
|
|
|
|
|
registerEndTime: form.registerEndTime || "",
|
|
|
|
|
|
submitRule: form.submitRule || "once",
|
|
|
|
|
|
submitStartTime: form.submitStartTime || "",
|
|
|
|
|
|
submitEndTime: form.submitEndTime || "",
|
2026-01-15 16:35:00 +08:00
|
|
|
|
reviewRuleId: form.reviewRuleId || undefined,
|
2026-01-16 16:35:43 +08:00
|
|
|
|
reviewStartTime: form.reviewStartTime || "",
|
|
|
|
|
|
reviewEndTime: form.reviewEndTime || "",
|
2026-01-15 16:35:00 +08:00
|
|
|
|
resultPublishTime: form.resultPublishTime || undefined,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (isEdit.value && contestId.value) {
|
2026-03-27 22:20:25 +08:00
|
|
|
|
// 编辑模式 - 更新活动
|
2026-01-15 16:35:00 +08:00
|
|
|
|
await contestsApi.update(contestId.value, submitData)
|
|
|
|
|
|
message.success("保存成功")
|
2026-01-08 09:17:46 +08:00
|
|
|
|
} else {
|
2026-01-15 16:35:00 +08:00
|
|
|
|
// 创建模式
|
|
|
|
|
|
const contest = await contestsApi.create(submitData)
|
|
|
|
|
|
const newContestId = contest.id
|
|
|
|
|
|
|
|
|
|
|
|
// 如果有附件,创建附件记录
|
|
|
|
|
|
if (attachmentFileList.value.length > 0) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const attachmentPromises = attachmentFileList.value.map((file) => {
|
|
|
|
|
|
const fileUrl =
|
|
|
|
|
|
file.url || file.response?.url || file.response?.data?.url
|
|
|
|
|
|
if (fileUrl && file.name) {
|
|
|
|
|
|
return attachmentsApi.create({
|
|
|
|
|
|
contestId: newContestId,
|
|
|
|
|
|
fileName: file.name,
|
|
|
|
|
|
fileUrl,
|
|
|
|
|
|
format: file.name.split(".").pop()?.toLowerCase(),
|
|
|
|
|
|
size: file.size?.toString(),
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
return Promise.resolve()
|
|
|
|
|
|
})
|
|
|
|
|
|
await Promise.all(attachmentPromises)
|
|
|
|
|
|
} catch (attachmentError) {
|
|
|
|
|
|
console.error("创建附件记录失败:", attachmentError)
|
2026-03-27 22:20:25 +08:00
|
|
|
|
message.warning("活动创建成功,但部分附件记录创建失败")
|
2026-01-15 16:35:00 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-01-08 09:17:46 +08:00
|
|
|
|
message.success("创建成功")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-27 22:20:25 +08:00
|
|
|
|
// 跳转到活动列表
|
2026-01-08 09:17:46 +08:00
|
|
|
|
router.push(`/${tenantCode}/contests`)
|
|
|
|
|
|
} catch (error: any) {
|
|
|
|
|
|
if (error?.errorFields) {
|
|
|
|
|
|
// 表单验证错误
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
2026-01-16 16:35:43 +08:00
|
|
|
|
message.error(
|
|
|
|
|
|
error?.response?.data?.message || (isEdit.value ? "保存失败" : "创建失败")
|
|
|
|
|
|
)
|
2026-01-08 09:17:46 +08:00
|
|
|
|
} finally {
|
|
|
|
|
|
submitLoading.value = false
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 取消
|
|
|
|
|
|
const handleCancel = () => {
|
|
|
|
|
|
router.back()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-15 16:35:00 +08:00
|
|
|
|
// 页面加载
|
|
|
|
|
|
onMounted(() => {
|
|
|
|
|
|
// 获取评审规则列表
|
|
|
|
|
|
fetchReviewRules()
|
2026-01-08 09:17:46 +08:00
|
|
|
|
|
2026-01-15 16:35:00 +08:00
|
|
|
|
if (isEdit.value) {
|
|
|
|
|
|
loadContestData()
|
2026-01-08 09:17:46 +08:00
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|
|
<style scoped>
|
|
|
|
|
|
.create-contest-page {
|
|
|
|
|
|
padding: 16px 24px;
|
|
|
|
|
|
max-width: 1200px;
|
2026-01-16 16:35:43 +08:00
|
|
|
|
background-color: #fff;
|
2026-01-08 09:17:46 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.create-contest-page :deep(.ant-card) {
|
|
|
|
|
|
border: none;
|
|
|
|
|
|
box-shadow: none;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.create-contest-page :deep(.ant-card-head) {
|
|
|
|
|
|
padding: 12px 0;
|
|
|
|
|
|
min-height: auto;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.create-contest-page :deep(.ant-card-body) {
|
|
|
|
|
|
padding: 16px 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.section-card {
|
|
|
|
|
|
margin-bottom: 24px;
|
|
|
|
|
|
border: none;
|
|
|
|
|
|
box-shadow: none;
|
|
|
|
|
|
background: transparent;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.section-card :deep(.ant-card-head) {
|
|
|
|
|
|
border-bottom: none;
|
|
|
|
|
|
padding-bottom: 8px;
|
|
|
|
|
|
background: transparent;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.section-card :deep(.ant-card-body) {
|
|
|
|
|
|
padding-top: 0;
|
|
|
|
|
|
background: transparent;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.section-card :deep(.ant-card-head-title) {
|
|
|
|
|
|
font-weight: bold;
|
|
|
|
|
|
font-size: 16px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.upload-tip {
|
|
|
|
|
|
margin-top: 8px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.form-actions {
|
|
|
|
|
|
margin-top: 24px;
|
|
|
|
|
|
padding-top: 24px;
|
|
|
|
|
|
border-top: 1px solid #f0f0f0;
|
|
|
|
|
|
text-align: right;
|
|
|
|
|
|
}
|
|
|
|
|
|
</style>
|