Compare commits

..

8 Commits

Author SHA1 Message Date
Tim
6f80d139ba fix: 投票UI优化 2025-09-01 10:27:02 +08:00
Tim
7454931fa5 Merge pull request #806 from nagisa77/codex/modify-postpoll.vue-for-single-choice-voting
feat: add join button for single polls
2025-09-01 09:54:37 +08:00
Tim
5814fb673a feat: add join button for single polls 2025-09-01 01:06:51 +08:00
Tim
4ee4266e3d Merge pull request #804 from nagisa77/codex/fix-jpasystemexception-for-pollpost
Fix poll multiple property null handling
2025-08-31 14:22:59 +08:00
Tim
6a27fbe1d7 Fix null multiple field for poll posts 2025-08-31 14:22:44 +08:00
Tim
38ff04c358 Merge pull request #803 from nagisa77/codex/add-baseswitch-component-to-voting-post
feat(poll): use BaseSwitch for multiple selection
2025-08-31 14:13:32 +08:00
Tim
fc27200ac1 feat(poll): use BaseSwitch for multiple selection 2025-08-31 14:13:18 +08:00
Tim
eefefac236 Merge pull request #801 from nagisa77/codex/add-multi-select-support-for-voting
feat: support multi-option polls
2025-08-31 12:13:54 +08:00
4 changed files with 68 additions and 23 deletions

View File

@@ -111,7 +111,7 @@ public class PostMapper {
.collect(Collectors.groupingBy(PollVote::getOptionIndex, .collect(Collectors.groupingBy(PollVote::getOptionIndex,
Collectors.mapping(v -> userMapper.toAuthorDto(v.getUser()), Collectors.toList()))); Collectors.mapping(v -> userMapper.toAuthorDto(v.getUser()), Collectors.toList())));
p.setOptionParticipants(optionParticipants); p.setOptionParticipants(optionParticipants);
p.setMultiple(pp.isMultiple()); p.setMultiple(Boolean.TRUE.equals(pp.getMultiple()));
dto.setPoll(p); dto.setPoll(p);
} }
} }

View File

@@ -33,7 +33,7 @@ public class PollPost extends Post {
private Set<User> participants = new HashSet<>(); private Set<User> participants = new HashSet<>();
@Column @Column
private boolean multiple = false; private Boolean multiple = false;
@Column @Column
private LocalDateTime endTime; private LocalDateTime endTime;

View File

@@ -19,10 +19,8 @@
</client-only> </client-only>
</div> </div>
<div class="poll-multiple-row"> <div class="poll-multiple-row">
<label class="poll-row-title"> <span class="poll-row-title">多选</span>
<input type="checkbox" v-model="data.multiple" class="multiple-checkbox" /> <BaseSwitch v-model="data.multiple" />
多选
</label>
</div> </div>
</div> </div>
</template> </template>
@@ -31,6 +29,7 @@
import 'flatpickr/dist/flatpickr.css' import 'flatpickr/dist/flatpickr.css'
import FlatPickr from 'vue-flatpickr-component' import FlatPickr from 'vue-flatpickr-component'
import BaseInput from '~/components/BaseInput.vue' import BaseInput from '~/components/BaseInput.vue'
import BaseSwitch from '~/components/BaseSwitch.vue'
const props = defineProps({ const props = defineProps({
data: { data: {
@@ -89,9 +88,7 @@ const removeOption = (idx) => {
.poll-multiple-row { .poll-multiple-row {
display: flex; display: flex;
align-items: center; align-items: center;
} gap: 10px;
.multiple-checkbox {
margin-right: 5px;
} }
.time-picker { .time-picker {
max-width: 200px; max-width: 200px;

View File

@@ -29,6 +29,15 @@
</div> </div>
</div> </div>
<div v-else> <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">
<div class="poll-left-time-title">离结束还有</div>
<div class="poll-left-time-value">{{ countdown }}</div>
</div>
</div>
<template v-if="poll.multiple"> <template v-if="poll.multiple">
<div <div
v-for="(opt, idx) in poll.options" v-for="(opt, idx) in poll.options"
@@ -45,12 +54,8 @@
</div> </div>
<div class="multi-selection-container"> <div class="multi-selection-container">
<div class="multi-selection-title">
<i class="fas fa-info-circle info-icon"></i>
该投票为多选
</div>
<div class="join-poll-button" @click="submitMultiPoll"> <div class="join-poll-button" @click="submitMultiPoll">
<i class="fas fa-plus"></i> 加入投票 <i class="fas fa-check"></i> 确认投票
</div> </div>
</div> </div>
</template> </template>
@@ -59,11 +64,22 @@
v-for="(opt, idx) in poll.options" v-for="(opt, idx) in poll.options"
:key="idx" :key="idx"
class="poll-option" class="poll-option"
@click="voteOption(idx)" @click="selectOption(idx)"
> >
<input type="radio" :checked="false" name="poll-option" class="poll-option-input" /> <input
type="radio"
:checked="selectedOption === idx"
name="poll-option"
class="poll-option-input"
/>
<span class="poll-option-text">{{ opt }}</span> <span class="poll-option-text">{{ opt }}</span>
</div> </div>
<div class="single-selection-container">
<div class="join-poll-button" @click="submitSinglePoll">
<i class="fas fa-check"></i> 确认投票
</div>
</div>
</template> </template>
</div> </div>
</div> </div>
@@ -87,10 +103,11 @@
> >
<i class="fas fa-chart-bar"></i> 结果 <i class="fas fa-chart-bar"></i> 结果
</div> </div>
<div v-else-if="pollEnded" class="poll-option-hint">
<div class="poll-left-time"> <i class="fas fa-stopwatch"></i> 投票已结束
<div class="poll-left-time-title">离结束还有</div> </div>
<div class="poll-left-time-value">{{ countdown }}</div> <div v-else class="poll-option-hint">
<i class="fas fa-stopwatch"></i> 您已投票等待结束查看结果
</div> </div>
</div> </div>
</div> </div>
@@ -198,6 +215,18 @@ const voteOption = async (idx) => {
} }
} }
const selectedOption = ref(null)
const selectOption = (idx) => {
selectedOption.value = idx
}
const submitSinglePoll = async () => {
if (selectedOption.value === null) {
toast.error('请选择一个选项')
return
}
await voteOption(selectedOption.value)
}
const selectedOptions = ref([]) const selectedOptions = ref([])
const toggleOption = (idx) => { const toggleOption = (idx) => {
const i = selectedOptions.value.indexOf(idx) const i = selectedOptions.value.indexOf(idx)
@@ -368,18 +397,37 @@ const submitMultiPoll = async () => {
color: var(--text-color); color: var(--text-color);
} }
.multi-selection-container { .multi-selection-container,
padding: 20px 15px 20px 5px; .single-selection-container {
margin-top: 30px;
margin-bottom: 10px;
display: flex; display: flex;
flex-direction: row; flex-direction: row;
justify-content: space-between; justify-content: space-between;
} }
.multi-selection-title { .multi-selection-title,
.single-selection-title {
font-size: 13px; font-size: 13px;
color: var(--text-color); color: var(--text-color);
} }
.poll-title-section {
display: flex;
gap: 30px;
flex-direction: row;
margin-bottom: 20px;
}
.poll-option-title {
font-size: 18px;
font-weight: bold;
}
.poll-left-time {
font-size: 18px;
}
.info-icon { .info-icon {
margin-right: 5px; margin-right: 5px;
} }