chore: baseline snapshot before moderation-strictness feature
Repo .git was damaged (refs/ missing, index truncated, object store near-empty). Repaired via git init (rebuilt refs), stale index preserved as .git/index.corrupt-20260910.bak, then full worktree committed as a rollback baseline.
This commit is contained in:
@@ -0,0 +1,392 @@
|
||||
{{template "base/header" .}}
|
||||
|
||||
<div class="uk-flex uk-flex-between uk-flex-middle uk-margin-bottom">
|
||||
<h3 class="uk-margin-remove">
|
||||
{{ if .IsEdit }}{{t .LangCode "vote.editTitle"}}{{ else }}{{t .LangCode "vote.newTitle"}}{{ end }}
|
||||
</h3>
|
||||
<a class="uk-button uk-button-default uk-button-small" href="/user/questions#votes">{{t .LangCode "common.back"}}</a>
|
||||
</div>
|
||||
|
||||
<form method="post" action="{{ if .IsEdit }}/user/votes/{{ .Vote.ID }}{{ else }}/user/votes{{ end }}" onsubmit="return validateBuilderForm(event)">
|
||||
{{ .CSRFTokenHTML }}
|
||||
|
||||
{{template "base/alert" .}}
|
||||
|
||||
<!-- 基本设置 -->
|
||||
<div class="uk-card uk-card-default uk-card-small uk-card-body uk-margin-bottom">
|
||||
<h4 class="uk-margin-small-bottom">{{t .LangCode "vote.formBasic"}}</h4>
|
||||
|
||||
{{ if .LockedContent }}
|
||||
<div class="uk-alert uk-alert-warning uk-margin-small-bottom">{{t .LangCode "vote.formLockedHint"}}</div>
|
||||
{{ end }}
|
||||
|
||||
<div class="uk-margin-small">
|
||||
<label class="uk-form-label">{{t .LangCode "vote.formTitle"}} *</label>
|
||||
<input class="uk-input" type="text" name="title" maxlength="100" required value="{{ if .Vote }}{{ .Vote.Title }}{{ end }}" placeholder="{{t .LangCode "vote.formTitlePlaceholder"}}">
|
||||
</div>
|
||||
|
||||
<div class="uk-margin-small">
|
||||
<label class="uk-form-label">{{t .LangCode "vote.formDescription"}}</label>
|
||||
<textarea class="uk-textarea" name="description" rows="3" maxlength="5000" placeholder="{{t .LangCode "vote.formDescriptionPlaceholder"}}">{{ if .Vote }}{{ .Vote.Description }}{{ end }}</textarea>
|
||||
</div>
|
||||
|
||||
<div class="uk-grid-small uk-child-width-1-2@s uk-margin-small-top" uk-grid>
|
||||
<div>
|
||||
<label class="uk-form-label">{{t .LangCode "vote.formType"}}</label>
|
||||
{{ if .LockedContent }}
|
||||
<input class="uk-input" type="text" disabled
|
||||
value="{{ if eq .Vote.Type "single" }}{{t $.LangCode "vote.typeSingle"}}{{ else if eq .Vote.Type "multiple" }}{{t $.LangCode "vote.typeMultiple"}}{{ else if eq .Vote.Type "rating" }}{{t $.LangCode "vote.typeRating"}}{{ else if eq .Vote.Type "rank" }}{{t $.LangCode "vote.typeRank"}}{{ else }}{{t $.LangCode "vote.typeForm"}}{{ end }}">
|
||||
{{ else }}
|
||||
<select class="uk-select" name="type" id="vote-type" onchange="toggleEditor()">
|
||||
<option value="single" {{ if and .Vote (eq .Vote.Type "single") }}selected{{ end }}>{{t .LangCode "vote.typeSingle"}}</option>
|
||||
<option value="multiple" {{ if and .Vote (eq .Vote.Type "multiple") }}selected{{ end }}>{{t .LangCode "vote.typeMultiple"}}</option>
|
||||
<option value="rating" {{ if and .Vote (eq .Vote.Type "rating") }}selected{{ end }}>{{t .LangCode "vote.typeRating"}}</option>
|
||||
<option value="rank" {{ if and .Vote (eq .Vote.Type "rank") }}selected{{ end }}>{{t .LangCode "vote.typeRank"}}</option>
|
||||
<option value="form" {{ if and .Vote (eq .Vote.Type "form") }}selected{{ end }}>{{t .LangCode "vote.typeForm"}}</option>
|
||||
</select>
|
||||
{{ end }}
|
||||
</div>
|
||||
<div>
|
||||
<label class="uk-form-label">{{t .LangCode "vote.formVisibility"}} *</label>
|
||||
<select class="uk-select" name="visibility">
|
||||
<option value="public" {{ if and .Vote (eq .Vote.Visibility "public") }}selected{{ end }}>{{t .LangCode "vote.formVisibilityPublic"}}</option>
|
||||
<option value="link_only" {{ if and .Vote (eq .Vote.Visibility "link_only") }}selected{{ end }}>{{t .LangCode "vote.formVisibilityLinkOnly"}}</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label class="uk-form-label">{{t .LangCode "vote.formVoterScope"}} *</label>
|
||||
<select class="uk-select" name="voter_scope">
|
||||
<option value="anyone" {{ if and .Vote (eq .Vote.VoterScope "anyone") }}selected{{ end }}>{{t .LangCode "vote.formVoterScopeAnyone"}}</option>
|
||||
<option value="registered_only" {{ if and .Vote (eq .Vote.VoterScope "registered_only") }}selected{{ end }}>{{t .LangCode "vote.formVoterScopeRegistered"}}</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label class="uk-form-label">{{t .LangCode "vote.formShowResult"}} *</label>
|
||||
<select class="uk-select" name="show_result">
|
||||
<option value="after_vote" {{ if and .Vote (eq .Vote.ShowResult "after_vote") }}selected{{ end }}>{{t .LangCode "vote.formShowResultAfterVote"}}</option>
|
||||
<option value="after_end" {{ if and .Vote (eq .Vote.ShowResult "after_end") }}selected{{ end }}>{{t .LangCode "vote.formShowResultAfterEnd"}}</option>
|
||||
<option value="owner_only" {{ if and .Vote (eq .Vote.ShowResult "owner_only") }}selected{{ end }}>{{t .LangCode "vote.formShowResultOwnerOnly"}}</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="uk-margin-small-top">
|
||||
<label class="uk-form-label">{{t .LangCode "vote.formStartAt"}}</label>
|
||||
<input class="uk-input" type="datetime-local" name="start_at" value="{{ .StartAtStr }}">
|
||||
</div>
|
||||
<div class="uk-margin-small-top">
|
||||
<label class="uk-form-label">{{t .LangCode "vote.formEndAt"}}</label>
|
||||
<input class="uk-input" type="datetime-local" name="end_at" value="{{ .EndAtStr }}">
|
||||
</div>
|
||||
<div class="uk-margin-small-top">
|
||||
<label class="uk-form-label">{{t .LangCode "vote.formQuotaLimit"}}</label>
|
||||
<input class="uk-input" type="number" name="quota_limit" min="0" max="1000000" value="{{ if .Vote }}{{ .Vote.QuotaLimit }}{{ else }}0{{ end }}">
|
||||
<span class="uk-text-meta uk-text-small">{{t .LangCode "vote.formQuotaHint"}}</span>
|
||||
</div>
|
||||
{{ if not .LockedContent }}
|
||||
<div id="max-choices-field" class="uk-margin-small-top">
|
||||
<label class="uk-form-label">{{t .LangCode "vote.formMaxChoices"}}</label>
|
||||
<input class="uk-input" type="number" name="max_choices" min="1" max="20" value="{{ if .Vote }}{{ .Vote.MaxChoices }}{{ else }}3{{ end }}">
|
||||
</div>
|
||||
{{ end }}
|
||||
</div>
|
||||
|
||||
<!-- 限投提醒:移出两列网格,桌面/移动均占满整行,避免与输入框并排显得局促 -->
|
||||
<!-- registered_only 时限制维度变为"每账号",否则为"每设备",文案随之切换 -->
|
||||
<div class="uk-margin-small-top">
|
||||
<div id="scope-hint" class="uk-alert uk-alert-primary uk-margin-remove-bottom" role="status">
|
||||
<span id="scope-hint-device"{{ if and .Vote (eq .Vote.VoterScope "registered_only") }} hidden{{ end }}>{{t .LangCode "vote.formMaxVotesPerFingerprint"}}</span><span id="scope-hint-account"{{ if and .Vote (eq .Vote.VoterScope "registered_only") }}{{ else }} hidden{{ end }}>{{t .LangCode "vote.formMaxVotesPerAccount"}}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="uk-margin-small-top uk-grid-small uk-child-width-1-2@s" uk-grid>
|
||||
<!-- 全局"允许补充自定义选项"仅对单选/多选/排序生效;表单收集与评分类型由各自机制控制,此处隐藏避免误导 -->
|
||||
<label id="global-custom-field" class="uk-form-label" {{ if and .Vote (or (eq .Vote.Type "form") (eq .Vote.Type "rating")) }}hidden{{ end }}><input class="uk-checkbox" type="checkbox" name="allow_custom" {{ if and .Vote .Vote.AllowCustom }}checked{{ end }}> {{t .LangCode "vote.formAllowCustom"}}</label>
|
||||
<label class="uk-form-label"><input class="uk-checkbox" type="checkbox" name="require_captcha" checked disabled> {{t .LangCode "vote.formRequireCaptcha"}}</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{ if not .LockedContent }}
|
||||
<!-- 选项编辑器(单选/多选/排序;评分题无需选项) -->
|
||||
<div class="uk-card uk-card-default uk-card-small uk-card-body uk-margin-bottom" id="options-editor">
|
||||
<h4 class="uk-margin-small-bottom">{{t .LangCode "vote.formOptions"}}</h4>
|
||||
<div id="rating-hint" class="uk-alert uk-alert-primary uk-margin-small-bottom" hidden>{{t .LangCode "vote.formRatingHint"}}</div>
|
||||
<div id="options-list"></div>
|
||||
<button type="button" class="uk-button uk-button-default uk-button-small" onclick="addOption()">{{t .LangCode "vote.addOption"}}</button>
|
||||
<input type="hidden" name="options" id="options-payload">
|
||||
</div>
|
||||
|
||||
<!-- 表单字段设计器(表单收集类型) -->
|
||||
<div class="uk-card uk-card-default uk-card-small uk-card-body uk-margin-bottom" id="fields-editor" hidden>
|
||||
<h4 class="uk-margin-small-bottom">{{t .LangCode "vote.formFields"}}</h4>
|
||||
<div id="fields-list"></div>
|
||||
<button type="button" class="uk-button uk-button-default uk-button-small" onclick="addField()">{{t .LangCode "vote.addField"}}</button>
|
||||
<input type="hidden" name="form_fields" id="fields-payload">
|
||||
</div>
|
||||
{{ end }}
|
||||
|
||||
<div class="uk-margin">
|
||||
<input type="hidden" name="action" id="vote-action" value="save">
|
||||
{{ if and .IsEdit (not .LockedContent) }}<button class="uk-button uk-button-default" type="submit" onclick="setVoteAction('preview')">{{t .LangCode "vote.preview"}}</button>{{ end }}
|
||||
<button class="uk-button uk-button-primary" type="submit" onclick="setVoteAction('save')">{{ if .IsEdit }}{{t .LangCode "common.save"}}{{ else }}{{t .LangCode "vote.createDraft"}}{{ end }}</button>
|
||||
{{ if not .LockedContent }}<button class="uk-button uk-button-secondary" type="submit" onclick="setVoteAction('publish')">{{t .LangCode "vote.publish"}}</button>{{ end }}
|
||||
<a class="uk-button uk-button-default" href="/user/questions#votes">{{t .LangCode "common.cancel"}}</a>
|
||||
</div>
|
||||
<div id="builder-hint" class="uk-alert uk-alert-danger uk-margin-small-top" hidden></div>
|
||||
</form>
|
||||
|
||||
<script>
|
||||
var INITIAL_OPTIONS = {{ toJSON .InitialOptions }};
|
||||
var INITIAL_FIELDS = {{ toJSON .InitialFields }};
|
||||
// 站长侧单个选项文本上限,与后端 conf.Limits.VoteOptionLen([limits] 配置段)同源。
|
||||
// 参与者的自定义补充文本上限是另一套 MAX_CUSTOM_LEN(conf.Limits.CustomAnswerLen),二者独立。
|
||||
var MAX_OPTION_LEN = {{ .MaxVoteOptionLen }};
|
||||
var optionsData = [];
|
||||
var fieldsData = [];
|
||||
|
||||
function toggleEditor() {
|
||||
var t = document.getElementById('vote-type').value;
|
||||
var optEditor = document.getElementById('options-editor');
|
||||
var fldEditor = document.getElementById('fields-editor');
|
||||
var maxField = document.getElementById('max-choices-field');
|
||||
var ratingHint = document.getElementById('rating-hint');
|
||||
var globalCustom = document.getElementById('global-custom-field');
|
||||
optEditor.hidden = (t === 'form');
|
||||
fldEditor.hidden = (t !== 'form');
|
||||
maxField.hidden = (t !== 'multiple');
|
||||
ratingHint.hidden = (t !== 'rating');
|
||||
if (globalCustom) { globalCustom.hidden = (t === 'form' || t === 'rating'); }
|
||||
updateBuilderHint();
|
||||
}
|
||||
|
||||
function addOption() { optionsData.push(''); renderOptions(); updateBuilderHint(); }
|
||||
function removeOption(i) { optionsData.splice(i, 1); renderOptions(); updateBuilderHint(); }
|
||||
|
||||
function renderOptions() {
|
||||
var list = document.getElementById('options-list');
|
||||
list.innerHTML = '';
|
||||
optionsData.forEach(function (val, i) {
|
||||
var row = document.createElement('div');
|
||||
row.className = 'uk-flex uk-flex-middle uk-margin-small-bottom';
|
||||
row.style.gap = '6px';
|
||||
row.innerHTML = '<span class="uk-text-meta">' + (i + 1) + '.</span>' +
|
||||
'<input class="uk-input" style="flex:1" type="text" maxlength="' + MAX_OPTION_LEN + '" placeholder="{{t .LangCode "vote.optionPlaceholder"}}" value="' + escapeAttr(val) + '" oninput="optionsData[' + i + ']=this.value">' +
|
||||
'<button type="button" class="uk-button uk-button-danger uk-button-small" onclick="removeOption(' + i + ')">{{t .LangCode "common.delete"}}</button>';
|
||||
list.appendChild(row);
|
||||
});
|
||||
}
|
||||
|
||||
function addField() {
|
||||
fieldsData.push({ field_type: 'radio', label: '', required: false, max_length: 0, optionsArr: [], allow_custom: false });
|
||||
renderFields();
|
||||
updateBuilderHint();
|
||||
}
|
||||
function removeField(i) { fieldsData.splice(i, 1); renderFields(); updateBuilderHint(); }
|
||||
// 字段上下移:交换数组元素后重渲染;保存时 payload 顺序即新顺序(后端按数组下标写 SortOrder)
|
||||
function moveField(i, dir) {
|
||||
var j = i + dir;
|
||||
if (j < 0 || j >= fieldsData.length) { return; }
|
||||
var tmp = fieldsData[i]; fieldsData[i] = fieldsData[j]; fieldsData[j] = tmp;
|
||||
renderFields();
|
||||
updateBuilderHint();
|
||||
}
|
||||
|
||||
function renderFields() {
|
||||
var list = document.getElementById('fields-list');
|
||||
list.innerHTML = '';
|
||||
fieldsData.forEach(function (f, i) {
|
||||
var box = document.createElement('div');
|
||||
box.className = 'uk-card uk-card-default uk-card-small uk-card-body uk-margin-small-bottom';
|
||||
box.innerHTML =
|
||||
'<div class="uk-flex uk-flex-between uk-flex-middle uk-margin-small-bottom">' +
|
||||
'<span class="uk-text-bold">{{t .LangCode "vote.field"}} ' + (i + 1) + '</span>' +
|
||||
'<div class="uk-flex uk-flex-middle" style="gap:6px">' +
|
||||
'<button type="button" class="uk-button uk-button-default uk-button-small"' + (i === 0 ? ' disabled' : '') + ' onclick="moveField(' + i + ',-1)" title="{{t .LangCode "common.moveUp"}}">↑</button>' +
|
||||
'<button type="button" class="uk-button uk-button-default uk-button-small"' + (i === fieldsData.length - 1 ? ' disabled' : '') + ' onclick="moveField(' + i + ',1)" title="{{t .LangCode "common.moveDown"}}">↓</button>' +
|
||||
'<button type="button" class="uk-button uk-button-danger uk-button-small" onclick="removeField(' + i + ')">{{t .LangCode "common.delete"}}</button>' +
|
||||
'</div>' +
|
||||
'</div>' +
|
||||
'<div class="uk-grid-small uk-child-width-1-2@s" uk-grid>' +
|
||||
'<div><label class="uk-form-label">{{t .LangCode "vote.formFieldType"}}</label>' +
|
||||
'<select class="uk-select" onchange="fieldsData[' + i + '].field_type=this.value;renderFields()">' +
|
||||
'<option value="radio"' + (f.field_type === 'radio' ? ' selected' : '') + '>{{t .LangCode "vote.fieldTypeRadio"}}</option>' +
|
||||
'<option value="text"' + (f.field_type === 'text' ? ' selected' : '') + '>{{t .LangCode "vote.fieldTypeText"}}</option>' +
|
||||
'<option value="textarea"' + (f.field_type === 'textarea' ? ' selected' : '') + '>{{t .LangCode "vote.fieldTypeTextarea"}}</option>' +
|
||||
'<option value="select"' + (f.field_type === 'select' ? ' selected' : '') + '>{{t .LangCode "vote.fieldTypeSelect"}}</option>' +
|
||||
'<option value="checkbox"' + (f.field_type === 'checkbox' ? ' selected' : '') + '>{{t .LangCode "vote.fieldTypeCheckbox"}}</option>' +
|
||||
'<option value="number"' + (f.field_type === 'number' ? ' selected' : '') + '>{{t .LangCode "vote.fieldTypeNumber"}}</option>' +
|
||||
'<option value="rating"' + (f.field_type === 'rating' ? ' selected' : '') + '>{{t .LangCode "vote.fieldTypeRating"}}</option>' +
|
||||
'<option value="rank"' + (f.field_type === 'rank' ? ' selected' : '') + '>{{t .LangCode "vote.fieldTypeRank"}}</option>' +
|
||||
'</select></div>' +
|
||||
'<div><label class="uk-form-label">{{t .LangCode "vote.formFieldLabel"}}</label>' +
|
||||
'<input class="uk-input" type="text" maxlength="100" value="' + escapeAttr(f.label) + '" oninput="fieldsData[' + i + '].label=this.value"></div>' +
|
||||
'</div>' +
|
||||
'<div class="uk-grid-small uk-child-width-1-3@s uk-margin-small-top" uk-grid>' +
|
||||
'<label class="uk-form-label"><input class="uk-checkbox" type="checkbox"' + (f.required ? ' checked' : '') + ' onchange="fieldsData[' + i + '].required=this.checked"> {{t .LangCode "vote.formFieldRequired"}}</label>' +
|
||||
'<div id="maxlen-' + i + '"' + (isTextType(f.field_type) ? '' : ' style="display:none"') + '><label class="uk-form-label">{{t .LangCode "vote.formFieldMaxLength"}}</label>' +
|
||||
'<input class="uk-input" type="number" min="0" max="2000" value="' + (f.max_length || 0) + '" oninput="fieldsData[' + i + '].max_length=parseInt(this.value||0)"></div>' +
|
||||
'</div>' +
|
||||
'<div class="uk-margin-small-top" id="opts-' + i + '"' + (isChoiceType(f.field_type) ? '' : ' hidden') + '>' +
|
||||
'<label class="uk-form-label">{{t .LangCode "vote.formFieldOptions"}}</label>' +
|
||||
'<div class="uk-text-meta uk-margin-small-top">{{t .LangCode "vote.formFieldOptionsHint"}}</div>' +
|
||||
'<div id="fopts-' + i + '" class="uk-margin-small-top"></div>' +
|
||||
'<button type="button" class="uk-button uk-button-default uk-button-small" onclick="addFieldOption(' + i + ')">{{t .LangCode "vote.addOption"}}</button>' +
|
||||
'</div>' +
|
||||
'<div class="uk-margin-small-top" id="custom-' + i + '"' + (isCustomType(f.field_type) ? '' : ' hidden') + '>' +
|
||||
'<label class="uk-form-label"><input class="uk-checkbox" type="checkbox"' + (f.allow_custom ? ' checked' : '') + ' onchange="fieldsData[' + i + '].allow_custom=this.checked"> {{t .LangCode "vote.fieldAllowCustom"}}</label>' +
|
||||
'</div>';
|
||||
list.appendChild(box);
|
||||
if (isChoiceType(f.field_type)) { renderFieldOptions(i); }
|
||||
});
|
||||
if (typeof UIkit !== 'undefined') { UIkit.update(list); }
|
||||
}
|
||||
|
||||
function isChoiceType(t) { return t === 'select' || t === 'radio' || t === 'checkbox' || t === 'rank'; }
|
||||
|
||||
// 支持"补充自定义选项"的字段类型(rank 排序题不开放)
|
||||
function isCustomType(t) { return t === 'select' || t === 'radio' || t === 'checkbox'; }
|
||||
|
||||
function isTextType(t) { return t === 'text' || t === 'textarea'; }
|
||||
|
||||
// 字段选项兼容处理:后端可能返回数组(新)或逗号字符串(旧数据/回填),统一为数组。
|
||||
function normalizeFieldOptions(v) {
|
||||
if (Array.isArray(v)) return v.slice();
|
||||
if (typeof v === 'string' && v !== '') return v.split(',').map(function (x) { return x.trim(); }).filter(function (x) { return x !== ''; });
|
||||
return [];
|
||||
}
|
||||
|
||||
// 渲染单个字段的选项行(逐条 add/remove),避免整段文本框 + 逗号分隔。
|
||||
function renderFieldOptions(i) {
|
||||
var box = document.getElementById('fopts-' + i);
|
||||
if (!box) return;
|
||||
box.innerHTML = '';
|
||||
var arr = fieldsData[i].optionsArr || [];
|
||||
arr.forEach(function (val, j) {
|
||||
var row = document.createElement('div');
|
||||
row.className = 'uk-flex uk-flex-middle uk-margin-small-bottom';
|
||||
row.style.gap = '6px';
|
||||
row.innerHTML = '<span class="uk-text-meta">' + (j + 1) + '.</span>' +
|
||||
'<input class="uk-input" style="flex:1" type="text" maxlength="' + MAX_OPTION_LEN + '" placeholder="{{t .LangCode "vote.optionPlaceholder"}}" value="' + escapeAttr(val) + '" oninput="fieldsData[' + i + '].optionsArr[' + j + ']=this.value">' +
|
||||
'<button type="button" class="uk-button uk-button-danger uk-button-small" onclick="removeFieldOption(' + i + ',' + j + ')">{{t .LangCode "common.delete"}}</button>';
|
||||
box.appendChild(row);
|
||||
});
|
||||
}
|
||||
|
||||
function addFieldOption(i) { (fieldsData[i].optionsArr = fieldsData[i].optionsArr || []).push(''); renderFieldOptions(i); }
|
||||
function removeFieldOption(i, j) { fieldsData[i].optionsArr.splice(j, 1); renderFieldOptions(i); }
|
||||
|
||||
// 设置提交动作:save=保存草稿(宽松),publish=发布(一次性完整校验)。
|
||||
function setVoteAction(a) { document.getElementById('vote-action').value = a; }
|
||||
|
||||
// 前端即时校验:根据题型/字段返回需提示给用户的信息;空字符串表示通过。
|
||||
// 锁定模式(已发布投票)下无选项/字段编辑器,直接返回通过。
|
||||
function computeBuilderHint() {
|
||||
if (!document.getElementById('options-payload') && !document.getElementById('fields-payload')) {
|
||||
return '';
|
||||
}
|
||||
var t = document.getElementById('vote-type').value;
|
||||
if (t === 'single' || t === 'multiple' || t === 'rank') {
|
||||
var cnt = 0;
|
||||
for (var i = 0; i < optionsData.length; i++) {
|
||||
if (optionsData[i].trim() !== '') cnt++;
|
||||
}
|
||||
if (cnt < 2) return '{{t .LangCode "vote.builderHintOptions"}}';
|
||||
} else if (t === 'form') {
|
||||
if (fieldsData.length < 1) return '{{t .LangCode "vote.builderHintFields"}}';
|
||||
for (var i = 0; i < fieldsData.length; i++) {
|
||||
var f = fieldsData[i];
|
||||
if (isChoiceType(f.field_type)) {
|
||||
var opts = (f.optionsArr || []).map(function (x) { return x.trim(); }).filter(function (x) { return x !== ''; });
|
||||
if (opts.length < (f.field_type === 'rank' ? 2 : 1)) {
|
||||
return '{{t .LangCode "vote.builderHintFieldOptions"}}';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
// 实时更新提示条(切换题型/增删选项或字段时调用)
|
||||
function updateBuilderHint() {
|
||||
var m = computeBuilderHint();
|
||||
var el = document.getElementById('builder-hint');
|
||||
if (m) { el.textContent = m; el.hidden = false; } else { el.hidden = true; }
|
||||
}
|
||||
|
||||
// 提交时执行:先收集 payload,再按当前动作做校验。
|
||||
// 发布动作下校验不通过则阻止提交;草稿动作仅提示,不阻止(草稿本就允许暂存)。
|
||||
function validateBuilderForm(event) {
|
||||
buildPayload();
|
||||
var m = computeBuilderHint();
|
||||
var el = document.getElementById('builder-hint');
|
||||
if (m) {
|
||||
el.textContent = m; el.hidden = false;
|
||||
if (document.getElementById('vote-action').value === 'publish') {
|
||||
resetSubmitState();
|
||||
var form = event && event.target ? event.target : document.querySelector('form');
|
||||
if (form) form.setAttribute('data-tb-skip-submit-feedback', '1');
|
||||
if (event) event.preventDefault();
|
||||
el.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
el.hidden = true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function buildPayload() {
|
||||
// 发布后锁定模式:无选项/字段编辑器,跳过 JSON 收集
|
||||
var optPayload = document.getElementById('options-payload');
|
||||
if (optPayload) {
|
||||
var options = optionsData.map(function (v) { return v.trim(); }).filter(function (v) { return v !== ''; });
|
||||
optPayload.value = JSON.stringify(options);
|
||||
}
|
||||
var fldPayload = document.getElementById('fields-payload');
|
||||
if (fldPayload) {
|
||||
var fields = fieldsData.map(function (f) {
|
||||
return { field_type: f.field_type, label: f.label, required: f.required, max_length: f.max_length || 0, options: (f.optionsArr || []), allow_custom: !!f.allow_custom };
|
||||
});
|
||||
fldPayload.value = JSON.stringify(fields);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function escapeAttr(s) {
|
||||
return String(s == null ? '' : s).replace(/&/g, '&').replace(/"/g, '"').replace(/</g, '<').replace(/>/g, '>');
|
||||
}
|
||||
|
||||
// 限投维度提示:仅注册用户投票时切换为"每账号",否则"每设备"。
|
||||
// 与提交逻辑一致——registered_only 下指纹维度不参与,约束实为每账号一票。
|
||||
function updateScopeHint() {
|
||||
var sel = document.querySelector('select[name="voter_scope"]');
|
||||
var dev = document.getElementById('scope-hint-device');
|
||||
var acc = document.getElementById('scope-hint-account');
|
||||
if (!sel || !dev || !acc) return;
|
||||
var isReg = sel.value === 'registered_only';
|
||||
dev.hidden = isReg;
|
||||
acc.hidden = !isReg;
|
||||
}
|
||||
|
||||
(function init() {
|
||||
// 发布后锁定模式:编辑器未渲染,跳过初始化
|
||||
{{ if .LockedContent }}return;{{ end }}
|
||||
optionsData = INITIAL_OPTIONS.slice();
|
||||
fieldsData = INITIAL_FIELDS.map(function (f) { return { field_type: f.field_type || 'text', label: f.label || '', required: !!f.required, max_length: f.max_length || 0, optionsArr: normalizeFieldOptions(f.options), allow_custom: !!f.allow_custom }; });
|
||||
renderOptions();
|
||||
renderFields();
|
||||
toggleEditor();
|
||||
updateBuilderHint();
|
||||
var scopeSel = document.querySelector('select[name="voter_scope"]');
|
||||
if (scopeSel) { scopeSel.addEventListener('change', updateScopeHint); }
|
||||
updateScopeHint();
|
||||
})();
|
||||
</script>
|
||||
|
||||
{{if .DraftSaved}}
|
||||
<script>
|
||||
if (window.history && window.history.replaceState) {
|
||||
window.history.replaceState({}, document.title, window.location.pathname);
|
||||
}
|
||||
</script>
|
||||
{{end}}
|
||||
|
||||
{{template "base/footer" .}}
|
||||
Reference in New Issue
Block a user