924 lines
41 KiB
JavaScript
924 lines
41 KiB
JavaScript
/* ==================== 视图二:项目设置(tab 切换) ==================== */
|
||
function renderProjectDetail() {
|
||
if (!state.project || !state.project.projectId) { switchView("projects"); return; }
|
||
$("#view-projectDetail").innerHTML = '<div class="card"><div class="empty"><span class="spin"></span></div></div>';
|
||
Core.api("project/info", { projectId: state.project.projectId })
|
||
.then(function (info) {
|
||
state.detail = info || {};
|
||
var base = info.Base || {};
|
||
setActiveProject({ projectId: base.project_id || state.project.projectId, name: base.name || state.project.name });
|
||
// 加载项目资料(新表)
|
||
return Core.api("project/data/load", { projectId: state.project.projectId });
|
||
})
|
||
.then(function (resp) {
|
||
state.projectData = resp || {};
|
||
// resp 即为栏目扁平 map(保存什么就读到什么)
|
||
var data = resp || {};
|
||
state.pjiSections = buildSectionsFromData(data);
|
||
// 保存初始快照(用于重置)
|
||
state.pjiInitialSections = JSON.parse(JSON.stringify(state.pjiSections));
|
||
renderProjectDetailForm();
|
||
})
|
||
.catch(function (e) {
|
||
$("#view-projectDetail").innerHTML = '<div class="card"><div class="empty"><div class="big">\u26a0</div>' + Core.esc(e.message) + "</div></div>";
|
||
});
|
||
}
|
||
|
||
/* ---------- 主 Tab 定义 ---------- */
|
||
var PD_TABS = [
|
||
["base", "\u57fa\u7840\u4fe1\u606f"],
|
||
["projectInfo", "\u9879\u76ee\u8d44\u6599"],
|
||
["settings", "\u8bbe\u7f6e"]
|
||
];
|
||
|
||
/* ---------- 栏目类型选项(与维度格式一致) ---------- */
|
||
var PJI_TYPE_OPTIONS = [
|
||
["string", "\u63cf\u8ff0"],
|
||
["object", "\u8be6\u7ec6\u4fe1\u606f"],
|
||
["array", "\u591a\u6761\u76ee"],
|
||
["list", "\u5217\u8868"]
|
||
];
|
||
|
||
var PJI_TYPE_LABELS = { string: "\u63cf\u8ff0", object: "\u8be6\u7ec6\u4fe1\u606f", array: "\u591a\u6761\u76ee", list: "\u5217\u8868" };
|
||
|
||
function renderProjectDetailForm() {
|
||
var base = state.detail.Base || {};
|
||
var sup = state.detail.ModelInfo || {};
|
||
var hasDimEditor = typeof dimBlock === "function";
|
||
|
||
// ===== 顶部卡片 =====
|
||
var html =
|
||
'<div class="card glow">' +
|
||
'<div class="card-title"><span class="bar"></span>\u9879\u76ee\u8bbe\u7f6e <span class="badge blue mono">#' + (base.project_id || state.project.projectId) + "</span></div>";
|
||
|
||
// ===== Tab 栏 =====
|
||
html += '<div class="tabs" id="pd-tabs">';
|
||
PD_TABS.forEach(function (t) {
|
||
html += '<div class="tab' + (t[0] === "base" ? " active" : "") + '" data-tab="' + t[0] + '">' + t[1] + "</div>";
|
||
});
|
||
html += "</div>";
|
||
|
||
// ===== Tab 1:基础信息 =====
|
||
html += '<div id="pd-tab-base">';
|
||
// 项目基本信息
|
||
html +=
|
||
'<div class="card-title" style="font-size:14px;margin-bottom:10px"><span class="bar"></span>\u9879\u76ee\u4fe1\u606f' +
|
||
'<span class="spacer"></span><button class="btn primary sm" id="pd-save-all">\u4fdd\u5b58\u57fa\u7840\u4fe1\u606f</button></div>' +
|
||
'<div class="grid cols-2">' +
|
||
'<div class="field"><label class="label">\u9879\u76ee\u540d\u79f0</label><input id="pd-name" class="input" value="' + Core.esc(base.name || "") + '"></div>' +
|
||
"</div>";
|
||
|
||
// 行业模板应用
|
||
html +=
|
||
'<div class="divider"></div>' +
|
||
'<div class="card-title" style="font-size:14px;margin-bottom:10px"><span class="bar"></span>\u884c\u4e1a\u6a21\u677f</div>' +
|
||
'<div class="card-sub">\u4ece\u884c\u4e1a\u6a21\u677f\u91cd\u65b0\u590d\u5236\u7ef4\u5ea6\u5185\u5bb9\uff08\u8986\u76d6\u4e0b\u65b9\u7ef4\u5ea6\u6a21\u677f\u5b57\u6bb5\uff0c\u4e0d\u5f71\u54cd\u5176\u4ed6\u6570\u636e\uff09</div>' +
|
||
'<div class="row"><div class="flex1"><select id="pd-industry" class="select"><option>\u52a0\u8f7d\u4e2d\u2026</option></select></div>' +
|
||
'<button class="btn success" id="pd-copy">\u27f3 \u5e94\u7528\u6a21\u677f</button></div>';
|
||
|
||
// 维度子 tab
|
||
html += '<div class="divider"></div>';
|
||
html += '<div class="card-title" style="font-size:14px;margin-bottom:10px"><span class="bar"></span>\u7ef4\u5ea6\u6a21\u677f</div>';
|
||
html += '<div class="tabs sub-tabs" id="pd-dim-tabs">';
|
||
DIM_NAMES.forEach(function (d, i) {
|
||
var label = d[1].replace(/ \u6a21\u677f$/, "");
|
||
html += '<div class="tab' + (i === 0 ? " active" : "") + '" data-dim="' + d[0] + '">' + label + "</div>";
|
||
});
|
||
html += "</div>";
|
||
// 维度内容区
|
||
DIM_NAMES.forEach(function (d, i) {
|
||
var hidden = i === 0 ? "" : ' style="display:none"';
|
||
html += '<div class="pd-dim-panel" data-dim="' + d[0] + '"' + hidden + ">";
|
||
if (d[0] === "desc" || d[0] === "advicer_desc") {
|
||
html += '<div class="field"><label class="label">' + d[1] + '</label>' +
|
||
'<textarea class="textarea pd-dim" data-field="' + d[0] + '" style="min-height:120px">' + Core.esc(base[d[0]] || "") + "</textarea></div>";
|
||
} else if (d[0] === "rule_dimension") {
|
||
html += '<div class="field"><label class="label">\u98ce\u63a7\u7ef4\u5ea6 \u6a21\u677f</label>' +
|
||
'<textarea class="textarea pd-dim" data-field="rule_dimension" style="min-height:100px" placeholder="\u53ef\u9009">' +
|
||
Core.esc(base.rule_dimension || "") + "</textarea></div>";
|
||
} else if (hasDimEditor) {
|
||
var dimLabel = d[1].replace(/ \u6a21\u677f$/, "");
|
||
html += dimBlock(dimLabel, base[d[0]] || "");
|
||
} else {
|
||
html += '<div class="field"><label class="label">' + d[1] + '</label>' +
|
||
'<textarea class="textarea pd-dim" data-field="' + d[0] + '" style="min-height:100px">' + Core.esc(base[d[0]] || "") + "</textarea></div>";
|
||
}
|
||
html += "</div>";
|
||
});
|
||
html += "</div>"; // end pd-tab-base
|
||
|
||
// ===== Tab 2:项目资料(动态编辑,初始无栏目) =====
|
||
html += '<div id="pd-tab-projectInfo" style="display:none">';
|
||
html += '<div class="card-title" style="font-size:14px;margin-bottom:10px"><span class="bar"></span>\u9879\u76ee\u8d44\u6599' +
|
||
'<span class="spacer"></span>' +
|
||
'<button class="btn ghost sm" id="pji-add-section">+ \u65b0\u589e\u680f\u76ee</button>' +
|
||
'<button class="btn ghost sm" id="pji-ai-preset" style="margin-left:8px">\u2726 AI \u9884\u8bbe</button>' +
|
||
'<button class="btn ghost sm" id="pji-reset" style="margin-left:8px">\u91cd\u7f6e</button>' +
|
||
'<button class="btn primary sm" id="pji-save" style="margin-left:8px">\u4fdd\u5b58\u9879\u76ee\u8d44\u6599</button></div>';
|
||
html += '<div class="card-sub" style="margin-bottom:12px">\u6bcf\u4e2a\u680f\u76ee\u7684 key \u5373\u4e3a\u5b58\u50a8\u65f6\u7684\u5b57\u6bb5\u540d\uff0c\u6570\u636e\u4ee5\u6241\u5e73 JSON \u683c\u5f0f\u5b58\u50a8\uff0cAI \u53ef\u76f4\u63a5\u8bfb\u53d6\u3002</div>';
|
||
html += '<div id="pji-sections"></div>';
|
||
html += '</div>'; // end pd-tab-projectInfo
|
||
|
||
// ===== Tab 3:设置(模型配置 + 微信接入 Token) =====
|
||
html += '<div id="pd-tab-settings" style="display:none">';
|
||
html += '<div class="card-title" style="font-size:14px;margin-bottom:10px"><span class="bar"></span>\u5fae\u4fe1\u63a5\u5165\u914d\u7f6e' +
|
||
'<span class="spacer"></span><button class="btn primary sm" id="pd-save-wx">\u4fdde\u5b58\u914d\u7f6e</button></div>' +
|
||
'<div class="card-sub">\u914d\u7f6e\u9879\u76ee\u7ea7\u5fae\u4fe1\u534f\u8bae Token\uff0c\u7528\u4e8e\u8be5\u9879\u76ee\u4e0b\u6240\u6709\u9500\u552e\u4eba\u5458\u5171\u4eab\u7684\u5fae\u4fe1\u5de5\u4f5c\u53f0\u63a5\u5165\u3002</div>' +
|
||
'<div class="field"><label class="label">X-finder-TOKEN</label>' +
|
||
'<input id="pd-wx-token" class="input mono" placeholder="\u8bf7\u8f93\u5165\u5fae\u4fe1\u534f\u8bae\u670d\u52a1\u4ee4\u724c" value="' + Core.esc(base.wx_token || '') + '"></div>' +
|
||
'<div class="fs-12 c-dim" style="line-height:1.8">' +
|
||
'\u00b7 \u8be5 Token \u7528\u4e8e\u5bf9\u63a5\u5fae\u4fe1\u534f\u8bae\u670d\u52a1\uff0c\u9879\u76ee\u7ea7\u5171\u4eab\uff1b<br>' +
|
||
'\u00b7 \u8fdb\u5165\u5fae\u4fe1\u5de5\u4f5c\u53f0\u524d\u4f1a\u68c0\u6d4b\u9879\u76ee\u662f\u5426\u5df2\u914d\u7f6e Token\uff1b<br>' +
|
||
'\u00b7 \u9500\u552e\u4eba\u5458\u8fd8\u9700\u914d\u7f6e\u4e2a\u4eba\u7684\u5fae\u4fe1\u8bbe\u5907 ID\uff08wx_device_id\uff09\u65b9\u53ef\u8fdb\u5165\u5de5\u4f5c\u53f0\u3002</div>' +
|
||
'<div class="divider"></div>' +
|
||
'<div class="card-title" style="font-size:14px;margin-bottom:10px"><span class="bar"></span>\u6a21\u578b\u914d\u7f6e</div>' +
|
||
'<div class="grid cols-2">' +
|
||
'<div class="field"><label class="label">\u5f53\u524d\u6a21\u578b\u914d\u7f6e</label><select id="pd-settings-model" class="select"></select></div>' +
|
||
'<div class="field"><label class="label">\u6a21\u578b\u8be6\u60c5</label><input class="input" disabled value="' + Core.esc((sup.sup_name || '-') + (sup.chat_model ? ' \u00b7 ' + sup.chat_model : '')) + '"></div>' +
|
||
'</div>' +
|
||
'<button class="btn primary sm" id="pd-save-settings-model">\u4fdde\u5b58\u6a21\u578b\u914d\u7f6e</button>' +
|
||
'</div>'; // end pd-tab-settings
|
||
|
||
html += "</div>"; // end card
|
||
|
||
$("#view-projectDetail").innerHTML = html;
|
||
loadIndustryOptions($("#pd-industry"), base.industry_id);
|
||
|
||
// ===== 渲染项目资料动态栏目 =====
|
||
// 复位编辑器上下文为项目资料默认(防止客户画像弹窗遗留的目标污染本页)
|
||
setPjiEditorTarget(null);
|
||
drawPjiSections();
|
||
|
||
// ===== 主 Tab 切换 =====
|
||
$$("#pd-tabs .tab").forEach(function (t) {
|
||
t.onclick = function () {
|
||
$$("#pd-tabs .tab").forEach(function (x) { x.classList.toggle("active", x === t); });
|
||
var tab = t.dataset.tab;
|
||
$("#pd-tab-base").style.display = tab === "base" ? "" : "none";
|
||
$("#pd-tab-projectInfo").style.display = tab === "projectInfo" ? "" : "none";
|
||
$("#pd-tab-settings").style.display = tab === "settings" ? "" : "none";
|
||
};
|
||
});
|
||
|
||
// ===== 检查是否有待切换到设置 tab 的请求 =====
|
||
if (state._pendingSettingsTab) {
|
||
state._pendingSettingsTab = false;
|
||
var settingsTab = $("#pd-tabs .tab[data-tab='settings']");
|
||
if (settingsTab) settingsTab.click();
|
||
}
|
||
|
||
// ===== 维度子 Tab 切换 =====
|
||
$$("#pd-dim-tabs .tab").forEach(function (t) {
|
||
t.onclick = function () {
|
||
$$("#pd-dim-tabs .tab").forEach(function (x) { x.classList.toggle("active", x === t); });
|
||
$$(".pd-dim-panel").forEach(function (p) {
|
||
p.style.display = p.dataset.dim === t.dataset.dim ? "" : "none";
|
||
});
|
||
};
|
||
});
|
||
|
||
// ===== 绑定维度结构化编辑器事件 =====
|
||
if (hasDimEditor) {
|
||
$$(".dim-editor").forEach(function (editor) {
|
||
bindDimEditor(editor);
|
||
});
|
||
$$(".dim-gen").forEach(function (btn) {
|
||
btn.onclick = function () {
|
||
var dim = btn.dataset.dim;
|
||
var editor = btn.closest(".dim-editor");
|
||
generateDialog({
|
||
dimension: dim,
|
||
name: base.name || "",
|
||
desc: base.desc || "",
|
||
advicerDesc: base.advicer_desc || "",
|
||
onApply: function (content) {
|
||
var wrap = $(".dim-row-wrap", editor);
|
||
var ta = $(".dim-input", editor);
|
||
var rows = parseDimJson(content);
|
||
if (wrap) {
|
||
wrap.innerHTML = rows.length
|
||
? rows.map(function (r, i) { return dimRowHtml(i, r); }).join("")
|
||
: '<div class="dim-empty">\u6682\u65e0\u5b57\u6bb5\u5b9a\u4e49</div>';
|
||
wrap.classList.remove("hidden");
|
||
ta.classList.add("hidden");
|
||
$$(".dim-row", wrap).forEach(function (row) { bindDimRowEvents(row, editor); });
|
||
var toggleBtn = $(".dim-toggle-json", editor);
|
||
if (toggleBtn) toggleBtn.textContent = "{ } JSON";
|
||
}
|
||
if (ta) ta.value = content;
|
||
Core.toast(dim + " \u5df2\u586b\u5165\u8868\u5355", "ok");
|
||
}
|
||
});
|
||
};
|
||
});
|
||
}
|
||
|
||
// ===== 保存基础信息 =====
|
||
$("#pd-save-all").onclick = function () {
|
||
var payload = {
|
||
projectId: base.project_id || state.project.projectId,
|
||
name: $("#pd-name").value.trim()
|
||
};
|
||
if (hasDimEditor) {
|
||
$$(".dim-editor").forEach(function (ed) {
|
||
var field = ed.dataset.field;
|
||
var ta = $(".dim-input", ed);
|
||
var wrap = $(".dim-row-wrap", ed);
|
||
if (wrap && !wrap.classList.contains("hidden")) {
|
||
payload[field] = serializeDimEditor(ed);
|
||
} else if (ta) {
|
||
payload[field] = ta.value;
|
||
}
|
||
});
|
||
$$(".pd-dim").forEach(function (ta) { payload[ta.dataset.field] = ta.value; });
|
||
} else {
|
||
$$(".pd-dim").forEach(function (ta) { payload[ta.dataset.field] = ta.value; });
|
||
}
|
||
Core.loading(true, "\u4fdd\u5b58\u4e2d\u2026");
|
||
Core.api("project/base/update", payload)
|
||
.then(function () {
|
||
Core.loading(false);
|
||
Core.toast("\u4fdd\u5b58\u6210\u529f", "ok");
|
||
setActiveProject({ projectId: payload.projectId, name: payload.name });
|
||
renderProjectDetail();
|
||
})
|
||
.catch(function (e) { Core.loading(false); Core.toast(e.message, "err"); });
|
||
};
|
||
|
||
// ===== 应用行业模板 =====
|
||
$("#pd-copy").onclick = function () {
|
||
var industryId = Number($("#pd-industry").value);
|
||
if (!industryId) { Core.toast("\u8bf7\u9009\u62e9\u884c\u4e1a\u6a21\u677f", "err"); return; }
|
||
Core.confirm("\u786e\u5b9a\u4ece\u8be5\u884c\u4e1a\u6a21\u677f\u590d\u5236\u7ef4\u5ea6\u5185\u5bb9\uff1f\u5c06\u8986\u76d6\u5f53\u524d\u9879\u76ee\u7ef4\u5ea6\u6a21\u677f\u3002").then(function (ok) {
|
||
if (!ok) return;
|
||
Core.loading(true, "\u590d\u5236\u6a21\u677f\u4e2d\u2026");
|
||
Core.api("project/template/copy", { projectId: state.project.projectId, industryId: industryId })
|
||
.then(function () { Core.loading(false); Core.toast("\u6a21\u677f\u5df2\u5e94\u7528", "ok"); renderProjectDetail(); })
|
||
.catch(function (e) { Core.loading(false); Core.toast(e.message, "err"); });
|
||
});
|
||
};
|
||
|
||
// ===== 保存项目资料 =====
|
||
$("#pji-save").onclick = function () {
|
||
var sections = collectPjiSectionsData();
|
||
var dataObj = {};
|
||
sections.forEach(function (s) { dataObj[s.key] = s.content; });
|
||
Core.loading(true, "\u4fdd\u5b58\u4e2d\u2026");
|
||
Core.api("project/data/save", { projectId: state.project.projectId, data: dataObj })
|
||
.then(function () {
|
||
Core.loading(false);
|
||
Core.toast("\u9879\u76ee\u8d44\u6599\u5df2\u4fdd\u5b58", "ok");
|
||
state.pjiSections = sections;
|
||
state.pjiInitialSections = JSON.parse(JSON.stringify(sections));
|
||
})
|
||
.catch(function (e) { Core.loading(false); Core.toast(e.message, "err"); });
|
||
};
|
||
|
||
// ===== 重置(撤销本次修改,恢复为接口数据) =====
|
||
$("#pji-reset").onclick = function () {
|
||
Core.confirm("\u786e\u5b9a\u91cd\u7f6e\uff1f\u5c06\u64a4\u9500\u672c\u6b21\u6240\u6709\u65b0\u589e\u548c\u4fee\u6539\uff0c\u6062\u590d\u4e3a\u63a5\u53e3\u6570\u636e\u3002").then(function (ok) {
|
||
if (!ok) return;
|
||
// 先清空 DOM,防止 drawPjiSections 内的 sync 读到旧数据
|
||
var sectionsEl = $("#pji-sections");
|
||
if (sectionsEl) sectionsEl.innerHTML = "";
|
||
state.pjiSections = [];
|
||
Core.loading(true, "\u91cd\u65b0\u52a0\u8f7d\u4e2d\u2026");
|
||
Core.api("project/data/load", { projectId: state.project.projectId })
|
||
.then(function (resp) {
|
||
Core.loading(false);
|
||
state.projectData = resp || {};
|
||
state.pjiSections = buildSectionsFromData(resp || {});
|
||
state.pjiInitialSections = JSON.parse(JSON.stringify(state.pjiSections));
|
||
drawPjiSections();
|
||
Core.toast("\u5df2\u91cd\u7f6e\u4e3a\u63a5\u53e3\u6570\u636e", "ok");
|
||
})
|
||
.catch(function (e) { Core.loading(false); Core.toast(e.message, "err"); });
|
||
});
|
||
};
|
||
|
||
// ===== 新增栏目 =====
|
||
$("#pji-add-section").onclick = function () {
|
||
addPjiSectionDialog();
|
||
};
|
||
|
||
// ===== AI 预设 =====
|
||
$("#pji-ai-preset").onclick = function () {
|
||
aiPresetDialog();
|
||
};
|
||
|
||
// ===== 设置 Tab:加载模型配置选项 =====
|
||
loadModelsupOptions($("#pd-settings-model"), base.model_sup_id);
|
||
|
||
// ===== 设置 Tab:保存微信接入 Token =====
|
||
$("#pd-save-wx").onclick = function () {
|
||
var wxToken = $("#pd-wx-token").value.trim();
|
||
Core.loading(true, "保存中…");
|
||
Core.api("project/base/update", { projectId: state.project.projectId, wxToken: wxToken })
|
||
.then(function () {
|
||
Core.loading(false);
|
||
Core.toast("微信接入配置已保存", "ok");
|
||
})
|
||
.catch(function (e) { Core.loading(false); Core.toast(e.message, "err"); });
|
||
};
|
||
|
||
// ===== 设置 Tab:保存模型配置 =====
|
||
$("#pd-save-settings-model").onclick = function () {
|
||
var modelSupId = Number($("#pd-settings-model").value) || 0;
|
||
Core.loading(true, "保存中…");
|
||
Core.api("project/base/update", { projectId: state.project.projectId, modelSupId: modelSupId })
|
||
.then(function () {
|
||
Core.loading(false);
|
||
Core.toast("模型配置已保存", "ok");
|
||
renderProjectDetail();
|
||
})
|
||
.catch(function (e) { Core.loading(false); Core.toast(e.message, "err"); });
|
||
};
|
||
}
|
||
|
||
/* ==================== 项目资料动态栏目(维度格式) ==================== */
|
||
|
||
/*
|
||
* 栏目编辑器上下文:默认读写项目资料页的 state.pjiSections + #pji-sections。
|
||
* 通过 setPjiEditorTarget 可将「数据源 + 容器」指向别处(如客户画像弹窗内的编辑器),
|
||
* 复用同一套渲染/绑定/收集逻辑;传 null 复位为项目资料默认。
|
||
*/
|
||
var __pjiTarget = null;
|
||
function setPjiEditorTarget(root, get, set) {
|
||
__pjiTarget = root ? { root: root, get: get, set: set } : null;
|
||
}
|
||
function pjiContainer() {
|
||
if (__pjiTarget && __pjiTarget.root) return __pjiTarget.root.querySelector("#pji-sections");
|
||
return document.querySelector("#pji-sections");
|
||
}
|
||
function pjiGetSections() {
|
||
if (__pjiTarget && __pjiTarget.get) return __pjiTarget.get() || [];
|
||
return state.pjiSections || [];
|
||
}
|
||
function pjiSetSections(v) {
|
||
if (__pjiTarget && __pjiTarget.set) { __pjiTarget.set(v); return; }
|
||
state.pjiSections = v;
|
||
}
|
||
|
||
/** 将 MongoDB 返回的 [{Key,Value}] 或其他格式统一转为 [{k,v}] */
|
||
function normalizeKvPairs(data) {
|
||
if (!data) return [];
|
||
if (Array.isArray(data)) {
|
||
return data.map(function (item) {
|
||
if (item && typeof item === "object") {
|
||
// 支持 {k,v} 和 {Key,Value} 两种格式
|
||
if (item.Key !== undefined) return { k: String(item.Key), v: String(item.Value || "") };
|
||
if (item.k !== undefined) return { k: String(item.k), v: String(item.v || "") };
|
||
}
|
||
return { k: String(item), v: "" };
|
||
});
|
||
}
|
||
if (typeof data === "object") {
|
||
return Object.keys(data).map(function (k) { return { k: k, v: String(data[k] || "") }; });
|
||
}
|
||
return [];
|
||
}
|
||
|
||
/** 从数据值推断栏目类型 */
|
||
function inferSectionType(val) {
|
||
if (typeof val === "string") return "string";
|
||
if (Array.isArray(val)) {
|
||
if (val.length && typeof val[0] === "object" && val[0] !== null) return "list";
|
||
return "array";
|
||
}
|
||
if (typeof val === "object" && val !== null) return "object";
|
||
return "string";
|
||
}
|
||
|
||
/** 从接口返回的扁平数据还原栏目数组 */
|
||
function buildSectionsFromData(data) {
|
||
var result = [];
|
||
Object.keys(data).forEach(function (key) {
|
||
var val = data[key];
|
||
var type = inferSectionType(val);
|
||
result.push({ key: key, label: key, type: type, content: parseSectionContent(val, type) });
|
||
});
|
||
return result;
|
||
}
|
||
|
||
/** 解析栏目内容为编辑器所需格式 */
|
||
function parseSectionContent(content, type) {
|
||
if (content == null || content === "") return type === "string" ? "" : [];
|
||
if (type === "string") return typeof content === "string" ? content : JSON.stringify(content);
|
||
if (typeof content === "string") {
|
||
try { content = JSON.parse(content); } catch (e) { return type === "array" ? [content] : []; }
|
||
}
|
||
if (type === "object") return normalizeKvPairs(content);
|
||
if (type === "list") {
|
||
// list: 数组,每个元素是一组 [{k,v}] 或 [{Key,Value}]
|
||
if (Array.isArray(content)) {
|
||
return content.map(function (entry) { return normalizeKvPairs(entry); });
|
||
}
|
||
return typeof parseExample === "function" ? parseExample(content, "list") : [];
|
||
}
|
||
if (type === "array") return typeof parseExample === "function" ? parseExample(content, "array") : [];
|
||
return [];
|
||
}
|
||
|
||
/** 生成栏目内容编辑器 HTML */
|
||
function sectionContentHtml(type, content) {
|
||
if (type === "string") {
|
||
return '<input class="input pji-content" placeholder="\u8bf7\u8f93\u5165\u5185\u5bb9" value="' + Core.esc(content || "") + '">';
|
||
}
|
||
if (type === "object") {
|
||
var pairs = (Array.isArray(content) ? content : []).map(function (p) { return kvRowHtml(p.k, p.v); }).join("");
|
||
return '<div class="dr-kv-editor">' + pairs +
|
||
'<button type="button" class="btn ghost sm dr-kv-add">+ \u6dfb\u52a0\u5b57\u6bb5</button></div>';
|
||
}
|
||
if (type === "array") {
|
||
var items = (Array.isArray(content) ? content : []).map(function (v, i) {
|
||
return '<div class="dr-example-row"><input class="input sm dr-example-item" placeholder="\u6761\u76ee ' + (i + 1) + '" value="' + Core.esc(v || "") + '">' +
|
||
'<button type="button" class="btn danger sm dr-example-del" title="\u5220\u9664">\u2715</button></div>';
|
||
}).join("");
|
||
return '<div class="dr-example-multi">' + items +
|
||
'<button type="button" class="btn ghost sm dr-example-add">+ \u6dfb\u52a0\u6761\u76ee</button></div>';
|
||
}
|
||
if (type === "list") {
|
||
var entries = (Array.isArray(content) ? content : []).map(function (pairs, idx) {
|
||
var kvHtml = (Array.isArray(pairs) ? pairs : []).map(function (p) { return kvRowHtml(p.k, p.v); }).join("");
|
||
return '<div class="dr-list-entry" data-entry="' + idx + '">' +
|
||
'<div class="dr-list-entry-head"><span class="dr-list-entry-idx">#' + (idx + 1) + '</span>' +
|
||
'<button type="button" class="btn danger sm dr-list-entry-del" title="\u5220\u9664\u6574\u6761">\u2715</button></div>' +
|
||
kvHtml +
|
||
'<button type="button" class="btn ghost sm dr-kv-add">+ \u6dfb\u52a0\u5b57\u6bb5</button></div>';
|
||
}).join("");
|
||
return '<div class="dr-list-editor">' + entries +
|
||
'<button type="button" class="btn ghost sm dr-list-entry-add">+ \u6dfb\u52a0\u6761\u76ee</button></div>';
|
||
}
|
||
return "";
|
||
}
|
||
|
||
/** 将任意格式内容统一转为字符串表示 */
|
||
function contentToString(content, type) {
|
||
if (content == null || content === "") return "";
|
||
if (type === "string") return typeof content === "string" ? content : JSON.stringify(content);
|
||
if (type === "object") return normalizeKvPairs(content).map(function (p) { return p.k + ": " + p.v; }).join("\n");
|
||
if (type === "array") {
|
||
if (Array.isArray(content)) return content.map(function (v) { return String(v || ""); }).join("\n");
|
||
if (typeof content === "string") return content;
|
||
return String(content);
|
||
}
|
||
if (type === "list") {
|
||
if (Array.isArray(content)) return content.map(function (entry) { return normalizeKvPairs(entry).map(function (p) { return p.k + ": " + p.v; }).join(", "); }).join("\n");
|
||
return typeof content === "string" ? content : JSON.stringify(content);
|
||
}
|
||
return "";
|
||
}
|
||
|
||
/** 将字符串内容解析为目标类型格式 */
|
||
function stringToType(str, toType) {
|
||
if (str == null || str === "") return toType === "string" ? "" : [];
|
||
if (toType === "string") return str;
|
||
if (toType === "object") {
|
||
var lines = str.split("\n").filter(function (s) { return s.trim(); });
|
||
if (!lines.length) return [];
|
||
return lines.map(function (line) {
|
||
var idx = line.indexOf(":");
|
||
if (idx > 0) return { k: line.substring(0, idx).trim(), v: line.substring(idx + 1).trim() };
|
||
return { k: line.trim(), v: "" };
|
||
});
|
||
}
|
||
if (toType === "array") {
|
||
return str.split("\n").filter(function (s) { return s.trim(); });
|
||
}
|
||
if (toType === "list") {
|
||
var listLines = str.split("\n").filter(function (s) { return s.trim(); });
|
||
if (!listLines.length) return [];
|
||
return [listLines.map(function (line) {
|
||
var idx = line.indexOf(":");
|
||
if (idx > 0) return { k: line.substring(0, idx).trim(), v: line.substring(idx + 1).trim() };
|
||
return { k: line.trim(), v: "" };
|
||
})];
|
||
}
|
||
return [];
|
||
}
|
||
|
||
/** 将内容从一种类型转换为另一种类型(统一走字符串中转) */
|
||
function convertSectionContent(content, fromType, toType) {
|
||
if (fromType === toType) return content;
|
||
// 先统一转为字符串,再从字符串转为目标类型
|
||
var str = contentToString(content, fromType);
|
||
return stringToType(str, toType);
|
||
}
|
||
|
||
/** 从单个卡片 DOM 直接读取内容(不依赖 state) */
|
||
function readCardContentFromDom(card, type) {
|
||
if (!card) return type === "string" ? "" : [];
|
||
if (type === "string") {
|
||
var inp = $(".pji-content", card);
|
||
return inp ? inp.value : "";
|
||
}
|
||
if (type === "object") {
|
||
var obj = {};
|
||
$$(".dr-kv-row", card).forEach(function (r) {
|
||
var k = $(".dr-kv-key", r).value.trim();
|
||
var v = $(".dr-kv-val", r).value.trim();
|
||
if (k) obj[k] = v;
|
||
});
|
||
return obj;
|
||
}
|
||
if (type === "array") {
|
||
var arr = [];
|
||
$$(".dr-example-item", card).forEach(function (inp) {
|
||
var v = inp.value.trim();
|
||
if (v) arr.push(v);
|
||
});
|
||
return arr;
|
||
}
|
||
if (type === "list") {
|
||
var list = [];
|
||
$$(".dr-list-entry", card).forEach(function (entry) {
|
||
var obj = {};
|
||
$$(".dr-kv-row", entry).forEach(function (r) {
|
||
var k = $(".dr-kv-key", r).value.trim();
|
||
var v = $(".dr-kv-val", r).value.trim();
|
||
if (k) obj[k] = v;
|
||
});
|
||
if (Object.keys(obj).length) list.push(obj);
|
||
});
|
||
return list;
|
||
}
|
||
return type === "string" ? "" : [];
|
||
}
|
||
|
||
/** 从 DOM 同步栏目内容到当前数据源(防止重绘丢失数据) */
|
||
function syncPjiSectionsFromDom() {
|
||
var sections = pjiGetSections();
|
||
var cont = pjiContainer();
|
||
var cards = $$(".pji-section-card", cont || document);
|
||
if (!cards.length || !sections.length) return;
|
||
cards.forEach(function (card) {
|
||
var idx = Number(card.dataset.idx);
|
||
var sec = sections[idx];
|
||
if (!sec) return;
|
||
if (sec.type === "string") {
|
||
var inp = $(".pji-content", card);
|
||
if (inp) sec.content = inp.value;
|
||
} else if (sec.type === "object") {
|
||
var obj = {};
|
||
$$(".dr-kv-row", card).forEach(function (r) {
|
||
var k = $(".dr-kv-key", r).value.trim();
|
||
var v = $(".dr-kv-val", r).value.trim();
|
||
if (k) obj[k] = v;
|
||
});
|
||
sec.content = obj;
|
||
} else if (sec.type === "array") {
|
||
var arr = [];
|
||
$$(".dr-example-item", card).forEach(function (inp) {
|
||
var v = inp.value.trim();
|
||
if (v) arr.push(v);
|
||
});
|
||
sec.content = arr;
|
||
} else if (sec.type === "list") {
|
||
var list = [];
|
||
$$(".dr-list-entry", card).forEach(function (entry) {
|
||
var obj = {};
|
||
$$(".dr-kv-row", entry).forEach(function (r) {
|
||
var k = $(".dr-kv-key", r).value.trim();
|
||
var v = $(".dr-kv-val", r).value.trim();
|
||
if (k) obj[k] = v;
|
||
});
|
||
if (Object.keys(obj).length) list.push(obj);
|
||
});
|
||
sec.content = list;
|
||
}
|
||
});
|
||
}
|
||
|
||
/** 绘制所有栏目(渲染到当前上下文容器) */
|
||
function drawPjiSections() {
|
||
var container = pjiContainer();
|
||
if (!container) return;
|
||
// 重绘前先从 DOM 同步数据,防止已填内容丢失
|
||
syncPjiSectionsFromDom();
|
||
var sections = pjiGetSections();
|
||
if (!sections.length) {
|
||
container.innerHTML = '<div class="empty" style="padding:30px 0"><div class="big">\u2726</div>\u6682\u65e0\u680f\u76ee\uff0c\u70b9\u51fb\u4e0a\u65b9\u300c+ \u65b0\u589e\u680f\u76ee\u300d\u6dfb\u52a0</div>';
|
||
return;
|
||
}
|
||
var typeOptsHtml = PJI_TYPE_OPTIONS.map(function (o) {
|
||
return '<option value="' + o[0] + '">' + o[1] + '</option>';
|
||
}).join("");
|
||
var html = "";
|
||
sections.forEach(function (sec, idx) {
|
||
var content = parseSectionContent(sec.content, sec.type);
|
||
html += '<div class="pji-section-card" data-idx="' + idx + '">' +
|
||
'<div class="pji-section-head">' +
|
||
'<span class="pji-section-label" data-idx="' + idx + '" title="\u70b9\u51fb\u4fee\u6539\u540d\u79f0" style="cursor:pointer">' + Core.esc(sec.label) + '</span>' +
|
||
'<select class="select sm pji-type-select" data-idx="' + idx + '" style="width:110px">' + typeOptsHtml + '</select>' +
|
||
'<span class="spacer"></span>' +
|
||
'<button class="btn danger sm pji-del" data-idx="' + idx + '">\u5220\u9664</button>' +
|
||
"</div>" +
|
||
'<div class="pji-section-body">' + sectionContentHtml(sec.type, content) + "</div>" +
|
||
"</div>";
|
||
});
|
||
container.innerHTML = html;
|
||
// 设置下拉框当前值
|
||
$$(".pji-type-select", container).forEach(function (sel) {
|
||
sel.value = sections[Number(sel.dataset.idx)].type;
|
||
});
|
||
bindPjiSectionEvents();
|
||
}
|
||
|
||
/** 绑定栏目内事件(增删改) */
|
||
function bindPjiSectionEvents() {
|
||
var container = pjiContainer();
|
||
if (!container) return;
|
||
var sections = pjiGetSections();
|
||
|
||
// 点击栏目名称 → 内联编辑
|
||
$$(".pji-section-label", container).forEach(function (span) {
|
||
span.onclick = function () {
|
||
var idx = Number(span.dataset.idx);
|
||
var sec = sections[idx];
|
||
if (!sec) return;
|
||
// 先同步当前 DOM 数据
|
||
syncPjiSectionsFromDom();
|
||
var input = document.createElement("input");
|
||
input.className = "input sm";
|
||
input.style.cssText = "width:160px;padding:2px 8px";
|
||
input.value = sec.label;
|
||
span.replaceWith(input);
|
||
input.focus();
|
||
input.select();
|
||
var finish = function () {
|
||
var newLabel = input.value.trim();
|
||
if (newLabel) {
|
||
sec.label = newLabel;
|
||
// 同步更新 key(存储字段名)
|
||
sec.key = newLabel.replace(/[^a-zA-Z0-9\u4e00-\u9fff]/g, "").replace(/^[0-9]+/, "") || ("sec_" + Date.now());
|
||
}
|
||
pjiSetSections(sections);
|
||
drawPjiSections();
|
||
};
|
||
input.onblur = finish;
|
||
input.onkeydown = function (e) { if (e.key === "Enter") { e.preventDefault(); finish(); } if (e.key === "Escape") { pjiSetSections(sections); drawPjiSections(); } };
|
||
};
|
||
});
|
||
|
||
// 删除栏目
|
||
$$(".pji-del", container).forEach(function (btn) {
|
||
btn.onclick = function () {
|
||
var idx = Number(btn.dataset.idx);
|
||
Core.confirm("\u786e\u5b9a\u5220\u9664\u680f\u76ee\u300c" + sections[idx].label + "\u300d\uff1f").then(function (ok) {
|
||
if (!ok) return;
|
||
sections.splice(idx, 1);
|
||
pjiSetSections(sections);
|
||
drawPjiSections();
|
||
});
|
||
};
|
||
});
|
||
|
||
// 下拉框切换类型(保留内容转换)
|
||
$$(".pji-type-select", container).forEach(function (sel) {
|
||
sel.onchange = function () {
|
||
var idx = Number(sel.dataset.idx);
|
||
var sec = sections[idx];
|
||
if (!sec) return;
|
||
var oldType = sec.type;
|
||
var newType = sel.value;
|
||
if (oldType === newType) return;
|
||
// 直接从当前卡片 DOM 读取内容(不走 syncPjiSectionsFromDom,避免干扰)
|
||
var card = sel.closest(".pji-section-card");
|
||
var domContent = readCardContentFromDom(card, oldType);
|
||
// 转换内容格式
|
||
sec.content = convertSectionContent(domContent, oldType, newType);
|
||
sec.type = newType;
|
||
pjiSetSections(sections);
|
||
drawPjiSections();
|
||
};
|
||
});
|
||
|
||
// 每个栏目卡片内的编辑器事件
|
||
$$(".pji-section-card", container).forEach(function (card) {
|
||
var idx = Number(card.dataset.idx);
|
||
var sec = sections[idx];
|
||
if (!sec || sec.type === "string") return;
|
||
|
||
if (sec.type === "object") {
|
||
// KV 添加字段
|
||
$$(".dr-kv-add", card).forEach(function (b) {
|
||
b.onclick = function () {
|
||
b.insertAdjacentHTML("beforebegin", kvRowHtml("", ""));
|
||
};
|
||
});
|
||
// KV 删除字段
|
||
card.addEventListener("click", function (e) {
|
||
if (e.target.classList.contains("dr-kv-del")) {
|
||
e.target.closest(".dr-kv-row").remove();
|
||
}
|
||
});
|
||
}
|
||
|
||
if (sec.type === "array") {
|
||
// 添加条目
|
||
$$(".dr-example-add", card).forEach(function (b) {
|
||
b.onclick = function () {
|
||
var wrap = b.closest(".dr-example-multi");
|
||
var count = $$(".dr-example-item", wrap).length;
|
||
var div = document.createElement("div");
|
||
div.className = "dr-example-row";
|
||
div.innerHTML = '<input class="input sm dr-example-item" placeholder="\u6761\u76ee ' + (count + 1) + '" value="">' +
|
||
'<button type="button" class="btn danger sm dr-example-del" title="\u5220\u9664">\u2715</button>';
|
||
wrap.insertBefore(div, b);
|
||
};
|
||
});
|
||
// 删除条目
|
||
card.addEventListener("click", function (e) {
|
||
if (e.target.classList.contains("dr-example-del")) {
|
||
e.target.closest(".dr-example-row").remove();
|
||
}
|
||
});
|
||
}
|
||
|
||
if (sec.type === "list") {
|
||
// 添加条目
|
||
$$(".dr-list-entry-add", card).forEach(function (b) {
|
||
b.onclick = function () {
|
||
var editor = b.closest(".dr-list-editor");
|
||
var lastIdx = $$(".dr-list-entry", editor).length;
|
||
var div = document.createElement("div");
|
||
div.className = "dr-list-entry";
|
||
div.dataset.entry = lastIdx;
|
||
div.innerHTML = '<div class="dr-list-entry-head"><span class="dr-list-entry-idx">#' + (lastIdx + 1) + '</span>' +
|
||
'<button type="button" class="btn danger sm dr-list-entry-del" title="\u5220\u9664\u6574\u6761">\u2715</button></div>' +
|
||
kvRowHtml("", "") +
|
||
'<button type="button" class="btn ghost sm dr-kv-add">+ \u6dfb\u52a0\u5b57\u6bb5</button>';
|
||
editor.insertBefore(div, b);
|
||
// 绑定新条目内的事件
|
||
$$(".dr-kv-add", div).forEach(function (kvAdd) {
|
||
kvAdd.onclick = function () { kvAdd.insertAdjacentHTML("beforebegin", kvRowHtml("", "")); };
|
||
});
|
||
};
|
||
});
|
||
// 删除条目
|
||
card.addEventListener("click", function (e) {
|
||
if (e.target.classList.contains("dr-list-entry-del")) {
|
||
e.target.closest(".dr-list-entry").remove();
|
||
}
|
||
});
|
||
// 条目内 KV 添加(委托,排除非 list 区域)
|
||
card.addEventListener("click", function (e) {
|
||
if (e.target.classList.contains("dr-kv-add") && !e.target.classList.contains("dr-list-entry-add")) {
|
||
var entry = e.target.closest(".dr-list-entry");
|
||
if (entry) { e.preventDefault(); e.stopPropagation(); e.target.insertAdjacentHTML("beforebegin", kvRowHtml("", "")); }
|
||
}
|
||
});
|
||
// 条目内 KV 删除
|
||
card.addEventListener("click", function (e) {
|
||
if (e.target.classList.contains("dr-kv-del")) {
|
||
var entry = e.target.closest(".dr-list-entry");
|
||
if (entry) { e.preventDefault(); e.stopPropagation(); e.target.closest(".dr-kv-row").remove(); }
|
||
}
|
||
});
|
||
}
|
||
});
|
||
}
|
||
|
||
/** 收集所有栏目数据用于保存 */
|
||
function collectPjiSectionsData() {
|
||
var sections = pjiGetSections();
|
||
var cont = pjiContainer();
|
||
var result = [];
|
||
$$(".pji-section-card", cont || document).forEach(function (card) {
|
||
var idx = Number(card.dataset.idx);
|
||
var sec = sections[idx];
|
||
if (!sec) return;
|
||
var content;
|
||
if (sec.type === "string") {
|
||
var inp = $(".pji-content", card);
|
||
content = inp ? inp.value : "";
|
||
} else if (sec.type === "object") {
|
||
content = {};
|
||
$$(".dr-kv-row", card).forEach(function (r) {
|
||
var k = $(".dr-kv-key", r).value.trim();
|
||
var v = $(".dr-kv-val", r).value.trim();
|
||
if (k) content[k] = v;
|
||
});
|
||
} else if (sec.type === "array") {
|
||
content = [];
|
||
$$(".dr-example-item", card).forEach(function (inp) {
|
||
var v = inp.value.trim();
|
||
if (v) content.push(v);
|
||
});
|
||
} else if (sec.type === "list") {
|
||
content = [];
|
||
$$(".dr-list-entry", card).forEach(function (entry) {
|
||
var obj = {};
|
||
$$(".dr-kv-row", entry).forEach(function (r) {
|
||
var k = $(".dr-kv-key", r).value.trim();
|
||
var v = $(".dr-kv-val", r).value.trim();
|
||
if (k) obj[k] = v;
|
||
});
|
||
if (Object.keys(obj).length) content.push(obj);
|
||
});
|
||
}
|
||
result.push({ key: sec.key, label: sec.label, type: sec.type, content: content });
|
||
});
|
||
return result;
|
||
}
|
||
|
||
/** 新增栏目对话框 */
|
||
function addPjiSectionDialog() {
|
||
var typeOpts = PJI_TYPE_OPTIONS.map(function (o) {
|
||
return '<option value="' + o[0] + '">' + o[1] + "</option>";
|
||
}).join("");
|
||
var m = Core.modal({
|
||
title: "\u65b0\u589e\u680f\u76ee",
|
||
body:
|
||
'<div class="field"><label class="label">\u680f\u76ee\u540d\u79f0<span class="req">*</span></label><input id="ns-label" class="input" placeholder="\u5982\uff1a\u5468\u8fb9\u914d\u5957\u3001\u5b66\u533a\u4fe1\u606f"></div>' +
|
||
'<div class="field"><label class="label">\u5185\u5bb9\u683c\u5f0f</label><select id="ns-type" class="select">' + typeOpts + '</select></div>' +
|
||
'<div class="fs-12 c-dim">\u680f\u76ee\u6807\u8bc6\u7b26\u5c06\u6839\u636e\u540d\u79f0\u81ea\u52a8\u751f\u6210\uff0c\u5185\u5bb9\u683c\u5f0f\u4e0e\u7ef4\u5ea6\u683c\u5f0f\u4e00\u81f4\u3002</div>',
|
||
footer: '<button class="btn ghost mf-cancel">\u53d6\u6d88</button><button class="btn primary mf-ok">\u6dfb\u52a0</button>'
|
||
});
|
||
$(".mf-cancel", m.root).onclick = m.close;
|
||
$(".mf-ok", m.root).onclick = function () {
|
||
var label = $("#ns-label", m.root).value.trim();
|
||
if (!label) { Core.toast("\u8bf7\u8f93\u5165\u680f\u76ee\u540d\u79f0", "err"); return; }
|
||
var type = $("#ns-type", m.root).value;
|
||
var key = label.replace(/[^a-zA-Z0-9\u4e00-\u9fff]/g, "").replace(/^[0-9]+/, "") || ("sec_" + Date.now());
|
||
var sections = pjiGetSections();
|
||
sections.push({ key: key, label: label, type: type, content: type === "string" ? "" : [] });
|
||
pjiSetSections(sections);
|
||
m.close();
|
||
drawPjiSections();
|
||
Core.toast("\u680f\u76ee\u300c" + label + "\u300d\u5df2\u6dfb\u52a0", "ok");
|
||
};
|
||
}
|
||
|
||
/** AI 预设项目资料对话框 */
|
||
function aiPresetDialog() {
|
||
var base = state.detail.Base || {};
|
||
// 拉取模型配置列表
|
||
Core.api("modelsup/list", { page: 1, page_size: 50 }).then(function (data) {
|
||
var sups = (data && data.list) || [];
|
||
if (sups.length === 0) {
|
||
Core.toast("尚未配置模型提供方,请先到「模型配置」添加", "err");
|
||
return;
|
||
}
|
||
var options = sups.map(function (s) {
|
||
var selected = (s.sup_id === base.model_sup_id) ? " selected" : "";
|
||
return '<option value="' + s.sup_id + '"' + selected + '>#' + s.sup_id + " " + Core.esc(s.sup_name) + " · " + Core.esc(s.json_model || "") + "</option>";
|
||
}).join("");
|
||
|
||
var m = Core.modal({
|
||
title: "✦ AI 预设项目资料",
|
||
width: "wide",
|
||
body:
|
||
'<div class="card-sub" style="margin-bottom:12px">AI 将根据项目名称和描述,自动生成 2~4 个项目资料栏目。生成后可在编辑器中继续调整。</div>' +
|
||
'<div class="field"><label class="label">模型配置<span class="req">*</span></label><select id="aip-sup" class="select">' + options + '</select></div>' +
|
||
'<div class="grid cols-2">' +
|
||
'<div class="field"><label class="label">项目名称</label><input class="input" disabled value="' + Core.esc(base.name || "") + '"></div>' +
|
||
'<div class="field"><label class="label">项目描述</label><input class="input" disabled value="' + Core.esc((base.desc || "").substring(0, 80)) + '"></div>' +
|
||
'</div>' +
|
||
'<div class="field"><label class="label">生成结果 <span class="c-dim fs-12">(AI 生成的栏目数据预览)</span></label>' +
|
||
'<textarea id="aip-result" class="textarea json" style="min-height:220px" placeholder="点击「开始生成」,AI 将根据项目信息自动生成资料栏目"></textarea></div>',
|
||
footer: '<button class="btn ghost mf-close">关闭</button>' +
|
||
'<button class="btn primary mf-gen">⟳ 开始生成</button>' +
|
||
'<button class="btn success mf-apply" disabled>应用到项目资料</button>'
|
||
});
|
||
var resultEl = $("#aip-result", m.root);
|
||
$(".mf-close", m.root).onclick = m.close;
|
||
$(".mf-gen", m.root).onclick = function () {
|
||
var supId = Number($("#aip-sup", m.root).value);
|
||
if (!supId) { Core.toast("请选择模型配置", "err"); return; }
|
||
Core.loading(true, "AI 生成中,请稍候…");
|
||
Core.api("project/data/generate", { projectId: state.project.projectId, supId: supId })
|
||
.then(function (data) {
|
||
Core.loading(false);
|
||
// data 是后端返回的展平 map,直接格式化显示
|
||
resultEl.value = Core.prettyJson(data);
|
||
$(".mf-apply", m.root).disabled = !resultEl.value.trim();
|
||
Core.toast("生成完成", "ok");
|
||
})
|
||
.catch(function (e) { Core.loading(false); Core.toast(e.message, "err"); });
|
||
};
|
||
$(".mf-apply", m.root).onclick = function () {
|
||
var content = resultEl.value.trim();
|
||
if (!content) return;
|
||
try {
|
||
var dataObj = JSON.parse(content);
|
||
// 将展平数据转为栏目数组
|
||
var newSections = buildSectionsFromData(dataObj);
|
||
if (!newSections.length) {
|
||
Core.toast("未解析到有效栏目", "err");
|
||
return;
|
||
}
|
||
// 先同步当前 DOM 数据,再追加新栏目
|
||
syncPjiSectionsFromDom();
|
||
var existing = state.pjiSections || [];
|
||
state.pjiSections = existing.concat(newSections);
|
||
drawPjiSections();
|
||
m.close();
|
||
Core.toast("已添加 " + newSections.length + " 个栏目,请检查后保存", "ok");
|
||
} catch (e) {
|
||
Core.toast("解析生成结果失败:" + e.message, "err");
|
||
}
|
||
};
|
||
}).catch(function (e) { Core.toast(e.message, "err"); });
|
||
}
|