duqing2026 commited on
Commit
1d666c5
·
1 Parent(s): cd70bf3

升级优化

Browse files
README.md CHANGED
@@ -30,6 +30,16 @@ short_description: 智能决策矩阵
30
 
31
  ## 🚀 快速开始
32
 
 
 
 
 
 
 
 
 
 
 
33
  ### Docker 部署
34
 
35
  ```bash
@@ -40,13 +50,21 @@ docker run -p 7860:7860 smart-decision-matrix
40
  ### 本地运行
41
 
42
  ```bash
 
43
  pip install -r requirements.txt
 
 
44
  python app.py
45
  ```
46
 
47
  打开浏览器访问 `http://localhost:7860` 即可。
48
 
49
- ## 💡 使用场景
 
 
 
 
 
50
 
51
  * **产品规划**:在多个功能需求中决定优先级。
52
  * **技术选型**:在 React、Vue、Angular 中选择最适合团队的框架。
 
30
 
31
  ## 🚀 快速开始
32
 
33
+ ### Hugging Face Spaces 部署
34
+
35
+ 1. 在 Hugging Face 创建一个新的 Space (SDK 选择 **Docker**)。
36
+ 2. 将本项目代码上传/推送到 Space 的仓库。
37
+ ```bash
38
+ git remote add space https://huggingface.co/spaces/YOUR_USERNAME/YOUR_SPACE_NAME
39
+ git push space main
40
+ ```
41
+ 3. 等待构建完成即可访问。
42
+
43
  ### Docker 部署
44
 
45
  ```bash
 
50
  ### 本地运行
51
 
52
  ```bash
53
+ # 1. 安装依赖
54
  pip install -r requirements.txt
55
+
56
+ # 2. 运行应用
57
  python app.py
58
  ```
59
 
60
  打开浏览器访问 `http://localhost:7860` 即可。
61
 
62
+ ## �️ 技术栈优化
63
+
64
+ * **Local-First**: 核心依赖 (Vue, Tailwind, Chart.js, html2canvas) 已本地化,无需 CDN,支持离线运行。
65
+ * **Zero External CSS**: 移除 FontAwesome,改用内联 SVG 图标,提升加载速度。
66
+ * **Backend**: Flask (Python) with Gunicorn ready.
67
+ * **Deployment**: Docker optimized for Hugging Face Spaces.
68
 
69
  * **产品规划**:在多个功能需求中决定优先级。
70
  * **技术选型**:在 React、Vue、Angular 中选择最适合团队的框架。
app.py CHANGED
@@ -1,15 +1,35 @@
1
- from flask import Flask, render_template, send_from_directory
2
  import os
3
 
4
  app = Flask(__name__)
5
 
 
 
 
 
6
  @app.route('/')
7
  def index():
8
- return render_template('index.html')
 
 
 
 
 
 
 
9
 
10
  @app.route('/static/<path:path>')
11
  def send_static(path):
12
  return send_from_directory('static', path)
13
 
 
 
 
 
 
 
 
 
 
14
  if __name__ == '__main__':
15
  app.run(host='0.0.0.0', port=7860)
 
1
+ from flask import Flask, render_template, send_from_directory, jsonify
2
  import os
3
 
4
  app = Flask(__name__)
5
 
6
+ # Ensure static folder exists to prevent errors
7
+ if not os.path.exists('static'):
8
+ os.makedirs('static')
9
+
10
  @app.route('/')
11
  def index():
12
+ try:
13
+ return render_template('index.html')
14
+ except Exception as e:
15
+ return f"Error rendering template: {str(e)}", 500
16
+
17
+ @app.route('/health')
18
+ def health():
19
+ return jsonify({"status": "healthy"}), 200
20
 
21
  @app.route('/static/<path:path>')
22
  def send_static(path):
23
  return send_from_directory('static', path)
24
 
25
+ # Error handlers
26
+ @app.errorhandler(404)
27
+ def page_not_found(e):
28
+ return render_template('index.html'), 404
29
+
30
+ @app.errorhandler(500)
31
+ def internal_server_error(e):
32
+ return jsonify({"error": "Internal Server Error", "details": str(e)}), 500
33
+
34
  if __name__ == '__main__':
35
  app.run(host='0.0.0.0', port=7860)
static/.gitkeep ADDED
File without changes
static/js/chart.js ADDED
The diff for this file is too large to render. See raw diff
 
static/js/html2canvas.min.js ADDED
The diff for this file is too large to render. See raw diff
 
static/js/tailwindcss.js ADDED
The diff for this file is too large to render. See raw diff
 
static/js/vue.global.js ADDED
The diff for this file is too large to render. See raw diff
 
templates/index.html CHANGED
@@ -4,11 +4,11 @@
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
  <title>智能决策矩阵 - Smart Decision Matrix</title>
7
- <script src="https://cdn.tailwindcss.com"></script>
8
- <script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
9
- <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
10
- <script src="https://html2canvas.hertzen.com/dist/html2canvas.min.js"></script>
11
- <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet">
12
  <style>
13
  @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap');
14
  body { font-family: 'Inter', sans-serif; }
@@ -25,21 +25,22 @@
25
  </style>
26
  </head>
27
  <body class="bg-gray-50 text-gray-800 min-h-screen">
 
28
  <div id="app" v-cloak class="min-h-screen flex flex-col">
29
  <!-- Navbar -->
30
  <nav class="bg-white shadow-sm border-b border-gray-200">
31
  <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
32
  <div class="flex justify-between h-16">
33
  <div class="flex items-center">
34
- <i class="fas fa-balance-scale text-indigo-600 text-2xl mr-3"></i>
35
  <h1 class="text-xl font-bold text-gray-900">智能决策矩阵</h1>
36
  </div>
37
  <div class="flex items-center space-x-4">
38
- <button @click="resetData" class="text-gray-500 hover:text-red-600 transition-colors text-sm">
39
- <i class="fas fa-trash-alt mr-1"></i> 重置
40
  </button>
41
- <button @click="exportImage" class="bg-indigo-600 hover:bg-indigo-700 text-white px-4 py-2 rounded-md text-sm font-medium transition-colors shadow-sm">
42
- <i class="fas fa-download mr-2"></i> 导出图片
43
  </button>
44
  </div>
45
  </div>
@@ -71,8 +72,8 @@
71
  <span class="bg-indigo-100 text-indigo-600 w-6 h-6 rounded-full flex items-center justify-center text-xs mr-2">1</span>
72
  评价标准 (Criteria)
73
  </h2>
74
- <button @click="addCriteria" class="text-indigo-600 hover:text-indigo-800 text-sm font-medium">
75
- <i class="fas fa-plus mr-1"></i> 添加标准
76
  </button>
77
  </div>
78
  <div class="bg-gray-50 rounded-lg p-4 space-y-3">
@@ -85,7 +86,7 @@
85
  <input type="range" v-model.number="crit.weight" min="1" max="10" class="w-24 h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer slider-thumb">
86
  </div>
87
  <button @click="removeCriteria(index)" class="text-gray-300 hover:text-red-500 transition-colors" v-if="criteria.length > 1">
88
- <i class="fas fa-times"></i>
89
  </button>
90
  </div>
91
  </div>
@@ -98,8 +99,8 @@
98
  <span class="bg-indigo-100 text-indigo-600 w-6 h-6 rounded-full flex items-center justify-center text-xs mr-2">2</span>
99
  选项评分 (Options & Scoring)
100
  </h2>
101
- <button @click="addOption" class="text-indigo-600 hover:text-indigo-800 text-sm font-medium">
102
- <i class="fas fa-plus mr-1"></i> 添加选项
103
  </button>
104
  </div>
105
 
@@ -133,7 +134,7 @@
133
  </td>
134
  <td class="px-2 py-4 whitespace-nowrap text-center">
135
  <button @click="removeOption(optIndex)" class="text-gray-300 hover:text-red-500 transition-colors" v-if="options.length > 1">
136
- <i class="fas fa-times"></i>
137
  </button>
138
  </td>
139
  </tr>
@@ -199,18 +200,19 @@
199
  setup() {
200
  // Initial Data
201
  const defaultCriteria = [
202
- { id: 'c1', name: '影响力 (Impact)', weight: 5 },
203
- { id: 'c2', name: '成本 (Cost)', weight: 3 }, // Lower cost is usually better, but here we assume score 10 = best (low cost)
204
- { id: 'c3', name: '执行难度 (Ease)', weight: 4 }
 
205
  ];
206
 
207
  const defaultOptions = [
208
- { id: 'o1', name: '方案 A', scores: { 'c1': 8, 'c2': 6, 'c3': 7 } },
209
- { id: 'o2', name: '方案 B', scores: { 'c1': 9, 'c2': 4, 'c3': 5 } },
210
- { id: 'o3', name: '方案 C', scores: { 'c1': 6, 'c2': 9, 'c3': 9 } }
211
  ];
212
 
213
- const projectTitle = ref('我的决策矩阵');
214
  const criteria = ref([...defaultCriteria]);
215
  const options = ref([...defaultOptions]);
216
  const chartInstance = ref(null);
@@ -247,20 +249,11 @@
247
  };
248
 
249
  const resetData = () => {
250
- if(confirm('确定要重置所有数据吗?')) {
251
- projectTitle.value = '决策项目';
252
- criteria.value = [
253
- { id: Date.now() + 'c1', name: '标准 1', weight: 5 },
254
- { id: Date.now() + 'c2', name: '标准 2', weight: 5 }
255
- ];
256
- options.value = [
257
- { id: Date.now() + 'o1', name: '选项 1', scores: {} },
258
- { id: Date.now() + 'o2', name: '选项 2', scores: {} }
259
- ];
260
- // Initialize scores
261
- options.value.forEach(o => {
262
- criteria.value.forEach(c => o.scores[c.id] = 5);
263
- });
264
  }
265
  };
266
 
@@ -319,27 +312,35 @@
319
 
320
  // Chart
321
  const renderChart = () => {
 
 
 
 
322
  const ctx = document.getElementById('scoreChart');
323
  if (!ctx) return;
324
 
325
- if (chartInstance.value) {
326
- chartInstance.value.destroy();
327
- }
 
328
 
329
- chartInstance.value = new Chart(ctx, {
330
- type: 'bar',
331
- data: getChartData(),
332
- options: {
333
- responsive: true,
334
- maintainAspectRatio: false,
335
- plugins: {
336
- legend: { display: false }
337
- },
338
- scales: {
339
- y: { beginAtZero: true }
 
340
  }
341
- }
342
- });
 
 
343
  };
344
 
345
  const updateChart = () => {
@@ -410,5 +411,6 @@
410
  }
411
  }).mount('#app');
412
  </script>
 
413
  </body>
414
  </html>
 
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
  <title>智能决策矩阵 - Smart Decision Matrix</title>
7
+ <link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>⚖️</text></svg>">
8
+ <script src="/static/js/tailwindcss.js"></script>
9
+ <script src="/static/js/vue.global.js"></script>
10
+ <script src="/static/js/chart.js"></script>
11
+ <script src="/static/js/html2canvas.min.js"></script>
12
  <style>
13
  @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap');
14
  body { font-family: 'Inter', sans-serif; }
 
25
  </style>
26
  </head>
27
  <body class="bg-gray-50 text-gray-800 min-h-screen">
28
+ {% raw %}
29
  <div id="app" v-cloak class="min-h-screen flex flex-col">
30
  <!-- Navbar -->
31
  <nav class="bg-white shadow-sm border-b border-gray-200">
32
  <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
33
  <div class="flex justify-between h-16">
34
  <div class="flex items-center">
35
+ <svg class="w-8 h-8 text-indigo-600 mr-3" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 6l3 1m0 0l-3 9a5.002 5.002 0 006.001 0M6 7l3 9M6 7l6-2m6 2l3-1m-3 1l-3 9a5.002 5.002 0 006.001 0M18 7l3 9m-3-9l-6-2m0-2v2m0 16V5m0 16H9m3 0h3"></path></svg>
36
  <h1 class="text-xl font-bold text-gray-900">智能决策矩阵</h1>
37
  </div>
38
  <div class="flex items-center space-x-4">
39
+ <button @click="resetData" class="text-gray-500 hover:text-red-600 transition-colors text-sm flex items-center">
40
+ <svg class="w-4 h-4 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"></path></svg> 重置
41
  </button>
42
+ <button @click="exportImage" class="bg-indigo-600 hover:bg-indigo-700 text-white px-4 py-2 rounded-md text-sm font-medium transition-colors shadow-sm flex items-center">
43
+ <svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4"></path></svg> 导出图片
44
  </button>
45
  </div>
46
  </div>
 
72
  <span class="bg-indigo-100 text-indigo-600 w-6 h-6 rounded-full flex items-center justify-center text-xs mr-2">1</span>
73
  评价标准 (Criteria)
74
  </h2>
75
+ <button @click="addCriteria" class="text-indigo-600 hover:text-indigo-800 text-sm font-medium flex items-center">
76
+ <svg class="w-4 h-4 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4"></path></svg> 添加标准
77
  </button>
78
  </div>
79
  <div class="bg-gray-50 rounded-lg p-4 space-y-3">
 
86
  <input type="range" v-model.number="crit.weight" min="1" max="10" class="w-24 h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer slider-thumb">
87
  </div>
88
  <button @click="removeCriteria(index)" class="text-gray-300 hover:text-red-500 transition-colors" v-if="criteria.length > 1">
89
+ <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path></svg>
90
  </button>
91
  </div>
92
  </div>
 
99
  <span class="bg-indigo-100 text-indigo-600 w-6 h-6 rounded-full flex items-center justify-center text-xs mr-2">2</span>
100
  选项评分 (Options & Scoring)
101
  </h2>
102
+ <button @click="addOption" class="text-indigo-600 hover:text-indigo-800 text-sm font-medium flex items-center">
103
+ <svg class="w-4 h-4 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4"></path></svg> 添加选项
104
  </button>
105
  </div>
106
 
 
134
  </td>
135
  <td class="px-2 py-4 whitespace-nowrap text-center">
136
  <button @click="removeOption(optIndex)" class="text-gray-300 hover:text-red-500 transition-colors" v-if="options.length > 1">
137
+ <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path></svg>
138
  </button>
139
  </td>
140
  </tr>
 
200
  setup() {
201
  // Initial Data
202
  const defaultCriteria = [
203
+ { id: 'c1', name: '功能完备性 (Features)', weight: 5 },
204
+ { id: 'c2', name: '易用性 (Usability)', weight: 4 },
205
+ { id: 'c3', name: '成本 (Cost)', weight: 3 },
206
+ { id: 'c4', name: '社区支持 (Community)', weight: 2 }
207
  ];
208
 
209
  const defaultOptions = [
210
+ { id: 'o1', name: '方案 A (React)', scores: { 'c1': 9, 'c2': 7, 'c3': 6, 'c4': 10 } },
211
+ { id: 'o2', name: '方案 B (Vue)', scores: { 'c1': 8, 'c2': 9, 'c3': 7, 'c4': 9 } },
212
+ { id: 'o3', name: '方案 C (Angular)', scores: { 'c1': 9, 'c2': 6, 'c3': 5, 'c4': 8 } }
213
  ];
214
 
215
+ const projectTitle = ref('技术选型决策矩阵 (示例)');
216
  const criteria = ref([...defaultCriteria]);
217
  const options = ref([...defaultOptions]);
218
  const chartInstance = ref(null);
 
249
  };
250
 
251
  const resetData = () => {
252
+ if(confirm('确定要重置所有数据吗?这将恢复到默认示例。')) {
253
+ projectTitle.value = '技术选型决策矩阵 (示例)';
254
+ // Clone defaults to avoid reference issues
255
+ criteria.value = JSON.parse(JSON.stringify(defaultCriteria));
256
+ options.value = JSON.parse(JSON.stringify(defaultOptions));
 
 
 
 
 
 
 
 
 
257
  }
258
  };
259
 
 
312
 
313
  // Chart
314
  const renderChart = () => {
315
+ if (typeof Chart === 'undefined') {
316
+ console.error('Chart.js not loaded');
317
+ return;
318
+ }
319
  const ctx = document.getElementById('scoreChart');
320
  if (!ctx) return;
321
 
322
+ try {
323
+ if (chartInstance.value) {
324
+ chartInstance.value.destroy();
325
+ }
326
 
327
+ chartInstance.value = new Chart(ctx, {
328
+ type: 'bar',
329
+ data: getChartData(),
330
+ options: {
331
+ responsive: true,
332
+ maintainAspectRatio: false,
333
+ plugins: {
334
+ legend: { display: false }
335
+ },
336
+ scales: {
337
+ y: { beginAtZero: true }
338
+ }
339
  }
340
+ });
341
+ } catch (e) {
342
+ console.error('Error rendering chart:', e);
343
+ }
344
  };
345
 
346
  const updateChart = () => {
 
411
  }
412
  }).mount('#app');
413
  </script>
414
+ {% endraw %}
415
  </body>
416
  </html>