+
${[
["P0 客户数", m.p0_customers, "sales"],
["跟进中销售机会", m.active_sales, "sales"],
@@ -83,11 +105,11 @@ function renderHome() {
["即将上线版本", m.upcoming_products, "products"],
].map(([label, value, tab]) => ``).join("")}
-
- ${card(`
财务趋势
${badge("YYYY-MM")}`, "p-5")}
- ${card(`
风险提醒
${(summary.risks.length ? summary.risks : [{ title: "暂无高风险", content: "当前无明确阻塞,按周更新即可。" }]).map((r) => `
`).join("")}
`, "p-5")}
+
+ ${card(`
财务趋势
${badge("YYYY-MM")}`, "p-4")}
+ ${card(`
风险提醒
${(summary.risks.length ? summary.risks : [{ title: "暂无高风险", content: "当前无明确阻塞,按周更新即可。" }]).map((r) => `
`).join("")}
`, "p-5")}
- ${card(`
近期动态
${summary.recent.map((r) => `
${r.content}${r.followed_at}
`).join("")}
`, "p-5")}
+ ${card(`
近期动态
${summary.recent.map((r) => `
${r.content}${r.followed_at}
`).join("")}
`, "p-5")}
`;
renderChart(financeMonthly);
@@ -108,7 +130,7 @@ function renderChart(data) {
{ label: "净利", data: data.map((x) => x.net_profit), borderColor: "#7c3aed", tension: 0.3 },
],
},
- options: { responsive: true, plugins: { legend: { position: "bottom" } } },
+ options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { position: "bottom", labels: { boxWidth: 12, font: { size: 11 } } } }, scales: { x: { ticks: { font: { size: 11 } }, grid: { display: false } }, y: { ticks: { font: { size: 11 } } } } },
});
}
@@ -250,7 +272,7 @@ function renderChartOn(id, data) {
{ label: "净利", data: data.map((x) => x.net_profit), borderColor: "#7c3aed", tension: 0.3 },
],
},
- options: { responsive: true, plugins: { legend: { position: "bottom" } } },
+ options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { position: "bottom", labels: { boxWidth: 12, font: { size: 11 } } } }, scales: { x: { ticks: { font: { size: 11 } }, grid: { display: false } }, y: { ticks: { font: { size: 11 } } } } },
});
}
@@ -296,12 +318,24 @@ function openDrawer(resource, id) {
${resource === "proposals" ? `
方案文件
${["方案","成本","SOP","财务流程"].map((cat) => fileGroup("proposal", item.id, item.version, cat, item.files.filter((f) => f.file_category === cat))).join("")}
` : ""}
${followupTarget ? `
活动 / 跟进
- ${(item.followups || []).map((f) => `
${f.follower} · ${f.follow_up_method}${f.followed_at}
${f.content}
${f.next_action ? `
下一步:${text(f.next_action)}
` : ""}
`).join("")}
+ ${(item.followups || []).map((f) => `
${f.follower} · ${f.follow_up_method}${f.followed_at}
${f.next_action ? `
下一步:${text(f.next_action)}
` : ""}
`).join("")}
@@ -310,6 +344,28 @@ function openDrawer(resource, id) {
drawer.classList.add("open");
bindDrawerAutosave(resource, item.id, item);
if (window.lucide) window.lucide.createIcons();
+ // Decode and render rich HTML content in followup records
+ drawer.querySelectorAll(".rich-content").forEach((el) => {
+ const html = el.dataset.html;
+ if (html) el.innerHTML = decodeURIComponent(html);
+ });
+ // Initialize Squire editor
+ const squireDiv = drawer.querySelector(".squire-editor");
+ if (squireDiv && window.Squire) {
+ const id = squireDiv.id;
+ if (window.squireInstances[id]) window.squireInstances[id].destroy();
+ const sq = new Squire(squireDiv, { blockTag: "P" });
+ sq.addEventListener("input", () => {
+ const form = squireDiv.closest("form");
+ const btn = form.querySelector(".comment-submit");
+ });
+ window.squireInstances[id] = sq;
+ // Handle placeholder
+ squireDiv.addEventListener("focus", () => squireDiv.classList.add("focused"));
+ squireDiv.addEventListener("blur", () => {
+ if (!squireDiv.textContent.trim()) squireDiv.classList.remove("focused");
+ });
+ }
}
function setDrawerSaveStatus(message, tone = "muted") {
@@ -352,12 +408,36 @@ function bindDrawerAutosave(resource, id, item) {
window.openDrawer = openDrawer;
window.closeDrawer = () => document.querySelector("#drawer").classList.remove("open");
+window.squireInstances = {};
+window.squireCmd = (cmd) => {
+ const currentEditor = document.querySelector(".squire-editor");
+ if (!currentEditor) return;
+ const id = currentEditor.id;
+ const sq = window.squireInstances[id];
+ if (!sq) return;
+ sq.focus();
+ setTimeout(() => {
+ if (cmd === "bold") {
+ sq.hasFormat("b") || sq.hasFormat("strong") ? sq.removeBold() : sq.bold();
+ } else if (cmd === "italic") {
+ sq.hasFormat("i") || sq.hasFormat("em") ? sq.removeItalic() : sq.italic();
+ } else if (cmd === "underline") {
+ sq.hasFormat("u") ? sq.changeFormat(null, { tag: "u" }, null) : sq.changeFormat({ tag: "u" }, null, null);
+ } else if (cmd === "strikethrough") {
+ sq.hasFormat("s") || sq.hasFormat("del") || sq.hasFormat("strike") ? sq.changeFormat(null, { tag: "s" }, null) : sq.changeFormat({ tag: "s" }, null, null);
+ } else {
+ sq[cmd]();
+ }
+ }, 10);
+};
+
window.submitComment = async (event, targetType, targetId, resource) => {
event.preventDefault();
const form = event.currentTarget;
- const editor = form.querySelector("trix-editor");
- const content = editor.editor.getDocument().toString().trim();
- if (!content) return;
+ const editorDiv = form.querySelector(".squire-editor");
+ const sq = window.squireInstances[editorDiv.id];
+ const content = sq ? sq.getHTML().trim() : "";
+ if (!content || content === "
" || content === "
") return;
const button = form.querySelector(".comment-submit");
button.disabled = true;
button.textContent = "发送中…";
@@ -374,10 +454,6 @@ window.deleteFollowup = async (event, followupId, resource, targetId) => {
openDrawer(resource, targetId);
};
-document.querySelector("#drawer").addEventListener("click", (event) => {
- if (event.target === event.currentTarget) closeDrawer();
-});
-
document.querySelector("#tabs").addEventListener("click", (event) => {
const button = event.target.closest("button[data-tab]");
if (button) switchTab(button.dataset.tab);
diff --git a/static/styles.css b/static/styles.css
index 2d3d984..014fb65 100644
--- a/static/styles.css
+++ b/static/styles.css
@@ -157,13 +157,14 @@ td {
box-shadow: -18px 0 45px rgba(15, 23, 42, 0.14);
height: 100vh;
overflow-y: auto;
- width: 560px;
+ width: 720px;
}
.file-link {
color: #1d4ed8;
font-size: 12px;
font-weight: 700;
+ white-space: nowrap;
}
.drawer-section-title {
@@ -313,14 +314,50 @@ td {
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.08);
}
-/* Trix editor inside comment box */
-.comment-trix {
- min-height: 80px;
+/* Squire editor */
+.squire-toolbar {
+ align-items: center;
+ background: #f8fafc;
+ border-bottom: 1px solid #e2e8f0;
+ display: flex;
+ gap: 2px;
+ padding: 5px 6px;
}
-.comment-trix trix-editor {
+.squire-btn {
+ align-items: center;
+ background: transparent;
+ border: none;
+ border-radius: 4px;
+ color: #475569;
+ cursor: pointer;
+ display: inline-flex;
+ height: 28px;
+ justify-content: center;
+ transition: background 0.15s;
+ width: 28px;
+}
+
+.squire-btn:hover {
+ background: #e2e8f0;
+ color: #0f172a;
+}
+
+.squire-btn [data-lucide] {
+ height: 15px;
+ width: 15px;
+}
+
+.squire-sep {
+ background: #e2e8f0;
+ display: inline-block;
+ height: 18px;
+ margin: 0 2px;
+ width: 1px;
+}
+
+.squire-editor {
border: 0;
- border-radius: 0;
font-size: 13px;
line-height: 1.55;
min-height: 80px;
@@ -328,30 +365,32 @@ td {
outline: none;
}
-.comment-trix trix-toolbar {
- border: 0;
- border-top: 1px solid #e2e8f0;
- background: #f8fafc;
- padding: 6px 6px;
+.squire-editor p {
+ margin: 0;
+ min-height: 1em;
}
-.comment-trix trix-toolbar .trix-button {
- border-radius: 4px;
- padding: 3px 5px;
- font-size: 12px;
- background: transparent;
- border-color: transparent;
+.squire-editor ul,
+.squire-editor ol {
+ list-style: revert;
+ padding-left: 24px;
+ margin: 4px 0;
+}
+.squire-editor ul {
+ list-style-type: disc;
+}
+.squire-editor ol {
+ list-style-type: decimal;
+}
+.squire-editor li {
+ margin: 2px 0;
}
-.comment-trix trix-toolbar .trix-button:hover,
-.comment-trix trix-toolbar .trix-button.trix-active {
- background: #e2e8f0;
- color: #1e293b;
-}
-
-.comment-trix trix-toolbar .trix-button-group {
- border-color: #e2e8f0;
- margin-right: 4px;
+.squire-editor blockquote {
+ border-left: 3px solid #e2e8f0;
+ color: #64748b;
+ margin: 4px 0;
+ padding-left: 10px;
}
.comment-toolbar {
@@ -414,26 +453,37 @@ td {
width: 14px;
}
/* Trix content in activity items */
-.activity-item .trix-content {
+.activity-item .rich-content {
font-size: 13px;
line-height: 1.55;
}
-.activity-item .trix-content div {
+.activity-item .rich-content div {
font-size: 13px;
}
-.activity-item .trix-content strong {
+.activity-item .rich-content strong {
font-weight: 600;
}
-.activity-item .trix-content a {
+.activity-item .rich-content a {
color: #1d4ed8;
text-decoration: underline;
}
-.activity-item .trix-content ul,
-.activity-item .trix-content ol {
- padding-left: 16px;
- margin: 4px 0;
+.activity-item .rich-content ul,
+.activity-item .rich-content ol {
+ padding-left: 24px;
+ list-style: revert;
}
-.activity-item .trix-content blockquote {
+.activity-item .rich-content ul {
+ list-style-type: disc;
+}
+.activity-item .rich-content ol {
+ list-style-type: decimal;
+}
+.activity-item .rich-content ul,
+.activity-item .rich-content ol,
+.activity-item .rich-content li {
+ margin: 2px 0;
+}
+.activity-item .rich-content blockquote {
border-left: 3px solid #e2e8f0;
padding-left: 10px;
color: #64748b;
diff --git a/templates/index.html b/templates/index.html
index f5dbfa7..cce25da 100644
--- a/templates/index.html
+++ b/templates/index.html
@@ -22,9 +22,8 @@
-
+
-