123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605 |
- <template>
- <div class="p-2">
- <transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
- <div v-show="showSearch" class="mb-[10px]">
- <el-card shadow="hover">
- <el-form ref="queryFormRef" :model="queryParams" :inline="true" label-width="100px">
- <el-form-item label="赛事编号" prop="eventCode">
- <el-input v-model="queryParams.eventCode" placeholder="请输入赛事编号" clearable @keyup.enter="handleQuery" />
- </el-form-item>
- <el-form-item label="赛事名称" prop="eventName">
- <el-input v-model="queryParams.eventName" placeholder="请输入赛事名称" clearable @keyup.enter="handleQuery" />
- </el-form-item>
- <el-form-item label="赛事类型" prop="eventType">
- <el-select v-model="queryParams.eventType" placeholder="请选择赛事类型" clearable>
- <el-option v-for="dict in game_event_type" :key="dict.value" :label="dict.label" :value="dict.value" />
- </el-select>
- </el-form-item>
- <el-form-item label="开始时间" prop="startTime">
- <el-date-picker clearable v-model="queryParams.startTime" type="date" value-format="YYYY-MM-DD" placeholder="请选择开始时间" />
- </el-form-item>
- <el-form-item label="是否默认赛事" prop="isDefault">
- <el-select v-model="queryParams.isDefault" placeholder="请选择是否默认赛事" clearable>
- <el-option v-for="dict in sys_yes_no" :key="dict.value" :label="dict.label" :value="dict.value" />
- </el-select>
- </el-form-item>
- <el-form-item label="状态" prop="status">
- <el-select v-model="queryParams.status" placeholder="请选择状态" clearable>
- <el-option v-for="dict in game_event_status" :key="dict.value" :label="dict.label" :value="dict.value" />
- </el-select>
- </el-form-item>
- <el-form-item>
- <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
- <el-button icon="Refresh" @click="resetQuery">重置</el-button>
- </el-form-item>
- </el-form>
- </el-card>
- </div>
- </transition>
- <el-card shadow="never">
- <template #header>
- <el-row :gutter="10" class="mb8">
- <el-col :span="1.5">
- <el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['system:gameEvent:add']">新增 </el-button>
- </el-col>
- <el-col :span="1.5">
- <el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()" v-hasPermi="['system:gameEvent:edit']"
- >修改
- </el-button>
- </el-col>
- <el-col :span="1.5">
- <el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['system:gameEvent:remove']"
- >删除
- </el-button>
- </el-col>
- <el-col :span="1.5">
- <el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['system:gameEvent:export']">导出 </el-button>
- </el-col>
- <!-- 新增的操作按钮,基于默认赛事 -->
- <el-col :span="1.5">
- <el-button type="warning" plain icon="Download" @click="handleDownloadTemplateDefault" v-hasPermi="['system:gameEvent:download']"
- >下载模板
- </el-button>
- </el-col>
- <el-col :span="1.5">
- <el-button type="info" plain icon="FolderOpened" @click="handleImportRegistrationDefault" v-hasPermi="['system:gameEvent:import']"
- >导入报名
- </el-button>
- </el-col>
- <el-col :span="1.5">
- <el-button type="success" plain icon="User" @click="handleAddParticipantDefault" v-hasPermi="['system:gameEvent:addParticipant']"
- >参赛者
- </el-button>
- </el-col>
- <el-col :span="1.5">
- <el-button type="primary" plain icon="Avatar" @click="handleAddRefereeDefault" v-hasPermi="['system:gameEvent:addReferee']"
- >裁判
- </el-button>
- </el-col>
- <el-col :span="1.5">
- <el-button type="info" plain icon="View" @click="handlePreviewDefault" v-hasPermi="['system:gameEvent:view']">预览 </el-button>
- </el-col>
- <el-col :span="1.5">
- <el-button type="warning" plain icon="DataAnalysis" @click="handleGameDataDefault" v-hasPermi="['system:gameEvent:gameData']"
- >排行榜
- </el-button>
- </el-col>
- <el-col :span="1.5">
- <el-button type="primary" plain icon="EditPen" @click="handleWriteArticleDefault" v-hasPermi="['system:gameEvent:writeArticle']"
- >编写文章
- </el-button>
- </el-col>
- <el-col :span="1.5">
- <el-button type="primary" plain icon="Download" @click="handleExportNumberTableDefault" v-hasPermi="['system:gameEvent:numberExport']"
- >导出号码对照表
- </el-button>
- </el-col>
- <el-col :span="1.5">
- <el-button type="success" plain icon="Postcard" @click="handleGenerateBib" v-hasPermi="['system:gameEvent:numberBib']"
- >生成参赛证
- </el-button>
- </el-col>
- <right-toolbar v-model:showSearch="showSearch" :columns="columns" @queryTable="getList"></right-toolbar>
- </el-row>
- </template>
- <el-table v-loading="loading" border :data="gameEventList" @selection-change="handleSelectionChange">
- <!-- 第一列:多选列 -->
- <el-table-column type="selection" width="55" align="center" />
- <!-- 第二列:操作列 -->
- <el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="120">
- <template #default="scope">
- <el-tooltip content="修改" placement="top">
- <el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['system:gameEvent:edit']"> 修改 </el-button>
- </el-tooltip>
- </template>
- </el-table-column>
- <el-table-column label="赛事id" align="center" prop="eventId" v-if="columns[0].visible" />
- <el-table-column label="赛事编号" align="center" prop="eventCode" v-if="columns[1].visible" />
- <el-table-column label="赛事名称" align="center" prop="eventName" v-if="columns[2].visible" />
- <el-table-column label="赛事类型" align="center" prop="eventType" v-if="columns[3].visible">
- <template #default="scope">
- <dict-tag :options="game_event_type" :value="scope.row.eventType" />
- </template>
- </el-table-column>
- <el-table-column label="举办地点" align="center" prop="location" v-if="columns[4].visible" />
- <el-table-column label="用途" align="center" prop="purpose" v-if="columns[5].visible">
- <template #default="scope">
- <dict-tag :options="game_event_purpose" :value="scope.row.purpose" />
- </template>
- </el-table-column>
- <el-table-column label="开始时间" align="center" prop="startTime" width="180" v-if="columns[6].visible">
- <template #default="scope">
- <span>{{ parseTime(scope.row.startTime, '{y}-{m}-{d}') }}</span>
- </template>
- </el-table-column>
- <el-table-column label="结束时间" align="center" prop="endTime" width="180" v-if="columns[7].visible">
- <template #default="scope">
- <span>{{ parseTime(scope.row.endTime, '{y}-{m}-{d}') }}</span>
- </template>
- </el-table-column>
- <el-table-column label="赛事链接" align="center" prop="eventUrlUrl" width="100" v-if="columns[8].visible">
- <template #default="scope">
- <image-preview :src="scope.row.eventUrlUrl" :width="50" :height="50" />
- </template>
- </el-table-column>
- <el-table-column label="裁判码" align="center" prop="refereeUrlUrl" width="100" v-if="columns[9].visible">
- <template #default="scope">
- <image-preview :src="scope.row.refereeUrlUrl" :width="50" :height="50" />
- </template>
- </el-table-column>
- <el-table-column label="举办单位" align="center" prop="unit" v-if="columns[10].visible" />
- <el-table-column label="是否默认赛事" align="center" prop="isDefault" v-if="columns[11].visible">
- <template #default="scope">
- <el-switch v-model="scope.row.isDefault" active-value="0" inactive-value="1" @change="handleStatusChange(scope.row)"></el-switch>
- </template>
- </el-table-column>
- <el-table-column label="创建时间" align="center" prop="createTime" width="180" v-if="columns[12].visible">
- <template #default="scope">
- <span>{{ parseTime(scope.row.createTime, '{y}-{m}-{d}') }}</span>
- </template>
- </el-table-column>
- <el-table-column label="更新时间" align="center" prop="updateTime" width="180" v-if="columns[13].visible">
- <template #default="scope">
- <span>{{ parseTime(scope.row.updateTime, '{y}-{m}-{d}') }}</span>
- </template>
- </el-table-column>
- <el-table-column label="状态" align="center" prop="status" v-if="columns[14].visible">
- <template #default="scope">
- <dict-tag :options="game_event_status" :value="scope.row.status" />
- </template>
- </el-table-column>
- <el-table-column label="备注" align="center" prop="remark" v-if="columns[15].visible" />
- </el-table>
- <pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
- </el-card>
- <!-- 注册 RefereeForm 组件 -->
- <RefereeForm ref="refereeFormRef" />
- <!-- 排行榜对话框 -->
- <!-- <el-dialog :title="`赛事 ${currentEventId} 排行榜`" v-model="rankingBoardVisible" width="800px" append-to-body>
- <RankingBoard :eventId="currentEventId" v-if="rankingBoardVisible" />
- </el-dialog> -->
- <!-- 文章编写对话框 -->
- <el-dialog v-model="articleDialog.visible" :title="articleDialog.title" width="1200px" append-to-body>
- <el-tabs v-model="activeTab" @tab-click="handleTabClick">
- <el-tab-pane label="竞赛流程" name="competition-process">
- <div class="article-form">
- <el-form-item label="标题">
- <el-input v-model="articleData.competitionProcess.title" placeholder="请输入标题" />
- </el-form-item>
- <el-form-item label="内容">
- <Editor v-model="articleData.competitionProcess.content" :min-height="300" />
- </el-form-item>
- <el-form-item label="备注">
- <el-input v-model="articleData.competitionProcess.remark" placeholder="请输入备注" type="textarea" :rows="3" />
- </el-form-item>
- </div>
- </el-tab-pane>
- <el-tab-pane label="竞赛项目" name="competition-items">
- <div class="article-form">
- <el-form-item label="标题">
- <el-input v-model="articleData.competitionItems.title" placeholder="请输入标题" />
- </el-form-item>
- <el-form-item label="内容">
- <Editor v-model="articleData.competitionItems.content" :min-height="300" />
- </el-form-item>
- <el-form-item label="备注">
- <el-input v-model="articleData.competitionItems.remark" placeholder="请输入备注" type="textarea" :rows="3" />
- </el-form-item>
- </div>
- </el-tab-pane>
- <el-tab-pane label="活动议程" name="activity-agenda">
- <div class="article-form">
- <el-form-item label="标题">
- <el-input v-model="articleData.activityAgenda.title" placeholder="请输入标题" />
- </el-form-item>
- <el-form-item label="内容">
- <Editor v-model="articleData.activityAgenda.content" :min-height="300" />
- </el-form-item>
- <el-form-item label="备注">
- <el-input v-model="articleData.activityAgenda.remark" placeholder="请输入备注" type="textarea" :rows="3" />
- </el-form-item>
- </div>
- </el-tab-pane>
- <el-tab-pane label="项目介绍" name="project-introduction">
- <div class="article-form">
- <el-form-item label="标题">
- <el-input v-model="articleData.projectIntroduction.title" placeholder="请输入标题" />
- </el-form-item>
- <el-form-item label="内容">
- <Editor v-model="articleData.projectIntroduction.content" :min-height="300" />
- </el-form-item>
- <el-form-item label="备注">
- <el-input v-model="articleData.projectIntroduction.remark" placeholder="请输入备注" type="textarea" :rows="3" />
- </el-form-item>
- </div>
- </el-tab-pane>
- <el-tab-pane label="竞赛流程" name="competition-flow">
- <div class="article-form">
- <el-form-item label="标题">
- <el-input v-model="articleData.competitionFlow.title" placeholder="请输入标题" />
- </el-form-item>
- <el-form-item label="内容">
- <Editor v-model="articleData.competitionFlow.content" :min-height="300" />
- </el-form-item>
- <el-form-item label="备注">
- <el-input v-model="articleData.competitionFlow.remark" placeholder="请输入备注" type="textarea" :rows="3" />
- </el-form-item>
- </div>
- </el-tab-pane>
- <el-tab-pane label="赛事分组" name="event-grouping">
- <div class="article-form">
- <el-form-item label="标题">
- <el-input v-model="articleData.eventGrouping.title" placeholder="请输入标题" />
- </el-form-item>
- <el-form-item label="内容">
- <Editor v-model="articleData.eventGrouping.content" :min-height="300" />
- </el-form-item>
- <el-form-item label="备注">
- <el-input v-model="articleData.eventGrouping.remark" placeholder="请输入备注" type="textarea" :rows="3" />
- </el-form-item>
- </div>
- </el-tab-pane>
- <el-tab-pane label="运动员号码簿" name="athlete-handbook">
- <div class="article-form">
- <el-form-item label="标题">
- <el-input v-model="articleData.athleteHandbook.title" placeholder="请输入标题" />
- </el-form-item>
- <el-form-item label="内容">
- <Editor v-model="articleData.athleteHandbook.content" :min-height="300" />
- </el-form-item>
- <el-form-item label="备注">
- <el-input v-model="articleData.athleteHandbook.remark" placeholder="请输入备注" type="textarea" :rows="3" />
- </el-form-item>
- </div>
- </el-tab-pane>
- <el-tab-pane label="项目场地" name="project-venue">
- <div class="article-form">
- <el-form-item label="标题">
- <el-input v-model="articleData.projectVenue.title" placeholder="请输入标题" />
- </el-form-item>
- <el-form-item label="内容">
- <Editor v-model="articleData.projectVenue.content" :min-height="300" />
- </el-form-item>
- <el-form-item label="备注">
- <el-input v-model="articleData.projectVenue.remark" placeholder="请输入备注" type="textarea" :rows="3" />
- </el-form-item>
- </div>
- </el-tab-pane>
- <el-tab-pane label="交通指示" name="traffic-guide">
- <div class="article-form">
- <el-form-item label="标题">
- <el-input v-model="articleData.trafficGuide.title" placeholder="请输入标题" />
- </el-form-item>
- <el-form-item label="内容">
- <Editor v-model="articleData.trafficGuide.content" :min-height="300" />
- </el-form-item>
- <el-form-item label="备注">
- <el-input v-model="articleData.trafficGuide.remark" placeholder="请输入备注" type="textarea" :rows="3" />
- </el-form-item>
- </div>
- </el-tab-pane>
- </el-tabs>
- <template #footer>
- <div class="dialog-footer">
- <el-button @click="handleCloseArticleDialog">取 消</el-button>
- <el-button type="primary" @click="handleSaveArticle">保 存</el-button>
- </div>
- </template>
- </el-dialog>
- <!-- 用户导入对话框 -->
- <el-dialog v-model="upload.open" :title="upload.title" width="400px" append-to-body>
- <el-upload
- ref="uploadRef"
- :limit="1"
- accept=".xlsx"
- :headers="upload.headers"
- :action="upload.url + '?updateSupport=' + upload.updateSupport"
- :disabled="upload.isUploading"
- :on-progress="handleFileUploadProgress"
- :on-success="handleFileSuccess"
- :auto-upload="false"
- drag
- >
- <el-icon class="el-icon--upload">
- <i-ep-upload-filled />
- </el-icon>
- <div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
- <template #tip>
- <div class="text-center el-upload__tip">
- <span>仅允许导入.xlsx格式文件。</span>
- </div>
- </template>
- </el-upload>
- <template #footer>
- <div class="dialog-footer">
- <el-button type="primary" @click="submitFileForm">确 定</el-button>
- <el-button @click="upload.open = false">取 消</el-button>
- </div>
- </template>
- </el-dialog>
- <!-- 生成参赛证对话框 -->
- <el-dialog v-model="bibDialog.visible" title="生成参赛证" width="800px" append-to-body @close="handleCloseBibDialog">
- <div class="bib-generator">
- <el-row :gutter="20">
- <!-- 左侧配置面板 -->
- <el-col :span="12">
- <el-form :model="bibForm" label-width="100px">
- <el-form-item label="背景图片">
- <el-upload ref="bgUploadRef" :limit="1" :auto-upload="false" :on-change="handleBgImageChange" accept="image/*" drag>
- <el-icon class="el-icon--upload">
- <i-ep-upload-filled />
- </el-icon>
- <div class="el-upload__text">拖拽背景图片到此处,或<em>点击上传</em></div>
- <div class="el-upload__tip">建议尺寸:842×595px (横向A4比例)</div>
- </el-upload>
- </el-form-item>
- <el-form-item label="Logo图片">
- <el-upload ref="logoUploadRef" :limit="1" :auto-upload="false" :on-change="handleLogoImageChange" accept="image/*" drag>
- <el-icon class="el-icon--upload">
- <i-ep-upload-filled />
- </el-icon>
- <div class="el-upload__text">拖拽Logo图片到此处,或<em>点击上传</em></div>
- <div class="el-upload__tip">建议尺寸:80×80px</div>
- </el-upload>
- </el-form-item>
- <el-form-item label="字体设置">
- <div style="display: flex; gap: 15px; align-items: center">
- <el-select v-model="bibForm.fontName" placeholder="字体" style="width: 100px">
- <el-option label="黑体" value="simhei"></el-option>
- <el-option label="宋体" value="simsun"></el-option>
- <el-option label="微软雅黑" value="microsoft-yahei"></el-option>
- </el-select>
- <el-input-number v-model="bibForm.fontSize" :min="38" :max="198" placeholder="字体大小" style="width: 140px"></el-input-number>
- <el-color-picker v-model="bibForm.fontColor" @change="handleFontColorChange"></el-color-picker>
- </div>
- </el-form-item>
- </el-form>
- </el-col>
- <!-- 右侧预览面板 -->
- <el-col :span="12">
- <div class="preview-container" ref="previewContainer">
- <div class="preview-canvas" :style="{ backgroundImage: bgImageUrl ? `url(${bgImageUrl})` : 'none' }">
- <!-- Logo元素 -->
- <div
- v-if="logoImageUrl"
- class="draggable-element logo-element"
- :style="{
- left: bibForm.logoX + 'px',
- top: bibForm.logoY + 'px'
- }"
- @mousedown="startDrag($event, 'logo')"
- >
- <img :src="logoImageUrl" alt="Logo" style="max-width: 80px; max-height: 80px" />
- </div>
- <!-- 示例条形码 -->
- <div
- class="draggable-element barcode-element"
- :style="{
- left: bibForm.qRCodeX + 'px',
- top: bibForm.qRCodeY + 'px'
- }"
- @mousedown="startDrag($event, 'barcode')"
- >
- <svg width="100" height="30" viewBox="0 0 100 30">
- <rect x="0" y="0" width="2" height="30" fill="black" />
- <rect x="4" y="0" width="1" height="30" fill="black" />
- <rect x="7" y="0" width="3" height="30" fill="black" />
- <rect x="12" y="0" width="1" height="30" fill="black" />
- <rect x="15" y="0" width="2" height="30" fill="black" />
- <rect x="19" y="0" width="1" height="30" fill="black" />
- <rect x="22" y="0" width="3" height="30" fill="black" />
- <rect x="27" y="0" width="2" height="30" fill="black" />
- <rect x="31" y="0" width="1" height="30" fill="black" />
- <rect x="34" y="0" width="2" height="30" fill="black" />
- <rect x="37" y="0" width="2" height="30" fill="black" />
- <rect x="42" y="0" width="2" height="30" fill="black" />
- <rect x="46" y="0" width="2" height="30" fill="black" />
- <rect x="49" y="0" width="2" height="30" fill="black" />
- <rect x="50" y="0" width="2" height="30" fill="black" />
- <rect x="52" y="0" width="2" height="30" fill="black" />
- <rect x="54" y="0" width="2" height="30" fill="black" />
- <rect x="58" y="0" width="2" height="30" fill="black" />
- <rect x="60" y="0" width="2" height="30" fill="black" />
- </svg>
- </div>
- <!-- 赛事名称预览 -->
- <div
- class="event-name-preview"
- :style="{
- fontSize: Math.round(bibForm.fontSize * 0.8) + 'px',
- color: bibForm.fontColorHex,
- fontFamily: bibForm.fontName
- }"
- >
- 赛事名称
- </div>
- <!-- 示例数字 1234 -->
- <div
- class="draggable-element number-element"
- :style="{
- left: '50%',
- top: '50%',
- transform: 'translate(-50%, -50%)',
- fontSize: bibForm.fontSize + 'px',
- color: bibForm.fontColorHex,
- fontFamily: bibForm.fontName
- }"
- >
- 1234
- </div>
- </div>
- </div>
- </el-col>
- </el-row>
- </div>
- <template #footer>
- <div class="dialog-footer">
- <el-button @click="handleCloseBibDialog">取 消</el-button>
- <el-button type="primary" @click="handleGenerateBibFile" :loading="bibDialog.loading">生成参赛证</el-button>
- </div>
- </template>
- </el-dialog>
- </div>
- </template>
- <script setup name="GameEvent" lang="ts">
- import {
- listGameEvent,
- changeEventDefault,
- delGameEvent,
- addGameEvent,
- updateGameEvent,
- generateNumberTable,
- generateBib,
- type GenerateBibBo
- } from '@/api/system/gameEvent';
- import { GameEventVO, GameEventQuery, GameEventForm } from '@/api/system/gameEvent/types';
- import { getEventMdByEventAndType, editEventMd } from '@/api/system/eventMd';
- import { EventMdVO, EventMdForm } from '@/api/system/eventMd/types';
- import { useRouter } from 'vue-router';
- import { ref, nextTick } from 'vue';
- import RefereeForm from '@/views/system/gameEvent/RefereeForm.vue';
- import RankingBoard from './RankingBoard.vue';
- import Editor from '@/components/Editor/index.vue';
- import { useTagsViewStore } from '@/store/modules/tagsView';
- import { globalHeaders } from '@/utils/request';
- import { useGameEventStore } from '@/store/modules/gameEvent';
- const router = useRouter();
- const { proxy } = getCurrentInstance() as ComponentInternalInstance;
- const { game_event_type, game_event_status, sys_yes_no, game_event_purpose } = toRefs<any>(
- proxy?.useDict('game_event_type', 'game_event_status', 'sys_yes_no', 'game_event_purpose')
- );
- // 定义 RefereeForm 组件的类型
- interface RefereeFormInstance {
- openDialog: (eventId: string) => void;
- }
- const refereeFormRef = ref<(InstanceType<typeof RefereeForm> & RefereeFormInstance) | null>(null);
- const gameEventList = ref<GameEventVO[]>([]);
- const buttonLoading = ref(false);
- const loading = ref(true);
- const showSearch = ref(true);
- const ids = ref<Array<string | number>>([]);
- const single = ref(true);
- const multiple = ref(true);
- const total = ref(0);
- // 列显隐数据
- const columns = ref<FieldOption[]>([
- { key: 0, label: '赛事id', visible: false },
- { key: 1, label: '赛事编号', visible: true },
- { key: 2, label: '赛事名称', visible: true },
- { key: 3, label: '赛事类型', visible: true },
- { key: 4, label: '举办地点', visible: true },
- { key: 5, label: '用途', visible: true },
- { key: 6, label: '开始时间', visible: true },
- { key: 7, label: '结束时间', visible: true },
- { key: 8, label: '赛事链接', visible: true },
- { key: 9, label: '裁判码', visible: true },
- { key: 10, label: '举办单位', visible: true },
- { key: 11, label: '是否默认赛事', visible: true },
- { key: 12, label: '创建时间', visible: true },
- { key: 13, label: '更新时间', visible: true },
- { key: 14, label: '状态', visible: true },
- { key: 15, label: '备注', visible: true }
- ]);
- const queryFormRef = ref<ElFormInstance>();
- const gameEventFormRef = ref<ElFormInstance>();
- const dialog = reactive<DialogOption>({
- visible: false,
- title: ''
- });
- const uploadRef = ref<ElUploadInstance>();
- /*** 用户导入参数 */
- const upload = reactive<ImportOption>({
- // 是否显示弹出层(用户导入)
- open: false,
- // 弹出层标题(用户导入)
- title: '',
- // 是否禁用上传
- isUploading: false,
- // 是否更新已经存在的用户数据
- updateSupport: 0,
- // 设置上传的请求头部
- headers: globalHeaders(),
- // 上传的地址
- url: import.meta.env.VITE_APP_BASE_API + '/system/enroll/importData'
- });
- const initFormData: GameEventForm = {
- eventId: undefined,
- eventCode: undefined,
- eventName: undefined,
- eventType: undefined,
- location: undefined,
- purpose: undefined,
- startTime: undefined,
- endTime: undefined,
- eventUrl: undefined,
- refereeUrl: undefined,
- registerUrl: undefined,
- unit: undefined,
- isDefault: undefined,
- status: undefined,
- remark: undefined
- };
- const data = reactive<PageData<GameEventForm, GameEventQuery>>({
- form: { ...initFormData },
- queryParams: {
- pageNum: 1,
- pageSize: 10,
- eventCode: undefined,
- eventName: undefined,
- eventType: undefined,
- purpose: undefined,
- startTime: undefined,
- isDefault: undefined,
- status: undefined,
- params: {},
- orderByColumn: '',
- isAsc: ''
- },
- rules: {
- eventCode: [{ required: true, message: '赛事编号不能为空', trigger: 'blur' }],
- eventName: [{ required: true, message: '赛事名称不能为空', trigger: 'blur' }],
- eventType: [{ required: true, message: '赛事类型不能为空', trigger: 'change' }]
- }
- });
- const { queryParams, form, rules } = toRefs(data);
- // 使用gameEvent store
- const gameEventStore = useGameEventStore();
- /** 查询赛事基本信息列表 */
- const getList = async () => {
- loading.value = true;
- const res = await listGameEvent(queryParams.value);
- gameEventList.value = res.rows;
- total.value = res.total;
- loading.value = false;
- };
- /** 取消按钮 */
- const cancel = () => {
- reset();
- dialog.visible = false;
- };
- /** 表单重置 */
- const reset = () => {
- form.value = { ...initFormData };
- gameEventFormRef.value?.resetFields();
- };
- /** 搜索按钮操作 */
- const handleQuery = () => {
- queryParams.value.pageNum = 1;
- getList();
- };
- /** 重置按钮操作 */
- const resetQuery = () => {
- queryFormRef.value?.resetFields();
- handleQuery();
- };
- /** 多选框选中数据 */
- const handleSelectionChange = (selection: GameEventVO[]) => {
- ids.value = selection.map((item) => item.eventId);
- single.value = selection.length != 1;
- multiple.value = !selection.length;
- };
- /** 新增按钮操作 */
- const handleAdd = () => {
- router.push('/system/gameEvent/add');
- };
- /** 修改按钮操作 */
- const handleUpdate = async (row?: GameEventVO) => {
- const _eventId = row?.eventId || ids.value[0];
- router.push(`/system/gameEvent/edit/${_eventId}`);
- };
- /** 提交按钮 */
- const submitForm = () => {
- gameEventFormRef.value?.validate(async (valid: boolean) => {
- if (valid) {
- buttonLoading.value = true;
- if (form.value.eventId) {
- await updateGameEvent(form.value).finally(() => (buttonLoading.value = false));
- } else {
- await addGameEvent(form.value).finally(() => (buttonLoading.value = false));
- }
- proxy?.$modal.msgSuccess('操作成功');
- dialog.visible = false;
- await getList();
- }
- });
- };
- /** 删除按钮操作 */
- const handleDelete = async (row?: GameEventVO) => {
- const _ids = row?.eventId || ids.value;
- if (!_ids || (Array.isArray(_ids) && _ids.length === 0)) {
- proxy?.$modal.msgError('请选择要删除的赛事');
- return;
- }
- // 如果是单个删除,需要检查时间限制
- if (row) {
- const endTime = new Date(row.endTime);
- const currentTime = new Date();
- const daysDiff = Math.floor((currentTime.getTime() - endTime.getTime()) / (1000 * 60 * 60 * 24));
- if (daysDiff < 100) {
- proxy?.$modal.msgError(`该赛事结束时间不足100天,无法删除。距离可删除时间还有 ${100 - daysDiff} 天。`);
- return;
- }
- await proxy?.$modal.confirm('是否确认删除赛事编号为"' + row.eventCode + '"的数据项?').finally(() => (loading.value = false));
- await delGameEvent(row.eventId);
- proxy?.$modal.msgSuccess('删除成功');
- } else {
- // 批量删除
- const selectedEvents = gameEventList.value.filter((event) => ids.value.includes(event.eventId));
- const invalidEvents = selectedEvents.filter((event) => {
- const endTime = new Date(event.endTime);
- const currentTime = new Date();
- const daysDiff = Math.floor((currentTime.getTime() - endTime.getTime()) / (1000 * 60 * 60 * 24));
- return daysDiff < 100;
- });
- if (invalidEvents.length > 0) {
- const eventNames = invalidEvents.map((event) => event.eventName).join('、');
- proxy?.$modal.msgError(`以下赛事结束时间不足100天,无法删除:${eventNames}`);
- return;
- }
- const eventCodes = selectedEvents.map((event) => event.eventCode).join('、');
- await proxy?.$modal.confirm(`是否确认删除以下赛事:${eventCodes}?`).finally(() => (loading.value = false));
- await delGameEvent(ids.value);
- proxy?.$modal.msgSuccess(`成功删除 ${selectedEvents.length} 个赛事`);
- }
- await getList();
- };
- /** 判断是否可以删除赛事 */
- const canDelete = (row: GameEventVO): boolean => {
- const endTime = new Date(row.endTime);
- const currentTime = new Date();
- const daysDiff = Math.floor((currentTime.getTime() - endTime.getTime()) / (1000 * 60 * 60 * 24));
- return daysDiff >= 100;
- };
- /** 获取删除按钮的提示信息 */
- const getDeleteTooltip = (row: GameEventVO): string => {
- if (canDelete(row)) {
- return '删除';
- } else {
- const endTime = new Date(row.endTime);
- const currentTime = new Date();
- const daysDiff = Math.floor((currentTime.getTime() - endTime.getTime()) / (1000 * 60 * 60 * 24));
- return `赛事结束时间不足100天,无法删除。距离可删除时间还有 ${100 - daysDiff} 天。`;
- }
- };
- /** 导出按钮操作 */
- const handleExport = () => {
- proxy?.download(
- 'system/gameEvent/export',
- {
- ...queryParams.value
- },
- `gameEvent_${new Date().getTime()}.xlsx`
- );
- };
- /* 下载模板 */
- const handleDownloadTemplate = (row: GameEventVO) => {
- proxy?.download(
- 'system/enroll/importTemplate',
- {
- eventId: row.eventId
- },
- `event_enroll_template_${new Date().getTime()}.xlsx`
- );
- };
- // 导入报名表逻辑
- const handleImportRegistration = (row) => {
- upload.url = import.meta.env.VITE_APP_BASE_API + `/system/enroll/importData/${row.eventId}`;
- upload.title = '报名表导入';
- upload.open = true;
- };
- /**文件上传中处理 */
- const handleFileUploadProgress = () => {
- upload.isUploading = true;
- };
- /** 文件上传成功处理 */
- const handleFileSuccess = (response: any, file: UploadFile) => {
- upload.open = false;
- upload.isUploading = false;
- uploadRef.value?.handleRemove(file);
- ElMessageBox.alert("<div style='overflow: auto;overflow-x: hidden;max-height: 70vh;padding: 10px 20px 0;'>" + response.msg + '</div>', '导入结果', {
- dangerouslyUseHTMLString: true
- });
- getList();
- };
- /** 提交上传文件 */
- function submitFileForm() {
- uploadRef.value?.submit();
- }
- // 添加参赛者操作
- const handleAddParticipant = (row: GameEventVO) => {
- // 跳转到新增或编辑参赛者信息页面,并传递 eventName 参数
- router.push({
- name: 'GameEventAthlete',
- params: { eventId: row.eventId, eventName: row.eventName }
- });
- };
- // 添加裁判按钮操作 1
- const handleAddReferee = async (row: GameEventVO) => {
- // 打开裁判表单对话框并传递 eventId
- refereeFormRef.value?.openDialog(String(row.eventId));
- };
- // 预览按钮点击事件
- const handlePreview = (row: GameEventVO) => {
- // 跳转到赛事详情页面
- router.push({
- name: 'GameEventDetail',
- params: { eventId: row.eventId }
- });
- };
- // 比赛成绩按钮点击事件
- const handleGameData = (row: GameEventVO) => {
- router.push({
- name: 'RankingBoardPage',
- params: { eventId: row.eventId }
- });
- };
- // 文章编写相关数据
- const articleDialog = reactive({
- visible: false,
- title: '',
- currentEventId: undefined as string | number | undefined
- });
- const activeTab = ref('competition-process');
- // 标签页与类型值的映射关系
- const tabTypeMapping: Record<string, number> = {
- 'competition-process': 1, // 竞赛流程
- 'competition-items': 2, // 竞赛项目
- 'activity-agenda': 3, // 活动议程
- 'project-introduction': 4, // 项目介绍
- 'competition-flow': 5, // 竞赛流程
- 'event-grouping': 6, // 赛事分组
- 'athlete-handbook': 7, // 运动员号码簿
- 'project-venue': 8, // 项目场地
- 'traffic-guide': 9 // 交通指示
- };
- const articleData = reactive({
- competitionProcess: { id: undefined, title: '', content: '', remark: '' },
- competitionItems: { id: undefined, title: '', content: '', remark: '' },
- activityAgenda: { id: undefined, title: '', content: '', remark: '' },
- projectIntroduction: { id: undefined, title: '', content: '', remark: '' },
- competitionFlow: { id: undefined, title: '', content: '', remark: '' },
- eventGrouping: { id: undefined, title: '', content: '', remark: '' },
- athleteHandbook: { id: undefined, title: '', content: '', remark: '' },
- projectVenue: { id: undefined, title: '', content: '', remark: '' },
- trafficGuide: { id: undefined, title: '', content: '', remark: '' }
- });
- // 打开排行榜组件并传递赛事ID
- // const openRankingBoard = (eventId: string) => {
- // currentEventId.value = eventId;
- // rankingBoardVisible.value = true;
- // };
- /** 状态修改 */
- const handleStatusChange = async (row: GameEventVO) => {
- const text = row.isDefault === '0' ? '启用' : '停用';
- try {
- await proxy?.$modal.confirm('确认要"' + text + '""' + row.eventName + '"为默认赛事吗?');
- await changeEventDefault(row.eventId, row.isDefault);
- await getList();
- // 更新全局默认赛事信息
- await gameEventStore.fetchDefaultEvent();
- // localStorage.setItem('defaultEventId', row.eventId);
- proxy?.$modal.msgSuccess(text + '成功');
- // 刷新当前标签页
- await useTagsViewStore().delOthersViews(router.currentRoute.value);
- } catch {
- return;
- } finally {
- row.isDefault = row.isDefault === '0' ? '1' : '0';
- }
- };
- /** 编写文章按钮操作 */
- const handleWriteArticle = async (row: GameEventVO) => {
- articleDialog.title = `编写文章 - ${row.eventName}`;
- articleDialog.currentEventId = row.eventId;
- articleDialog.visible = true;
- activeTab.value = 'competition-process';
- // 加载默认标签页(竞赛流程)的数据
- await loadTabData('competition-process');
- };
- /** 加载指定标签页的数据 */
- const loadTabData = async (tabName: string) => {
- const type = tabTypeMapping[tabName];
- if (articleDialog.currentEventId && type) {
- try {
- const response = await getEventMdByEventAndType(articleDialog.currentEventId, type);
- const eventMd = response.data;
- const dataKey = getDataKeyByTabName(tabName);
- if (dataKey && articleData[dataKey]) {
- if (eventMd) {
- articleData[dataKey].id = eventMd.id;
- articleData[dataKey].title = eventMd.title || '';
- articleData[dataKey].content = eventMd.content || '';
- articleData[dataKey].remark = eventMd.remark || '';
- } else {
- articleData[dataKey].id = undefined;
- articleData[dataKey].title = '';
- articleData[dataKey].content = '';
- articleData[dataKey].remark = '';
- }
- }
- } catch (error) {
- const dataKey = getDataKeyByTabName(tabName);
- if (dataKey && articleData[dataKey]) {
- articleData[dataKey].id = undefined;
- articleData[dataKey].title = '';
- articleData[dataKey].content = '';
- articleData[dataKey].remark = '';
- }
- }
- }
- };
- /** 标签页点击事件 */
- const handleTabClick = async (tab: any) => {
- const tabName = tab.props.name;
- await loadTabData(tabName);
- };
- /** 根据标签页名称获取数据键名 */
- const getDataKeyByTabName = (tabName: string): keyof typeof articleData | null => {
- const mapping: Record<string, keyof typeof articleData> = {
- 'competition-process': 'competitionProcess',
- 'competition-items': 'competitionItems',
- 'activity-agenda': 'activityAgenda',
- 'project-introduction': 'projectIntroduction',
- 'competition-flow': 'competitionFlow',
- 'event-grouping': 'eventGrouping',
- 'athlete-handbook': 'athleteHandbook',
- 'project-venue': 'projectVenue',
- 'traffic-guide': 'trafficGuide'
- };
- return mapping[tabName] || null;
- };
- /** 关闭文章编写对话框 */
- const handleCloseArticleDialog = () => {
- // 清空所有文章数据
- Object.keys(articleData).forEach((key) => {
- const dataKey = key as keyof typeof articleData;
- articleData[dataKey].id = undefined;
- articleData[dataKey].title = '';
- articleData[dataKey].content = '';
- articleData[dataKey].remark = '';
- });
- // 重置对话框状态
- articleDialog.visible = false;
- articleDialog.currentEventId = undefined;
- activeTab.value = 'competition-process';
- };
- /** 保存文章 */
- const handleSaveArticle = async () => {
- if (!articleDialog.currentEventId) {
- proxy?.$modal.msgError('赛事ID不能为空');
- return;
- }
- const currentTabName = activeTab.value;
- const type = tabTypeMapping[currentTabName];
- const dataKey = getDataKeyByTabName(currentTabName);
- if (!dataKey || !articleData[dataKey]) {
- proxy?.$modal.msgError('获取当前标签页数据失败');
- return;
- }
- const currentData = articleData[dataKey];
- if (!currentData.title?.trim()) {
- proxy?.$modal.msgError('标题不能为空');
- return;
- }
- try {
- const formData: EventMdForm = {
- id: currentData.id,
- eventId: articleDialog.currentEventId,
- title: currentData.title,
- content: currentData.content || '',
- type: type,
- remark: currentData.remark || ''
- };
- await editEventMd(formData);
- proxy?.$modal.msgSuccess('文章保存成功');
- // 重新加载当前标签页数据以获取最新的ID
- await loadTabData(currentTabName);
- } catch (error) {
- console.error('保存文章失败:', error);
- proxy?.$modal.msgError('保存文章失败');
- }
- };
- // 基于默认赛事的操作函数
- /** 下载模板(默认赛事) */
- const handleDownloadTemplateDefault = async () => {
- const defaultEvent = gameEventStore.defaultEventInfo;
- if (!defaultEvent) {
- proxy?.$modal.msgError('请先设置默认赛事');
- return;
- }
- handleDownloadTemplate(defaultEvent);
- };
- /** 导入报名信息(默认赛事) */
- const handleImportRegistrationDefault = async () => {
- const defaultEvent = gameEventStore.defaultEventInfo;
- if (!defaultEvent) {
- proxy?.$modal.msgError('请先设置默认赛事');
- return;
- }
- handleImportRegistration(defaultEvent);
- };
- /** 添加参赛者(默认赛事) */
- const handleAddParticipantDefault = async () => {
- const defaultEvent = gameEventStore.defaultEventInfo;
- if (!defaultEvent) {
- proxy?.$modal.msgError('请先设置默认赛事');
- return;
- }
- handleAddParticipant(defaultEvent);
- };
- /** 添加裁判(默认赛事) */
- const handleAddRefereeDefault = async () => {
- const defaultEvent = gameEventStore.defaultEventInfo;
- if (!defaultEvent) {
- proxy?.$modal.msgError('请先设置默认赛事');
- return;
- }
- handleAddReferee(defaultEvent);
- };
- /** 预览(默认赛事) */
- const handlePreviewDefault = async () => {
- const defaultEvent = gameEventStore.defaultEventInfo;
- if (!defaultEvent) {
- proxy?.$modal.msgError('请先设置默认赛事');
- return;
- }
- handlePreview(defaultEvent);
- };
- /** 比赛数据(默认赛事) */
- const handleGameDataDefault = async () => {
- const defaultEvent = gameEventStore.defaultEventInfo;
- if (!defaultEvent) {
- proxy?.$modal.msgError('请先设置默认赛事');
- return;
- }
- handleGameData(defaultEvent);
- };
- /** 编写文章(默认赛事) */
- const handleWriteArticleDefault = async () => {
- const defaultEvent = gameEventStore.defaultEventInfo;
- if (!defaultEvent) {
- proxy?.$modal.msgError('请先设置默认赛事');
- return;
- }
- handleWriteArticle(defaultEvent);
- };
- const handleExportNumberTableDefault = async () => {
- await proxy?.download('system/number/export', {}, `号码对照表_${new Date().getTime()}.xlsx`);
- };
- // 生成参赛证相关
- const bibDialog = reactive({
- visible: false,
- loading: false
- });
- const bibForm = reactive({
- logoX: 50,
- logoY: 50,
- qRCodeX: 100,
- qRCodeY: 200,
- fontName: 'simhei',
- fontSize: 36,
- fontColor: '#000000',
- fontColorHex: '#000000'
- });
- const bgImageFile = ref<File | null>(null);
- const logoImageFile = ref<File | null>(null);
- const bgImageUrl = ref<string>('');
- const logoImageUrl = ref<string>('');
- const previewContainer = ref<HTMLElement>();
- const bgImageDimensions = ref<{ width: number; height: number } | null>(null);
- const bgUploadRef = ref<ElUploadInstance>();
- const logoUploadRef = ref<ElUploadInstance>();
- // 拖拽相关
- const dragState = reactive({
- isDragging: false,
- dragTarget: '',
- startX: 0,
- startY: 0,
- startLeft: 0,
- startTop: 0
- });
- // 生成参赛证按钮处理
- const handleGenerateBib = () => {
- // 强制设置默认值,不使用条件判断(像素单位)
- bibForm.logoX = bibForm.logoX || 50;
- bibForm.logoY = bibForm.logoY || 50;
- bibForm.qRCodeX = bibForm.qRCodeX || 100; // 修正:使用独立的默认值
- bibForm.qRCodeY = bibForm.qRCodeY || 200; // 修正:使用独立的默认值
- bibForm.fontName = bibForm.fontName || 'simhei';
- bibForm.fontSize = bibForm.fontSize || 36;
- bibForm.fontColor = bibForm.fontColor || '#000000';
- bibForm.fontColorHex = bibForm.fontColorHex || '#000000';
- bibDialog.visible = true;
- };
- // 关闭对话框
- const handleCloseBibDialog = () => {
- bibDialog.visible = false;
- resetBibForm();
- // 清除上传的文件
- if (bgUploadRef.value) {
- bgUploadRef.value.clearFiles();
- }
- if (logoUploadRef.value) {
- logoUploadRef.value.clearFiles();
- }
- };
- // 重置表单
- const resetBibForm = () => {
- // 设置默认值(像素单位)
- bibForm.logoX = 50;
- bibForm.logoY = 50;
- bibForm.qRCodeX = 100;
- bibForm.qRCodeY = 200;
- bibForm.fontName = 'simhei';
- bibForm.fontSize = 36;
- bibForm.fontColor = '#000000';
- bibForm.fontColorHex = '#000000';
- bgImageFile.value = null;
- logoImageFile.value = null;
- bgImageUrl.value = '';
- logoImageUrl.value = '';
- // 清除上传的文件
- if (bgUploadRef.value) {
- bgUploadRef.value.clearFiles();
- }
- if (logoUploadRef.value) {
- logoUploadRef.value.clearFiles();
- }
- };
- // 背景图片改变处理
- const handleBgImageChange = async (file: any) => {
- if (file.raw) {
- bgImageFile.value = file.raw;
- const reader = new FileReader();
- reader.onload = (e) => {
- bgImageUrl.value = e.target?.result as string;
- };
- reader.readAsDataURL(file.raw);
- // 获取背景图片的实际尺寸
- try {
- const dimensions = await getImageDimensions(file.raw);
- bgImageDimensions.value = dimensions;
- } catch (error) {
- console.error('获取图片尺寸失败:', error);
- }
- }
- };
- // Logo图片改变处理
- const handleLogoImageChange = (file: any) => {
- if (file.raw) {
- logoImageFile.value = file.raw;
- const reader = new FileReader();
- reader.onload = (e) => {
- logoImageUrl.value = e.target?.result as string;
- };
- reader.readAsDataURL(file.raw);
- }
- };
- // 字体颜色改变处理
- const handleFontColorChange = (color: string) => {
- bibForm.fontColor = color;
- bibForm.fontColorHex = color;
- };
- // 开始拖拽
- const startDrag = (event: MouseEvent, target: string) => {
- event.preventDefault();
- dragState.isDragging = true;
- dragState.dragTarget = target;
- dragState.startX = event.clientX;
- dragState.startY = event.clientY;
- if (target === 'logo') {
- dragState.startLeft = bibForm.logoX;
- dragState.startTop = bibForm.logoY;
- } else if (target === 'barcode') {
- dragState.startLeft = bibForm.qRCodeX;
- dragState.startTop = bibForm.qRCodeY;
- }
- document.addEventListener('mousemove', handleDrag);
- document.addEventListener('mouseup', stopDrag);
- };
- // 处理拖拽
- const handleDrag = (event: MouseEvent) => {
- if (!dragState.isDragging) return;
- const deltaX = event.clientX - dragState.startX;
- const deltaY = event.clientY - dragState.startY;
- if (dragState.dragTarget === 'logo') {
- bibForm.logoX = Math.max(0, dragState.startLeft + deltaX);
- bibForm.logoY = Math.max(0, dragState.startTop + deltaY);
- } else if (dragState.dragTarget === 'barcode') {
- bibForm.qRCodeX = Math.max(0, dragState.startLeft + deltaX);
- bibForm.qRCodeY = Math.max(0, dragState.startTop + deltaY);
- }
- };
- // 停止拖拽
- const stopDrag = () => {
- dragState.isDragging = false;
- dragState.dragTarget = '';
- document.removeEventListener('mousemove', handleDrag);
- document.removeEventListener('mouseup', stopDrag);
- };
- // 坐标转换函数:根据实际背景图片尺寸调整坐标
- const convertCoordinatesWithScale = (x: number, y: number): { x: number; y: number } => {
- // 获取预览容器尺寸,使用更精确的方法
- const container = previewContainer.value;
- const previewWidth = container?.clientWidth || container?.offsetWidth || 400;
- const previewHeight = container?.clientHeight || container?.offsetHeight || 400;
- // 使用实际背景图片尺寸,如果没有则使用默认A4尺寸
- const actualWidth = bgImageDimensions.value?.width || 595;
- const actualHeight = bgImageDimensions.value?.height || 842;
- // 计算实际比例
- const scaleX = actualWidth / previewWidth;
- const scaleY = actualHeight / previewHeight;
- // 微调系数,用于补偿微小偏差
- const fineTuneX = 1.15; // 进一步缩小X坐标,让元素向左移动
- const fineTuneY = 0.9; // 进一步放大Y坐标,让元素向下移动
- // 根据实际效果图,需要调整坐标系统
- // 实际效果显示Logo在左上角,二维码在左下角
- const adjustedX = x * scaleX * fineTuneX;
- const adjustedY = (previewHeight - y) * scaleY * fineTuneY;
- // 添加额外的偏移量调整
- const offsetX = 8; // 向右偏移8pt(进一步减少向右偏移)
- const offsetY = 65; // 向下偏移65pt(进一步增加向下偏移)
- // 根据元素类型进行特殊调整
- let finalX = adjustedX + offsetX;
- let finalY = adjustedY + offsetY;
- // 如果是Logo,进行特殊调整
- if (x < 100 && y < 100) {
- // 假设Logo在左上角区域
- finalX += 5; // Logo额外向右偏移
- finalY -= 10; // Logo额外向下偏移
- }
- // 如果是二维码,进行特殊调整
- if (x > 200 && y > 200) {
- // 假设二维码在右下角区域
- finalX -= 3; // 二维码额外向左偏移
- finalY += 5; // 二维码额外向下偏移
- }
- return {
- x: finalX,
- y: finalY
- };
- };
- // 新增:获取背景图片实际尺寸的函数
- const getImageDimensions = (file: File): Promise<{ width: number; height: number }> => {
- return new Promise((resolve) => {
- const img = new Image();
- img.onload = () => {
- resolve({ width: img.width, height: img.height });
- };
- img.src = URL.createObjectURL(file);
- });
- };
- // 生成参赛证文件
- const handleGenerateBibFile = async () => {
- if (!bgImageFile.value) {
- proxy?.$modal.msgError('请上传背景图片');
- return;
- }
- bibDialog.loading = true;
- try {
- let qRCodeX = bibForm.qRCodeX;
- let qRCodeY = bibForm.qRCodeY;
- // 如果值为null或undefined,强制使用默认值
- if (qRCodeX === null || qRCodeX === undefined || isNaN(qRCodeX)) {
- qRCodeX = 100;
- console.warn('qRCodeX值异常,使用默认值100px');
- }
- if (qRCodeY === null || qRCodeY === undefined || isNaN(qRCodeY)) {
- qRCodeY = 200;
- console.warn('qRCodeY值异常,使用默认值200px');
- }
- // 获取预览容器的高度用于坐标转换
- const containerHeight = previewContainer.value?.clientHeight || 400;
- // 检查背景图片尺寸
- if (!bgImageDimensions.value) {
- proxy?.$modal.msgWarning('正在获取背景图片尺寸,请稍后再试');
- return;
- }
- // 等待一帧确保所有尺寸都已计算完成
- await nextTick();
- // Logo坐标(左上角)
- const logoCoords = convertCoordinatesWithScale(bibForm.logoX || 50, bibForm.logoY || 50);
- // 二维码坐标(左上角)
- const qrCoords = convertCoordinatesWithScale(qRCodeX, qRCodeY);
- const bibParams = {
- logoX: logoCoords.x,
- logoY: logoCoords.y,
- qRCodeX: qrCoords.x,
- qRCodeY: qrCoords.y,
- fontName: bibForm.fontName || 'simhei',
- fontSize: Math.round((bibForm.fontSize || 36) * 0.75), // 字体大小转换为PDF点并四舍五入为整数
- fontColor: parseInt((bibForm.fontColor || '#000000').replace('#', ''), 16)
- };
- // 最后一次检查,确保二维码坐标不为null
- if (bibParams.qRCodeX === null || bibParams.qRCodeY === undefined) {
- bibParams.qRCodeX = 148.75; // 100px * 1.4875
- console.error('最后一次修复:qRCodeX仍为null,设置为100px转换后的值');
- }
- if (bibParams.qRCodeY === null || bibParams.qRCodeY === undefined) {
- bibParams.qRCodeY = 421; // 200px * 2.105
- console.error('最后一次修复:qRCodeY仍为null,设置为200px转换后的值');
- }
- const response = await generateBib(bgImageFile.value, logoImageFile.value, bibParams);
- // 处理文件下载 - response已经是blob数据,不需要再访问.data属性
- const blob = new Blob([response as any], { type: 'application/zip' });
- const url = window.URL.createObjectURL(blob);
- const link = document.createElement('a');
- link.href = url;
- link.download = `参赛证_${new Date().getTime()}.zip`;
- link.click();
- window.URL.revokeObjectURL(url);
- proxy?.$modal.msgSuccess('参赛证生成成功');
- handleCloseBibDialog();
- } catch (error) {
- console.error('生成参赛证失败:', error);
- proxy?.$modal.msgError('生成参赛证失败');
- } finally {
- bibDialog.loading = false;
- }
- };
- onMounted(() => {
- // 获取默认赛事信息
- gameEventStore.fetchDefaultEvent();
- getList();
- });
- // 监听路由变化,当从编辑页返回时检查是否需要刷新列表
- onActivated(() => {
- // 检查是否有需要刷新的标识
- const needRefresh = sessionStorage.getItem('needRefreshGameEventList');
- if (needRefresh === 'true') {
- // 清除标识
- sessionStorage.removeItem('needRefreshGameEventList');
- // 刷新列表数据
- getList();
- }
- });
- </script>
- <style scoped>
- .operation-buttons {
- display: flex;
- flex-direction: column;
- gap: 4px;
- align-items: center;
- min-width: 200px;
- }
- .button-row {
- display: flex;
- gap: 6px;
- justify-content: center;
- align-items: center;
- flex-wrap: wrap;
- }
- .operation-buttons .el-button {
- min-width: 28px;
- min-height: 28px;
- padding: 4px 6px;
- border-radius: 4px;
- transition: all 0.2s ease;
- display: flex;
- align-items: center;
- justify-content: center;
- }
- /* 生成参赛证样式 */
- .bib-generator {
- padding: 20px;
- }
- .bib-generator .el-upload__tip {
- color: #909399;
- font-size: 12px;
- margin-top: 8px;
- text-align: center;
- }
- .preview-container {
- border: 2px dashed #ddd;
- border-radius: 8px;
- min-height: 400px;
- position: relative;
- overflow: hidden;
- }
- .preview-canvas {
- width: 100%;
- height: 400px;
- position: relative;
- background-size: cover;
- background-position: center;
- background-repeat: no-repeat;
- background-color: #f5f5f5;
- }
- .draggable-element {
- position: absolute;
- cursor: move;
- user-select: none;
- z-index: 10;
- }
- .draggable-element:hover {
- opacity: 0.8;
- }
- .logo-element {
- border: 2px dashed transparent;
- }
- .logo-element:hover {
- border-color: #409eff;
- }
- .barcode-element {
- border: 2px dashed transparent;
- padding: 5px;
- }
- .barcode-element:hover {
- border-color: #67c23a;
- background-color: rgba(103, 194, 58, 0.1);
- }
- .number-element {
- font-weight: bold;
- text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.3);
- border: 2px dashed transparent;
- padding: 5px;
- white-space: nowrap;
- user-select: none;
- pointer-events: none;
- }
- .event-name-preview {
- position: absolute;
- top: 20px;
- left: 50%;
- transform: translateX(-50%);
- font-weight: bold;
- text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.3);
- user-select: none;
- pointer-events: none;
- z-index: 5;
- }
- .operation-buttons .el-button:hover {
- transform: translateY(-1px);
- box-shadow: 0 2px 6px rgba(0, 0, 0, 0.12);
- }
- .operation-buttons .el-button[disabled] {
- opacity: 0.5;
- cursor: not-allowed;
- }
- .operation-buttons .el-button[disabled]:hover {
- transform: none;
- box-shadow: none;
- }
- /* 为不同类型的按钮设置不同的颜色主题 */
- .operation-buttons .el-button--primary {
- color: #409eff;
- }
- .operation-buttons .el-button--danger {
- color: #f56c6c;
- }
- .operation-buttons .el-button--warning {
- color: #e6a23c;
- }
- .operation-buttons .el-button--info {
- color: #909399;
- }
- .operation-buttons .el-button--success {
- color: #67c23a;
- }
- /* 响应式设计 */
- @media (max-width: 1400px) {
- .operation-buttons {
- min-width: 180px;
- }
- .button-row {
- gap: 4px;
- }
- .operation-buttons .el-button {
- min-width: 24px;
- min-height: 24px;
- padding: 3px 5px;
- }
- }
- @media (max-width: 1200px) {
- .operation-buttons {
- min-width: 160px;
- gap: 2px;
- }
- .button-row {
- gap: 2px;
- }
- .operation-buttons .el-button {
- min-width: 22px;
- min-height: 22px;
- padding: 2px 4px;
- }
- }
- </style>
|