forked from Trustie/forgeplus
Merge remote-tracking branch 'origin/dev_osredm_server' into dev_osredm_server
This commit is contained in:
commit
220e65772d
|
|
@ -1,5 +1,5 @@
|
|||
class Admins::PlatformCommunicatesController < Admins::BaseController
|
||||
before_action :get_communicate, only: [:edit, :update, :destroy, :show, :online_switch]
|
||||
before_action :get_communicate, only: [:edit, :update, :destroy, :show, :online_switch, :new_vote, :create_vote, :edit_vote, :update_vote]
|
||||
|
||||
def index
|
||||
params[:location] = "pc"
|
||||
|
|
@ -85,6 +85,42 @@ class Admins::PlatformCommunicatesController < Admins::BaseController
|
|||
# render_ok
|
||||
end
|
||||
|
||||
def new_vote
|
||||
@vote = Vote.new(platform_communicate_id: @communicate.id)
|
||||
2.times { @vote.vote_options.build }
|
||||
end
|
||||
|
||||
def create_vote
|
||||
@vote = Vote.new(platform_communicate_id: @communicate.id)
|
||||
@vote.attributes = vote_params
|
||||
|
||||
if @vote.save
|
||||
redirect_to applet_admins_platform_communicates_path, notice: '投票创建成功'
|
||||
else
|
||||
# 确保至少有2个选项显示在表单中
|
||||
@vote.vote_options.build if @vote.vote_options.empty?
|
||||
@vote.vote_options.build if @vote.vote_options.size == 1
|
||||
|
||||
render :new_vote
|
||||
end
|
||||
end
|
||||
|
||||
def edit_vote
|
||||
@vote = @communicate.vote
|
||||
# 确保至少有2个选项显示在表单中
|
||||
@vote.vote_options.build if @vote.vote_options.empty?
|
||||
@vote.vote_options.build if @vote.vote_options.size == 1
|
||||
end
|
||||
|
||||
def update_vote
|
||||
@vote = @communicate.vote
|
||||
if @vote.update(vote_params)
|
||||
redirect_to applet_admins_platform_communicates_path, notice: '投票更新成功'
|
||||
else
|
||||
render :edit_vote
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def get_communicate
|
||||
|
|
@ -94,4 +130,19 @@ class Admins::PlatformCommunicatesController < Admins::BaseController
|
|||
def communicate_params
|
||||
params.require(:platform_communicate).permit!
|
||||
end
|
||||
|
||||
def vote_params
|
||||
params.require(:vote).permit(
|
||||
:description,
|
||||
:is_multiple_choice,
|
||||
:max_choice_vote_options_count,
|
||||
:due_at,
|
||||
vote_options_attributes: [
|
||||
:id,
|
||||
:title,
|
||||
:order_index,
|
||||
:_destroy
|
||||
]
|
||||
)
|
||||
end
|
||||
end
|
||||
|
|
@ -1,8 +1,110 @@
|
|||
class Home::PlatformCommunicatesController < ApplicationController
|
||||
before_action :require_login, only: [:submit_vote, :vote_results]
|
||||
before_action :load_vote, only: [:submit_vote, :vote_results]
|
||||
|
||||
def index
|
||||
location = params[:location] || "pc"
|
||||
scope = PlatformCommunicate.where(status:true).where(location: location).order(order_index: :desc)
|
||||
@communicates = kaminari_paginate(scope)
|
||||
@communicates = kaminari_paginate(scope.includes(:vote))
|
||||
end
|
||||
|
||||
def submit_vote
|
||||
return render_error("投票已结束!") if @vote.expired?
|
||||
|
||||
# 处理投票选项
|
||||
selected_option_ids = vote_params[:vote_option_ids] || []
|
||||
# 验证选择数量
|
||||
validation_result = validate_vote_selections(selected_option_ids)
|
||||
return render_error(validation_result[:message]) unless validation_result[:valid]
|
||||
# 保存投票
|
||||
result = save_user_votes(selected_option_ids)
|
||||
if result[:success]
|
||||
render_ok
|
||||
else
|
||||
render_error(result[:errors].join(', '))
|
||||
end
|
||||
end
|
||||
|
||||
def vote_results
|
||||
@vote_results = @vote.results_with_cache
|
||||
@selected_option_ids = current_user.user_vote_options.where(vote_option: @vote.vote_options).pluck(:vote_option_id)
|
||||
end
|
||||
|
||||
private
|
||||
def load_vote
|
||||
@communicate = PlatformCommunicate.find_by_id(params[:id])
|
||||
@vote = @communicate.vote
|
||||
return render_error("投票不存在!") unless @vote.present?
|
||||
end
|
||||
|
||||
def vote_params
|
||||
params.require(:vote).permit(vote_option_ids: [])
|
||||
end
|
||||
|
||||
def validate_vote_selections(selected_option_ids)
|
||||
selected_option_ids = Array(selected_option_ids).map(&:to_i).uniq
|
||||
|
||||
# 检查是否选择了选项
|
||||
if selected_option_ids.empty?
|
||||
return { valid: false, message: '请至少选择一个投票选项' }
|
||||
end
|
||||
|
||||
# 检查选项是否属于该投票
|
||||
valid_option_ids = @vote.vote_options.pluck(:id)
|
||||
invalid_options = selected_option_ids - valid_option_ids
|
||||
|
||||
unless invalid_options.empty?
|
||||
return { valid: false, message: '包含无效的投票选项' }
|
||||
end
|
||||
|
||||
# 检查选择数量限制
|
||||
if @vote.is_multiple_choice
|
||||
max_choices = @vote.max_choice_vote_options_count || selected_option_ids.length
|
||||
if selected_option_ids.length > max_choices
|
||||
return {
|
||||
valid: false,
|
||||
message: "最多只能选择 #{max_choices} 个选项"
|
||||
}
|
||||
end
|
||||
else
|
||||
if selected_option_ids.length > 1
|
||||
return { valid: false, message: '只能选择一个选项' }
|
||||
end
|
||||
end
|
||||
|
||||
{ valid: true }
|
||||
end
|
||||
|
||||
def save_user_votes(selected_option_ids)
|
||||
selected_option_ids = Array(selected_option_ids).map(&:to_i)
|
||||
saved_selections = []
|
||||
errors = []
|
||||
|
||||
ActiveRecord::Base.transaction do
|
||||
# 如果是多选,先删除用户之前的投票记录(重新投票)
|
||||
if @vote.is_multiple_choice
|
||||
@vote.user_vote_options.where(user_id: current_user.id).destroy_all
|
||||
end
|
||||
|
||||
selected_option_ids.each do |option_id|
|
||||
vote_option = @vote.vote_options.find(option_id)
|
||||
user_vote_option = current_user.user_vote_options.new(
|
||||
vote_option: vote_option
|
||||
)
|
||||
|
||||
if user_vote_option.save
|
||||
saved_selections << user_vote_option
|
||||
else
|
||||
errors << user_vote_option.errors.full_messages
|
||||
raise ActiveRecord::Rollback
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if errors.empty?
|
||||
{ success: true, selections: saved_selections }
|
||||
else
|
||||
{ success: false, errors: errors.flatten }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -21,6 +21,8 @@ class PlatformCommunicate < ApplicationRecord
|
|||
before_save :add_fake_id, on: [:create, :update]
|
||||
|
||||
has_many :watchers, as: :watchable, dependent: :destroy
|
||||
has_one :vote, dependent: :destroy
|
||||
has_many :vote_options, through: :vote
|
||||
|
||||
def watched_ext_info(user_id)
|
||||
watcher = self.watchers.find_by(user_id: user_id)
|
||||
|
|
|
|||
|
|
@ -25,8 +25,8 @@ class ProjectActions::PullRequestEvent < ProjectAction
|
|||
pull_id: pull_request.id,
|
||||
pull_number: pull_request.gitea_number,
|
||||
title: pull_request.title,
|
||||
body: pull_request.body,
|
||||
git_diff: gitea_pull_request,
|
||||
# body: pull_request.body,
|
||||
# git_diff: gitea_pull_request,
|
||||
additions: gitea_pull_request['TotalAddition'],
|
||||
deletions: gitea_pull_request['TotalDeletion'],
|
||||
changed_files: gitea_pull_request['NumFiles'],
|
||||
|
|
|
|||
|
|
@ -192,6 +192,9 @@ class User < Owner
|
|||
|
||||
has_one :page, :dependent => :destroy
|
||||
has_many :home_top_settings, dependent: :destroy
|
||||
|
||||
has_many :user_vote_options, dependent: :destroy
|
||||
has_many :vote_options, through: :user_vote_options
|
||||
|
||||
# Groups and active users
|
||||
scope :active, lambda { where(status: [STATUS_ACTIVE, STATUS_EDIT_INFO]) }
|
||||
|
|
|
|||
|
|
@ -0,0 +1,17 @@
|
|||
class UserVoteOption < ApplicationRecord
|
||||
belongs_to :user
|
||||
belongs_to :vote_option, counter_cache: true
|
||||
|
||||
validates :user_id, uniqueness: { scope: :vote_option_id, message: "您已经投过票了" }
|
||||
|
||||
private
|
||||
def user_can_vote_only_once_per_vote
|
||||
return unless user && vote_option
|
||||
|
||||
existing_vote = UserVoteOption.joins(:vote_option)
|
||||
.where(user_id: user_id, vote_options: { vote_id: vote_option.vote_id })
|
||||
.where.not(id: id) # 排除自身(更新时)
|
||||
.exists?
|
||||
errors.add(:base, '您已经投过票了') if existing_vote
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,75 @@
|
|||
class Vote < ApplicationRecord
|
||||
self.table_name = 'new_votes'
|
||||
|
||||
belongs_to :platform_communicate
|
||||
has_many :vote_options, dependent: :destroy
|
||||
has_many :user_vote_options, through: :vote_options
|
||||
|
||||
accepts_nested_attributes_for :vote_options, allow_destroy: true
|
||||
|
||||
validates :max_choice_vote_options_count,
|
||||
numericality: {
|
||||
greater_than: 1,
|
||||
less_than_or_equal_to: 20,
|
||||
allow_nil: true
|
||||
},
|
||||
if: :is_multiple_choice?
|
||||
|
||||
validate :validate_multiple_choice_settings
|
||||
|
||||
|
||||
def expired?
|
||||
due_at.present? && due_at < Time.current
|
||||
end
|
||||
|
||||
def user_voted?(user)
|
||||
return false unless user
|
||||
|
||||
user_vote_options.where(user_id: user.id).exists?
|
||||
end
|
||||
|
||||
def user_selections(user)
|
||||
return UserVoteOption.none unless user
|
||||
|
||||
user_vote_options.where(user_id: user.id)
|
||||
end
|
||||
|
||||
def total_votes_count
|
||||
# 计算参与投票的总人数(去重)
|
||||
user_vote_options.select(:user_id).distinct.count
|
||||
end
|
||||
|
||||
def total_votes_options_count
|
||||
vote_options.sum(:user_vote_options_count)
|
||||
end
|
||||
|
||||
# 获取投票结果(使用缓存字段,性能更好)
|
||||
def results_with_cache
|
||||
vote_options.select(:id, :title, :user_vote_options_count).map do |option|
|
||||
{
|
||||
id: option.id,
|
||||
title: option.title,
|
||||
vote_count: option.user_vote_options_count,
|
||||
percentage: calculate_percentage_for_option(option.user_vote_options_count)
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def calculate_percentage_for_option(option_votes)
|
||||
total = total_votes_options_count
|
||||
return 0 if total.zero?
|
||||
(option_votes.to_f / total * 100).round(2)
|
||||
end
|
||||
|
||||
def validate_multiple_choice_settings
|
||||
if is_multiple_choice? && max_choice_vote_options_count.nil?
|
||||
errors.add(:max_choice_vote_options_count, '多选投票必须设置最大选择数')
|
||||
end
|
||||
|
||||
if !is_multiple_choice? && max_choice_vote_options_count.present?
|
||||
errors.add(:max_choice_vote_options_count, '单选投票不需要设置最大选择数')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
class VoteOption < ApplicationRecord
|
||||
|
||||
belongs_to :vote
|
||||
has_many :user_vote_options, dependent: :destroy
|
||||
has_many :users, through: :user_vote_options
|
||||
validates :title, presence: true
|
||||
|
||||
def self.selected?(option, user)
|
||||
option.user_vote_options.exists?(user_id: user.id)
|
||||
end
|
||||
end
|
||||
|
|
@ -21,6 +21,11 @@
|
|||
<td><%= c.order_index %></td>
|
||||
<td><%= c.tag_field.to_s.include?("活动") ? link_to(c.watchers_count, "/admins/platform_communicates/#{c.id}") : "--"%></td>
|
||||
<td class="action-container">
|
||||
<% if c.vote.present? %>
|
||||
<%= link_to "编辑投票", edit_vote_admins_platform_communicate_path(c), class: "action" %>
|
||||
<% else %>
|
||||
<%= link_to "新增投票", new_vote_admins_platform_communicate_path(c), class: "action" %>
|
||||
<% end %>
|
||||
<%= link_to c.status ? "下架" : "上架", "/admins/platform_communicates/#{c.id}/online_switch.js", style: "#{c.status? ? 'color:#FF6800;' : ''}", method: :post, remote: true%>
|
||||
<%= link_to "编辑", edit_admins_platform_communicate_path(c), remote: true, class: "action" %>
|
||||
<%= link_to "删除", admins_platform_communicate_path(c), method: :delete, data:{confirm: "确认删除的吗?"}, class: "action" %>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,312 @@
|
|||
<%= form_for @vote, url: {controller: "platform_communicates", action: "#{type}"} do |form| %>
|
||||
<% if vote.errors.any? %>
|
||||
<div class="alert alert-danger">
|
||||
<h5><%= pluralize(vote.errors.count, "error") %> 阻止了此投票的保存:</h5>
|
||||
<ul>
|
||||
<% vote.errors.full_messages.each do |message| %>
|
||||
<li><%= message %></li>
|
||||
<% end %>
|
||||
</ul>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<!-- 基本信息 -->
|
||||
<div class="card mb-4">
|
||||
<div class="card-header">
|
||||
<h6 class="m-0 font-weight-bold text-primary">基本信息</h6>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
|
||||
<div class="form-group">
|
||||
<%= form.label :description, "投票描述", class: "font-weight-bold" %>
|
||||
<%= form.text_area :description, class: "form-control", rows: 3, placeholder: "输入投票描述(可选)" %>
|
||||
<small class="form-text text-muted">详细描述投票的目的和背景</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 投票设置 -->
|
||||
<div class="card mb-4">
|
||||
<div class="card-header">
|
||||
<h6 class="m-0 font-weight-bold text-primary">投票设置</h6>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="form-group">
|
||||
<div class="form-check">
|
||||
<%= form.check_box :is_multiple_choice, class: "form-check-input", id: "is_multiple_choice" %>
|
||||
<%= form.label :is_multiple_choice, "允许多选", class: "form-check-label font-weight-bold" %>
|
||||
</div>
|
||||
<small class="form-text text-muted">启用后用户可以选择多个选项</small>
|
||||
</div>
|
||||
|
||||
<div class="form-group" id="max_choices_field" style="<%= 'display: none;' unless @vote.is_multiple_choice %>">
|
||||
<%= form.label :max_choice_vote_options_count, "最多选择数", class: "font-weight-bold" %>
|
||||
<%= form.number_field :max_choice_vote_options_count, class: "form-control", min: 2, max: 20, placeholder: "输入最多可选择的数量" %>
|
||||
<small class="form-text text-muted">设置用户最多可以选择的选项数量(2-20)</small>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<%= form.label :due_at, "结束时间", class: "font-weight-bold" %>
|
||||
<div class="input-group">
|
||||
<%= form.datetime_local_field :due_at, class: "form-control" %>
|
||||
<div class="input-group-append">
|
||||
<button type="button" class="btn btn-outline-secondary" id="clear_ends_at">
|
||||
清除
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<small class="form-text text-muted">不设置则投票永久有效</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 投票选项 -->
|
||||
<div class="card mb-4">
|
||||
<div class="card-header d-flex justify-content-between align-items-center">
|
||||
<h6 class="m-0 font-weight-bold text-primary">投票选项</h6>
|
||||
<button type="button" class="btn btn-sm btn-primary" id="add_vote_option">
|
||||
添加选项
|
||||
</button>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div id="vote_options">
|
||||
<%= form.fields_for :vote_options do |vote_option_fields| %>
|
||||
<div class="vote-option-item card mb-3">
|
||||
<div class="card-body">
|
||||
<div class="row">
|
||||
<div class="col-md-8">
|
||||
<div class="form-group">
|
||||
<%= vote_option_fields.label :title, "选项名称", class: "font-weight-bold" %>
|
||||
<%= vote_option_fields.text_field :title, class: "form-control", placeholder: "输入选项名称", required: true %>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="form-group">
|
||||
<%= vote_option_fields.label :order_index, "排序", class: "font-weight-bold" %>
|
||||
<%= vote_option_fields.number_field :order_index, class: "form-control", min: 1, value: vote_option_fields.object.order_index || 1 %>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-1 d-flex align-items-end">
|
||||
<% unless vote_option_fields.object.persisted? %>
|
||||
<button type="button" class="btn btn-danger btn-sm remove-option" style="margin-bottom: 1rem;">
|
||||
删除
|
||||
</button>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
<% if vote_option_fields.object.persisted? %>
|
||||
<div class="form-group">
|
||||
<div class="form-check">
|
||||
<%= vote_option_fields.check_box :_destroy, class: "form-check-input" %>
|
||||
<%= vote_option_fields.label :_destroy, "删除此选项", class: "form-check-label text-danger" %>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<div class="alert alert-info">
|
||||
<strong>提示:</strong> 至少需要添加2个投票选项,最多支持50个选项。
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 表单操作 -->
|
||||
<div class="form-group text-center">
|
||||
<%= form.submit vote.persisted? ? "更新投票" : "创建投票", class: "btn btn-primary btn-lg mr-2" %>
|
||||
<%= link_to "取消", applet_admins_platform_communicates_path, class: "btn btn-secondary btn-lg" %>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<!-- 投票选项模板(用于动态添加) -->
|
||||
<template id="vote_option_template">
|
||||
<div class="vote-option-item card mb-3">
|
||||
<div class="card-body">
|
||||
<div class="row">
|
||||
<div class="col-md-8">
|
||||
<div class="form-group">
|
||||
<label class="font-weight-bold">选项名称</label>
|
||||
<input type="text" name="vote[vote_options_attributes][INDEX][title]" class="form-control" placeholder="输入选项名称" required>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="form-group">
|
||||
<label class="font-weight-bold">排序</label>
|
||||
<input type="number" name="vote[vote_options_attributes][INDEX][order_index]" class="form-control" min="1" value="1">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-1 d-flex align-items-end">
|
||||
<button type="button" class="btn btn-danger btn-sm remove-option" style="margin-bottom: 1rem;">
|
||||
删除
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<%= javascript_tag nonce: true do %>
|
||||
// 简化的投票表单交互功能
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
initVoteForm();
|
||||
});
|
||||
|
||||
// 如果使用 Turbolinks/Turbo
|
||||
document.addEventListener('turbolinks:load', initVoteForm);
|
||||
document.addEventListener('turbo:load', initVoteForm);
|
||||
|
||||
function initVoteForm() {
|
||||
console.log('初始化投票表单...');
|
||||
|
||||
// 防止重复初始化
|
||||
if (window.voteFormInitialized) {
|
||||
console.log('投票表单已经初始化过,跳过');
|
||||
return;
|
||||
}
|
||||
window.voteFormInitialized = true;
|
||||
|
||||
// 多选切换
|
||||
const multipleChoiceCheckbox = document.getElementById('is_multiple_choice');
|
||||
const maxChoicesField = document.getElementById('max_choices_field');
|
||||
|
||||
if (multipleChoiceCheckbox && maxChoicesField) {
|
||||
function toggleMaxChoices() {
|
||||
maxChoicesField.style.display = multipleChoiceCheckbox.checked ? 'block' : 'none';
|
||||
if (!multipleChoiceCheckbox.checked) {
|
||||
const maxChoiceInput = document.getElementById('vote_max_choice_vote_options_count');
|
||||
if (maxChoiceInput) maxChoiceInput.value = '';
|
||||
}
|
||||
}
|
||||
|
||||
multipleChoiceCheckbox.addEventListener('change', toggleMaxChoices);
|
||||
toggleMaxChoices(); // 初始状态
|
||||
}
|
||||
|
||||
// 清除结束时间
|
||||
const clearEndsAtBtn = document.getElementById('clear_ends_at');
|
||||
if (clearEndsAtBtn) {
|
||||
clearEndsAtBtn.addEventListener('click', function() {
|
||||
const endsAtInput = document.getElementById('vote_ends_at');
|
||||
if (endsAtInput) endsAtInput.value = '';
|
||||
});
|
||||
}
|
||||
|
||||
// 添加选项 - 关键修复:确保只绑定一次
|
||||
const addOptionButton = document.getElementById('add_vote_option');
|
||||
if (addOptionButton && !addOptionButton.hasListener) {
|
||||
addOptionButton.hasListener = true; // 标记已绑定
|
||||
|
||||
addOptionButton.addEventListener('click', function(event) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
addNewOption();
|
||||
});
|
||||
}
|
||||
|
||||
// 删除选项 - 使用事件委托
|
||||
const optionsContainer = document.getElementById('vote_options');
|
||||
if (optionsContainer) {
|
||||
optionsContainer.addEventListener('click', function(event) {
|
||||
if (event.target.closest('.remove-option')) {
|
||||
event.preventDefault();
|
||||
event.target.closest('.vote-option-item').remove();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 表单验证
|
||||
const form = document.querySelector('.vote-form');
|
||||
if (form) {
|
||||
form.addEventListener('submit', validateForm);
|
||||
}
|
||||
}
|
||||
|
||||
// 添加新选项
|
||||
function addNewOption() {
|
||||
console.log('执行添加选项函数');
|
||||
|
||||
const optionsContainer = document.getElementById('vote_options');
|
||||
const optionTemplate = document.getElementById('vote_option_template');
|
||||
|
||||
if (!optionsContainer || !optionTemplate) {
|
||||
console.error('找不到选项容器或模板');
|
||||
return;
|
||||
}
|
||||
|
||||
// 计算新的索引和位置
|
||||
const currentOptions = optionsContainer.querySelectorAll('.vote-option-item');
|
||||
const newIndex = currentOptions.length;
|
||||
const newPosition = getNextPosition();
|
||||
|
||||
const newOption = optionTemplate.content.cloneNode(true);
|
||||
const html = newOption.querySelector('.vote-option-item').outerHTML;
|
||||
let newHtml = html.replace(/INDEX/g, newIndex);
|
||||
newHtml = newHtml.replace(/POSITION_VALUE/g, newPosition);
|
||||
|
||||
optionsContainer.insertAdjacentHTML('beforeend', newHtml);
|
||||
|
||||
console.log(`成功添加选项,索引: ${newIndex}, 位置: ${newPosition}`);
|
||||
}
|
||||
|
||||
// 获取下一个位置值
|
||||
function getNextPosition() {
|
||||
const positionInputs = document.querySelectorAll('input[name*="position"]');
|
||||
let maxPosition = 0;
|
||||
|
||||
positionInputs.forEach(input => {
|
||||
const value = parseInt(input.value) || 0;
|
||||
if (value > maxPosition) maxPosition = value;
|
||||
});
|
||||
|
||||
return maxPosition + 1;
|
||||
}
|
||||
|
||||
// 表单验证
|
||||
function validateForm(event) {
|
||||
const optionsContainer = document.getElementById('vote_options');
|
||||
const optionItems = optionsContainer ? optionsContainer.querySelectorAll('.vote-option-item') : [];
|
||||
let validOptionCount = 0;
|
||||
|
||||
// 计算有效的选项数量
|
||||
optionItems.forEach(item => {
|
||||
const nameInput = item.querySelector('input[type="text"][name*="name"]');
|
||||
if (nameInput && nameInput.value.trim()) {
|
||||
validOptionCount++;
|
||||
}
|
||||
});
|
||||
|
||||
if (validOptionCount < 2) {
|
||||
event.preventDefault();
|
||||
alert('请至少填写2个有效的投票选项!');
|
||||
return false;
|
||||
}
|
||||
|
||||
// 验证选项名称是否重复
|
||||
const optionNames = [];
|
||||
let hasDuplicate = false;
|
||||
|
||||
optionItems.forEach(item => {
|
||||
const nameInput = item.querySelector('input[type="text"][name*="name"]');
|
||||
if (nameInput && nameInput.value.trim()) {
|
||||
const name = nameInput.value.trim().toLowerCase();
|
||||
if (optionNames.includes(name)) {
|
||||
hasDuplicate = true;
|
||||
nameInput.classList.add('is-invalid');
|
||||
} else {
|
||||
optionNames.push(name);
|
||||
nameInput.classList.remove('is-invalid');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (hasDuplicate) {
|
||||
event.preventDefault();
|
||||
alert('存在重复的选项名称,请修改后重新提交!');
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
<% end %>
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
<% content_for :page_title, "编辑投票" %>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-lg-12 mx-auto">
|
||||
<div class="card shadow">
|
||||
<div class="card-header py-3">
|
||||
<h6 class="m-0 font-weight-bold text-default">编辑投票</h6>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<%= render 'vote_form', vote: @vote, type: "update_vote" %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
<% content_for :page_title, "创建新投票" %>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-lg-12 mx-auto">
|
||||
<div class="card shadow">
|
||||
<div class="card-header py-3">
|
||||
<h6 class="m-0 font-weight-bold text-default">创建新投票</h6>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<%= render 'vote_form', vote: @vote, type: "create_vote" %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -19,6 +19,7 @@ on:
|
|||
<% end %>
|
||||
<%if node.name.to_s.include?("on-pull_request") %>
|
||||
pull_request:
|
||||
types: [opened, reopened]
|
||||
<% end %>
|
||||
<%if node.name.to_s.include?("on-fork") %>
|
||||
fork:
|
||||
|
|
|
|||
|
|
@ -0,0 +1,4 @@
|
|||
json.(vote, :id, :description, :is_multiple_choice, :max_choice_vote_options_count)
|
||||
json.created_at vote.created_at.to_i
|
||||
json.due_at vote.due_at.to_i
|
||||
json.total_votes_count vote.total_votes_count # 计算参与投票的总人数(去重)
|
||||
|
|
@ -9,5 +9,6 @@ json.communicates do
|
|||
json.enroll_field communicate.enroll_field.to_s.split(",")
|
||||
json.enroll_info current_user.watched?(communicate) ? communicate.watched_ext_info(current_user.id) : []
|
||||
json.url communicate.url_or_uuid
|
||||
json.has_vote communicate.vote.present?
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
json.vote do
|
||||
json.partial! "home/platform_communicates/vote", locals: {vote: @vote}
|
||||
end
|
||||
json.selected_option_ids @selected_option_ids
|
||||
json.vote_results @vote_results do |result|
|
||||
json.(result, :id, :title, :vote_count, :percentage)
|
||||
end
|
||||
|
|
@ -15,7 +15,8 @@ Rails.application.configure do
|
|||
# Enable/disable caching. By default caching is disabled.
|
||||
# Run rails dev:cache to toggle caching.
|
||||
|
||||
config.cache_store = :file_store, "#{Rails.root }/files/cache_store/"
|
||||
# config.cache_store = :file_store, "#{Rails.root }/files/cache_store/"
|
||||
config.cache_store = :redis_store, { url: 'redis://127.0.0.1:6379/1', namespace: 'cache_store' }
|
||||
|
||||
# if Rails.root.join('tmp', 'caching-dev.txt').exist?
|
||||
# config.action_controller.perform_caching = true
|
||||
|
|
|
|||
|
|
@ -145,7 +145,12 @@ Rails.application.routes.draw do
|
|||
namespace :home do
|
||||
resources :platform_statistics, only: [:index]
|
||||
resources :competitions, only:[:index]
|
||||
resources :platform_communicates, only: [:index]
|
||||
resources :platform_communicates, only: [:index] do
|
||||
member do
|
||||
post :submit_vote
|
||||
get :vote_results
|
||||
end
|
||||
end
|
||||
resources :platform_people, only: [:index]
|
||||
end
|
||||
get 'home/index'
|
||||
|
|
@ -1221,6 +1226,10 @@ Rails.application.routes.draw do
|
|||
get :applet, on: :collection
|
||||
member do
|
||||
post :online_switch
|
||||
get :new_vote
|
||||
post :create_vote
|
||||
get :edit_vote
|
||||
patch :update_vote
|
||||
end
|
||||
end
|
||||
resources :platform_people
|
||||
|
|
|
|||
|
|
@ -0,0 +1,13 @@
|
|||
class CreateVotes < ActiveRecord::Migration[5.2]
|
||||
def change
|
||||
create_table :new_votes do |t|
|
||||
t.references :platform_communicate
|
||||
t.text :description
|
||||
t.boolean :is_multiple_choice, default: false
|
||||
t.integer :max_choice_vote_options_count, default: 1
|
||||
t.datetime :due_at
|
||||
|
||||
t.timestamps
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
class CreateVoteOptions < ActiveRecord::Migration[5.2]
|
||||
def change
|
||||
create_table :vote_options do |t|
|
||||
t.references :vote
|
||||
t.string :title
|
||||
t.integer :order_index, default: 0
|
||||
t.integer :user_vote_options_count, default: 0
|
||||
|
||||
t.timestamps
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
class CreateUserVoteOptions < ActiveRecord::Migration[5.2]
|
||||
def change
|
||||
create_table :user_vote_options do |t|
|
||||
t.references :user, index: true
|
||||
t.references :vote_option, index: true
|
||||
|
||||
t.timestamps
|
||||
end
|
||||
end
|
||||
end
|
||||
Loading…
Reference in New Issue