Spaces:
Sleeping
Sleeping
| <html lang="zh-CN"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>利润逻辑工作室 | Profit Logic Studio</title> | |
| <!-- Tailwind CSS --> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| <!-- Vue 3 --> | |
| <script src="https://unpkg.com/vue@3/dist/vue.global.js"></script> | |
| <!-- Chart.js --> | |
| <script src="https://cdn.jsdelivr.net/npm/chart.js"></script> | |
| <!-- FontAwesome --> | |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> | |
| <style> | |
| body { | |
| font-family: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; | |
| background-color: #f3f4f6; | |
| } | |
| .var-card { | |
| transition: all 0.2s; | |
| } | |
| .var-card:hover { | |
| transform: translateY(-2px); | |
| box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06); | |
| } | |
| /* Custom Scrollbar */ | |
| ::-webkit-scrollbar { | |
| width: 8px; | |
| height: 8px; | |
| } | |
| ::-webkit-scrollbar-track { | |
| background: #f1f1f1; | |
| } | |
| ::-webkit-scrollbar-thumb { | |
| background: #c1c1c1; | |
| border-radius: 4px; | |
| } | |
| ::-webkit-scrollbar-thumb:hover { | |
| background: #a8a8a8; | |
| } | |
| </style> | |
| </head> | |
| <body class="h-screen overflow-hidden flex flex-col"> | |
| <div id="app" class="flex-1 flex flex-col h-full"> | |
| <!-- Header --> | |
| <header class="bg-white border-b border-gray-200 h-16 flex items-center justify-between px-6 shadow-sm z-10"> | |
| <div class="flex items-center gap-3"> | |
| <div class="bg-indigo-600 text-white p-2 rounded-lg"> | |
| <i class="fa-solid fa-chart-line"></i> | |
| </div> | |
| <h1 class="text-xl font-bold text-gray-800">利润逻辑工作室 <span class="text-xs text-gray-500 font-normal ml-2">商业研判与模拟系统</span></h1> | |
| </div> | |
| <div class="flex items-center gap-4"> | |
| <button @click="saveModel" class="text-gray-600 hover:text-indigo-600 transition" title="保存模型到本地"> | |
| <i class="fa-solid fa-save mr-1"></i> 保存 | |
| </button> | |
| <button @click="loadModel" class="text-gray-600 hover:text-indigo-600 transition" title="加载本地模型"> | |
| <i class="fa-solid fa-folder-open mr-1"></i> 加载 | |
| </button> | |
| <!-- Templates Dropdown --> | |
| <div class="relative group"> | |
| <button class="text-gray-600 hover:text-indigo-600 transition flex items-center gap-1" title="加载预设场景"> | |
| <i class="fa-solid fa-shapes mr-1"></i> 模板 | |
| </button> | |
| <div class="absolute right-0 mt-2 w-48 bg-white rounded-md shadow-lg py-1 z-20 hidden group-hover:block border border-gray-100"> | |
| <a href="#" @click.prevent="loadTemplate('ecommerce')" class="block px-4 py-2 text-sm text-gray-700 hover:bg-indigo-50">电商利润模型</a> | |
| <a href="#" @click.prevent="loadTemplate('saas')" class="block px-4 py-2 text-sm text-gray-700 hover:bg-indigo-50">SaaS MRR模型</a> | |
| </div> | |
| </div> | |
| <button @click="showHelp = true" class="text-gray-600 hover:text-indigo-600 transition"> | |
| <i class="fa-solid fa-circle-question"></i> | |
| </button> | |
| </div> | |
| </header> | |
| <!-- Main Content --> | |
| <main class="flex-1 flex overflow-hidden"> | |
| <!-- Sidebar: Model Builder --> | |
| <aside class="w-1/3 bg-white border-r border-gray-200 flex flex-col shadow-lg z-0"> | |
| <div class="p-4 border-b border-gray-100 bg-gray-50 flex justify-between items-center"> | |
| <h2 class="font-semibold text-gray-700"><i class="fa-solid fa-cubes mr-2"></i>商业变量构建</h2> | |
| <button @click="addVariable" class="bg-indigo-600 hover:bg-indigo-700 text-white px-3 py-1 rounded text-sm transition shadow-sm"> | |
| <i class="fa-solid fa-plus mr-1"></i> 新增变量 | |
| </button> | |
| </div> | |
| <div class="flex-1 overflow-y-auto p-4 space-y-3 bg-gray-50/50"> | |
| <div v-for="(v, index) in variables" :key="index" class="var-card bg-white p-4 rounded-lg border border-gray-200 relative group"> | |
| <!-- Delete Button --> | |
| <button @click="removeVariable(index)" class="absolute top-2 right-2 text-gray-300 hover:text-red-500 opacity-0 group-hover:opacity-100 transition"> | |
| <i class="fa-solid fa-times"></i> | |
| </button> | |
| <!-- Variable Name --> | |
| <div class="mb-3"> | |
| <label class="block text-xs font-medium text-gray-500 mb-1 uppercase tracking-wide">变量名称 (ID)</label> | |
| <input v-model="v.name" type="text" class="w-full border-b border-gray-300 focus:border-indigo-500 outline-none py-1 text-gray-800 font-medium bg-transparent" placeholder="例如: 流量"> | |
| </div> | |
| <!-- Variable Type --> | |
| <div class="mb-3"> | |
| <label class="block text-xs font-medium text-gray-500 mb-1">类型</label> | |
| <div class="flex rounded-md shadow-sm" role="group"> | |
| <button type="button" @click="v.type = 'constant'" :class="{'bg-indigo-100 text-indigo-700 border-indigo-200': v.type === 'constant', 'bg-white text-gray-600 border-gray-300 hover:bg-gray-50': v.type !== 'constant'}" class="flex-1 px-2 py-1 text-xs font-medium border rounded-l-md focus:z-10 focus:ring-1 focus:ring-indigo-500"> | |
| 常量 | |
| </button> | |
| <button type="button" @click="v.type = 'distribution'" :class="{'bg-indigo-100 text-indigo-700 border-indigo-200': v.type === 'distribution', 'bg-white text-gray-600 border-gray-300 hover:bg-gray-50': v.type !== 'distribution'}" class="flex-1 px-2 py-1 text-xs font-medium border-t border-b border-gray-300 focus:z-10 focus:ring-1 focus:ring-indigo-500"> | |
| 分布(概率) | |
| </button> | |
| <button type="button" @click="v.type = 'formula'" :class="{'bg-indigo-100 text-indigo-700 border-indigo-200': v.type === 'formula', 'bg-white text-gray-600 border-gray-300 hover:bg-gray-50': v.type !== 'formula'}" class="flex-1 px-2 py-1 text-xs font-medium border rounded-r-md focus:z-10 focus:ring-1 focus:ring-indigo-500"> | |
| 公式 | |
| </button> | |
| </div> | |
| </div> | |
| <!-- Type Specific Inputs --> | |
| <!-- Constant --> | |
| <div v-if="v.type === 'constant'" class="animate-fade-in"> | |
| <label class="block text-xs font-medium text-gray-500 mb-1">数值</label> | |
| <input v-model="v.value" type="number" class="w-full border rounded px-2 py-1 text-sm focus:ring-1 focus:ring-indigo-500 border-gray-300"> | |
| </div> | |
| <!-- Distribution --> | |
| <div v-if="v.type === 'distribution'" class="space-y-2 animate-fade-in"> | |
| <div class="flex gap-2"> | |
| <select v-model="v.dist" class="w-1/2 border rounded px-2 py-1 text-sm bg-white border-gray-300"> | |
| <option value="normal">正态分布 (Normal)</option> | |
| <option value="uniform">均匀分布 (Uniform)</option> | |
| <option value="triangular">三角分布 (Triangular)</option> | |
| </select> | |
| </div> | |
| <div class="flex gap-2 items-center"> | |
| <div v-if="v.dist === 'normal'" class="flex gap-2 w-full"> | |
| <div class="flex-1"> | |
| <label class="text-[10px] text-gray-500">均值 (Mean)</label> | |
| <input v-model="v.params[0]" type="number" class="w-full border rounded px-2 py-1 text-sm border-gray-300"> | |
| </div> | |
| <div class="flex-1"> | |
| <label class="text-[10px] text-gray-500">标准差 (Std)</label> | |
| <input v-model="v.params[1]" type="number" class="w-full border rounded px-2 py-1 text-sm border-gray-300"> | |
| </div> | |
| </div> | |
| <div v-if="v.dist === 'uniform'" class="flex gap-2 w-full"> | |
| <div class="flex-1"> | |
| <label class="text-[10px] text-gray-500">最小值 (Min)</label> | |
| <input v-model="v.params[0]" type="number" class="w-full border rounded px-2 py-1 text-sm border-gray-300"> | |
| </div> | |
| <div class="flex-1"> | |
| <label class="text-[10px] text-gray-500">最大值 (Max)</label> | |
| <input v-model="v.params[1]" type="number" class="w-full border rounded px-2 py-1 text-sm border-gray-300"> | |
| </div> | |
| </div> | |
| <div v-if="v.dist === 'triangular'" class="flex gap-2 w-full"> | |
| <div class="flex-1"> | |
| <label class="text-[10px] text-gray-500">Min</label> | |
| <input v-model="v.params[0]" type="number" class="w-full border rounded px-2 py-1 text-sm border-gray-300"> | |
| </div> | |
| <div class="flex-1"> | |
| <label class="text-[10px] text-gray-500">Mode</label> | |
| <input v-model="v.params[1]" type="number" class="w-full border rounded px-2 py-1 text-sm border-gray-300"> | |
| </div> | |
| <div class="flex-1"> | |
| <label class="text-[10px] text-gray-500">Max</label> | |
| <input v-model="v.params[2]" type="number" class="w-full border rounded px-2 py-1 text-sm border-gray-300"> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Formula --> | |
| <div v-if="v.type === 'formula'" class="animate-fade-in"> | |
| <label class="block text-xs font-medium text-gray-500 mb-1">计算公式 (使用变量名)</label> | |
| <div class="relative"> | |
| <input v-model="v.formula" type="text" class="w-full border rounded px-2 py-1 text-sm pl-6 border-gray-300 font-mono text-indigo-600 bg-indigo-50" placeholder="例如: 流量 * 转化率"> | |
| <i class="fa-solid fa-calculator absolute left-2 top-2 text-indigo-400 text-xs"></i> | |
| </div> | |
| <p class="text-[10px] text-gray-400 mt-1">支持 +, -, *, /, (, ) 及 min(), max()</p> | |
| </div> | |
| </div> | |
| <!-- Empty State --> | |
| <div v-if="variables.length === 0" class="text-center py-10 text-gray-400 border-2 border-dashed border-gray-200 rounded-lg"> | |
| <i class="fa-solid fa-arrow-up mb-2"></i> | |
| <p>点击上方添加第一个变量</p> | |
| </div> | |
| </div> | |
| </aside> | |
| <!-- Main: Simulation Dashboard --> | |
| <section class="w-2/3 bg-gray-100 flex flex-col p-6 overflow-y-auto"> | |
| <!-- Control Panel --> | |
| <div class="bg-white rounded-xl shadow-sm p-6 mb-6"> | |
| <div class="flex justify-between items-end mb-4"> | |
| <div> | |
| <h2 class="text-lg font-bold text-gray-800">模拟控制台</h2> | |
| <p class="text-sm text-gray-500">配置模拟参数并运行分析</p> | |
| </div> | |
| <div class="flex gap-4 items-center"> | |
| <div> | |
| <label class="block text-xs font-medium text-gray-500 mb-1">模拟次数 (Iterations)</label> | |
| <input v-model.number="iterations" type="number" class="border rounded px-2 py-1 text-sm w-32 border-gray-300"> | |
| </div> | |
| <div> | |
| <label class="block text-xs font-medium text-gray-500 mb-1">目标分析变量</label> | |
| <select v-model="targetVariable" class="border rounded px-2 py-1 text-sm w-40 border-gray-300"> | |
| <option v-for="v in variables" :value="v.name">{{ v.name }}</option> | |
| </select> | |
| </div> | |
| <button @click="runSimulation" :disabled="loading" class="bg-indigo-600 hover:bg-indigo-700 disabled:bg-indigo-300 text-white px-6 py-2 rounded-lg font-medium shadow-md transition flex items-center gap-2"> | |
| <i v-if="loading" class="fa-solid fa-circle-notch fa-spin"></i> | |
| <span v-else><i class="fa-solid fa-play"></i></span> | |
| 运行模拟 | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Results --> | |
| <div v-if="results" class="animate-fade-in space-y-6"> | |
| <!-- KPI Cards --> | |
| <div class="grid grid-cols-4 gap-4"> | |
| <div class="bg-white p-4 rounded-xl shadow-sm border-l-4 border-blue-500"> | |
| <div class="text-xs text-gray-500 uppercase">平均值 (Mean)</div> | |
| <div class="text-xl font-bold text-gray-800">{{ formatNumber(results.stats.mean) }}</div> | |
| </div> | |
| <div class="bg-white p-4 rounded-xl shadow-sm border-l-4 border-green-500"> | |
| <div class="text-xs text-gray-500 uppercase">中位数 (Median)</div> | |
| <div class="text-xl font-bold text-gray-800">{{ formatNumber(results.stats.median) }}</div> | |
| </div> | |
| <div class="bg-white p-4 rounded-xl shadow-sm border-l-4 border-yellow-500"> | |
| <div class="text-xs text-gray-500 uppercase" title="90% 的概率会高于此值">保守估计 (P10)</div> | |
| <div class="text-xl font-bold text-gray-800">{{ formatNumber(results.stats.p10) }}</div> | |
| </div> | |
| <div class="bg-white p-4 rounded-xl shadow-sm border-l-4 border-purple-500"> | |
| <div class="text-xs text-gray-500 uppercase" title="10% 的概率会高于此值">乐观估计 (P90)</div> | |
| <div class="text-xl font-bold text-gray-800">{{ formatNumber(results.stats.p90) }}</div> | |
| </div> | |
| </div> | |
| <!-- Chart --> | |
| <div class="bg-white p-6 rounded-xl shadow-sm"> | |
| <div class="flex justify-between items-center mb-4"> | |
| <h3 class="text-md font-bold text-gray-800">概率分布图 - {{ targetVariable }}</h3> | |
| <button @click="exportCSV" class="text-xs bg-white border border-gray-300 hover:bg-gray-50 text-gray-700 px-3 py-1 rounded transition flex items-center gap-1" title="下载模拟结果数据"> | |
| <i class="fa-solid fa-download"></i> 导出 CSV | |
| </button> | |
| </div> | |
| <div class="h-64"> | |
| <canvas id="resultChart"></canvas> | |
| </div> | |
| <div class="mt-4 text-sm text-gray-500 text-center"> | |
| X 轴: {{ targetVariable }} 数值区间 | Y 轴: 出现频次 (模拟 {{ iterations }} 次) | |
| </div> | |
| </div> | |
| <!-- Risk Analysis --> | |
| <div class="bg-white p-6 rounded-xl shadow-sm"> | |
| <h3 class="text-md font-bold text-gray-800 mb-2">研判分析结论</h3> | |
| <div class="text-sm text-gray-600 space-y-2"> | |
| <p><i class="fa-solid fa-circle-info text-blue-500 mr-2"></i>在当前模型假设下,<b>{{ targetVariable }}</b> 的预期平均值为 <b>{{ formatNumber(results.stats.mean) }}</b>。</p> | |
| <p><i class="fa-solid fa-triangle-exclamation text-yellow-500 mr-2"></i><b>风险提示</b>:有 10% 的可能性,结果会低于 <b>{{ formatNumber(results.stats.p10) }}</b>(P10 下限)。</p> | |
| <p><i class="fa-solid fa-rocket text-purple-500 mr-2"></i><b>潜力评估</b>:如果运气好(Top 10%),结果可能达到 <b>{{ formatNumber(results.stats.p90) }}</b>(P90 上限)。</p> | |
| </div> | |
| </div> | |
| </div> | |
| <div v-else class="flex-1 flex flex-col items-center justify-center text-gray-400"> | |
| <i class="fa-solid fa-chart-simple text-6xl mb-4 text-gray-200"></i> | |
| <p>配置左侧变量并点击“运行模拟”查看结果</p> | |
| </div> | |
| </section> | |
| </main> | |
| <!-- Help Modal --> | |
| <div v-if="showHelp" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50"> | |
| <div class="bg-white rounded-lg p-6 max-w-lg w-full shadow-2xl"> | |
| <div class="flex justify-between items-center mb-4"> | |
| <h3 class="text-xl font-bold">使用指南</h3> | |
| <button @click="showHelp = false" class="text-gray-500 hover:text-gray-800"><i class="fa-solid fa-times"></i></button> | |
| </div> | |
| <div class="space-y-4 text-sm text-gray-600"> | |
| <p><b>1. 定义变量:</b> 在左侧添加商业变量。可以是固定值(常量),也可以是带有不确定性的范围(分布),或者是依赖其他变量的计算公式。</p> | |
| <p><b>2. 设置公式:</b> 公式支持基本的数学运算。请确保公式中引用的变量名与定义的一致。</p> | |
| <p><b>3. 运行模拟:</b> 选择你最关心的结果指标(如“利润”),设置模拟次数(建议 1000 次以上),点击运行。</p> | |
| <p><b>4. 分析结果:</b> 系统会通过蒙特卡洛方法(Monte Carlo Simulation)计算出成千上万种可能的情境,并告诉你“平均情况”、“最坏情况”和“最好情况”。</p> | |
| <div class="bg-indigo-50 p-3 rounded text-indigo-700"> | |
| <i class="fa-solid fa-lightbulb mr-1"></i> <b>提示:</b> 这是一个帮助你做“模糊决策”的工具。不要只看平均值,更要关注 P10(下限风险)。 | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <script src="static/js/app.js"></script> | |
| </body> | |
| </html> | |