2026-01-19 18:03:45 +08:00
|
|
|
|
<script setup lang="ts">
|
2026-02-04 18:01:52 +08:00
|
|
|
|
import { Editor, Toolbar } from '@wangeditor/editor-for-vue'
|
2026-01-19 18:03:45 +08:00
|
|
|
|
import { NCard, NDatePicker, NGrid, NGridItem, NInput, NInputNumber } from 'naive-ui'
|
2026-02-04 18:01:52 +08:00
|
|
|
|
import { computed, onBeforeUnmount, ref, shallowRef, watch } from 'vue'
|
|
|
|
|
|
import OssImageUpload from '@/components/common/oss-image-upload/index.vue'
|
|
|
|
|
|
import { PublishStatus } from '@/enum/business'
|
|
|
|
|
|
import '@wangeditor/editor/dist/css/style.css'
|
2026-01-19 18:03:45 +08:00
|
|
|
|
|
2026-02-04 18:01:52 +08:00
|
|
|
|
const props = defineProps<{
|
|
|
|
|
|
isEdit: boolean
|
|
|
|
|
|
baseInfo: Api.Competition.ActivityDetail
|
|
|
|
|
|
publishStatus: number
|
|
|
|
|
|
}>()
|
2026-01-19 18:03:45 +08:00
|
|
|
|
|
2026-02-04 18:01:52 +08:00
|
|
|
|
const emit = defineEmits(['enterEdit', 'save', 'cancel'])
|
|
|
|
|
|
|
|
|
|
|
|
const form = ref<Api.Competition.ActivityDetail>({
|
|
|
|
|
|
Id: 0,
|
|
|
|
|
|
RoomID: 0,
|
|
|
|
|
|
Name: '',
|
|
|
|
|
|
StartTime: '',
|
|
|
|
|
|
EndTime: '',
|
|
|
|
|
|
Teams: 0,
|
|
|
|
|
|
TeamGroupNumber: 0,
|
|
|
|
|
|
PublishStatus: 0,
|
|
|
|
|
|
BackgroundImg: '',
|
|
|
|
|
|
CreatedTime: '',
|
|
|
|
|
|
ActivityTitle: '',
|
|
|
|
|
|
ActivityContent: '',
|
|
|
|
|
|
ExtraTimeContent: '',
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
// Editor Logic
|
|
|
|
|
|
const editorRef = shallowRef()
|
|
|
|
|
|
const mode = 'default'
|
|
|
|
|
|
const toolbarConfig = {
|
|
|
|
|
|
excludeKeys: ['group-video', 'insertVideo', 'uploadVideo'],
|
|
|
|
|
|
}
|
|
|
|
|
|
const editorConfig = { placeholder: '请输入具体的比赛参与规则及计分标准...' }
|
|
|
|
|
|
const activeTab = ref('normal') // normal or extra
|
|
|
|
|
|
|
|
|
|
|
|
function handleCreated(editor: any) {
|
|
|
|
|
|
editorRef.value = editor
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
onBeforeUnmount(() => {
|
|
|
|
|
|
const editor = editorRef.value
|
|
|
|
|
|
if (editor == null)
|
|
|
|
|
|
return
|
|
|
|
|
|
editor.destroy()
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
/** 同步父组件基础信息到本地表单 */
|
|
|
|
|
|
watch(
|
|
|
|
|
|
() => props.baseInfo,
|
|
|
|
|
|
(val) => {
|
|
|
|
|
|
if (val) {
|
|
|
|
|
|
form.value = { ...val }
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
{ immediate: true, deep: true },
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
/** 日期范围计算属性 */
|
|
|
|
|
|
const dateRange = computed({
|
|
|
|
|
|
get() {
|
|
|
|
|
|
if (form.value.StartTime && form.value.EndTime) {
|
|
|
|
|
|
return [form.value.StartTime, form.value.EndTime] as [string, string]
|
|
|
|
|
|
}
|
|
|
|
|
|
return null
|
|
|
|
|
|
},
|
|
|
|
|
|
set(val: [string, string] | null) {
|
|
|
|
|
|
if (val) {
|
|
|
|
|
|
form.value.StartTime = val[0]
|
|
|
|
|
|
form.value.EndTime = val[1]
|
|
|
|
|
|
}
|
|
|
|
|
|
else {
|
|
|
|
|
|
form.value.StartTime = ''
|
|
|
|
|
|
form.value.EndTime = ''
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
2026-01-19 18:03:45 +08:00
|
|
|
|
})
|
2026-02-04 18:01:52 +08:00
|
|
|
|
|
|
|
|
|
|
/** 预览模式下的日期文案 */
|
|
|
|
|
|
const previewDateText = computed(() => {
|
|
|
|
|
|
if (!form.value.StartTime || !form.value.EndTime) {
|
|
|
|
|
|
return { start: '-', end: '-' }
|
|
|
|
|
|
}
|
|
|
|
|
|
const fmt = (str: string) => str.slice(0, 10)
|
|
|
|
|
|
return { start: fmt(form.value.StartTime), end: fmt(form.value.EndTime) }
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
const activeContent = computed(() => {
|
|
|
|
|
|
return activeTab.value === 'normal' ? form.value.ActivityContent : form.value.ExtraTimeContent
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
/** 是否显示编辑按钮 */
|
|
|
|
|
|
const showEnterEditButton = computed(() => {
|
|
|
|
|
|
return !props.isEdit && [PublishStatus.Unpublished, PublishStatus.Published].includes(props.publishStatus)
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
/** 是否显示保存/取消按钮 */
|
|
|
|
|
|
const showEditActions = computed(() => props.isEdit)
|
|
|
|
|
|
|
|
|
|
|
|
defineExpose({ form })
|
2026-01-19 18:03:45 +08:00
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|
|
<template>
|
2026-02-04 18:01:52 +08:00
|
|
|
|
<div class="flex flex-col gap-6">
|
|
|
|
|
|
<NCard :bordered="false" class="rounded-xl shadow-sm">
|
|
|
|
|
|
<div class="mb-6 flex items-center justify-between border-l-4 border-blue-600 pl-3">
|
|
|
|
|
|
<span class="text-lg text-gray-800 font-bold">基础信息</span>
|
|
|
|
|
|
<div class="flex gap-2">
|
|
|
|
|
|
<NButton v-if="showEnterEditButton" size="small" type="primary" secondary @click="emit('enterEdit')">
|
|
|
|
|
|
编辑
|
|
|
|
|
|
</NButton>
|
|
|
|
|
|
<template v-else-if="showEditActions">
|
|
|
|
|
|
<NButton size="small" secondary @click="emit('cancel')">
|
|
|
|
|
|
取消
|
|
|
|
|
|
</NButton>
|
|
|
|
|
|
<NButton size="small" type="primary" @click="emit('save')">
|
|
|
|
|
|
保存
|
|
|
|
|
|
</NButton>
|
|
|
|
|
|
</template>
|
2026-01-19 18:03:45 +08:00
|
|
|
|
</div>
|
2026-02-04 18:01:52 +08:00
|
|
|
|
</div>
|
2026-01-19 18:03:45 +08:00
|
|
|
|
|
2026-02-04 18:01:52 +08:00
|
|
|
|
<!-- 赛事封面 -->
|
|
|
|
|
|
<div class="mb-6">
|
2026-01-19 18:03:45 +08:00
|
|
|
|
<div class="mb-2 text-xs text-gray-400 font-medium">
|
2026-02-04 18:01:52 +08:00
|
|
|
|
赛事封面
|
2026-01-19 18:03:45 +08:00
|
|
|
|
</div>
|
2026-02-04 18:01:52 +08:00
|
|
|
|
<div v-if="isEdit">
|
|
|
|
|
|
<OssImageUpload v-model="form.BackgroundImg" :max-size="5120" />
|
|
|
|
|
|
<div class="mt-2 text-xs text-gray-400">
|
|
|
|
|
|
建议上传 16:9 或 4:3 比例的图片,以获得最佳的全屏显示效果。
|
|
|
|
|
|
</div>
|
2026-01-19 18:03:45 +08:00
|
|
|
|
</div>
|
2026-02-04 18:01:52 +08:00
|
|
|
|
<div v-else class="w-full overflow-hidden border border-gray-100 rounded-3xl bg-gray-50">
|
|
|
|
|
|
<div v-if="form.BackgroundImg" class="h-[200px] w-full flex items-center justify-center">
|
|
|
|
|
|
<!-- <img :src="form.BackgroundImg" class="h-full w-full object-cover"> -->
|
|
|
|
|
|
<NImage width="100%" height="100%" :src="form.BackgroundImg" object-fit="contain" />
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div v-else class="h-[200px] w-full flex items-center justify-center text-sm text-gray-400">
|
|
|
|
|
|
暂无封面
|
|
|
|
|
|
</div>
|
2026-01-19 18:03:45 +08:00
|
|
|
|
</div>
|
2026-02-04 18:01:52 +08:00
|
|
|
|
</div>
|
2026-01-19 18:03:45 +08:00
|
|
|
|
|
2026-02-04 18:01:52 +08:00
|
|
|
|
<NGrid :x-gap="24" :cols="4" class="py-2">
|
|
|
|
|
|
<!-- 赛事名称 -->
|
|
|
|
|
|
<NGridItem>
|
|
|
|
|
|
<div class="mb-2 text-xs text-gray-400 font-medium">
|
|
|
|
|
|
赛事名称
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<NInput v-if="isEdit" v-model:value="form.Name" placeholder="输入名称" />
|
|
|
|
|
|
<div v-else class="text-base text-gray-800 font-bold">
|
|
|
|
|
|
{{ form.Name }}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</NGridItem>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 赛事副标题 -->
|
|
|
|
|
|
<NGridItem>
|
|
|
|
|
|
<div class="mb-2 text-xs text-gray-400 font-medium">
|
|
|
|
|
|
赛事副标题
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<NInput v-if="isEdit" v-model:value="form.ActivityTitle" placeholder="输入副标题" />
|
|
|
|
|
|
<div v-else class="text-base text-gray-800 font-bold">
|
|
|
|
|
|
{{ form.ActivityTitle || '-' }}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</NGridItem>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 起止时间 -->
|
|
|
|
|
|
<NGridItem>
|
|
|
|
|
|
<div class="mb-2 text-xs text-gray-400 font-medium">
|
|
|
|
|
|
起止时间
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<NDatePicker
|
|
|
|
|
|
v-if="isEdit" v-model:formatted-value="dateRange" value-format="yyyy-MM-dd'T'HH:mm:ss"
|
|
|
|
|
|
type="daterange" clearable
|
|
|
|
|
|
/>
|
|
|
|
|
|
<div v-else class="flex items-center gap-2 text-sm text-gray-700 font-medium">
|
|
|
|
|
|
<div class="i-carbon-calendar text-gray-400" />
|
|
|
|
|
|
<span>{{ previewDateText.start }}</span>
|
|
|
|
|
|
<span class="text-gray-300">/</span>
|
|
|
|
|
|
<span>{{ previewDateText.end }}</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</NGridItem>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 参赛总人数 -->
|
|
|
|
|
|
<NGridItem>
|
|
|
|
|
|
<div class="mb-2 text-xs text-gray-400 font-medium">
|
|
|
|
|
|
参赛规模
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="flex items-center gap-2">
|
|
|
|
|
|
<span class="text-xs text-gray-400">总计</span>
|
|
|
|
|
|
<NInputNumber v-if="isEdit" v-model:value="form.Teams" :disabled="true" :show-button="false" class="w-20">
|
|
|
|
|
|
<template #suffix>
|
|
|
|
|
|
对
|
|
|
|
|
|
</template>
|
|
|
|
|
|
</NInputNumber>
|
|
|
|
|
|
<div
|
|
|
|
|
|
v-else
|
|
|
|
|
|
class="inline-flex items-center rounded bg-blue-100 px-2.5 py-0.5 text-sm text-blue-700 font-bold"
|
|
|
|
|
|
>
|
|
|
|
|
|
{{ form.Teams }}对
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<span class="ml-2 text-xs text-gray-400">单组</span>
|
|
|
|
|
|
<div v-if="isEdit">
|
|
|
|
|
|
<NInputNumber
|
|
|
|
|
|
v-model:value="form.TeamGroupNumber" :disabled="true" size="small" placeholder="分组数"
|
|
|
|
|
|
:show-button="false" class="w-16"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div v-else class="text-gray-700 font-bold">
|
|
|
|
|
|
{{ Math.floor(form.Teams / (form.TeamGroupNumber || 1)) }} 队
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</NGridItem>
|
|
|
|
|
|
</NGrid>
|
|
|
|
|
|
</NCard>
|
|
|
|
|
|
|
|
|
|
|
|
<NCard :bordered="false" class="rounded-xl shadow-sm">
|
|
|
|
|
|
<div class="mb-6 flex items-center justify-between">
|
|
|
|
|
|
<div class="flex items-center gap-3 border-l-4 border-orange-500 pl-3">
|
|
|
|
|
|
<span class="text-lg text-gray-800 font-bold">赛事规则配置</span>
|
2026-01-19 18:03:45 +08:00
|
|
|
|
</div>
|
2026-02-04 18:01:52 +08:00
|
|
|
|
<!-- 切换 Tab -->
|
|
|
|
|
|
<div class="flex rounded-lg bg-gray-100 p-1">
|
|
|
|
|
|
<button
|
|
|
|
|
|
class="rounded-md px-4 py-1.5 text-sm font-medium transition-all"
|
|
|
|
|
|
:class="activeTab === 'normal' ? 'bg-blue-600 text-white shadow-sm' : 'text-gray-500 hover:text-gray-700'"
|
|
|
|
|
|
@click="activeTab = 'normal'"
|
|
|
|
|
|
>
|
|
|
|
|
|
普通赛事环节
|
|
|
|
|
|
</button>
|
|
|
|
|
|
<button
|
|
|
|
|
|
class="rounded-md px-4 py-1.5 text-sm font-medium transition-all"
|
|
|
|
|
|
:class="activeTab === 'extra' ? 'bg-blue-600 text-white shadow-sm' : 'text-gray-500 hover:text-gray-700'"
|
|
|
|
|
|
@click="activeTab = 'extra'"
|
|
|
|
|
|
>
|
|
|
|
|
|
加时挑战环节
|
|
|
|
|
|
</button>
|
2026-01-19 18:03:45 +08:00
|
|
|
|
</div>
|
2026-02-04 18:01:52 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="min-h-[200px]">
|
|
|
|
|
|
<template v-if="isEdit">
|
|
|
|
|
|
<div class="overflow-hidden border border-gray-100 rounded-lg">
|
|
|
|
|
|
<Toolbar
|
|
|
|
|
|
style="border-bottom: 1px solid #eee" :editor="editorRef" :default-config="toolbarConfig"
|
|
|
|
|
|
:mode="mode"
|
|
|
|
|
|
/>
|
|
|
|
|
|
<div class="relative h-[400px]">
|
|
|
|
|
|
<div v-show="activeTab === 'normal'" class="h-full">
|
|
|
|
|
|
<Editor
|
|
|
|
|
|
v-model="form.ActivityContent" style="height: 100%; overflow-y: hidden;"
|
|
|
|
|
|
:default-config="{ ...editorConfig, placeholder: '请输入普通赛事的参与规则及计分标准...' }" :mode="mode"
|
|
|
|
|
|
@on-created="handleCreated"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div v-show="activeTab === 'extra'" class="h-full">
|
|
|
|
|
|
<Editor
|
|
|
|
|
|
v-model="form.ExtraTimeContent" style="height: 100%; overflow-y: hidden;"
|
|
|
|
|
|
:default-config="{ ...editorConfig, placeholder: '请输入加时赛的参与规则及计分标准...' }" :mode="mode"
|
|
|
|
|
|
@on-created="handleCreated"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
<template v-else>
|
|
|
|
|
|
<div
|
|
|
|
|
|
class="prose max-w-none rounded-lg bg-gray-50/50 p-4"
|
|
|
|
|
|
v-html="activeContent || '<div class=\'text-gray-400 text-center py-8\'>暂无规则配置</div>'"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</NCard>
|
|
|
|
|
|
</div>
|
2026-01-19 18:03:45 +08:00
|
|
|
|
</template>
|
2026-02-04 18:01:52 +08:00
|
|
|
|
|
|
|
|
|
|
<style scoped>
|
|
|
|
|
|
:deep(.w-e-text-container) {
|
|
|
|
|
|
background-color: transparent;
|
|
|
|
|
|
}
|
|
|
|
|
|
</style>
|