调查投票提交接口多选修正

This commit is contained in:
liweiyi 2025-06-21 17:03:49 +08:00
parent 3b838ec2b5
commit eeb7d951ab
19 changed files with 113 additions and 66 deletions

View File

@ -77,6 +77,6 @@ public class CmsArticleDetailDAO extends BackupServiceImpl<CmsArticleDetailMappe
if (StringUtils.isEmpty(backupIds)) { if (StringUtils.isEmpty(backupIds)) {
return; return;
} }
this.getBackupMapper().deleteBatchIds(backupIds); this.getBackupMapper().deleteByIds(backupIds);
} }
} }

View File

@ -90,6 +90,6 @@ public class CmsContentDAO extends BackupServiceImpl<CmsContentMapper, CmsConten
if (StringUtils.isEmpty(backupIds)) { if (StringUtils.isEmpty(backupIds)) {
return; return;
} }
this.getBackupMapper().deleteBatchIds(backupIds); this.getBackupMapper().deleteByIds(backupIds);
} }
} }

View File

@ -73,7 +73,7 @@ public class ImageListener {
new LambdaQueryWrapper<BCmsImage>().select(List.of(BCmsImage::getImageId)) new LambdaQueryWrapper<BCmsImage>().select(List.of(BCmsImage::getImageId))
.eq(BCmsImage::getSiteId, site.getSiteId()) .eq(BCmsImage::getSiteId, site.getSiteId())
).getRecords().stream().map(BCmsImage::getBackupId).toList(); ).getRecords().stream().map(BCmsImage::getBackupId).toList();
backupMapper.deleteBatchIds(backupIds); backupMapper.deleteByIds(backupIds);
} }
} catch (Exception e) { } catch (Exception e) {
AsyncTaskManager.addErrMessage("删除图片内容备份错误:" + e.getMessage()); AsyncTaskManager.addErrMessage("删除图片内容备份错误:" + e.getMessage());

View File

@ -79,7 +79,7 @@ public class MediaListener {
.eq(BCmsAudio::getSiteId, site.getSiteId()) .eq(BCmsAudio::getSiteId, site.getSiteId())
).getRecords().stream().map(BCmsAudio::getBackupId).toList(); ).getRecords().stream().map(BCmsAudio::getBackupId).toList();
backupMapper.deleteBatchIds(backupIds); backupMapper.deleteByIds(backupIds);
} }
} catch (Exception e) { } catch (Exception e) {
AsyncTaskManager.addErrMessage("删除音频内容备份错误:" + e.getMessage()); AsyncTaskManager.addErrMessage("删除音频内容备份错误:" + e.getMessage());
@ -116,7 +116,7 @@ public class MediaListener {
.eq(BCmsVideo::getSiteId, site.getSiteId()) .eq(BCmsVideo::getSiteId, site.getSiteId())
).getRecords().stream().map(BCmsVideo::getBackupId).toList(); ).getRecords().stream().map(BCmsVideo::getBackupId).toList();
backupMapper.deleteBatchIds(backupIds); backupMapper.deleteByIds(backupIds);
} }
} catch (Exception e) { } catch (Exception e) {
AsyncTaskManager.addErrMessage("删除视频内容备份错误:" + e.getMessage()); AsyncTaskManager.addErrMessage("删除视频内容备份错误:" + e.getMessage());

View File

@ -124,7 +124,7 @@ public class BackupServiceImpl<M extends BaseMapper<T>, T extends IBackupable<B>
@Override @Override
public void deleteBackupByIds(Collection<Serializable> backupIds) { public void deleteBackupByIds(Collection<Serializable> backupIds) {
this.backupMapper.deleteBatchIds(backupIds); this.backupMapper.deleteByIds(backupIds);
} }
@Override @Override

View File

@ -155,7 +155,7 @@ public class SysScheduledTaskController extends BaseRestController {
@DeleteMapping("/logs") @DeleteMapping("/logs")
public R<?> removeLogs(@RequestBody @NotEmpty List<Long> logIds) { public R<?> removeLogs(@RequestBody @NotEmpty List<Long> logIds) {
Assert.isTrue(IdUtils.validate(logIds), () -> CommonErrorCode.INVALID_REQUEST_ARG.exception()); Assert.isTrue(IdUtils.validate(logIds), () -> CommonErrorCode.INVALID_REQUEST_ARG.exception());
this.logMapper.deleteBatchIds(logIds); this.logMapper.deleteByIds(logIds);
return R.ok(); return R.ok();
} }
} }

View File

@ -94,7 +94,7 @@ public class GroovyController extends BaseRestController {
@Log(title = "刪除Groovy脚本", businessType = BusinessType.DELETE) @Log(title = "刪除Groovy脚本", businessType = BusinessType.DELETE)
@DeleteMapping("/delete") @DeleteMapping("/delete")
public R<?> deleteGroovyScript(@RequestBody @NotEmpty List<Long> scriptIds) { public R<?> deleteGroovyScript(@RequestBody @NotEmpty List<Long> scriptIds) {
this.groovyScriptMapper.deleteBatchIds(scriptIds); this.groovyScriptMapper.deleteByIds(scriptIds);
return R.ok(); return R.ok();
} }

View File

@ -18,7 +18,6 @@ package com.chestnut.vote.controller;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.chestnut.common.domain.R; import com.chestnut.common.domain.R;
import com.chestnut.common.exception.CommonErrorCode; import com.chestnut.common.exception.CommonErrorCode;
import com.chestnut.common.i18n.I18nUtils;
import com.chestnut.common.log.annotation.Log; import com.chestnut.common.log.annotation.Log;
import com.chestnut.common.log.enums.BusinessType; import com.chestnut.common.log.enums.BusinessType;
import com.chestnut.common.security.anno.Priv; import com.chestnut.common.security.anno.Priv;
@ -40,7 +39,6 @@ import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import java.util.List; import java.util.List;
import java.util.Map;
@RequiredArgsConstructor @RequiredArgsConstructor
@RestController @RestController
@ -74,17 +72,13 @@ public class VoteController extends BaseRestController {
@Priv(type = AdminUserType.TYPE, value = VotePriv.View) @Priv(type = AdminUserType.TYPE, value = VotePriv.View)
@GetMapping("/userTypes") @GetMapping("/userTypes")
public R<?> getVoteUserTypes() { public R<?> getVoteUserTypes() {
List<Map<String, String>> list = this.userTypes.stream() return bindSelectOptions(this.userTypes, IVoteUserType::getId, IVoteUserType::getName);
.map(vut -> Map.of("id", vut.getId(), "name", I18nUtils.get(vut.getName()))).toList();
return R.ok(list);
} }
@Priv(type = AdminUserType.TYPE) @Priv(type = AdminUserType.TYPE)
@GetMapping("/item/types") @GetMapping("/item/types")
public R<?> getVoteItemTypes() { public R<?> getVoteItemTypes() {
List<Map<String, String>> list = this.itemTypes.stream() return bindSelectOptions(this.itemTypes, IVoteItemType::getId, IVoteItemType::getName);
.map(vut -> Map.of("id", vut.getId(), "name", I18nUtils.get(vut.getName()))).toList();
return R.ok(list);
} }
@Log(title = "新增问卷调查", businessType = BusinessType.INSERT) @Log(title = "新增问卷调查", businessType = BusinessType.INSERT)

View File

@ -23,15 +23,15 @@ package com.chestnut.vote.core;
*/ */
public interface IVoteItemType { public interface IVoteItemType {
public String BEAN_PREFIX = "VoteItemType_"; String BEAN_PREFIX = "VoteItemType_";
/** /**
* 问卷调查选项类型ID唯一标识 * 问卷调查选项类型ID唯一标识
*/ */
public String getId(); String getId();
/** /**
* 问卷调查选项类型名称 * 问卷调查选项类型名称
*/ */
public String getName(); String getName();
} }

View File

@ -23,7 +23,7 @@ package com.chestnut.vote.core;
*/ */
public interface IVoteUserType { public interface IVoteUserType {
public String BEAN_PREFIX = "VoteUserType_"; String BEAN_PREFIX = "VoteUserType_";
/** /**
* 问卷调查用户类型ID唯一标识 * 问卷调查用户类型ID唯一标识

View File

@ -15,19 +15,19 @@
*/ */
package com.chestnut.vote.domain; package com.chestnut.vote.domain;
import java.io.Serializable;
import java.time.LocalDateTime;
import java.util.Map;
import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName; import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler; import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
import com.chestnut.vote.domain.dto.VoteSubmitDTO;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
import java.io.Serializable;
import java.time.LocalDateTime;
import java.util.List;
/** /**
* 问卷调查日志表 * 问卷调查日志表
* *
@ -63,11 +63,9 @@ public class VoteLog implements Serializable {
/** /**
* 投票结果 * 投票结果
*
* 格式<subjectId, itemId|inputText>
*/ */
@TableField(typeHandler = JacksonTypeHandler.class) @TableField(typeHandler = JacksonTypeHandler.class)
private Map<Long, String> result; private List<VoteSubmitDTO.SubjectResult> result;
/** /**
* 日志记录时间 * 日志记录时间

View File

@ -15,11 +15,12 @@
*/ */
package com.chestnut.vote.domain.dto; package com.chestnut.vote.domain.dto;
import java.util.List;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
import java.util.ArrayList;
import java.util.List;
@Getter @Getter
@Setter @Setter
public class VoteSubmitDTO { public class VoteSubmitDTO {
@ -52,10 +53,15 @@ public class VoteSubmitDTO {
* 主题ID * 主题ID
*/ */
private Long subjectId; private Long subjectId;
/**
* 主题类型
*/
private String type;
/** /**
* 结果itemId || inputText * 结果itemId || inputText
*/ */
private String result; private ArrayList<String> result;
} }
} }

View File

@ -17,6 +17,8 @@ package com.chestnut.vote.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.chestnut.vote.domain.Vote; import com.chestnut.vote.domain.Vote;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Update;
/** /**
* <p> * <p>
@ -28,4 +30,7 @@ import com.chestnut.vote.domain.Vote;
*/ */
public interface VoteMapper extends BaseMapper<Vote> { public interface VoteMapper extends BaseMapper<Vote> {
@Update("UPDATE " + Vote.TABLE_NAME + " SET total = total + ${delta} WHERE vote_id = #{voteId}")
void increaseVoteTotal(@Param("voteId") Long voteId, @Param("delta") Integer delta);
} }

View File

@ -17,6 +17,8 @@ package com.chestnut.vote.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.chestnut.vote.domain.VoteSubjectItem; import com.chestnut.vote.domain.VoteSubjectItem;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Update;
/** /**
* <p> * <p>
@ -28,4 +30,6 @@ import com.chestnut.vote.domain.VoteSubjectItem;
*/ */
public interface VoteSubjectItemMapper extends BaseMapper<VoteSubjectItem> { public interface VoteSubjectItemMapper extends BaseMapper<VoteSubjectItem> {
@Update("UPDATE " + VoteSubjectItem.TABLE_NAME + " SET total = total + ${delta} WHERE item_id = #{itemId}")
void increaseVoteSubjectItemTotal(@Param("itemId") Long itemId, @Param("delta") Integer delta);
} }

View File

@ -22,7 +22,6 @@ import com.chestnut.common.utils.DateUtils;
import com.chestnut.vote.core.IVoteUserType; import com.chestnut.vote.core.IVoteUserType;
import com.chestnut.vote.domain.VoteLog; import com.chestnut.vote.domain.VoteLog;
import com.chestnut.vote.domain.dto.VoteSubmitDTO; import com.chestnut.vote.domain.dto.VoteSubmitDTO;
import com.chestnut.vote.domain.dto.VoteSubmitDTO.SubjectResult;
import com.chestnut.vote.domain.vo.VoteVO; import com.chestnut.vote.domain.vo.VoteVO;
import com.chestnut.vote.exception.VoteErrorCode; import com.chestnut.vote.exception.VoteErrorCode;
import com.chestnut.vote.service.IVoteApiService; import com.chestnut.vote.service.IVoteApiService;
@ -35,8 +34,6 @@ import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.Map;
import java.util.stream.Collectors;
@Service @Service
@RequiredArgsConstructor @RequiredArgsConstructor
@ -82,13 +79,11 @@ public class VoteApiServiceImpl implements IVoteApiService {
Assert.isTrue(dayCount < vote.getDayLimit(), VoteErrorCode.VOTE_DAY_LIMIT::exception); Assert.isTrue(dayCount < vote.getDayLimit(), VoteErrorCode.VOTE_DAY_LIMIT::exception);
// 记录日志 // 记录日志
Map<Long, String> result = dto.getSubjects().stream()
.collect(Collectors.toMap(SubjectResult::getSubjectId, SubjectResult::getResult));
VoteLog voteLog = new VoteLog(); VoteLog voteLog = new VoteLog();
voteLog.setVoteId(dto.getVoteId()); voteLog.setVoteId(dto.getVoteId());
voteLog.setUserType(vote.getUserType()); voteLog.setUserType(vote.getUserType());
voteLog.setUserId(userId); voteLog.setUserId(userId);
voteLog.setResult(result); voteLog.setResult(dto.getSubjects());
voteLog.setLogTime(LocalDateTime.now()); voteLog.setLogTime(LocalDateTime.now());
voteLog.setIp(dto.getIp()); voteLog.setIp(dto.getIp());
voteLog.setUserAgent(dto.getUserAgent()); voteLog.setUserAgent(dto.getUserAgent());

View File

@ -16,7 +16,6 @@
package com.chestnut.vote.service.impl; package com.chestnut.vote.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.conditions.query.LambdaQueryChainWrapper;
import com.baomidou.mybatisplus.extension.conditions.update.LambdaUpdateChainWrapper; import com.baomidou.mybatisplus.extension.conditions.update.LambdaUpdateChainWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.chestnut.common.exception.CommonErrorCode; import com.chestnut.common.exception.CommonErrorCode;
@ -30,6 +29,7 @@ import com.chestnut.vote.domain.Vote;
import com.chestnut.vote.domain.VoteLog; import com.chestnut.vote.domain.VoteLog;
import com.chestnut.vote.domain.VoteSubject; import com.chestnut.vote.domain.VoteSubject;
import com.chestnut.vote.domain.VoteSubjectItem; import com.chestnut.vote.domain.VoteSubjectItem;
import com.chestnut.vote.domain.dto.VoteSubmitDTO;
import com.chestnut.vote.domain.vo.VoteSubjectItemVO; import com.chestnut.vote.domain.vo.VoteSubjectItemVO;
import com.chestnut.vote.domain.vo.VoteSubjectVO; import com.chestnut.vote.domain.vo.VoteSubjectVO;
import com.chestnut.vote.domain.vo.VoteVO; import com.chestnut.vote.domain.vo.VoteVO;
@ -51,8 +51,7 @@ import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import java.util.List; import java.util.*;
import java.util.Map;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@RequiredArgsConstructor @RequiredArgsConstructor
@ -67,12 +66,14 @@ public class VoteServiceImpl extends ServiceImpl<VoteMapper, Vote> implements IV
private final VoteSubjectItemMapper itemMapper; private final VoteSubjectItemMapper itemMapper;
private final VoteMapper voteMapper;
private final VoteLogMapper voteLogMapper; private final VoteLogMapper voteLogMapper;
private final RedisCache redisCache; private final RedisCache redisCache;
private final Map<String, IVoteUserType> voteUserTypes; private final Map<String, IVoteUserType> voteUserTypes;
private final Map<String, IVoteItemType> voteItemTypes; private final Map<String, IVoteItemType> voteItemTypes;
private final RedissonClient redissonClient; private final RedissonClient redissonClient;
@ -235,22 +236,66 @@ public class VoteServiceImpl extends ServiceImpl<VoteMapper, Vote> implements IV
try { try {
VoteVO vote = this.getVote(voteLog.getVoteId()); VoteVO vote = this.getVote(voteLog.getVoteId());
// 问卷调查参与数+1 // 问卷调查参与数+1
Vote voteEntity = this.lambdaQuery().select(Vote::getVoteId, Vote::getTotal) vote.setTotal(Objects.requireNonNullElse(vote.getTotal(), 0) + 1);
.eq(Vote::getVoteId, voteLog.getVoteId()).one(); voteMapper.increaseVoteTotal(vote.getVoteId(), 1);
this.lambdaUpdate().set(Vote::getTotal, voteEntity.getTotal() + 1)
.eq(Vote::getVoteId, voteLog.getVoteId()).update();
// 单选/多选主题选项票数+1 // 单选/多选主题选项票数+1
vote.getSubjects().forEach(subject -> { vote.getSubjects().forEach(subject -> {
if (!VoteSubjectType.isInput(subject.getType())) { if (!VoteSubjectType.isInput(subject.getType())) {
String result = voteLog.getResult().get(subject.getSubjectId()); List<VoteSubmitDTO.SubjectResult> subjectResults = voteLog.getResult();
VoteSubjectItem item = new LambdaQueryChainWrapper<>(this.itemMapper) Optional<VoteSubmitDTO.SubjectResult> opt = subjectResults.stream()
.select(VoteSubjectItem::getItemId, VoteSubjectItem::getTotal) .filter(r -> subject.getSubjectId().equals(r.getSubjectId()) && subject.getType().equals(r.getType()))
.eq(VoteSubjectItem::getItemId, result) .findFirst();
.one(); if (opt.isPresent()) {
new LambdaUpdateChainWrapper<>(this.itemMapper) VoteSubmitDTO.SubjectResult result = opt.get();
.set(VoteSubjectItem::getTotal, item.getTotal() + 1) for (String itemIdStr : result.getResult()) {
.eq(VoteSubjectItem::getItemId, item.getItemId()) Long itemId = Long.parseLong(itemIdStr);
.update(); this.itemMapper.increaseVoteSubjectItemTotal(itemId, 1);
}
}
}
});
// 更新缓存
this.redisCache.setCacheObject(CACHE_PREFIX + vote.getVoteId(), vote);
} finally {
lock.unlock();
}
}
public void resetVoteStat(Long voteId) {
RLock lock = redissonClient.getLock("VoteTotalUpdate-" + voteId);
lock.lock();
try {
VoteVO vote = this.getVote(voteId);
List<VoteLog> voteLogs = voteLogMapper.selectList(new LambdaQueryWrapper<VoteLog>()
.eq(VoteLog::getVoteId, vote.getVoteId()));
// 问卷调查参与数
vote.setTotal(voteLogs.size());
this.lambdaUpdate().set(Vote::getTotal, voteLogs.size()).eq(Vote::getVoteId, vote.getVoteId()).update();
// 单选/多选主题选项票数
vote.getSubjects().forEach(subject -> {
if (!VoteSubjectType.isInput(subject.getType())) {
Map<Long, Integer> itemTotalMap = new HashMap<>();
List<Long> itemIds = subject.getItems().stream().map(VoteSubjectItemVO::getItemId).toList();
for (VoteLog voteLog : voteLogs) {
Optional<VoteSubmitDTO.SubjectResult> opt = voteLog.getResult().stream().filter(r ->
r.getSubjectId().equals(subject.getSubjectId()) && r.getType().equals(subject.getTitle())
).findFirst();
if (opt.isPresent()) {
VoteSubmitDTO.SubjectResult result = opt.get();
for (String itemIdStr : result.getResult()) {
long itemId = Long.parseLong(itemIdStr);
if (itemIds.contains(itemId)) {
itemTotalMap.put(itemId, itemTotalMap.getOrDefault(itemId, 0) + 1);
}
}
}
}
for (Map.Entry<Long, Integer> entry : itemTotalMap.entrySet()) {
new LambdaUpdateChainWrapper<>(itemMapper).set(VoteSubjectItem::getTotal, entry.getValue())
.eq(VoteSubjectItem::getItemId, entry.getKey())
.update();
}
} }
}); });
// 更新缓存 // 更新缓存

View File

@ -143,7 +143,7 @@ public class VoteSubjectServiceImpl extends ServiceImpl<VoteSubjectMapper, VoteS
List<Long> removeItemIds = dbItems.stream().map(VoteSubjectItem::getItemId) List<Long> removeItemIds = dbItems.stream().map(VoteSubjectItem::getItemId)
.filter(itemId -> !updateItemIds.contains(itemId)).toList(); .filter(itemId -> !updateItemIds.contains(itemId)).toList();
if (!removeItemIds.isEmpty()) { if (!removeItemIds.isEmpty()) {
this.voteSubjectItemMapper.deleteBatchIds(removeItemIds); this.voteSubjectItemMapper.deleteByIds(removeItemIds);
} }
Map<Long, VoteSubjectItem> updateMap = dbItems.stream().filter(item -> updateItemIds.contains(item.getItemId())) Map<Long, VoteSubjectItem> updateMap = dbItems.stream().filter(item -> updateItemIds.contains(item.getItemId()))

View File

@ -172,9 +172,9 @@
<el-radio-group v-model="form.userType"> <el-radio-group v-model="form.userType">
<el-radio <el-radio
v-for="ut in userTypeOptions" v-for="ut in userTypeOptions"
:key="ut.id" :key="ut.value"
:label="ut.id" :label="ut.value"
>{{ ut.name }}</el-radio> >{{ ut.label }}</el-radio>
</el-radio-group> </el-radio-group>
</el-form-item> </el-form-item>
<el-form-item :label="$t('Vote.DayLimit')" prop="totalLimit"> <el-form-item :label="$t('Vote.DayLimit')" prop="totalLimit">

View File

@ -76,9 +76,9 @@
<el-select v-model="scope2.row.type" size="mini" disabled> <el-select v-model="scope2.row.type" size="mini" disabled>
<el-option <el-option
v-for="type in subjectItemTypes" v-for="type in subjectItemTypes"
:key="type.id" :key="type.value"
:label="type.name" :label="type.label"
:value="type.id" :value="type.value"
/> />
</el-select> </el-select>
</template> </template>
@ -221,14 +221,14 @@
<el-select v-model="scope.row.type"> <el-select v-model="scope.row.type">
<el-option <el-option
v-for="type in subjectItemTypes" v-for="type in subjectItemTypes"
:key="type.id" :key="type.value"
:label="type.name" :label="type.label"
:value="type.id" :value="type.value"
/> />
</el-select> </el-select>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column :label="$t('Comment.Details')"> <el-table-column :label="$t('Common.Details')">
<template slot-scope="scope"> <template slot-scope="scope">
<el-input v-if="scope.row.type==='Text'" type="text" v-model="scope.row.content"></el-input> <el-input v-if="scope.row.type==='Text'" type="text" v-model="scope.row.content"></el-input>
</template> </template>