mirror of
https://github.com/nagisa77/OpenIsle.git
synced 2026-02-21 14:30:59 +08:00
fix: 分类提案投票UI
This commit is contained in:
20
backend/src/main/java/com/openisle/dto/ProposalDto.java
Normal file
20
backend/src/main/java/com/openisle/dto/ProposalDto.java
Normal file
@@ -0,0 +1,20 @@
|
||||
package com.openisle.dto;
|
||||
|
||||
import com.openisle.model.CategoryProposalStatus;
|
||||
import java.time.LocalDateTime;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class ProposalDto extends PollDto {
|
||||
|
||||
private CategoryProposalStatus proposalStatus;
|
||||
private String proposedName;
|
||||
private String description;
|
||||
private int approveThreshold;
|
||||
private int quorum;
|
||||
private LocalDateTime startAt;
|
||||
private String resultSnapshot;
|
||||
private String rejectReason;
|
||||
}
|
||||
@@ -6,7 +6,9 @@ import com.openisle.dto.LotteryDto;
|
||||
import com.openisle.dto.PollDto;
|
||||
import com.openisle.dto.PostDetailDto;
|
||||
import com.openisle.dto.PostSummaryDto;
|
||||
import com.openisle.dto.ProposalDto;
|
||||
import com.openisle.dto.ReactionDto;
|
||||
import com.openisle.model.CategoryProposalPost;
|
||||
import com.openisle.model.CommentSort;
|
||||
import com.openisle.model.LotteryPost;
|
||||
import com.openisle.model.PollPost;
|
||||
@@ -113,26 +115,40 @@ public class PostMapper {
|
||||
dto.setLottery(l);
|
||||
}
|
||||
|
||||
if (post instanceof PollPost pp) {
|
||||
PollDto p = new PollDto();
|
||||
p.setOptions(pp.getOptions());
|
||||
p.setVotes(pp.getVotes());
|
||||
p.setEndTime(pp.getEndTime());
|
||||
p.setParticipants(
|
||||
pp.getParticipants().stream().map(userMapper::toAuthorDto).collect(Collectors.toList())
|
||||
);
|
||||
Map<Integer, List<AuthorDto>> optionParticipants = pollVoteRepository
|
||||
.findByPostId(pp.getId())
|
||||
.stream()
|
||||
.collect(
|
||||
Collectors.groupingBy(
|
||||
PollVote::getOptionIndex,
|
||||
Collectors.mapping(v -> userMapper.toAuthorDto(v.getUser()), Collectors.toList())
|
||||
)
|
||||
);
|
||||
p.setOptionParticipants(optionParticipants);
|
||||
p.setMultiple(Boolean.TRUE.equals(pp.getMultiple()));
|
||||
dto.setPoll(p);
|
||||
if (post instanceof CategoryProposalPost cp) {
|
||||
ProposalDto proposalDto = (ProposalDto) buildPollDto(cp, new ProposalDto());
|
||||
proposalDto.setProposalStatus(cp.getProposalStatus());
|
||||
proposalDto.setProposedName(cp.getProposedName());
|
||||
proposalDto.setDescription(cp.getDescription());
|
||||
proposalDto.setApproveThreshold(cp.getApproveThreshold());
|
||||
proposalDto.setQuorum(cp.getQuorum());
|
||||
proposalDto.setStartAt(cp.getStartAt());
|
||||
proposalDto.setResultSnapshot(cp.getResultSnapshot());
|
||||
proposalDto.setRejectReason(cp.getRejectReason());
|
||||
dto.setPoll(proposalDto);
|
||||
} else if (post instanceof PollPost pp) {
|
||||
dto.setPoll(buildPollDto(pp, new PollDto()));
|
||||
}
|
||||
}
|
||||
|
||||
private PollDto buildPollDto(PollPost pollPost, PollDto target) {
|
||||
target.setOptions(pollPost.getOptions());
|
||||
target.setVotes(pollPost.getVotes());
|
||||
target.setEndTime(pollPost.getEndTime());
|
||||
target.setParticipants(
|
||||
pollPost.getParticipants().stream().map(userMapper::toAuthorDto).collect(Collectors.toList())
|
||||
);
|
||||
Map<Integer, List<AuthorDto>> optionParticipants = pollVoteRepository
|
||||
.findByPostId(pollPost.getId())
|
||||
.stream()
|
||||
.collect(
|
||||
Collectors.groupingBy(
|
||||
PollVote::getOptionIndex,
|
||||
Collectors.mapping(v -> userMapper.toAuthorDto(v.getUser()), Collectors.toList())
|
||||
)
|
||||
);
|
||||
target.setOptionParticipants(optionParticipants);
|
||||
target.setMultiple(Boolean.TRUE.equals(pollPost.getMultiple()));
|
||||
return target;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,30 @@
|
||||
<div class="post-poll-container" v-if="poll">
|
||||
<div class="poll-top-container">
|
||||
<div class="poll-options-container">
|
||||
<div class="poll-title-section">
|
||||
<div class="poll-title-section-row">
|
||||
<div class="poll-option-title" v-if="poll.multiple">多选</div>
|
||||
<div class="poll-option-title" v-else-if="isProposal">
|
||||
拟议分类:{{ poll.proposedName }}
|
||||
<ToolTip
|
||||
content="🗳️ 提案提交后将开放3天投票,需达到至少60%的赞成率并满10人参与方可通过。"
|
||||
placement="bottom"
|
||||
v-if="isProposal"
|
||||
>
|
||||
<info-icon class="info-icon" />
|
||||
</ToolTip>
|
||||
</div>
|
||||
<div class="poll-option-title" v-else>单选</div>
|
||||
<div class="poll-left-time">
|
||||
<stopwatch class="poll-left-time-icon" />
|
||||
<div class="poll-left-time-title">离结束</div>
|
||||
<div class="poll-left-time-value">{{ countdown }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="poll-title-section-row">
|
||||
<div v-if="poll.description" class="proposal-description">{{ poll.description }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="showPollResult || pollEnded || hasVoted">
|
||||
<div v-for="(opt, idx) in poll.options" :key="idx" class="poll-option-result">
|
||||
<div class="poll-option-info-container">
|
||||
@@ -29,16 +53,6 @@
|
||||
</div>
|
||||
</div>
|
||||
<div v-else>
|
||||
<div class="poll-title-section">
|
||||
<div class="poll-option-title" v-if="poll.multiple">多选</div>
|
||||
<div class="poll-option-title" v-else>单选</div>
|
||||
|
||||
<div class="poll-left-time">
|
||||
<stopwatch class="poll-left-time-icon" />
|
||||
<div class="poll-left-time-title">离结束</div>
|
||||
<div class="poll-left-time-value">{{ countdown }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<template v-if="poll.multiple">
|
||||
<div
|
||||
v-for="(opt, idx) in poll.options"
|
||||
@@ -103,11 +117,6 @@
|
||||
<div v-else-if="pollEnded" class="poll-option-hint"><stopwatch /> 投票已结束</div>
|
||||
<div v-else class="poll-option-hint">
|
||||
<div>您已投票,等待结束查看结果</div>
|
||||
<div class="poll-left-time">
|
||||
<stopwatch class="poll-left-time-icon" />
|
||||
<div class="poll-left-time-title">离结束</div>
|
||||
<div class="poll-left-time-value">{{ countdown }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -130,6 +139,9 @@ const emit = defineEmits(['refresh'])
|
||||
const loggedIn = computed(() => authState.loggedIn)
|
||||
const showPollResult = ref(false)
|
||||
|
||||
const isProposal = computed(() =>
|
||||
Object.prototype.hasOwnProperty.call(props.poll || {}, 'proposedName'),
|
||||
)
|
||||
const pollParticipants = computed(() => props.poll?.participants || [])
|
||||
const pollOptionParticipants = computed(() => props.poll?.optionParticipants || {})
|
||||
const pollVotes = computed(() => props.poll?.votes || {})
|
||||
@@ -233,6 +245,34 @@ const submitMultiPoll = async () => {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.proposal-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
padding: 8px 10px;
|
||||
border-radius: 8px;
|
||||
background-color: var(--background-color);
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
||||
.proposal-name {
|
||||
font-weight: 600;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.proposal-status {
|
||||
font-size: 14px;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.proposal-description {
|
||||
font-size: 16px;
|
||||
margin-top: 10px;
|
||||
line-height: 1.5;
|
||||
white-space: pre-wrap;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.poll-option-button {
|
||||
color: var(--text-color);
|
||||
padding: 5px 10px;
|
||||
@@ -385,12 +425,20 @@ const submitMultiPoll = async () => {
|
||||
}
|
||||
|
||||
.poll-title-section {
|
||||
display: flex;
|
||||
gap: 30px;
|
||||
flex-direction: row;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.poll-title-section-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-direction: row;
|
||||
gap: 30px;
|
||||
}
|
||||
|
||||
.info-icon {
|
||||
margin-right: 20px;
|
||||
}
|
||||
|
||||
.poll-option-title {
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
|
||||
Reference in New Issue
Block a user