This commit is contained in:
@@ -84,7 +84,9 @@ function renderExpense() {
|
||||
EXPENSE_TYPES.forEach(function(t) { modal += '<option>' + t + '</option>'; });
|
||||
modal += '</select></label><label class="block"><span class="text-xs text-slate-500 mb-1 block">月份 <span class="text-red-500">*</span></span><select name="expense_month" required class="form-ctrl bg-white">' + monthOpts + '</select></label></div><div class="grid grid-cols-2 gap-3"><label class="block"><span class="text-xs text-slate-500 mb-1 block">金额(元)</span><input name="amount" type="number" step="0.01" class="form-ctrl" placeholder="0.00"></label><label class="block"><span class="text-xs text-slate-500 mb-1 block">已发生金额(元)</span><input name="incurred_amount" type="number" step="0.01" class="form-ctrl" placeholder="0.00"></label></div><label class="block"><span class="text-xs text-slate-500 mb-1 block">费用说明</span><textarea name="notes" class="form-ctrl" rows="3" placeholder="备注信息"></textarea></label><div class="flex justify-end gap-3 pt-2"><button type="button" class="btn btn-ghost btn-sm px-6" onclick="closeExpenseModal()">取消</button><button type="submit" class="btn btn-primary btn-sm px-8">保存</button></div></form></div></div>';
|
||||
|
||||
document.querySelector("#expense").innerHTML = '<div class="grid gap-4">' +
|
||||
var target = document.querySelector("#financeExpense") || document.querySelector("#financeTabExpense") || document.querySelector("#expense");
|
||||
if (!target) return;
|
||||
target.innerHTML = '<div class="grid gap-4">' +
|
||||
'<div class="card p-3"><div class="flex justify-between items-center"><div class="flex items-center gap-2">' + toolbar + '</div><button class="btn btn-primary btn-sm" onclick="openExpenseModal()">新增费用</button></div></div>' +
|
||||
'<div class="grid grid-cols-3 gap-3">' +
|
||||
'<div class="metric-card p-3"><span class="flex items-center gap-2 text-xs text-slate-500">费用记录</span><strong class="mt-2 block text-xl">' + filtered.length + '</strong></div>' +
|
||||
|
||||
@@ -116,11 +116,11 @@ function renderFinance() {
|
||||
const finHeaderBase = `<span class="text-xs text-slate-500">视图:</span><select onchange="setFinView(this.value)" class="text-xs font-medium py-1 px-2.5 cursor-pointer" style="appearance:none;-webkit-appearance:none;background-color:transparent;border:0;outline:none;background:url('data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 width=%2212%22 height=%2212%22 viewBox=%220 0 24 24%22 fill=%22none%22 stroke=%22%236b7280%22 stroke-width=%222%22 stroke-linecap=%22round%22 stroke-linejoin=%22round%22%3E%3Cpolyline points=%226 9 12 15 18 9%22%3E%3C/polyline%3E%3C/svg%3E') no-repeat right 4px center;padding-right:22px;min-height:30px"><option value="overview" ${state.finView==='overview'?'selected':''}>总视图</option><option value="quarterly" ${state.finView==='quarterly'?'selected':''}>季度视图</option><option value="monthly" ${state.finView==='monthly'?'selected':''}>月度视图</option></select>${toolDateSelect}`;
|
||||
const finAddBtn = `<button class="btn btn-primary btn-sm" onclick="openFinanceModal()">新增财务项目</button>`;
|
||||
|
||||
const finFilterTabs = `<div class="finance-tabs" style="padding:0;border-bottom:1px solid #e2e8f0;margin-bottom:0"><button onclick="switchFinFilter('已签约')" class="finance-tab${state.finFilter==='已签约'?' active':''}" style="font-size:14px;font-weight:600">已签约 (${pfs.filter(x=>x.status==='已签约').length})</button><button onclick="switchFinFilter('待签约')" class="finance-tab${state.finFilter==='待签约'?' active':''}" style="font-size:14px;font-weight:600">待签约 (${pfs.filter(x=>x.status==='待签约').length})</button></div>`;
|
||||
const finFilterTabs = `<div class="finance-tabs" style="padding:0;border-bottom:1px solid #e2e8f0;margin-bottom:0"><button onclick="switchFinFilter('已签约')" class="finance-tab${state.finFilter==='已签约'?' active':''}" style="font-size:14px;font-weight:600">已签约 (${pfs.filter(x=>x.status==='已签约').length})</button><button onclick="switchFinFilter('待签约')" class="finance-tab${state.finFilter==='待签约'?' active':''}" style="font-size:14px;font-weight:600">待签约 (${pfs.filter(x=>x.status==='待签约').length})</button><button onclick="switchFinFilter('expense')" class="finance-tab${state.finFilter==='expense'?' active':''}" style="font-size:14px;font-weight:600">平台费用</button></div>`;
|
||||
|
||||
document.querySelector("#finance").innerHTML = `<div class="grid gap-4">
|
||||
${finFilterTabs}
|
||||
${card(`<div class="flex justify-between items-center"><div class="flex items-center gap-2">${finHeaderBase}</div>${finAddBtn}</div>`, "p-4")}
|
||||
${state.finFilter !== 'expense' ? card(`<div class="flex justify-between items-center"><div class="flex items-center gap-2">${finHeaderBase}</div>${finAddBtn}</div>`, "p-4") : ''}
|
||||
<div id="financeModal" class="hidden fixed inset-0 z-50 flex items-center justify-center bg-black/40" onclick="closeFinanceModal()"><div class="bg-white rounded-2xl shadow-2xl w-full max-w-6xl mx-4 max-h-[92vh] overflow-y-auto" onclick="event.stopPropagation()"><div class="sticky top-0 z-10 bg-white/95 backdrop-blur border-b border-slate-100 px-8 py-5 flex items-center justify-between"><div><h3 class="text-xl font-bold text-slate-800" id="financeModalTitle">新增项目财务</h3><p class="text-xs text-slate-400 mt-0.5">填写项目财务信息与月度预算</p></div><div class="flex items-center gap-2"><button class="btn btn-ghost btn-sm text-red-600 hidden" id="financeDeleteBtn" onclick="deleteFinanceItem()"><i data-lucide="trash-2"></i>删除</button><button class="btn btn-ghost btn-sm rounded-full w-8 h-8 p-0" onclick="closeFinanceModal()"><i data-lucide="x"></i></button></div></div>
|
||||
<div class="finance-tabs">
|
||||
<button class="finance-tab active" data-tab="info" onclick="switchFinanceTab('info')"><i data-lucide="info" style="width:14px;height:14px;display:inline-block;vertical-align:-2px;margin-right:4px"></i>基本信息</button>
|
||||
@@ -237,6 +237,10 @@ function renderFinance() {
|
||||
</div>
|
||||
</div><div class="flex justify-end gap-3 pt-2 finance-form-actions"><button type="button" class="btn btn-ghost btn-sm px-6" onclick="closeFinanceModal()">取消</button><button type="submit" class="btn btn-primary btn-sm px-8">保存</button></div></form></div></div>
|
||||
${(() => {
|
||||
if (state.finFilter === 'expense') {
|
||||
setTimeout(() => renderExpense(), 10);
|
||||
return `<div id="financeExpense"></div>`;
|
||||
}
|
||||
const isUnsigned = state.finFilter === '待签约';
|
||||
const METRIC_CARDS = [
|
||||
{ label: '签约金额', value: ctx => moneyWan(ctx.signTotal), color: 'text-slate-700' },
|
||||
|
||||
124
static/modules/performance.js
Normal file
124
static/modules/performance.js
Normal file
@@ -0,0 +1,124 @@
|
||||
// performance.js — 绩效模块
|
||||
window.renderPerformance = () => {
|
||||
const data = state.data;
|
||||
if (!data) return;
|
||||
|
||||
if (state.perfQuarter === undefined) state.perfQuarter = Math.floor(new Date().getMonth() / 3);
|
||||
const q = state.perfQuarter;
|
||||
const qLabel = ['Q1 (1-3月)', 'Q2 (4-6月)', 'Q3 (7-9月)', 'Q4 (10-12月)'];
|
||||
const qRanges = [[1,2,3],[4,5,6],[7,8,9],[10,11,12]];
|
||||
const qRange = qRanges[q];
|
||||
const storageKey = 'opc-performance-Q' + (q + 1);
|
||||
|
||||
const pfs = (data.projectFinances || []).filter(p => p.status === '已签约');
|
||||
|
||||
const sumQ = (field) => {
|
||||
let total = 0;
|
||||
pfs.forEach(p => {
|
||||
try { JSON.parse(p.budget_data || '[]').forEach(b => { const m = parseInt((b.month || '').substring(5)) || 0; if (qRange.includes(m)) total += parseFloat(b[field] || 0); }); } catch (e) {}
|
||||
});
|
||||
return Math.round(total);
|
||||
};
|
||||
|
||||
const signTotal = Math.round(pfs.filter(x => { const m = parseInt((x.sign_month || '0').substring(5)) || 0; return qRange.includes(m); }).reduce((a, p) => a + (p.sign_amount || 0), 0));
|
||||
const revTotal = sumQ('rev');
|
||||
const grossTotal = sumQ('gross');
|
||||
const paymentTotal = sumQ('payment');
|
||||
|
||||
const rows = [
|
||||
{ key: 'sign', label: '签约', complete: signTotal, icon: 'file-text', defWeight: 10 },
|
||||
{ key: 'rev', label: '收入', complete: revTotal, icon: 'dollar-sign', defWeight: 20 },
|
||||
{ key: 'gross', label: '毛利', complete: grossTotal, icon: 'trending-up', defWeight: 50 },
|
||||
{ key: 'payment', label: '回款', complete: paymentTotal, icon: 'coins', defWeight: 20 },
|
||||
];
|
||||
|
||||
const saved = JSON.parse(localStorage.getItem(storageKey) || '{}');
|
||||
|
||||
function val(key, field, def) {
|
||||
const r = saved[key] || {};
|
||||
return r[field] !== undefined && r[field] !== '' ? Number(r[field]) : def;
|
||||
}
|
||||
|
||||
function moneyWan(v) {
|
||||
if (v >= 10000) return (v / 10000).toFixed(1) + '万';
|
||||
return v.toLocaleString();
|
||||
}
|
||||
|
||||
let totalScore = 0;
|
||||
const tableRows = rows.map(r => {
|
||||
const targetWan = val(r.key, 'targetWan', 0);
|
||||
const target = Math.round(targetWan * 10000);
|
||||
const weight = val(r.key, 'weight', r.defWeight);
|
||||
const score = target > 0 ? Math.round((r.complete / target) * weight * 10) / 10 : 0;
|
||||
totalScore += score;
|
||||
|
||||
return `<tr class="border-b border-slate-100">
|
||||
<td class="p-3 align-middle text-left font-medium text-slate-700"><i data-lucide="${r.icon}" style="width:16px;height:16px;display:inline-block;vertical-align:-3px;margin-right:6px" class="text-blue-500"></i>${r.label}</td>
|
||||
<td class="p-3 align-middle text-center">
|
||||
<div class="inline-flex items-center gap-1">
|
||||
<input type="number" step="0.1" min="0" value="${targetWan.toFixed(1)}" class="perf-target form-ctrl form-ctrl-sm w-20 text-center" style="appearance:none;-webkit-appearance:none" data-key="${r.key}" onchange="updatePerfScore('${r.key}','targetWan',this.value)">
|
||||
<span class="text-xs text-slate-400">万</span>
|
||||
</div>
|
||||
</td>
|
||||
<td class="p-3 align-middle text-center text-slate-700 font-mono text-sm">${moneyWan(r.complete)}</td>
|
||||
<td class="p-3 align-middle text-center">
|
||||
<div class="inline-flex justify-center">
|
||||
<input type="number" step="0.1" min="0" max="100" value="${weight}" class="perf-weight form-ctrl form-ctrl-sm w-16 text-center" style="appearance:none;-webkit-appearance:none" data-key="${r.key}" onchange="updatePerfScore('${r.key}','weight',this.value)">
|
||||
</div>
|
||||
</td>
|
||||
<td class="p-3 align-middle text-center font-semibold text-blue-700 text-sm">${score.toFixed(1)}</td>
|
||||
</tr>`;
|
||||
}).join('');
|
||||
|
||||
const quarterTabs = `<div class="finance-tabs" style="padding:0;border-bottom:1px solid #e2e8f0;margin-bottom:0">` + qLabel.map((l, i) => {
|
||||
return `<button onclick="switchPerfQuarter(${i})" class="finance-tab${i === q ? ' active' : ''}" style="font-size:14px;font-weight:600">${l}</button>`;
|
||||
}).join('') + `</div>`;
|
||||
|
||||
document.querySelector("#performance").innerHTML = `
|
||||
<style>
|
||||
.perf-target::-webkit-outer-spin-button,
|
||||
.perf-target::-webkit-inner-spin-button,
|
||||
.perf-weight::-webkit-outer-spin-button,
|
||||
.perf-weight::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
|
||||
.perf-target, .perf-weight { -moz-appearance: textfield; }
|
||||
</style>
|
||||
<div class="grid gap-4">
|
||||
${quarterTabs}
|
||||
<section class="card p-4">
|
||||
<table class="w-full text-sm">
|
||||
<thead><tr class="bg-slate-50 border-b border-slate-200">
|
||||
<th class="p-3 text-left font-semibold text-slate-500">指标</th>
|
||||
<th class="p-3 text-center font-semibold text-slate-500">目标(万)</th>
|
||||
<th class="p-3 text-center font-semibold text-slate-500">完成情况</th>
|
||||
<th class="p-3 text-center font-semibold text-slate-500">权重</th>
|
||||
<th class="p-3 text-center font-semibold text-slate-500">评分</th>
|
||||
</tr></thead>
|
||||
<tbody>${tableRows}</tbody>
|
||||
<tfoot><tr class="border-t-2 border-slate-200 bg-slate-50 font-semibold">
|
||||
<td class="p-3 text-left text-slate-700">合计</td>
|
||||
<td class="p-3 text-center text-slate-500">—</td>
|
||||
<td class="p-3 text-center text-slate-500">—</td>
|
||||
<td class="p-3 text-center text-slate-700">100</td>
|
||||
<td class="p-3 text-center text-blue-700 text-base">${totalScore.toFixed(1)}</td>
|
||||
</tr></tfoot>
|
||||
</table>
|
||||
</section>
|
||||
</div>`;
|
||||
|
||||
if (window.lucide) window.lucide.createIcons();
|
||||
};
|
||||
|
||||
window.switchPerfQuarter = (q) => {
|
||||
state.perfQuarter = q;
|
||||
renderPerformance();
|
||||
};
|
||||
|
||||
window.updatePerfScore = (key, field, value) => {
|
||||
const q = state.perfQuarter || 0;
|
||||
const storageKey = 'opc-performance-Q' + (q + 1);
|
||||
const saved = JSON.parse(localStorage.getItem(storageKey) || '{}');
|
||||
saved[key] = saved[key] || {};
|
||||
saved[key][field] = value;
|
||||
localStorage.setItem(storageKey, JSON.stringify(saved));
|
||||
renderPerformance();
|
||||
};
|
||||
@@ -187,8 +187,13 @@ function renderProducts() {
|
||||
return productSort.dir > 0 ? '<i data-lucide="chevron-up" style="width:12px;height:12px"></i>' : '<i data-lucide="chevron-down" style="width:12px;height:12px"></i>';
|
||||
};
|
||||
const sortTh = (f, label, extra='') => `<th class="px-3 py-2.5 font-semibold align-middle whitespace-nowrap cursor-pointer hover:text-blue-600 select-none" onclick="sortProducts('${f}')"><span class="inline-flex items-center gap-1">${label}${sortIcon(f)}</span>${extra}</th>`;
|
||||
const tab = state.productTab || 'version';
|
||||
const tabs = `<div class="finance-tabs" style="padding:0;border-bottom:1px solid #e2e8f0"><button onclick="switchProductTab('version')" class="finance-tab${tab==='version'?' active':''}" style="font-size:14px;font-weight:600">版本迭代</button><button onclick="switchProductTab('ops')" class="finance-tab${tab==='ops'?' active':''}" style="font-size:14px;font-weight:600">产品运营</button></div>`;
|
||||
|
||||
document.querySelector("#products").innerHTML = `
|
||||
<div class="grid gap-4">
|
||||
${tabs}
|
||||
${tab === 'version' ? `
|
||||
<div class="flex justify-end">
|
||||
<button class="btn btn-primary btn-sm" onclick="openProductDrawer()"><i data-lucide="plus"></i>新增产品版本</button>
|
||||
</div>
|
||||
@@ -230,6 +235,7 @@ function renderProducts() {
|
||||
</div>
|
||||
</div>
|
||||
<aside id="productDrawer" class="task-drawer"></aside>
|
||||
` : `<section class="card p-6 max-w-4xl"><p class="text-slate-700 leading-relaxed mb-4"><strong>和内部联动,真正快速做实交付很重要</strong></p><ol class="list-decimal list-inside space-y-3 text-slate-600 leading-relaxed"><li>门店的大屏幕,要上,一个视频拍摄出来,然后在几百家门店同步轮巡播放,还是很震撼的</li><li>拍摄的内容,按病种整理成不同的专区,然后以 H5 或者小程序的形式,在门店科普教育,患者群,企业微信推送下去</li><li>同理,在保险的理赔环节,报案环节推送下去,而不是只是挂一个 H5 的专区</li><li>和门店、医生联动,把患者群都建立起来,把群的数量建起来</li><li>用源泉把患者的档案都建立起来,真正的有档案沉淀,AI 推送沉淀</li><li>用 AI + 人工,把随访量也都弄起来</li><li>对应的运营活动,还有排行版,在有一定量以后,要逐步的做起来,包括科普的排行版,科普的明星医生等等</li></ol><hr class="my-6 border-slate-200"><p class="text-slate-700 font-semibold leading-relaxed">要形成一份专门的报告,真正的证明我们科普和患者管理的价值</p></section>`}
|
||||
`;
|
||||
if (window.lucide) window.lucide.createIcons();
|
||||
}
|
||||
@@ -342,3 +348,8 @@ window.cyclePriority = async (id) => {
|
||||
renderProducts();
|
||||
} catch (e) { toast("更新失败:" + e.message, "error"); }
|
||||
};
|
||||
|
||||
window.switchProductTab = (v) => {
|
||||
state.productTab = v;
|
||||
renderProducts();
|
||||
};
|
||||
|
||||
@@ -47,13 +47,11 @@ function renderProposals() {
|
||||
const isStandard = state.proposalTab === "standard";
|
||||
|
||||
document.querySelector("#proposals").innerHTML = `<div class="grid gap-4">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex gap-1" id="proposalTabToggle">
|
||||
<button class="btn btn-sm ${isStandard ? 'btn-primary' : 'btn-ghost'}" onclick="switchProposalTab('standard')">标准资料库</button>
|
||||
<button class="btn btn-sm ${!isStandard ? 'btn-primary' : 'btn-ghost'}" onclick="switchProposalTab('other')">其他资料</button>
|
||||
</div>
|
||||
${!isStandard ? `<button class="btn btn-primary btn-sm" onclick="openProposalModal()"><i data-lucide="plus"></i>新增方案</button>` : ''}
|
||||
<div class="finance-tabs" style="padding:0;border-bottom:1px solid #e2e8f0">
|
||||
<button class="finance-tab${isStandard ? ' active' : ''}" style="font-size:14px;font-weight:600" onclick="switchProposalTab('standard')">标准资料库</button>
|
||||
<button class="finance-tab${!isStandard ? ' active' : ''}" style="font-size:14px;font-weight:600" onclick="switchProposalTab('other')">其他资料</button>
|
||||
</div>
|
||||
<div class="flex justify-end">${!isStandard ? `<button class="btn btn-primary btn-sm" onclick="openProposalModal()"><i data-lucide="plus"></i>新增方案</button>` : ''}</div>
|
||||
${isStandard ? `<div class="flex items-start gap-2 rounded-lg bg-blue-50 border border-blue-100 px-4 py-3 text-sm text-blue-700"><i data-lucide="info" style="width:16px;height:16px;flex-shrink:0;margin-top:1px"></i><span>这是每一条 OPC 线,必须要梳理清楚的 7 份资料,项目不可以删除,只可以更新附件,请大家将最新的材料上传</span></div>` : `<div class="flex items-start gap-2 rounded-lg bg-emerald-50 border border-emerald-100 px-4 py-3 text-sm text-emerald-700"><i data-lucide="lightbulb" style="width:16px;height:16px;flex-shrink:0;margin-top:1px"></i><span>在这里新建,并且上传您希望与团队其他成员共享的资料</span></div>`}
|
||||
${isStandard ? renderStandardTable(standardItems) : renderOtherTable(otherItems)}
|
||||
</div>
|
||||
|
||||
@@ -130,6 +130,12 @@ function switchTab(tab) {
|
||||
localStorage.setItem("opc-active-tab", tab);
|
||||
document.querySelectorAll(".sidebar-tab").forEach((btn) => btn.classList.toggle("active", btn.dataset.tab === tab));
|
||||
document.querySelectorAll(".panel").forEach((panel) => panel.classList.toggle("active", panel.id === tab));
|
||||
|
||||
// 更新顶部标题
|
||||
const titles = { home: 'OPC 工作台', finance: '业务管理', projects: '重点工作台账', proposals: '业务方案', products: '版本与运营', performance: '季度绩效考核' };
|
||||
const titleEl = document.querySelector('#workspaceTitle');
|
||||
if (titleEl) titleEl.textContent = titles[tab] || state.tenant + ' OPC 工作台';
|
||||
|
||||
render();
|
||||
}
|
||||
|
||||
@@ -151,9 +157,8 @@ function render() {
|
||||
renderProjects();
|
||||
renderProposals();
|
||||
renderProducts();
|
||||
renderOperations();
|
||||
renderPerformance();
|
||||
renderFinance();
|
||||
renderExpense();
|
||||
if (window.lucide) window.lucide.createIcons();
|
||||
}
|
||||
|
||||
@@ -164,7 +169,7 @@ function renderActive() {
|
||||
else if (tab === "projects") renderProjects();
|
||||
else if (tab === "proposals") renderProposals();
|
||||
else if (tab === "products") renderProducts();
|
||||
else if (tab === "operations") renderOperations();
|
||||
else if (tab === "performance") renderPerformance();
|
||||
else if (tab === "finance") renderFinance();
|
||||
if (window.lucide) window.lucide.createIcons();
|
||||
}
|
||||
|
||||
@@ -49,17 +49,13 @@
|
||||
<i data-lucide="home" style="width:20px;height:20px"></i>
|
||||
<span class="text-[10px] mt-1">首页</span>
|
||||
</div>
|
||||
<div class="sidebar-tab" data-tab="finance" onclick="switchTab('finance')" title="项目管理">
|
||||
<div class="sidebar-tab" data-tab="finance" onclick="switchTab('finance')" title="业务">
|
||||
<i data-lucide="folder-open-dot" style="width:20px;height:20px"></i>
|
||||
<span class="text-[10px] mt-1">项目</span>
|
||||
<span class="text-[10px] mt-1">业务</span>
|
||||
</div>
|
||||
<div class="sidebar-tab" data-tab="expense" onclick="switchTab('expense')" title="费用管理">
|
||||
<i data-lucide="receipt" style="width:20px;height:20px"></i>
|
||||
<span class="text-[10px] mt-1">费用</span>
|
||||
</div>
|
||||
<div class="sidebar-tab" data-tab="projects" onclick="switchTab('projects')" title="重点工作与台账">
|
||||
<div class="sidebar-tab" data-tab="projects" onclick="switchTab('projects')" title="Focus">
|
||||
<i data-lucide="file-text" style="width:20px;height:20px"></i>
|
||||
<span class="text-[10px] mt-1">台账</span>
|
||||
<span class="text-[10px] mt-1">Focus</span>
|
||||
</div>
|
||||
<div class="sidebar-tab" data-tab="proposals" onclick="switchTab('proposals')" title="业务方案">
|
||||
<i data-lucide="package" style="width:20px;height:20px"></i>
|
||||
@@ -69,18 +65,18 @@
|
||||
<i data-lucide="wallet-cards" style="width:20px;height:20px"></i>
|
||||
<span class="text-[10px] mt-1">产品</span>
|
||||
</div>
|
||||
<div class="sidebar-tab" data-tab="operations" onclick="switchTab('operations')" title="运营">
|
||||
<i data-lucide="trending-up" style="width:20px;height:20px"></i>
|
||||
<span class="text-[10px] mt-1">运营</span>
|
||||
<div class="sidebar-tab" data-tab="performance" onclick="switchTab('performance')" title="绩效">
|
||||
<i data-lucide="target" style="width:20px;height:20px"></i>
|
||||
<span class="text-[10px] mt-1">绩效</span>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
<!-- 主内容区 -->
|
||||
<div class="flex-1 min-w-0">
|
||||
<header class="topbar border-b border-slate-200 bg-white px-8 py-5">
|
||||
<div class="flex items-center justify-between gap-3">
|
||||
<div>
|
||||
<p class="eyebrow text-xs font-semibold uppercase tracking-[0.18em] text-blue-700">OPC Manager</p>
|
||||
<div class="flex items-center gap-3 w-full">
|
||||
<div class="flex-1">
|
||||
<p class="eyebrow text-xs font-semibold uppercase tracking-[0.18em] text-blue-700">OPC Manager <span class="ml-2 text-xs font-normal text-slate-400 tracking-normal">v1.0.0.2</span></p>
|
||||
<div class="flex items-center gap-4 mt-1">
|
||||
<h1 class="text-2xl font-semibold" id="workspaceTitle">科普 OPC 工作台</h1>
|
||||
<div class="hidden sm:flex items-center gap-1.5 bg-gradient-to-r from-blue-50 to-indigo-50 border border-blue-100 rounded-full px-4 py-1.5">
|
||||
@@ -89,6 +85,9 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button onclick="document.getElementById('opcLetterModal').classList.remove('hidden')" class="ml-auto flex-shrink-0 flex items-center justify-center w-10 h-10 rounded-full bg-blue-50 border border-blue-200 hover:bg-blue-100 transition-colors" title="OPC 来信">
|
||||
<i data-lucide="mail" class="text-blue-500" style="width:18px;height:18px"></i>
|
||||
</button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
@@ -97,9 +96,9 @@
|
||||
<section id="projects" class="panel"></section>
|
||||
<section id="proposals" class="panel"></section>
|
||||
<section id="products" class="panel"></section>
|
||||
<section id="operations" class="panel"></section>
|
||||
<section id="performance" class="panel"></section>
|
||||
<section id="performance" class="panel"></section>
|
||||
<section id="finance" class="panel"></section>
|
||||
<section id="expense" class="panel"></section>
|
||||
</main>
|
||||
</div><!-- 关闭主内容区 -->
|
||||
</div><!-- 关闭 flex 容器 -->
|
||||
@@ -127,11 +126,47 @@
|
||||
<script src="{{ url_for('static', filename='modules/projects.js') }}"></script>
|
||||
<script src="{{ url_for('static', filename='modules/proposals.js') }}"></script>
|
||||
<script src="{{ url_for('static', filename='modules/products.js') }}"></script>
|
||||
<script src="{{ url_for('static', filename='modules/operations.js') }}"></script>
|
||||
<script src="{{ url_for('static', filename='modules/performance.js') }}"></script>
|
||||
<script src="{{ url_for('static', filename='modules/finance.js') }}"></script>
|
||||
<script src="{{ url_for('static', filename='modules/expense.js') }}"></script>
|
||||
<script src="{{ url_for('static', filename='modules/drawer.js') }}"></script>
|
||||
<script src="{{ url_for('static', filename='modules/admin.js') }}"></script>
|
||||
<script src="{{ url_for('static', filename='app.js') }}"></script>
|
||||
|
||||
<!-- OPC 来信弹窗 -->
|
||||
<div id="opcLetterModal" class="hidden fixed inset-0 z-50 flex items-center justify-center bg-black/40" onclick="this.classList.add('hidden')">
|
||||
<div class="bg-white rounded-2xl shadow-2xl w-full max-w-2xl mx-4 max-h-[85vh] overflow-y-auto" onclick="event.stopPropagation()">
|
||||
<div class="sticky top-0 bg-white/95 backdrop-blur border-b border-slate-100 px-8 py-5 flex items-center justify-between">
|
||||
<h2 class="text-xl font-bold text-slate-800">致所有 OPC 的一封信</h2>
|
||||
<button class="btn btn-ghost btn-sm rounded-full w-8 h-8 p-0" onclick="document.getElementById('opcLetterModal').classList.add('hidden')"><i data-lucide="x"></i></button>
|
||||
</div>
|
||||
<div class="p-8 text-slate-700 leading-relaxed" style="background:#fdfcf8;font-family:Georgia,'Times New Roman',serif">
|
||||
<p class="text-slate-500 text-sm mb-6">亲爱的 OPC:</p>
|
||||
<p class="mb-6">对于每一位业务 Owner(OPC),公司和我个人都发自内心地充满了期待:</p>
|
||||
<ul class="list-disc list-inside mb-6 space-y-1.5 text-slate-600">
|
||||
<li>期待每一个人的业务都能够突破</li>
|
||||
<li>期待每一个人都成为自己赛道最璀璨的明星</li>
|
||||
<li>期待每一个人都实现事业上的成功与财务上的自由</li>
|
||||
</ul>
|
||||
<p class="mb-8">所以竭尽所能,把自己对业务管理的每一份理解,都融入这个系统,希望能够助力每一位 OPC 都快速地成长。</p>
|
||||
<hr class="my-6 border-slate-200">
|
||||
<ol class="list-decimal list-inside space-y-4 mb-6 text-sm" style="padding-left:0">
|
||||
<li><strong>财务:</strong>以终为始,用清晰的财务指标,让每个人都清晰地知道自己当下的成绩</li>
|
||||
<li><strong>业务:</strong>将目标转换成不同的项目,每一个项目就是一场战役</li>
|
||||
<li><strong>Focus:</strong>理清工作重点,并把每个重点工作的落地转换成工作清单</li>
|
||||
<li><strong>方案:</strong>如何清晰地和客户描述清楚我们的价值、产品、运营与报价</li>
|
||||
<li><strong>产品:</strong>
|
||||
<ul class="list-disc list-inside ml-4 mt-1 space-y-1 text-slate-500">
|
||||
<li>版本迭代:构建一个数字系统,对业务加杠杆,让业务变得有规模、有流程、有壁垒</li>
|
||||
<li>产品运营:产品在真实场景是否真实地落地,真正地成为可以交付的价值</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><strong>绩效:</strong>实时评分,了解当前阶段,我们和目标还有什么差距</li>
|
||||
</ol>
|
||||
<hr class="my-6 border-slate-200">
|
||||
<p class="text-right text-sm text-slate-500">—— 与你同行的团队</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user