公开数据看板、四位 Key 录入面板、管理后台与三级权限、动态字段配置、 PostgreSQL/SQLite 双支持、数据库备份,以及本版新增的成品图上传与预览。 图片相关: - 录入表单支持相册选图与手机端直接拍照,每条记录最多 6 张 - 浏览器内压缩到最长边 1600 并剥离 EXIF,入库统一为 JPG/PNG - 二进制直接入库,现有备份自动覆盖图片,部署无需新增卷 - 详情页缩略图宫格与全屏 lightbox,公开看板同样可见 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
21 lines
3.4 KiB
JavaScript
21 lines
3.4 KiB
JavaScript
(function(){
|
|
const escapeHtml=value=>{const node=document.createElement('div');node.textContent=value??'';return node.innerHTML;};
|
|
async function load(){const response=await fetch('/api/form-fields');const data=await response.json();if(!response.ok)throw new Error(data.message||'扩展字段加载失败');return data.data;}
|
|
function inputHtml(definition,raw,disabled,preserveIfEmpty=false){
|
|
const name=escapeHtml(definition.field_key),value=raw??'',required=definition.is_required&&!preserveIfEmpty?'required':'',off=disabled?'disabled':'',preserve=preserveIfEmpty?'data-preserve-if-empty="true"':'',hint=preserveIfEmpty?`placeholder="原值:${escapeHtml(raw)}(输入新值可替换)"`:'';
|
|
if(definition.input_type==='long_text')return `<textarea data-custom-key="${name}" rows="4" ${required} ${off} ${preserve} ${hint}>${escapeHtml(value)}</textarea>`;
|
|
if(definition.input_type==='boolean')return `<select data-custom-key="${name}" ${required} ${off} ${preserve}><option value="">请选择</option><option value="true" ${String(value)==='true'?'selected':''}>是</option><option value="false" ${String(value)==='false'?'selected':''}>否</option></select>`;
|
|
if(definition.input_type==='select')return `<select data-custom-key="${name}" ${required} ${off} ${preserve}><option value="">请选择</option>${definition.options.map(option=>`<option value="${escapeHtml(option)}" ${String(value)===option?'selected':''}>${escapeHtml(option)}</option>`).join('')}</select>`;
|
|
const types={number:'number',integer:'number',date:'date',text:'text'};const step=definition.input_type==='number'?'step="any"':definition.input_type==='integer'?'step="1"':'';
|
|
return `<input type="${types[definition.input_type]||'text'}" data-custom-key="${name}" value="${escapeHtml(value)}" ${step} ${required} ${off} ${preserve} ${hint}>`;
|
|
}
|
|
function render(container,definitions,values={},disabled=false){
|
|
const section=container.closest('.custom-fields-section'),activeKeys=new Set(definitions.map(item=>item.field_key));
|
|
const active=definitions.map(definition=>{const saved=values[definition.field_key],incompatible=saved&&typeof saved==='object'&&saved.compatible===false,raw=saved&&typeof saved==='object'?saved.raw_value:saved,inputValue=incompatible?'':raw,warning=incompatible?`<span class="field-format-warning">历史值“${escapeHtml(raw)}”与当前类型不兼容;留空会保留原值,填写新值后替换</span>`:'';return `<label>${escapeHtml(definition.label)}${definition.unit?` (${escapeHtml(definition.unit)})`:''} ${definition.is_required?'<em>*</em>':'<small>可选</small>'}${inputHtml(definition,inputValue,disabled,incompatible)}${warning}</label>`;}).join('');
|
|
const historical=Object.entries(values).filter(([key,value])=>!activeKeys.has(key)&&value&&typeof value==='object').map(([,value])=>`<div class="historical-custom-value"><small>已停用字段 · ${escapeHtml(value.label)}</small><b>${escapeHtml(value.display_value||value.raw_value||'未填写')}</b>${value.compatible===false?'<span>历史格式</span>':''}</div>`).join('');
|
|
container.innerHTML=active+historical;section.hidden=!definitions.length&&!historical;
|
|
}
|
|
function collect(form){const result={};form.querySelectorAll('[data-custom-key]').forEach(input=>{if(input.value===''&&input.dataset.preserveIfEmpty==='true')return;result[input.dataset.customKey]=input.value===''?null:input.value;});return result;}
|
|
window.DynamicFields={load,render,collect};
|
|
})();
|