/* ==================== 视图二:项目设置(tab 切换) ==================== */
function renderProjectDetail() {
if (!state.project || !state.project.projectId) { switchView("projects"); return; }
$("#view-projectDetail").innerHTML = '
';
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 = '\u26a0
' + Core.esc(e.message) + "
";
});
}
/* ---------- 主 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 =
'' +
'
\u9879\u76ee\u8bbe\u7f6e #' + (base.project_id || state.project.projectId) + "
";
// ===== Tab 栏 =====
html += '
';
PD_TABS.forEach(function (t) {
html += '
' + t[1] + "
";
});
html += "
";
// ===== Tab 1:基础信息 =====
html += '
';
// 项目基本信息
html +=
'
\u9879\u76ee\u4fe1\u606f' +
'
' +
'
' +
'
' +
"
";
// 行业模板应用
html +=
'
' +
'
\u884c\u4e1a\u6a21\u677f
' +
'
\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
' +
'
' +
'
';
// 维度子 tab
html += '
';
html += '
\u7ef4\u5ea6\u6a21\u677f
';
html += '
';
DIM_NAMES.forEach(function (d, i) {
var label = d[1].replace(/ \u6a21\u677f$/, "");
html += '
' + label + "
";
});
html += "
";
// 维度内容区
DIM_NAMES.forEach(function (d, i) {
var hidden = i === 0 ? "" : ' style="display:none"';
html += '
";
if (d[0] === "desc" || d[0] === "advicer_desc") {
html += '
' +
'
";
} else if (d[0] === "rule_dimension") {
html += '
' +
'
";
} else if (hasDimEditor) {
var dimLabel = d[1].replace(/ \u6a21\u677f$/, "");
html += dimBlock(dimLabel, base[d[0]] || "");
} else {
html += '
' +
'
";
}
html += "
";
});
html += "
"; // end pd-tab-base
// ===== Tab 2:项目资料(动态编辑,初始无栏目) =====
html += '
';
html += '
\u9879\u76ee\u8d44\u6599' +
'' +
'' +
'' +
'' +
'
';
html += '
\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
';
html += '
';
html += '
'; // end pd-tab-projectInfo
// ===== Tab 3:设置(模型配置 + 微信接入 Token) =====
html += '
';
html += '
\u5fae\u4fe1\u63a5\u5165\u914d\u7f6e' +
'
' +
'
\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
' +
'
' +
'
' +
'
' +
'\u00b7 \u8be5 Token \u7528\u4e8e\u5bf9\u63a5\u5fae\u4fe1\u534f\u8bae\u670d\u52a1\uff0c\u9879\u76ee\u7ea7\u5171\u4eab\uff1b
' +
'\u00b7 \u8fdb\u5165\u5fae\u4fe1\u5de5\u4f5c\u53f0\u524d\u4f1a\u68c0\u6d4b\u9879\u76ee\u662f\u5426\u5df2\u914d\u7f6e Token\uff1b
' +
'\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
' +
'
' +
'
\u6a21\u578b\u914d\u7f6e
' +
'
' +
'
' +
'
' +
'
' +
'
' +
'
'; // end pd-tab-settings
html += "
"; // 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("")
: '\u6682\u65e0\u5b57\u6bb5\u5b9a\u4e49
';
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 '';
}
if (type === "object") {
var pairs = (Array.isArray(content) ? content : []).map(function (p) { return kvRowHtml(p.k, p.v); }).join("");
return '' + pairs +
'
';
}
if (type === "array") {
var items = (Array.isArray(content) ? content : []).map(function (v, i) {
return '' +
'
';
}).join("");
return '' + items +
'
';
}
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 '' +
'
#' + (idx + 1) + '' +
'
' +
kvHtml +
'
';
}).join("");
return '' + entries +
'
';
}
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 = '\u2726
\u6682\u65e0\u680f\u76ee\uff0c\u70b9\u51fb\u4e0a\u65b9\u300c+ \u65b0\u589e\u680f\u76ee\u300d\u6dfb\u52a0
';
return;
}
var typeOptsHtml = PJI_TYPE_OPTIONS.map(function (o) {
return '';
}).join("");
var html = "";
sections.forEach(function (sec, idx) {
var content = parseSectionContent(sec.content, sec.type);
html += '' +
'
' +
'' + Core.esc(sec.label) + '' +
'' +
'' +
'' +
"
" +
'
' + sectionContentHtml(sec.type, content) + "
" +
"
";
});
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 = '' +
'';
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 = '#' + (lastIdx + 1) + '' +
'
' +
kvRowHtml("", "") +
'';
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 '";
}).join("");
var m = Core.modal({
title: "\u65b0\u589e\u680f\u76ee",
body:
'' +
'' +
'\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
',
footer: ''
});
$(".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 '";
}).join("");
var m = Core.modal({
title: "✦ AI 预设项目资料",
width: "wide",
body:
'AI 将根据项目名称和描述,自动生成 2~4 个项目资料栏目。生成后可在编辑器中继续调整。
' +
'' +
'' +
'' +
'
',
footer: '' +
'' +
''
});
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"); });
}