index.vue 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544
  1. <template>
  2. <div class="p-2">
  3. <transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
  4. <div v-show="showSearch" class="mb-[10px]">
  5. <!-- <el-card shadow="hover">
  6. <el-form ref="queryFormRef" :model="queryParams" :inline="true">
  7. <el-form-item label="模板ID" prop="templateId">
  8. <el-input v-model="queryParams.templateId" placeholder="请输入模板ID" clearable @keyup.enter="handleQuery" />
  9. </el-form-item>
  10. <el-form-item label="创建方案ID" prop="creationSchemeId">
  11. <el-input v-model="queryParams.creationSchemeId" placeholder="请输入创建方案ID" clearable @keyup.enter="handleQuery" />
  12. </el-form-item>
  13. <el-form-item label="开始日期" prop="startDate">
  14. <el-date-picker clearable v-model="queryParams.startDate" type="date" value-format="YYYY-MM-DD" placeholder="请选择开始日期" />
  15. </el-form-item>
  16. <el-form-item label="结束日期" prop="endDate">
  17. <el-date-picker clearable v-model="queryParams.endDate" type="date" value-format="YYYY-MM-DD" placeholder="请选择结束日期" />
  18. </el-form-item>
  19. <el-form-item label="创建时间" prop="createdAt">
  20. <el-date-picker clearable v-model="queryParams.createdAt" type="date" value-format="YYYY-MM-DD" placeholder="请选择创建时间" />
  21. </el-form-item>
  22. <el-form-item label="更新时间" prop="updatedAt">
  23. <el-date-picker clearable v-model="queryParams.updatedAt" type="date" value-format="YYYY-MM-DD" placeholder="请选择更新时间" />
  24. </el-form-item>
  25. <el-form-item>
  26. <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
  27. <el-button icon="Refresh" @click="resetQuery">重置</el-button>
  28. </el-form-item>
  29. </el-form>
  30. </el-card>-->
  31. </div>
  32. </transition>
  33. <el-card shadow="never">
  34. <template #header>
  35. <el-row :gutter="10" class="mb8">
  36. <el-col :span="1.5">
  37. <el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['system:config:add']">新增投放方案</el-button>
  38. </el-col>
  39. <router-link to="/business/tournamentsTemplate">
  40. <el-button type="success" plain icon="Edit">自动比赛模版</el-button>
  41. </router-link>
  42. <!-- <el-col :span="1.5">
  43. <el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['system:config:remove']"
  44. >删除</el-button
  45. >
  46. </el-col>
  47. <el-col :span="1.5">
  48. <el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['system:config:export']">导出</el-button>
  49. </el-col>-->
  50. <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
  51. </el-row>
  52. </template>
  53. <el-table v-loading="loading" border :data="configList" @selection-change="handleSelectionChange">
  54. <!-- <el-table-column type="selection" width="55" align="center" />-->
  55. <el-table-column label="" align="center" prop="id" v-if="false" />
  56. <el-table-column label="比赛名" align="center" prop="name" />
  57. <el-table-column label="比赛Logo" align="center">
  58. <template #default="scope">
  59. <el-image
  60. v-if="scope.row.competitionIcon"
  61. :src="scope.row.competitionIcon"
  62. style="width: 40px; height: 40px; border-radius: 4px; cursor: zoom-in"
  63. :preview-src-list="[scope.row.competitionIcon]"
  64. :preview-teleported="true"
  65. fit="cover"
  66. />
  67. <span v-else></span>
  68. </template>
  69. </el-table-column>
  70. <el-table-column label="频率" align="center">
  71. <template #default="scope">
  72. <span v-html="formatFrequency(scope.row)"></span>
  73. </template>
  74. </el-table-column>
  75. <el-table-column label="报名要求" align="center">
  76. <template #default="scope">
  77. {{
  78. scope.row.itemsName && scope.row.itemsNum
  79. ? `${scope.row.itemsName} x ${scope.row.itemsNum}`
  80. : scope.row.itemsName || scope.row.itemsNum || '—'
  81. }}
  82. </template>
  83. </el-table-column>
  84. <el-table-column label="报名截止盲注等级" align="center" prop="lateRegistrationLevel" />
  85. <el-table-column label="盲注表" align="center" prop="blindStructuresName" />
  86. <el-table-column label="奖励" align="center">
  87. <template #default="scope">
  88. <div v-for="(prize, index) in scope.row.itemsPrizeList" :key="index">
  89. 第{{ prize.ranking }}名:{{ prize.quantity }} {{ prize.itemsName }}
  90. </div>
  91. </template>
  92. </el-table-column>
  93. <el-table-column label="状态" align="center" prop="statusText" />
  94. <el-table-column label="创建时间" align="center" prop="createdAt" width="180">
  95. <template #default="scope">
  96. <span>{{ parseTime(scope.row.createdAt, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
  97. </template>
  98. </el-table-column>
  99. <el-table-column label="操作" align="center" width="320">
  100. <template #default="scope">
  101. <!-- 查看按钮 -->
  102. <el-tooltip content="查看" placement="top">
  103. <el-button link type="primary" size="small" @click="handleUpdate(scope.row, 'view')" v-hasPermi="['system:config:edit']">
  104. <el-icon><View /></el-icon>
  105. 查看
  106. </el-button>
  107. </el-tooltip>
  108. <!-- 投放配置按钮 -->
  109. <el-tooltip content="投放配置" placement="top">
  110. <el-button link type="success" size="small" @click="handleUpdate(scope.row, 'edit')" v-hasPermi="['system:config:edit']">
  111. <el-icon><Setting /></el-icon>
  112. 投放配置
  113. </el-button>
  114. </el-tooltip>
  115. <!-- 停止按钮 -->
  116. <!-- 动态:停止 或 开始 按钮 -->
  117. <template v-if="scope.row.enabled">
  118. <!-- 已启用,显示:停止按钮 -->
  119. <el-tooltip content="停止" placement="top">
  120. <el-button link type="warning" size="small" @click="handleStop(scope.row)" v-hasPermi="['system:config:remove']">
  121. <el-icon><VideoPause /></el-icon> 停止
  122. </el-button>
  123. </el-tooltip>
  124. </template>
  125. <template v-else>
  126. <!-- 已禁用,显示:开始按钮 -->
  127. <el-tooltip content="开始" placement="top">
  128. <el-button link type="success" size="small" @click="handleStart(scope.row)" v-hasPermi="['system:config:add']">
  129. <el-icon><VideoPlay /></el-icon> 开始
  130. </el-button>
  131. </el-tooltip>
  132. </template>
  133. <!-- 删除按钮 -->
  134. <el-tooltip content="删除" placement="top">
  135. <el-button link type="danger" size="small" @click="handleDelete(scope.row)" v-hasPermi="['system:config:remove']">
  136. <el-icon><Delete /></el-icon>
  137. 删除
  138. </el-button>
  139. </el-tooltip>
  140. </template>
  141. </el-table-column>
  142. </el-table>
  143. <pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
  144. </el-card>
  145. <!-- 添加或修改排期配置:用于保存具体的排期实例配置对话框 -->
  146. <el-dialog :title="dialog.title" v-model="dialog.visible" width="500px" append-to-body>
  147. <el-form ref="configFormRef" :model="form" :rules="mode === 'view' ? {} : rules" label-width="80px">
  148. <!-- 选择模版 -->
  149. <el-form-item label="选择模版" prop="templateId">
  150. <div style="display: flex; align-items: center">
  151. <el-select v-model="form.templateId" :disabled="mode === 'view'" placeholder="选择模版" style="width: 200px">
  152. <el-option v-for="item in itemOptionsTemplateList" :key="item.id" :label="item.label" :value="item.id" />
  153. </el-select>
  154. </div>
  155. </el-form-item>
  156. <!-- 重复设置 -->
  157. <el-form-item label="重复设置" prop="repeatTypes">
  158. <el-select v-model="form.repeatTypes" :disabled="mode === 'view'" placeholder="请选择重复类型" multiple style="width: 100%">
  159. <el-option v-for="dict in repeat_type" :key="dict.value" :label="dict.label" :value="dict.value" />
  160. </el-select>
  161. </el-form-item>
  162. <!-- 时间设置 -->
  163. <el-form-item label="时间设置">
  164. <div style="margin-bottom: 8px">设置投放方案后,系统会依据设置时间和方案自动创建当天比赛;</div>
  165. <div v-for="(reward, index) in formPrize.rewards" :key="index" style="display: flex; align-items: center; margin-bottom: 8px">
  166. <span style="margin-right: 16px">场次{{ index + 1 }}:</span>
  167. <!-- 时间选择器 -->
  168. <el-time-picker
  169. v-model="reward.execDates"
  170. :disabled="mode === 'view'"
  171. format="HH:mm"
  172. value-format="HH:mm"
  173. :picker-options="{
  174. disabledMinutes: handleDisabledMinutes,
  175. selectableRange: '00:00:00 - 23:59:59'
  176. }"
  177. placeholder="请选择时间"
  178. style="width: 220px; margin-right: 8px"
  179. />
  180. <!-- 操作按钮:查看时不显示 +/- -->
  181. <el-button v-if="mode !== 'view' && index === 0" type="primary" @click="addReward" size="small"> + </el-button>
  182. <el-button v-if="mode !== 'view' && index !== 0" type="primary" @click="removeReward(index)" size="small"> - </el-button>
  183. </div>
  184. </el-form-item>
  185. <!-- 创建方案 -->
  186. <el-form-item label="创建方案" prop="creationSchemeCode">
  187. <div style="display: flex; align-items: center">
  188. <el-select v-model="form.creationSchemeCode" :disabled="mode === 'view'" placeholder="选择创建方案" style="width: 200px">
  189. <el-option v-for="item in itemOptionsSchemeList" :key="item.id" :label="item.label" :value="item.code" />
  190. </el-select>
  191. </div>
  192. </el-form-item>
  193. </el-form>
  194. <!-- 底部按钮 -->
  195. <template #footer>
  196. <div class="dialog-footer">
  197. <!-- 查看模式下不显示确定按钮 -->
  198. <el-button v-if="mode !== 'view'" :loading="buttonLoading" type="primary" @click="submitForm"> 确 定 </el-button>
  199. <el-button @click="cancel">
  200. {{ mode === 'view' ? '关 闭' : '取 消' }}
  201. </el-button>
  202. </div>
  203. </template>
  204. </el-dialog>
  205. </div>
  206. </template>
  207. <script setup name="Config" lang="ts">
  208. import {
  209. listConfig,
  210. getConfig,
  211. delConfig,
  212. createSchedule,
  213. updateConfig,
  214. selectTemplateTourList,
  215. updateScheduleConfig,
  216. deleteScheduleConfig,
  217. stopScheduleConfig,
  218. startScheduleConfig
  219. } from '@/api/system/business/config';
  220. import { selectAllCreationSchemesAllList } from '@/api/system/business/scheme';
  221. import { ConfigVO, ConfigQuery, ConfigForm } from '@/api/system/business/config/types';
  222. const { proxy } = getCurrentInstance() as ComponentInternalInstance;
  223. const { repeat_type } = toRefs<any>(proxy?.useDict('repeat_type'));
  224. const configList = ref<ConfigVO[]>([]);
  225. const buttonLoading = ref(false);
  226. const loading = ref(true);
  227. const showSearch = ref(true);
  228. const ids = ref<Array<string | number>>([]);
  229. const single = ref(true);
  230. const multiple = ref(true);
  231. const total = ref(0);
  232. const queryFormRef = ref<ElFormInstance>();
  233. const configFormRef = ref<ElFormInstance>();
  234. const dialog = reactive<DialogOption>({
  235. visible: false,
  236. title: ''
  237. });
  238. // 新增 mode 字段:'add' | 'edit' | 'view'
  239. const mode = ref('add'); // 默认为添加
  240. const initFormData: ConfigForm = {
  241. id: undefined,
  242. templateId: undefined,
  243. creationSchemeId: undefined,
  244. startDate: undefined,
  245. endDate: undefined,
  246. enabled: undefined,
  247. createdAt: undefined,
  248. updatedAt: undefined
  249. };
  250. const data = reactive<PageData<ConfigForm, ConfigQuery>>({
  251. form: { ...initFormData },
  252. queryParams: {
  253. pageNum: 1,
  254. pageSize: 10,
  255. templateId: undefined,
  256. creationSchemeId: undefined,
  257. startDate: undefined,
  258. endDate: undefined,
  259. enabled: undefined,
  260. createdAt: undefined,
  261. updatedAt: undefined,
  262. params: {}
  263. },
  264. rules: {
  265. id: [{ required: true, message: '不能为空', trigger: 'blur' }],
  266. templateId: [{ required: true, message: '模板ID不能为空', trigger: 'blur' }],
  267. startDate: [{ required: true, message: '开始日期不能为空', trigger: 'blur' }],
  268. endDate: [{ required: true, message: '结束日期不能为空', trigger: 'blur' }]
  269. }
  270. });
  271. const { queryParams, form, rules } = toRefs(data);
  272. /** 查询排期配置:用于保存具体的排期实例配置列表 */
  273. const getList = async () => {
  274. loading.value = true;
  275. const res = await listConfig(queryParams.value);
  276. configList.value = res.rows;
  277. total.value = res.total;
  278. loading.value = false;
  279. };
  280. /** 取消按钮 */
  281. const cancel = () => {
  282. reset();
  283. resetFormPrize();
  284. dialog.visible = false;
  285. };
  286. /** 表单重置 */
  287. const reset = () => {
  288. form.value = { ...initFormData };
  289. configFormRef.value?.resetFields();
  290. resetFormPrize();
  291. };
  292. /** 搜索按钮操作 */
  293. const handleQuery = () => {
  294. queryParams.value.pageNum = 1;
  295. getList();
  296. };
  297. /** 重置按钮操作 */
  298. const resetQuery = () => {
  299. queryFormRef.value?.resetFields();
  300. handleQuery();
  301. };
  302. /** 多选框选中数据 */
  303. const handleSelectionChange = (selection: ConfigVO[]) => {
  304. ids.value = selection.map((item) => item.id);
  305. single.value = selection.length != 1;
  306. multiple.value = !selection.length;
  307. };
  308. /** 新增按钮操作 */
  309. const handleAdd = () => {
  310. reset();
  311. resetFormPrize();
  312. mode.value = 'add';
  313. dialog.visible = true;
  314. dialog.title = '新增投放方案';
  315. };
  316. /**
  317. * 处理修改或查看操作
  318. * @param row 当前行数据
  319. * @param action 'edit' | 'view'
  320. */
  321. const handleUpdate = async (row, action = 'edit') => {
  322. reset();
  323. resetFormPrize();
  324. mode.value = action; // 设置模式
  325. dialog.title = action === 'view' ? '查看投放设置' : '编辑投放设置';
  326. const _id = row?.id || ids.value[0];
  327. const res = await getConfig(_id);
  328. // 1. 赋值 form
  329. Object.assign(form.value, res.data);
  330. // 2. 处理 execTimes -> formPrize.rewards
  331. if (res.data.execTimes && Array.isArray(res.data.execTimes)) {
  332. formPrize.rewards = res.data.execTimes.filter((time) => time).map((time) => ({ execDates: time }));
  333. } else {
  334. formPrize.rewards = [{ execDates: '' }]; // 默认一个空行
  335. }
  336. dialog.visible = true;
  337. };
  338. /** 提交按钮 */
  339. const submitForm = () => {
  340. configFormRef.value?.validate(async (valid: boolean) => {
  341. if (valid) {
  342. buttonLoading.value = true;
  343. // ✅ 关键步骤:将 formPrize.rewards 中的时间提取为 execTimes 数组
  344. form.value.execTimes = formPrize.rewards.map((reward) => reward.execDates).filter((time) => time); // 可选:过滤掉空值或 null
  345. if (form.value.id) {
  346. await updateScheduleConfig(form.value).finally(() => (buttonLoading.value = false));
  347. } else {
  348. await createSchedule(form.value).finally(() => (buttonLoading.value = false));
  349. }
  350. proxy?.$modal.msgSuccess('操作成功');
  351. dialog.visible = false;
  352. await getList();
  353. }
  354. });
  355. };
  356. /** 删除按钮操作 */
  357. const handleDelete = async (row?: ConfigVO) => {
  358. const _ids = row?.id || ids.value;
  359. await proxy?.$modal.confirm('是否确认删除排期配置,编号为"' + _ids + '"的数据项?').finally(() => (loading.value = false));
  360. await deleteScheduleConfig(row.configId);
  361. proxy?.$modal.msgSuccess('删除成功');
  362. await getList();
  363. };
  364. //停止操作
  365. const handleStop = async (row?: ConfigVO) => {
  366. const _ids = row?.id || ids.value;
  367. await proxy?.$modal.confirm('是否确认停止排期配置,编号为"' + _ids + '"的数据项?').finally(() => (loading.value = false));
  368. await stopScheduleConfig(row.configId);
  369. proxy?.$modal.msgSuccess('操作成功');
  370. await getList();
  371. };
  372. const handleStart = async (row?: ConfigVO) => {
  373. const _ids = row?.id || ids.value;
  374. await proxy?.$modal.confirm('是否开启排期配置,编号为"' + _ids + '"的数据项?').finally(() => (loading.value = false));
  375. await startScheduleConfig(row.configId);
  376. proxy?.$modal.msgSuccess('操作成功');
  377. await getList();
  378. };
  379. /** 导出按钮操作 */
  380. const handleExport = () => {
  381. proxy?.download(
  382. 'system/config/export',
  383. {
  384. ...queryParams.value
  385. },
  386. `config_${new Date().getTime()}.xlsx`
  387. );
  388. };
  389. onMounted(() => {
  390. getList();
  391. handleOptionsTemplateChange();
  392. handleOptionsSchemeChange();
  393. });
  394. // 下拉选项数据
  395. const itemOptionsTemplateList = ref<{ id: number; label: string }[]>([]);
  396. // 加载报名条件选项
  397. const handleOptionsTemplateChange = async () => {
  398. try {
  399. const res = await selectTemplateTourList();
  400. if (res.code === 200) {
  401. // 使用 unknown 中间类型进行类型转换
  402. const data = res.data as unknown as { id: number; name: number }[];
  403. const list = [];
  404. for (let i = 0; i < data.length; i++) {
  405. const item = data[i];
  406. list.push({
  407. id: item.id,
  408. label: item.name
  409. });
  410. }
  411. itemOptionsTemplateList.value = list;
  412. } else {
  413. alert('加载失败:' + res.msg);
  414. }
  415. } catch (error) {
  416. console.error('请求出错:', error);
  417. }
  418. };
  419. // 下拉选项数据
  420. const itemOptionsSchemeList = ref<{ id: number; label: string; code: string }[]>([]);
  421. // 加载报名条件选项
  422. const handleOptionsSchemeChange = async () => {
  423. try {
  424. const res = await selectAllCreationSchemesAllList();
  425. if (res.code === 200) {
  426. // 使用 unknown 中间类型进行类型转换
  427. const data = res.data as unknown as { id: number; name: string; code: string }[];
  428. const list = [];
  429. for (let i = 0; i < data.length; i++) {
  430. const item = data[i];
  431. list.push({
  432. id: item.id,
  433. label: item.name,
  434. code: item.code
  435. });
  436. }
  437. itemOptionsSchemeList.value = list;
  438. } else {
  439. alert('加载失败:' + res.msg);
  440. }
  441. } catch (error) {
  442. console.error('请求出错:', error);
  443. }
  444. };
  445. const formPrize = reactive({
  446. // ...其他表单字段
  447. rewards: [
  448. {
  449. execDates: null
  450. }
  451. ]
  452. });
  453. const addReward = () => {
  454. formPrize.rewards.push({
  455. execDates: null
  456. });
  457. };
  458. const removeReward = (index: number) => {
  459. formPrize.rewards.splice(index, 1);
  460. };
  461. // 下拉选项数据
  462. const itemOptionsTimeList = ref<{ label: string; value: string }[]>([]);
  463. const handleDisabledMinutes = () => {
  464. const minutes = [];
  465. for (let i = 0; i < 60; i++) {
  466. if (i % 30 !== 0) {
  467. minutes.push(i);
  468. }
  469. }
  470. return minutes;
  471. };
  472. // 用于保存原始的默认奖励结构
  473. const defaultTimes = {
  474. execDates: null
  475. };
  476. // 重置函数
  477. const resetFormPrize = () => {
  478. // 清空 rewards 并添加默认项
  479. formPrize.rewards.splice(0); // 清空数组
  480. formPrize.rewards.push({ ...defaultTimes }); // 添加初始项
  481. };
  482. // 格式化频率逻辑
  483. const formatFrequency = (row) => {
  484. const { weekDayList = [], weekDaySize = 0 } = row;
  485. // 1. 截取指定数量的周几
  486. const selectedWeekDays = weekDayList;
  487. // 2. 拼接最终文本
  488. if (selectedWeekDays.length === 0) {
  489. return '无'; // 如果没有周几信息
  490. } else {
  491. return `每日${weekDaySize}场<br>${selectedWeekDays.join('、')}每天${weekDaySize}场`;
  492. }
  493. };
  494. </script>