@@ -2,6 +2,7 @@ const state = {
active : "home" ,
active : "home" ,
data : null ,
data : null ,
opFilter : "all" ,
opFilter : "all" ,
projectView : null ,
chart : null ,
chart : null ,
chart2 : null ,
chart2 : null ,
productPlatform : "all" ,
productPlatform : "all" ,
@@ -170,56 +171,171 @@ window.createSales = (event) => createResource(event, "sales");
window . createProposal = ( event ) => createResource ( event , "proposals" ) ;
window . createProposal = ( event ) => createResource ( event , "proposals" ) ;
window . createOperation = ( event ) => createResource ( event , "operations" ) ;
window . createOperation = ( event ) => createResource ( event , "operations" ) ;
window . createProduct = ( event ) => createResource ( event , "products" ) ;
window . createProduct = ( event ) => createResource ( event , "products" ) ;
window . openTaskForm = ( projectId , taskId ) => {
const drawer = document . querySelector ( ` #task-drawer- ${ projectId } ` ) ;
const titleEl = drawer . querySelector ( ".task-drawer-title" ) ;
if ( taskId === null ) {
document . querySelector ( ` #task-id- ${ projectId } ` ) . value = "" ;
document . querySelector ( ` #task-name- ${ projectId } ` ) . value = "" ;
document . querySelector ( ` #task-phase- ${ projectId } ` ) . value = "商务洽谈" ;
document . querySelector ( ` #task-owner- ${ projectId } ` ) . value = "" ;
document . querySelector ( ` #task-due- ${ projectId } ` ) . value = "" ;
document . querySelector ( ` #task-notes- ${ projectId } ` ) . value = "" ;
document . querySelector ( ` #task-blockers- ${ projectId } ` ) . value = "" ;
document . querySelector ( ` #task-submit-btn- ${ projectId } ` ) . textContent = "确认新增" ;
if ( titleEl ) titleEl . textContent = "新增任务" ;
} else {
const task = ( state . data . tasks || [ ] ) . find ( ( t ) => t . id === taskId ) ;
if ( ! task ) return ;
document . querySelector ( ` #task-id- ${ projectId } ` ) . value = task . id ;
document . querySelector ( ` #task-name- ${ projectId } ` ) . value = task . task || "" ;
document . querySelector ( ` #task-phase- ${ projectId } ` ) . value = task . phase || "商务洽谈" ;
document . querySelector ( ` #task-owner- ${ projectId } ` ) . value = task . owner || "" ;
document . querySelector ( ` #task-due- ${ projectId } ` ) . value = task . due _date || "" ;
document . querySelector ( ` #task-notes- ${ projectId } ` ) . value = task . notes || "" ;
document . querySelector ( ` #task-blockers- ${ projectId } ` ) . value = task . blockers || "" ;
document . querySelector ( ` #task-submit-btn- ${ projectId } ` ) . textContent = "保存修改" ;
if ( titleEl ) titleEl . textContent = "编辑任务" ;
}
drawer . classList . add ( "open" ) ;
} ;
window . closeTaskDrawer = ( projectId ) => {
document . querySelector ( ` #task-drawer- ${ projectId } ` ) . classList . remove ( "open" ) ;
} ;
window . submitTaskForm = async ( event , projectId ) => {
event . preventDefault ( ) ;
const data = Object . fromEntries ( new FormData ( event . currentTarget ) . entries ( ) ) ;
data . project _id = Number ( projectId ) ;
const taskId = data . task _id ;
delete data . task _id ;
try {
if ( taskId ) {
await api ( ` /api/tasks/ ${ taskId } ` , { method : "PUT" , body : JSON . stringify ( { data } ) } ) ;
} else {
await api ( "/api/tasks" , { method : "POST" , body : JSON . stringify ( { data } ) } ) ;
}
await load ( ) ;
} catch ( error ) {
alert ( "保存失败:" + error . message ) ;
}
} ;
window . createFinance = ( event ) => createResource ( event , "finance" ) ;
window . createFinance = ( event ) => createResource ( event , "finance" ) ;
window . switchTab = switchTab ;
window . switchTab = switchTab ;
function renderProjects ( ) {
function renderProjects ( ) {
// Merge sales_leads and operation_projects into one table
// 二级页面:项目任务详情
const salesItems = state . data . sales . map ( ( x ) => ( {
if ( state . projectView ) {
name : x . target _customer ,
return renderProjectTasks ( state . projectView ) ;
version : "" ,
}
type : "opportunity" ,
const items = state . data . operations ;
status : x . status ,
const rows = items . map ( ( x ) => [
amount : 0 ,
` <strong> ${ x . project _name } </strong> ` ,
stage : "" ,
text ( x . customer _need || x . notes ) ,
files : 0 ,
badge ( x . current _stage || x . project _status ) ,
followup : x . latest _follow _up _record ,
x . expected _contract _amount ? money ( x . expected _contract _amount ) : "—" ,
resource : "sales" ,
text ( x . owner || "—" ) ,
id : x . id ,
` <button class="btn btn-ghost btn-sm text-blue-600" onclick="event.stopPropagation(); state.projectView= ${ x . id } ; renderProjects()"><i data-lucide="eye"></i>查看</button> `
} ) ) ;
] ) ;
const opItems = state . data . operations . map ( ( x ) => ( {
name : x . project _name ,
version : x . project _version ,
type : x . project _type ,
status : x . project _status ,
amount : x . expected _contract _amount || 0 ,
stage : x . current _stage || x . sop _stage ,
files : x . files . length ,
followup : x . latest _follow _up _record ,
resource : "operations" ,
id : x . id ,
} ) ) ;
const allItems = [ ... salesItems , ... opItems ] ;
const items = state . opFilter === "all" ? allItems : allItems . filter ( ( x ) => x . type === state . opFilter || ( state . opFilter === "opportunity" && x . type === "opportunity" ) ) ;
const rows = items . map ( ( x ) => [ ` <strong> ${ x . name } </strong> ${ x . version ? ` <p class="text-xs text-slate-500"> ${ x . version } </p> ` : "" } ` , badge ( x . type ) , badge ( x . status ) , x . amount ? money ( x . amount ) : "—" , text ( x . stage ) , text ( x . followup ) ] ) ;
const clicks = items . map ( ( x ) => ( { resource : x . resource , id : x . id } ) ) ;
document . querySelector ( "#projects" ) . innerHTML = ` <div class="grid gap-4">
document . querySelector ( "#projects" ) . innerHTML = ` <div class="grid gap-4">
${ card ( formHtml ( [
<div class="flex items-center justify-end">
{ label : "业务机会" , input : ` <input name="target_customer" required placeholder="客户名称"> ` } ,
<button class="btn btn-primary" onclick="document.querySelector('#project-form').classList.toggle('hidden')">
{ label: "优先级", input: ` < select name = "priority" > < option > P0 < / o p t i o n > < o p t i o n s e l e c t e d > P 1 < / o p t i o n > < o p t i o n > P 2 < / o p t i o n > < o p t i o n > P 3 < / o p t i o n > < / s e l e c t > ` } ,
<i data-lucide="plus"></i>新增项目
{ label : "状态" , input : ` <select name="status"><option>待跟进</option><option>跟进中</option><option>方案中</option><option>商务谈判</option><option>已签约</option><option>暂缓</option><option>已丢单</option></select> ` } ,
</button>
] , { handler : "createSales" , text : ` <i data-lucide="plus"></i>新增业务机会 ` } ) , "p-4" ) }
</div>
<div id="project-form" class="hidden">
${ card ( formHtml ( [
${ card ( formHtml ( [
{ label : "项目名称" , input : ` <input name="project_name" required> ` } ,
{ label : "项目名称" , input : ` <input name="project_name" required> ` } ,
{ label : "项目版本" , input : ` <input name="project_version" value="v1.0" > ` } ,
{ label: "当前阶段", input: ` < select name = "current_stage" > < option > 商务洽谈 < / o p t i o n > < o p t i o n > 系 统 上 线 < / o p t i o n > < o p t i o n > 团 队 分 工 < / o p t i o n > < o p t i o n > 项 目 交 付 < / o p t i o n > < o p t i o n > 上 线 推 广 < / o p t i o n > < o p t i o n > 结 项 验 收 < / o p t i o n > < / s e l e c t > ` } ,
{ label : "项目类型 " , input : ` <selec t name="project_type"><option value="opportunity">业务机会项目</option><option value="execution">已签约执行项目</option></select > ` } ,
{ label : "项目金额 " , input : ` <inpu t name="expected_contract_amount" type="number" step="0.01" placeholder="万元" > ` } ,
{ label : "状态 " , input : ` <input name="project_status" value="线索发现 "> ` } ,
{ label : "负责人 " , input : ` <input name="owner "> ` } ,
] , { handler : "createOperation" , text : "新增项目 " } ) , "p-4" ) }
] , { handler : "createOperation" , text : "确认 新增" } ) , "p-4" ) }
< div class = "flex gap-2" > $ { [ [ "all" , "全部" ] , [ "opportunity" , "业务机会" ] , [ "execution" , "已签约执行" ] ] . map ( ( [ k , v ] ) => ` <button class="btn ${ state . opFilter === k ? "btn-primary" : "btn-ghost" } " onclick="state.opFilter=' ${ k } '; renderProjects()"> ${ v } </button> ` ) . join ( "" ) } < / d i v >
< / d i v >
$ { renderTable ( [ "项目/客户 " , "类型 " , "状态 " , "金额" , "当前阶段 " , "最新跟 进" ] , rows , clicks ) }
$ { renderTable ( [ "项目" , "项目说明 " , "当前阶段 " , "项目 金额" , "负责人 " , "进展 " ] , rows , items . map ( ( x ) => ( { resource : "operations" , id : x . id } ) ) ) }
< / d i v > ` ;
< / d i v > ` ;
if ( window . lucide ) window . lucide . createIcons ( ) ;
}
}
function renderProjectTasks ( projectId ) {
const project = state . data . operations . find ( ( x ) => x . id === projectId ) ;
if ( ! project ) { state . projectView = null ; renderProjects ( ) ; return ; }
const tasks = ( state . data . tasks || [ ] ) . filter ( ( t ) => t . project _id === projectId ) ;
const phases = [ "商务洽谈" , "系统上线" , "团队分工" , "项目交付" , "上线推广" , "结项验收" ] ;
document . querySelector ( "#projects" ) . innerHTML = ` <div class="grid gap-4">
<div class="flex items-center justify-between">
<button class="btn btn-ghost btn-sm" onclick="state.projectView=null;renderProjects()"><i data-lucide="arrow-left"></i>返回项目列表</button>
<div class="flex items-center gap-3">
<span class="text-sm font-semibold text-slate-700"> ${ project . project _name } </span>
<button class="btn btn-primary btn-sm" onclick="openTaskForm( ${ projectId } , null)"><i data-lucide="plus"></i>新增任务</button>
</div>
</div>
<div class="task-page-wrap">
<div class="task-body">
${ phases . map ( ( phase ) => {
const pt = tasks . filter ( ( t ) => t . phase === phase ) ;
if ( ! pt . length ) return "" ;
return ` <div class="task-group"><div class="task-group-hd"><span class="task-group-icon"><i data-lucide="layers"></i></span><span class="task-group-label"> ${ phase } </span><span class="task-group-n"> ${ pt . length } </span></div><div class="task-group-list"> ${ pt . map ( ( t ) => ` <div class="task-row" data-id=" ${ t . id } " onclick="openTaskForm( ${ projectId } , ${ t . id } )"><span class="task-dot"><i data-lucide=" ${ t . status === 'done' ? 'check-circle' : 'circle' } "></i></span><div class="task-main"><span class="task-name"> ${ t . task } </span> ${ t . notes ? ` <span class="task-desc"> ${ t . notes } </span> ` : "" } ${ t . blockers ? ` <span class="task-blocker">⚠ ${ t . blockers } </span> ` : "" } </div><span class="task-col"> ${ t . owner || "" } </span><span class="task-col-badge"> ${ t . due _date || "" } </span></div> ` ) . join ( "" ) } </div></div> ` ;
} ).join("")}
</div>
<div id="task-drawer- ${ projectId } " class="task-drawer">
<div class="task-drawer-hd"><span class="task-drawer-title">编辑任务</span><button class="task-close" onclick="closeTaskDrawer( ${ projectId } )"><i data-lucide="x"></i></button></div>
<form class="task-drawer-form" onsubmit="submitTaskForm(event, ${ projectId } )">
<input type="hidden" name="task_id" id="task-id- ${ projectId } " value="">
<label class="task-field"><span>任务名称</span><input name="task" required id="task-name- ${ projectId } "></label>
<label class="task-field"><span>任务阶段</span><select name="phase" id="task-phase- ${ projectId } "> ${ phases . map ( ( p ) => ` <option> ${ p } </option> ` ) . join ( "" ) } </select></label>
<label class="task-field"><span>负责人</span><input name="owner" id="task-owner- ${ projectId } "></label>
<label class="task-field"><span>截止时间</span><input name="due_date" type="date" id="task-due- ${ projectId } "></label>
<label class="task-field"><span>任务说明</span><textarea name="notes" rows="3" id="task-notes- ${ projectId } "></textarea></label>
<label class="task-field"><span>卡点&备注</span><textarea name="blockers" rows="2" id="task-blockers- ${ projectId } " placeholder="风险卡点、依赖项等"></textarea></label>
<div class="flex justify-end gap-2 mt-4 pt-3 border-t border-slate-100">
<button type="button" class="btn btn-ghost btn-sm" onclick="closeTaskDrawer( ${ projectId } )">取消</button>
<button type="submit" class="btn btn-primary btn-sm" id="task-submit-btn- ${ projectId } ">确认新增</button>
</div>
</form>
</div>
</div>
</div> ` ;
if ( window . lucide ) window . lucide . createIcons ( ) ;
}
function showTaskModal ( projectId ) {
const project = state . data . operations . find ( ( x ) => x . id === projectId ) ;
const tasks = ( state . data . tasks || [ ] ) . filter ( ( t ) => t . project _id === projectId ) ;
const phases = [ "商务洽谈" , "系统上线" , "团队分工" , "项目交付" , "上线推广" , "结项验收" ] ;
document . querySelector ( "#taskModal" ) . innerHTML = ` <div class="task-overlay" onclick="closeTaskModal()"><div class="task-panel" onclick="event.stopPropagation()"><div class="task-header"><h2 class="task-title"> ${ project . project _name } · 任务清单</h2><div class="flex items-center gap-3"><button class="btn btn-primary btn-sm" onclick="event.stopPropagation(); openTaskForm( ${ projectId } , null)"><i data-lucide="plus"></i>新增任务</button><button class="task-close" onclick="closeTaskModal()"><i data-lucide="x"></i></button></div></div><div class="task-body-wrap">
<div class="task-body">
${ phases . map ( ( phase ) => {
const pt = tasks . filter ( ( t ) => t . phase === phase ) ;
if ( ! pt . length ) return "" ;
return ` <div class="task-group"><div class="task-group-hd"><span class="task-group-icon"><i data-lucide="layers"></i></span><span class="task-group-label"> ${ phase } </span><span class="task-group-n"> ${ pt . length } </span></div><div class="task-group-list"> ${ pt . map ( ( t ) => ` <div class="task-row" data-id=" ${ t . id } " onclick="event.stopPropagation(); openTaskForm( ${ projectId } , ${ t . id } )"><span class="task-dot"><i data-lucide=" ${ t . status === 'done' ? 'check-circle' : 'circle' } "></i></span><div class="task-main"><span class="task-name"> ${ t . task } </span> ${ t . notes ? ` <span class="task-desc"> ${ t . notes } </span> ` : "" } ${ t . blockers ? ` <span class="task-blocker">⚠ ${ t . blockers } </span> ` : "" } </div><span class="task-col"> ${ t . owner || "" } </span><span class="task-col-badge"> ${ t . due _date || "" } </span></div> ` ) . join ( "" ) } </div></div> ` ;
} ).join("")}
</div>
<div id="task-drawer- ${ projectId } " class="task-drawer">
<div class="task-drawer-hd"><span class="task-drawer-title">编辑任务</span><button class="task-close" onclick="closeTaskDrawer( ${ projectId } )"><i data-lucide="x"></i></button></div>
<form class="task-drawer-form" onsubmit="submitTaskForm(event, ${ projectId } )">
<input type="hidden" name="task_id" id="task-id- ${ projectId } " value="">
<label class="task-field"><span>任务名称</span><input name="task" required id="task-name- ${ projectId } "></label>
<label class="task-field"><span>任务阶段</span><select name="phase" id="task-phase- ${ projectId } "> ${ phases . map ( ( p ) => ` <option> ${ p } </option> ` ) . join ( "" ) } </select></label>
<label class="task-field"><span>负责人</span><input name="owner" id="task-owner- ${ projectId } "></label>
<label class="task-field"><span>截止时间</span><input name="due_date" type="date" id="task-due- ${ projectId } "></label>
<label class="task-field"><span>任务说明</span><textarea name="notes" rows="3" id="task-notes- ${ projectId } "></textarea></label>
<label class="task-field"><span>卡点&备注</span><textarea name="blockers" rows="2" id="task-blockers- ${ projectId } " placeholder="风险卡点、依赖项等"></textarea></label>
<div class="flex justify-end gap-2 mt-4 pt-3 border-t border-slate-100">
<button type="button" class="btn btn-ghost btn-sm" onclick="closeTaskDrawer( ${ projectId } )">取消</button>
<button type="submit" class="btn btn-primary btn-sm" id="task-submit-btn- ${ projectId } ">确认新增</button>
</div>
</form>
</div>
</div></div></div> ` ;
document . querySelector ( "#taskModal" ) . classList . add ( "active" ) ;
if ( window . lucide ) window . lucide . createIcons ( ) ;
}
window . closeTaskModal = ( ) => {
document . querySelector ( "#taskModal" ) . classList . remove ( "active" ) ;
document . querySelector ( "#taskModal" ) . innerHTML = "" ;
} ;
function renderProposals ( ) {
function renderProposals ( ) {
const proposalRows = state . data . proposals . map ( ( p ) => [ p . customer _or _project _name , p . version , badge ( p . status ) , p . files . length + " 个" ] ) ;
const proposalRows = state . data . proposals . map ( ( p ) => [ p . customer _or _project _name , p . version , badge ( p . status ) , p . files . length + " 个" ] ) ;
const proposalClicks = state . data . proposals . map ( ( p ) => ( { resource : "proposals" , id : p . id } ) ) ;
const proposalClicks = state . data . proposals . map ( ( p ) => ( { resource : "proposals" , id : p . id } ) ) ;
@@ -332,14 +448,14 @@ function openDrawer(resource, id) {
const fields = resource === "sales"
const fields = resource === "sales"
? [ [ "target_customer" , "业务机会" ] , [ "priority" , "优先级" ] , [ "status" , "状态" ] ]
? [ [ "target_customer" , "业务机会" ] , [ "priority" , "优先级" ] , [ "status" , "状态" ] ]
: resource === "operations"
: resource === "operations"
? [ [ "project_name" , "项目名称" ] , [ "project_version" , "项目版本" ] , [ "project_status " , "项目状态 " ] , [ "current_stage" , "当前阶段" ] , [ "target_customer" , "业务机会" ] , [ "customer_need " , "客户需求 " ] , [ "expected_contract_amount" , "预计签约 金额" ] , [ "expected_sign_date" , "预计签约时间" ] , [ "sign_probability" , "签约概率" ] , [ "sop_stage" , "SOP 阶段" ] , [ "execution_progress" , "执行进度" ] , [ "current_deliverable" , "当前交付物" ] , [ "risks" , "风险与阻塞" ] , [ "next_action" , "下一步动作 " ] ]
? [ [ "project_name" , "项目名称" ] , [ "owner " , "负责人 " ] , [ "current_stage" , "当前阶段" ] , [ "expected_sign_date " , "截止时间 " ] , [ "expected_contract_amount" , "金额" ] , [ "notes" , "项目说明 " ] ]
: resource === "proposals"
: resource === "proposals"
? [ [ "customer_or_project_name" , "客户/项目" ] , [ "version" , "版本号" ] , [ "description" , "版本说明" ] , [ "created_date" , "创建日期" ] , [ "status" , "状态" ] ]
? [ [ "customer_or_project_name" , "客户/项目" ] , [ "version" , "版本号" ] , [ "description" , "版本说明" ] , [ "created_date" , "创建日期" ] , [ "status" , "状态" ] ]
: [ [ "product_name" , "产品名称" ] , [ "version" , "版本号" ] , [ "version_goal" , "版本目标" ] , [ "feature_list" , "核心功能清单" ] , [ "platform" , "平台" ] , [ "launch_date" , "上线日期" ] , [ "status" , "当前状态" ] , [ "notes" , "备注" ] ] ;
: [ [ "product_name" , "产品名称" ] , [ "version" , "版本号" ] , [ "version_goal" , "版本目标" ] , [ "feature_list" , "核心功能清单" ] , [ "platform" , "平台" ] , [ "launch_date" , "上线日期" ] , [ "status" , "当前状态" ] , [ "notes" , "备注" ] ] ;
const fieldIcons = {
const fieldIcons = {
target _customer : "user" , priority : "flag" , status : "circle-dot" ,
target _customer : "user" , priority : "flag" , status : "circle-dot" ,
project _name : "briefcase-business" , project _version : "git-branch" , project _status : "circle-dot" , current _stage : "map-pin" ,
project _name : "briefcase-business" , project _version : "git-branch" , project _status : "circle-dot" , current _stage : "map-pin" ,
customer _need : "file-text" , expected _contract _amount : "banknote" , expected _sign _date : "calendar" ,
owner : "user" , customer_need : "file-text" , expected _contract _amount : "banknote" , expected _sign _date : "calendar" ,
sign _probability : "percent" , sop _stage : "list-checks" , execution _progress : "activity" ,
sign _probability : "percent" , sop _stage : "list-checks" , execution _progress : "activity" ,
current _deliverable : "package" , risks : "alert-triangle" , next _action : "arrow-right" ,
current _deliverable : "package" , risks : "alert-triangle" , next _action : "arrow-right" ,
product _name : "box" , version : "tag" , version _goal : "target" , feature _list : "list" , platform : "layers" ,
product _name : "box" , version : "tag" , version _goal : "target" , feature _list : "list" , platform : "layers" ,
@@ -354,20 +470,6 @@ function openDrawer(resource, id) {
<form id="drawerForm" class="drawer-fields"> ${ fields . map ( ( [ key , label ] ) => drawerField ( fieldIcons [ key ] || "circle" , label , key , item [ key ] , multilineFields . includes ( key ) ) ) . join ( "" ) } </form>
<form id="drawerForm" class="drawer-fields"> ${ fields . map ( ( [ key , label ] ) => drawerField ( fieldIcons [ key ] || "circle" , label , key , item [ key ] , multilineFields . includes ( key ) ) ) . join ( "" ) } </form>
</section>
</section>
${ resource === "proposals" ? ` <section><h3 class="drawer-section-title">方案文件</h3><div class="grid gap-2"> ${ [ "方案" , "成本" , "SOP" , "财务流程" ] . map ( ( cat ) => fileGroup ( "proposal" , item . id , item . version , cat , item . files . filter ( ( f ) => f . file _category === cat ) ) ) . join ( "" ) } </div></section> ` : "" }
${ resource === "proposals" ? ` <section><h3 class="drawer-section-title">方案文件</h3><div class="grid gap-2"> ${ [ "方案" , "成本" , "SOP" , "财务流程" ] . map ( ( cat ) => fileGroup ( "proposal" , item . id , item . version , cat , item . files . filter ( ( f ) => f . file _category === cat ) ) ) . join ( "" ) } </div></section> ` : "" }
${ resource === "operations" ? ( ( ) => {
const tasks = ( state . data . tasks || [ ] ) . filter ( ( t ) => t . project _id === id ) ;
if ( ! tasks . length ) return "" ;
const phases = [ ... new Set ( tasks . map ( ( t ) => t . phase ) ) ] ;
return ` <section><h3 class="drawer-section-title">项目任务</h3><div class="grid gap-3"> ${ phases . map ( ( phase ) => {
const pt = tasks . filter ( ( t ) => t . phase === phase ) ;
return ` <div class="rounded-md border border-slate-200 p-3"><p class="text-[13px] font-semibold text-slate-700 mb-2"><i data-lucide="layers" style="width:14px;height:14px;display:inline;vertical-align:-2px;margin-right:4px"></i> ${ phase } </p><div class="grid gap-1.5"> ${ pt . map ( ( t ) => {
const due = t . due _date ? ` <span class="text-[11px] text-slate-400 ml-1">📅 ${ t . due _date } </span> ` : "" ;
const owner = t . owner ? ` <span class="text-[11px] text-slate-500 ml-1">👤 ${ t . owner } </span> ` : "" ;
const blocker = t . blockers ? ` <p class="text-[11px] text-red-600 mt-0.5">⚠ ${ t . blockers } </p> ` : "" ;
return ` <div class="rounded bg-slate-50 px-2.5 py-1.5"><p class="text-[12px] text-slate-800"><strong> ${ t . milestone ? t . milestone + ": " : "" } </strong> ${ t . task } ${ due } ${ owner } </p> ${ blocker } </div> ` ;
} ).join("")}</div></div> ` ;
} ).join("")}</div></section> ` ;
} )() : ""}
${ followupTarget ? ` <section>
${ followupTarget ? ` <section>
<h3 class="drawer-section-title">活动 / 跟进</h3>
<h3 class="drawer-section-title">活动 / 跟进</h3>
<div class="grid gap-2"> ${ ( item . followups || [ ] ) . map ( ( f ) => ` <div class="activity-item"><div class="activity-icon"><i data-lucide="message-square"></i></div><div class="min-w-0 flex-1"><div class="flex justify-between gap-3 text-[12px] text-slate-500"><span> ${ f . follower } · ${ f . follow _up _method } </span><span> ${ f . followed _at } </span></div><div class="mt-1 leading-5 text-slate-800 rich-content" data-html=" ${ encodeURIComponent ( f . content || '' ) } "></div> ${ f . next _action ? ` <p class="mt-1 leading-5 text-blue-700">下一步: ${ text ( f . next _action ) } </p> ` : "" } </div><button class="activity-delete" onclick="deleteFollowup(event, ${ f . id } , ' ${ resource } ', ${ item . id } )" title="删除评论"><i data-lucide="trash-2"></i></button></div> ` ) . join ( "" ) } </div>
<div class="grid gap-2"> ${ ( item . followups || [ ] ) . map ( ( f ) => ` <div class="activity-item"><div class="activity-icon"><i data-lucide="message-square"></i></div><div class="min-w-0 flex-1"><div class="flex justify-between gap-3 text-[12px] text-slate-500"><span> ${ f . follower } · ${ f . follow _up _method } </span><span> ${ f . followed _at } </span></div><div class="mt-1 leading-5 text-slate-800 rich-content" data-html=" ${ encodeURIComponent ( f . content || '' ) } "></div> ${ f . next _action ? ` <p class="mt-1 leading-5 text-blue-700">下一步: ${ text ( f . next _action ) } </p> ` : "" } </div><button class="activity-delete" onclick="deleteFollowup(event, ${ f . id } , ' ${ resource } ', ${ item . id } )" title="删除评论"><i data-lucide="trash-2"></i></button></div> ` ) . join ( "" ) } </div>