0%

Anki 笔记数据库

Anki 的笔记数据都存储在 collection.anki2 文件中,包含了用户的所有笔记、卡片、标签等信息。

文件路径
1
C:\Users\Administrator\AppData\Roaming\Anki2\账户1\collection.anki2

可以使用 SQLite 或 Python 的 sqlite3 模块连接并查询该数据库,查看具体的表和字段。

本地笔记检索

使用 sqlite3 链接数据库

打印 collection.anki2 数据库中的所有表格
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import sqlite3  

# 连接到 Anki 数据库(此处注意替换为自己的文件路径)
anki_db_path = r'C:\Users\Administrator\AppData\Roaming\Anki2\本地账户\collection.anki2'
conn = sqlite3.connect(anki_db_path)

# 查看 notes 表的结构
cursor = conn.cursor()
cursor.execute("PRAGMA table_info(notes);")
columns = cursor.fetchall()

# 打印字段名
for column in columns:
print(column)

# 关闭连接
conn.close()

输出结果为

1
2
3
4
5
6
7
8
9
10
11
(0, 'id', 'INTEGER', 0, None, 1)  
(1, 'guid', 'TEXT', 1, None, 0)
(2, 'mid', 'INTEGER', 1, None, 0)
(3, 'mod', 'INTEGER', 1, None, 0)
(4, 'usn', 'INTEGER', 1, None, 0)
(5, 'tags', 'TEXT', 1, None, 0)
(6, 'flds', 'TEXT', 1, None, 0)
(7, 'sfld', 'INTEGER', 1, None, 0)
(8, 'csum', 'INTEGER', 1, None, 0)
(9, 'flags', 'INTEGER', 1, None, 0)
(10, 'data', 'TEXT', 1, None, 0)

说明可以正常连接数据库,准备工作已做好

制作一个 Python 小程序读取数据库

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
import sys
import sqlite3
import pandas as pd
import re
from PyQt6.QtWidgets import (
QApplication, QWidget, QVBoxLayout, QPushButton,
QLineEdit, QTextEdit, QMessageBox
)

class AnkiNoteReader(QWidget):
def __init__(self):
super().__init__()
self.initUI()
self.notes_df = None

def initUI(self):
self.setWindowTitle('Anki 笔记读取器')

layout = QVBoxLayout()

self.load_button = QPushButton('加载笔记', self)
self.load_button.clicked.connect(self.load_notes)
layout.addWidget(self.load_button)

self.search_box = QLineEdit(self)
self.search_box.setPlaceholderText('搜索关键词')
layout.addWidget(self.search_box)

self.search_button = QPushButton('搜索', self)
self.search_button.clicked.connect(self.search_notes)
layout.addWidget(self.search_button)

self.text_area = QTextEdit(self)
layout.addWidget(self.text_area)

self.setLayout(layout)

def load_notes(self):
try:
# 连接到 Anki 数据库
anki_db_path = r'C:\Users\Administrator\AppData\Roaming\Anki2\本地账户\collection.anki2'
conn = sqlite3.connect(anki_db_path)
query = 'SELECT id, flds FROM notes'
self.notes_df = pd.read_sql_query(query, conn)
conn.close()

self.display_notes(self.notes_df)
except Exception as e:
QMessageBox.critical(self, "错误", str(e))

def display_notes(self, notes):
self.text_area.clear()
for index, row in notes.iterrows():
# 清除不可见字符,保留中文和其他可见字符
fields = re.sub(r'[^\u4e00-\u9fa5\x20-\x7E]', ' ', row['flds']) # 保留中文和可见字符
self.text_area.append(f"Note ID: {row['id']}\nFields: {fields.strip()}\n")

def search_notes(self):
if self.notes_df is not None:
search_term = self.search_box.text()
if search_term:
filtered_notes = self.notes_df[self.notes_df['flds'].str.contains(search_term, na=False)]
self.display_notes(filtered_notes)
else:
self.display_notes(self.notes_df)
else:
QMessageBox.warning(self, "警告", "请先加载笔记!")

if __name__ == '__main__':
app = QApplication(sys.argv)
ex = AnkiNoteReader()
ex.resize(600, 400)
ex.show()
sys.exit(app.exec())

image.png

加载笔记以及搜索笔记如下图
image.png

image.png

远程笔记检索

功能实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
from flask import Flask, request, jsonify  
import sqlite3
import pandas as pd

app = Flask(__name__)

# 数据库文件路径
DATABASE_PATH = r'C:\Users\Administrator\AppData\Roaming\Anki2\本地账户\collection.anki2'


def query_database(query):
"""执行 SQL 查询并返回结果为 DataFrame""" conn = sqlite3.connect(DATABASE_PATH)
df = pd.read_sql_query(query, conn)
conn.close()
return df


@app.route('/search', methods=['GET'])
def search_notes():
"""根据查询词搜索笔记"""
search_term = request.args.get('term', '')
query = f"SELECT id, flds FROM notes WHERE flds LIKE '%{search_term}%'"

try:
results = query_database(query)
notes = results.to_dict(orient='records')
return jsonify(notes)
except Exception as e:
return jsonify({'error': str(e)})


if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000) # 可访问所有 IP

运行后提示:

1
2
3
4
5
6
 * Serving Flask app 'app'
* Debug mode: off
WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
* Running on all addresses (0.0.0.0)
* Running on http://127.0.0.1:5000
* Running on http://192.168.1.94:5000

这时,我们可以在浏览器输入

1
http://192.168.1.94:5000/search?term= 要搜索的内容

image.png

完善输出

  • 增加搜索按钮
  • 优化输出前端效果
  • 表格形式输出
  • 添加图片显示(需添加图片数据库地址)
  • 添加导出 CSV 功能

项目结构为:

1
2
3
4
5
6
7
/project
/static
styles.css
javascript.js
/templates
index.html
app.py

image.png

Anki.gif

主程序

以下代码中,需要将 Anki 数据库路径及图片媒体路径替换为自己电脑对应的路径

app.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
from flask import Flask, request, send_from_directory, render_template, send_file
import sqlite3
import pandas as pd
import os
import re
import csv

app = Flask(__name__)

# 数据库文件路径(需要替换成自己Anki数据库及图片路径)
DATABASE_PATH = r'C:\Users\Administrator\Desktop\collection.anki2'
MEDIA_PATH = r'C:\Users\Administrator\AppData\Roaming\Anki2\账户1\collection.media'

# 允许访问媒体文件
@app.route('/media/<path:filename>')
def media(filename):
full_path = os.path.join(MEDIA_PATH, filename)
print(f"Attempting to access: {full_path}")
return send_from_directory(MEDIA_PATH, filename)

def query_database(query, params):
"""执行 SQL 查询并返回结果为 DataFrame"""
conn = sqlite3.connect(DATABASE_PATH)
df = pd.read_sql_query(query, conn, params=params)
conn.close()
return df

@app.route('/', methods=['GET', 'POST'])
def index():
results = []
message = ""
if request.method == 'POST':
search_term = request.form.get('term', '')
print(f"Searching for: {search_term}")
# query = "SELECT id, flds FROM notes WHERE flds LIKE ?"

# 更新查询,获取 flags 字段
# query = """
# SELECT id, flds, flags
# FROM notes
# WHERE flds LIKE ?
# """

query = """
SELECT id, flds
FROM notes
WHERE flds LIKE ?
"""

try:
results_df = query_database(query, (f'%{search_term}%',))
print(f"Query results: {results_df}")
if results_df.empty:
message = "未找到相关结果。"
else:
for _, row in results_df.iterrows():
fields = row['flds'].split('\x1f')
print(f"Fields content: {fields}")

for i, field in enumerate(fields):
matches = re.findall(r'src="([^"]+\.(?:jpg|png|gif))"', field)
for match in matches:
img_tag = f' class="image" src="/media/{match}" alt="Image" '
field = re.sub(r'src="[^"]*"', img_tag, field, count=1)
fields[i] = field.strip()

# results.append({'id': row['id'], 'flds': fields})

# 添加 flags 数据
# results.append({'id': row['id'], 'flds': fields, 'flags': row['flags']})
results.append({'id': row['id'], 'flds': fields})

except Exception as e:
message = f"查询错误: {str(e)}"
print(message)

# 使用 render_template 加载 HTML 模板
return render_template('index.html', results=results, message=message)


@app.route('/export', methods=['GET'])
def export_notes():
search_term = request.args.get('term', '')
print(f"导出请求,搜索词: {search_term}") # 确认请求是否到达

# query = """
# SELECT id, flds, flags
# FROM notes
# WHERE flds LIKE ?
# """

query = """
SELECT id, flds
FROM notes
WHERE flds LIKE ?
"""

try:
results_df = query_database(query, (f'%{search_term}%',))
temp_file = 'exported_notes.csv'

# 使用 utf-8-sig 编码
results_df.to_csv(temp_file, index=False, encoding='utf-8-sig')

return send_file(temp_file, as_attachment=True)

except Exception as e:
print(f"导出错误: {str(e)}")
return "导出失败", 500

if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000)

HTML

index.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Anki 笔记查询</title>
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='styles.css') }}">
<script src="static/javascript.js"></script> <!-- 引入 JavaScript 文件 -->
</head>
<body>
<h1>Anki 笔记查询</h1>
<form method="post">
<input type="text" name="term" placeholder="输入搜索关键词" required>
<input type="text" id="searchInput" placeholder="搜索内容并导出内容">
<button type="submit">搜索</button>
<button onclick="exportNotes()">导出笔记</button>
</form>
<h2>查询结果</h2>
<div class="message">{{ message }}</div>
<table>
<tr>
<th>Note ID</th>
<th>Fields</th>
<!-- <th>flags</th>-->
</tr>
{% for note in results %}
<tr>
<td>{{ note.id }}</td>
<td>{{ note.flds | join('<br>') | safe }}</td>
<!-- 展示旗帜数据 -->
<!-- <td>{{ note.flags }}</td>-->
</tr>
{% endfor %}
</table>
</body>
</html>

javascript

javascript.js
1
2
3
4
5
6
function exportNotes() {
const searchTerm = document.getElementById('searchInput').value; // 获取搜索词
const url = `/export?term=${encodeURIComponent(searchTerm)}`; // 构建请求 URL

window.location.href = url; // 直接跳转到 URL 下载文件
}

CSS

styles.css
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
/* styles.css */
body {
font-family: Arial, sans-serif;
margin: 20px;
background-color: #f9f9f9; /* 背景颜色 */
}

table {
width: 100%;
border-collapse: collapse; /* 确保边框合并 */
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); /* 添加阴影 */
}

th, td {
border: 1px solid #ddd; /* 添加边框 */
padding: 12px; /* 增加内边距 */
text-align: left;
}

th {
background-color: #4CAF50; /* 表头背景色 */
color: white; /* 表头字体颜色 */
}

tr:nth-child(even) {
background-color: #f2f2f2; /* 偶数行背景色 */
}

tr:hover {
background-color: #e0f7fa; /* 鼠标悬停行的背景色 */
}

.message {
color: red;
}

.image {
max-width: 600px; /* 调整图片尺寸 */
max-height: 600px;
border-radius: 5px; /* 圆角 */
}

缘由

最近在统计多组数据的重复性,利用到了标准差相关的公式,自然而然想到是使用 Excel 来处理公式。总之,模板是实现了,将公式一步一步拆解,饶了一大圈,发现最后 Excel 本身就内置了求取标准差的函数,还不如不要让我知道这个结果呢,伤心: (

image.png

不过,这也带来了一些思考。自认为自己用 Excel 还算用得多的了,根本没有听过用过这样的内置函数,能不能写个简单的程序,只要将数据往里面一扔,就可以自动出来计算结果呢 —> 于是开始了折腾

Step 1 搭建环境

  • 这里选择社区版的 Pycharm,免费,集成开发环境用起来也方便
  • 程序开发使用的是 PyQt 框架

刚安装完 Pycharm,会检测系统中是否已经安装好了 python,如果恰好没有安装过,那么根据软件的建议安装即可

写好的代码中若检测有问题,Pycharm 会在对应的位置进行提示,如一下这段程序,刚安装的 python 肯定是没有 PyQT6 的程序包的,可以通过提示(小红灯泡处,Alt+Enter)进行安装

image.png

新建项目,新建 python 文件,粘贴如下代码进行测试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from PyQt6.QtWidgets import QApplication, QWidget, QVBoxLayout, QPushButton  

app = QApplication([]) # 创建应用程序对象

window = QWidget() # 创建窗口部件
layout = QVBoxLayout() # 创建布局管理器

button = QPushButton("点击我") # 创建按钮
layout.addWidget(button) # 将按钮添加到布局管理器中

window.setLayout(layout) # 设置窗口部件的布局
window.show() # 显示窗口

app.exec() # 进入应用程序的主循环

image.png

很好,说明各种环境已经搭建好了!

Step 2 实现基础功能

接下来尽管提需求,从开发者的角度出发,一步步给 GPT 进行引导,从易到难,功能从简单到复杂

例如我开始给 GPT 提需求:

我提供一组数据,点击按钮之后,就可以在一个输出位置输出这组数据的标准差

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
import sys  
import numpy as np
from PyQt6.QtWidgets import QApplication, QMainWindow, QWidget, QVBoxLayout, QPushButton, QLabel, QLineEdit

class MainWindow(QMainWindow):
def __init__(self):
super().__init__()

self.setWindowTitle("标准差计算器")
self.setGeometry(100, 100, 400, 200)

# 主部件
self.central_widget = QWidget()
self.setCentralWidget(self.central_widget)

# 布局
self.layout = QVBoxLayout()
self.central_widget.setLayout(self.layout)

# 输入框
self.data_input = QLineEdit()
self.data_input.setPlaceholderText("输入数据,用逗号分隔")
self.layout.addWidget(self.data_input)

# 计算按钮
self.calculate_button = QPushButton("计算标准差")
self.calculate_button.clicked.connect(self.calculate_standard_deviation)
self.layout.addWidget(self.calculate_button)

# 输出标签
self.result_label = QLabel("标准差:")
self.layout.addWidget(self.result_label)

def calculate_standard_deviation(self):
# 获取输入数据
data_text = self.data_input.text()
if not data_text:
self.result_label.setText("请先输入数据")
return

# 解析数据
try:
data = list(map(float, data_text.split(',')))
except ValueError:
self.result_label.setText("数据格式错误,请输入有效的数字")
return

# 计算标准差
std_dev = np.std(data)
self.result_label.setText(f"标准差:{std_dev:.2f}")

if __name__ == "__main__":
app = QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec())

image.png

好了,手动输入几组数据,确实能够计算出来标准差了,不过我们的数据来自 Excel,而且用逗号分隔数据的方式也不方便。于是就有了如下需求:

但是,你知道的,我的输入数据一般从 excel 中复制过来的,这种用逗号分隔数字的方式让我在输入的时候非常麻烦

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
from PyQt6.QtWidgets import QApplication, QWidget, QVBoxLayout, QPushButton, QPlainTextEdit, QLabel
import numpy as np

class DataProcessingApp(QWidget):
def __init__(self):
super().__init__()

# 设置窗口标题和尺寸
self.setWindowTitle("数据处理应用")
self.setGeometry(100, 100, 400, 300)

# 创建布局和组件
layout = QVBoxLayout()

self.text_edit = QPlainTextEdit(self)
self.text_edit.setPlaceholderText("在这里粘贴数据...")
layout.addWidget(self.text_edit)

self.calculate_button = QPushButton("计算标准差", self)
self.calculate_button.clicked.connect(self.calculate_std_dev)
layout.addWidget(self.calculate_button)

self.result_label = QLabel("标准差:", self)
layout.addWidget(self.result_label)

self.setLayout(layout)

def calculate_std_dev(self):
# 获取文本内容
text = self.text_edit.toPlainText()

# 处理文本数据
try:
# 将文本数据按行分隔,然后按空格或制表符分隔
data = [float(num) for line in text.splitlines() for num in line.split()]
if data:
# 计算标准差
std_dev = np.std(data)
self.result_label.setText(f"标准差:{std_dev:.2f}")
else:
self.result_label.setText("标准差:无效数据")
except ValueError:
self.result_label.setText("标准差:数据格式错误")

app = QApplication([])
window = DataProcessingApp()
window.show()
app.exec()

image.png

以上这组数据就是直接从 Excel 粘贴进去的。看来功能 GPT 是帮我们实现了

Step 3 完善程序

接下来按照上面的步骤,进一步提需求,进一步完善程序代码
例如,可以增加一些数据清洗、数据输入验证、错处处理、历史记录、导出数据等功能
也可以对程序进行拆分,不同功能的代码写到不同的 py 文件中

image.png

当前的程序结构为

1
2
3
4
5
6
data_processor/

├── main.py
├── ui.py
├── processor.py
└── utils.py

主程序

main.py
1
2
3
4
5
6
7
8
9
import sys
from PyQt6.QtWidgets import QApplication
from ui import DataProcessorApp

if __name__ == "__main__":
app = QApplication(sys.argv)
processor = DataProcessorApp()
processor.show()
sys.exit(app.exec())

UI 界面

ui.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
from PyQt6.QtWidgets import QWidget, QVBoxLayout, QPushButton, QTextEdit, QLabel  
from PyQt6.QtGui import QFont, QColor
from PyQt6.QtCore import Qt
from PyQt6.QtGui import QPalette
from processor import DataProcessor


class DataProcessorApp(QWidget):
def __init__(self):
super().__init__()
self.processor = DataProcessor()
self.init_ui()

def init_ui(self):
# Set the overall layout and style
layout = QVBoxLayout()
self.setStyleSheet("background-color: #f0f0f0;")
self.setPalette(QPalette(QColor('#f0f0f0')))

# Create and style widgets
self.input_text = QTextEdit(self)
self.input_text.setPlaceholderText("在这里粘贴数据,每行一个数字")
self.input_text.setStyleSheet("font-size: 14px; padding: 10px; border: 1px solid #ccc;")
layout.addWidget(self.input_text)

self.mean_label = QLabel("平均值:", self)
self.mean_label.setStyleSheet("font-size: 16px; color: #333;")
layout.addWidget(self.mean_label)

self.variance_label = QLabel("方差:", self)
self.variance_label.setStyleSheet("font-size: 16px; color: #333;")
layout.addWidget(self.variance_label)

self.std_dev_label = QLabel("标准差:", self)
self.std_dev_label.setStyleSheet("font-size: 16px; color: #333;")
layout.addWidget(self.std_dev_label)

self.reliability_label = QLabel("重复性:", self)
self.reliability_label.setStyleSheet("font-size: 16px; color: #333;")
layout.addWidget(self.reliability_label)

process_button = QPushButton("计算标准差", self)
process_button.setStyleSheet(
"background-color: #4CAF50; color: white; font-size: 14px; padding: 10px; border: none; border-radius: 5px;")
process_button.clicked.connect(self.calculate_std_dev)
layout.addWidget(process_button)

reliability_button = QPushButton("计算重复性", self)
reliability_button.setStyleSheet(
"background-color: #2196F3; color: white; font-size: 14px; padding: 10px; border: none; border-radius: 5px;")
reliability_button.clicked.connect(self.calculate_reliability)
layout.addWidget(reliability_button)

self.history_text = QTextEdit(self)
self.history_text.setPlaceholderText("历史记录")
self.history_text.setReadOnly(True)
self.history_text.setStyleSheet("font-size: 14px; padding: 10px; border: 1px solid #ccc;")
layout.addWidget(self.history_text)

self.setLayout(layout)
self.setWindowTitle("数据处理程序")
self.setGeometry(100, 100, 500, 400)
self.setFont(QFont('Arial', 12))

def calculate_std_dev(self):
data = self.input_text.toPlainText().strip()
try:
numbers = [float(x) for x in data.split() if x.replace('.', '', 1).isdigit()]
if numbers:
std_dev = self.processor.calculate_std_dev(numbers)
mean, variance, std_dev, _ = self.processor.calculate_reliability(numbers)
self.mean_label.setText(f"平均值:{mean:.6f}")
self.variance_label.setText(f"方差:{variance:.6f}")
self.std_dev_label.setText(f"标准差:{std_dev:.6f}")
else:
self.std_dev_label.setText("标准差:无效数据")
self.mean_label.setText("平均值:无效数据")
self.variance_label.setText("方差:无效数据")
history = self.history_text.toPlainText()
new_entry = f"数据:{', '.join(map(str, numbers))} | 标准差:{std_dev:.6f} | 平均值:{mean:.6f} | 方差:{variance:.6f}\n"
self.history_text.setText(history + new_entry)
except Exception as e:
self.std_dev_label.setText(f"错误:{e}")
self.mean_label.setText("平均值:无效数据")
self.variance_label.setText("方差:无效数据")

def calculate_reliability(self):
data = self.input_text.toPlainText().strip()
try:
numbers = [float(x) for x in data.split() if x.replace('.', '', 1).isdigit()]
if numbers:
mean, variance, std_dev, reliability = self.processor.calculate_reliability(numbers)
self.mean_label.setText(f"平均值:{mean:.6f}")
self.variance_label.setText(f"方差:{variance:.6f}")
self.std_dev_label.setText(f"标准差:{std_dev:.6f}")
self.reliability_label.setText(f"重复性:{reliability:.6f}")
else:
self.reliability_label.setText("重复性:无效数据")
self.mean_label.setText("平均值:无效数据")
self.variance_label.setText("方差:无效数据")
self.std_dev_label.setText("标准差:无效数据")
history = self.history_text.toPlainText()
new_entry = f"数据:{', '.join(map(str, numbers))} | 标准差:{std_dev:.6f} | 重复性:{reliability:.6f} | 平均值:{mean:.6f} | 方差:{variance:.6f}\n"
self.history_text.setText(history + new_entry)
except Exception as e:
self.reliability_label.setText(f"错误:{e}")
self.mean_label.setText("平均值:无效数据")
self.variance_label.setText("方差:无效数据")
self.std_dev_label.setText("标准差:无效数据")

数据处理

processor.py
1
2
3
4
5
6
7
8
9
10
11
12
13
import numpy as np

class DataProcessor:
def calculate_std_dev(self, numbers):
return np.std(numbers, ddof=1)

def calculate_reliability(self, numbers):
mean = np.mean(numbers)
variance = np.var(numbers, ddof=1)
std_dev = np.sqrt(variance)
reliability = std_dev / mean
return mean, variance, std_dev, reliability

Step 4 打包程序

想要将整个程序打包成 exe 文件,这样方便在没有 python 环境时依然可以运行,步骤如下:

  • 安装 PyInstaller 包(可以直接在 powershell 中安装)
  • 使用 PyInstaller 打包脚本

1、在 PowerShell 中,运行如下命令

1
pip install pyinstaller

注意:前面已经安装了 python 程序,应该是可以使用 pip 命令了的。可输入 pip --version 命令查看。若该命令无法使用,很可能是没有将 python 解释器添加到环境变量中,可进行如下操作:

1
2
C:\Users\Administrator\AppData\Local\Programs\Python\Python312\
C:\Users\Administrator\AppData\Local\Programs\Python\Python312\Scripts\

这里的路径请替换为实际安装 Python 的路径

image.png

2、运行 pyinstaller 命令打包程序

1
pyinstaller --onefile --windowed your_script.py
  • --onefile:将所有内容打包成一个单独的 .exe 文件。
  • --windowed:用于创建一个没有命令行窗口的图形界面应用。

打包完成后,会生成几个文件夹:

  • dist 中包含生成的 exe 文件
  • build 中包含构建过程中的临时文件,可以删除。
  • your_script.spec 文件是 PyInstaller 的配置文件,可以根据需要进行修改。

image.png

.gif

恭喜你,得到了一款简易的小程序:)

明明程序实现的功能很简单,为什么生成的程序还那么大呢,例如生成上面的这个 exe 文件就有 50M。因为软件将一些必要的程序包(如 numpy 模块)都打包进去了,若之后再增加一些计算相关的各种功能进去,程序体积可能也只会大一丢丢吧

另外,打包时如何优化资源,排除掉一些不必要的模块还值得学学。不过,尽管文件体积大点,功能总算是实现了,还是蛮有成就感的

插件想法

想法来自于 Anki 插件:ankiweb.net/shared/info/1915225457,没想到 Anki 程序内部还能内嵌浏览器,不可思议

换在从前,想想自己那么菜的技术也就过了,可是目前有了 GPT 的加持,事情就变得不可思议了。当然,想要开发一款体验性能都好的插件,一个可称之为 项目 的插件,GPT 目前还是不太行的,下面为使用 GPT 辅助开发的两个简单 Anki 插件

插件目录

打开 Anki,快捷键 Ctrl+Shift+A 可弹出插件管理器,点击 查看文件 可以跳转到 Anki 插件的安装目录

image.png

例如我的 Anki 插件文件路径为:C:\Users\Administrator\AppData\Roaming\Anki2\addons21

创建插件

新建文件夹,例如 BrowserPlugin,在文件夹中新建 __init__.py 文件,之后在该 python 文件中编辑代码即可。

PS:每次修改代码之后,需重启 Anki 以重新加载插件,插件为自动加载方式

ANKI 插件的完整生成过程可参考与 GPT 的对话:ChatGPT - 开发 Anki 插件.

我只负责引导和给出调试报错信息,代码全程由 GPT 负责生成,代码如下:

image.png

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
from aqt import mw
from aqt.qt import QAction, QMenu
from PyQt6.QtCore import Qt
import webbrowser

def onGoogleSearch():
# 获取当前选中的文本
selected_text = mw.web.selectedText()

if selected_text:
# 使用浏览器打开Google搜索页面
webbrowser.open(f"https://www.google.com/search?q={selected_text}")

def showContextMenu(point):
menu = QMenu()

selected_text = mw.web.selectedText()

if selected_text:
action = QAction("Google搜索", mw)
action.triggered.connect(onGoogleSearch)
menu.addAction(action)

menu.exec(mw.web.mapToGlobal(point))

def setup_menu():
mw.web.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu)
mw.web.customContextMenuRequested.connect(showContextMenu)

setup_menu()

重启 Anki 之后(插件会自动加载),选中文字右键,即可弹出 Google 搜索菜单项,点击即可使用外部浏览器对选中的文字进行搜索,效果如下:

image.png

进一步完善插件

如上所述,搜索时使用外部浏览器进行搜索,能否在 Anki 程序中内嵌浏览器呢 —> 接下来还是通过 GPT 的辅助,完成初步的想法,完整示例代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
from aqt import mw
from aqt.qt import *
from PyQt6.QtWebEngineWidgets import QWebEngineView
from PyQt6.QtCore import QUrl, Qt

class BrowserDockWidget(QDockWidget):
def __init__(self, parent=None):
super().__init__("内置浏览器", parent)
self.browser = QWebEngineView()
self.setWidget(self.browser)
self.setup_browser_settings()
self.browser.setUrl(QUrl("https://www.google.com"))

def setup_browser_settings(self):
# 确保启用 JavaScript 和其他必要的功能
settings = self.browser.settings()
settings.setAttribute(QWebEngineSettings.WebAttribute.JavascriptEnabled, True)
settings.setAttribute(QWebEngineSettings.WebAttribute.PluginsEnabled, True)
settings.setAttribute(QWebEngineSettings.WebAttribute.LocalStorageEnabled, True)
settings.setAttribute(QWebEngineSettings.WebAttribute.DnsPrefetchEnabled, True)
settings.setAttribute(QWebEngineSettings.WebAttribute.AutoLoadImages, True)

def search(self, query):
self.browser.setUrl(QUrl(f"https://www.google.com/search?q={query}"))

def show_browser_dock_widget():
if not hasattr(mw, 'browser_dock'):
mw.browser_dock = BrowserDockWidget(mw)
mw.addDockWidget(Qt.DockWidgetArea.RightDockWidgetArea, mw.browser_dock)
mw.browser_dock.show()

def on_search_google():
selected_text = mw.web.selectedText()
if selected_text:
show_browser_dock_widget()
mw.browser_dock.search(selected_text)

def context_menu_event(point):
menu = QMenu(mw)
menu.addAction("Google搜索", on_search_google)
menu.exec(mw.web.mapToGlobal(point))

def add_search_context_menu():
mw.web.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu)
mw.web.customContextMenuRequested.connect(context_menu_event)

# 添加右键菜单项
add_search_context_menu()

浏览器右上角的几个按钮可以对内置浏览器进行关闭,拖动还可以自定义浏览器在 Anki 程序中的位置,展示效果如下:

image.png

Anki.gif

参考资料

用法

  • \AddToShipoutPictureBG:为每一页添加背景
  • \AddToShipoutPictureBG*:仅为当前页面添加背景
  • \AddToShipoutPictureFG:类似于 BG,但只作用于文档内容部分

一些预定于好的位置命令

  • AtPageUpperLeft
  • AtPageLowerLeft
  • AtPageCenter
  • AtTextUpperLeft
  • AtTextLowerLeft
  • AtTextCenter
  • AtStockUpperLeft
  • AtStockLowerLeft
  • AtStockCenter

案例分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
\documentclass{ctexart}
\usepackage{graphicx}
\usepackage{zhlipsum}
\usepackage{eso-pic}
\begin{document}
\zhlipsum[1-3]
\clearpage
\AddToShipoutPictureBG{
\includegraphics[width=\paperwidth,height=\paperheight]{example-image-duck}
}
\clearpage
\zhlipsum[1]
\clearpage
\zhlipsum[1]
\section{第一部分}

\end{document}

插入长宽和当前页面长宽一致的鸭子的背景图片

image.png

如果我们有比较好的背景图片,比如四角带有阴影的 png 图片或者设计好的图片,那么使用该宏包还是挺有效果的

部分说明

1
width=\paperwidth,height=\paperheight

表示图片的长宽和页面的长宽一致,将图片强制拉伸至页面长宽

1
2
3
example-image-duck
example-image-a
example-grid-100x100bp

这几个图片名称经常用于插入示范图片,分别代表 鸭子字母A长宽为10个单元格的网格表

注意使用 graphics 宏包(正常应为 graphicx 宏包)插入 includegraphics 命令时 texstudio 不会出现红色背景提示,但是编译出来老是报错 \end{document}

image.png

参考资料

完整案例如下,所有浮动体默认在环境中都是居中布置了,并且题注采用比正文小一号的字号。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
\documentclass{ctexart}
\usepackage{graphicx}
\usepackage{xpatch}
\makeatletter
\xpatchcmd\@floatboxreset{\normalsize}{\centering\small}{}{}
\makeatother
\begin{document}
比如正文字号\fontname\font
\begin{figure}
\includegraphics[width=3in]{example-image}
\caption{浮动体环境中的字体比正文字体小一号}
比如查看浮动体中的字号\fontname\font
\end{figure}
\end{document}

image.png

参考资料

残余应力的概念

在无外力或外力矩的作用下,存在于材料内部,且达到自平衡状态的力。

从微观上看,残余应力的本质是晶格畸变,以 “畸变能” 到形式存在于材料内部。

这些能量不会随着加工过程中能量的交换或材料的变形与剥离而全部消散掉,其中一部分转换为势能,以残余应力及形变的形式存储在介质中。
一旦原有的平衡被打破,上述势能中的一部分转化为残余应力作用下发生形变所作的功,使工件整体进入新的平衡,从而造成各加工环节中由残余应力引起的不同程度的加工变形。

残余应力的影响

残余应力是零件质量性能的关键指标,直接影响零件的加工精度、尺寸稳定性、腐蚀开裂、疲劳强度以及使用寿命等性能。

—> 为使零件达到良好的质量状态,应对其残余应力加以控制和利用:

  • 对过高的残余应力进行消除;
  • 对不均匀的残余应力进行均化处理;
  • 对有特殊要求的零件进行表面强化,引入压应力。

残余应力消除的主要方法

自然实效

把工件露天放置于室外,经过几个月至几年的风吹、日晒、雨淋和季节的温度变化,给工件多次造成反复的温度应力,促使残余应力发生松弛,获得稳定的状态。

  • 特点:处理简单、进程缓慢
  • 应用:
    • 结构或尺寸特殊的工件或原材料应用多
    • 在加工过程中也有应用(装夹下,减少卸除夹具后的变形量)

热实效

热实效是把工件放进热实效炉中进行热处理,由室温缓慢均匀加热至一定温度,保温一段时间后,再严格控制降温速度最终出炉。

热实效主要是利用材料在高温状态下,原子活动能力增强,而材料的屈服强度降低的特点,促进位错开动和原子回复,使得晶格畸变得到消除,残余应力得到消除。

典型应用为去应力退火,将材料加热至再结晶温度以下,保温一定时间后再缓慢冷却,以达到消除材料内部残余内应力的效果。(注意:材料热实效的工艺参数,可通过相关材料手册的热处理制度查得。由于材料成分、加工方法、内应力大小和分布不同,去应力退火的温度范围很广)

应用:热锻轧、铸造、各种冷变形加工、切削、焊接、热处理,甚至零部件装配后,都可以在不改变组织状态、保留冷作、热作或者表面硬化的条件下,加热到一定温度去除构件的内应力。

  • 优点:
    • 对于大多数材料能取得较好的效果,工程上有广泛、成熟的应用
  • 缺点:
    • 去应力效果与温度相关性大,温度影响材料组织和力学性能,处理过程中可能产生:氧化烧损、相变析出、热变形等情况
    • 对于大型构件难以适用
    • 能耗高

振动时效

工程材料常用的一种消除内部残余应力的方法,通过振动,使材料发生微量的塑性变形,从而使材料内部的应力得以松弛。分为亚共振实效、模态宽屏实效、频谱谐波实效、频谱谐波定位实效等方式。

以振动的形式给工件施加动应力,当动应力与工件残余应力叠加后,达到或超过材料的屈服极限时,工件发生微观或宏观塑性变形,从而使工件内部的残余应力得以松弛。

振动时效能够在一些场景下取代热实效。同时由于振动时效的机理与热实效不同,因此它也能够在热实效之后再次应用,并达到进一步消除残余应力的效果。

其他方法

  • 机械拉伸发
  • 爆炸法
  • 超声冲击法

image.png

新建文本文件,将以下两种方式中的任意一种粘贴到文本中,并保存为 bat 格式双击运行即可

1
2
3
4
5
6
echo off & color 0A
echo 当前目录:"%cd%" >fileTree.txt
tree /f >>fileTree.txt
echo 目录树已生成,按任意键查看。
pause>nul
start fileTree.txt
1
tree /f > tree_output.txt
  • 如果只想输出文件夹信息,加 /a 参数
  • 如果想输出文件夹下的文件名,加 /f 参数

或者不新建文件,在文件夹中,按住 Shift 右键,在此处打开 powershell,然后粘贴代码 tree /f > tree_output.txt 即可

image.png

在当前文件夹搜索栏左侧的路径下,直接敲入 CMD,回车即可

保留源列宽

目的:复制表格时,保持表格列宽格式相同

image.png

尽量不要合并单元格

  • 通过筛选之后,合并的单元格无法进行复制
  • 跨行合并的数据可能影响筛选的结果

统计数据出现的次数

image.png

1
=IF(A2<>A1, COUNTIF(A:A, A2), "")

为什么第 6 行和第 7 行单元格为空呢:通过 IF 函数进行判断,上下两行内容相同,所以显示为空

统计不同种类的数据

image.png

1
=IF(COUNTIF($A$2:A2,A2)=1,MAX($B$1:B1)+1,"")
1
2
3
4
5
6
7
% 判断某一行的数据在整列当中是否为第一次出现
COUNTIF($A$2:A2,A2)=1

% 如果第一次出现,则值为1;之后每出现一个第一次出现的新数据,则该数据每次+1
—> MAX($B$1:B1)+1

% 数据不是第一次出现,则为空值

如上图所示,第 6,7,9 行都为空值,能不能让这些行都带上编号呢:

image.png

1
=IF(COUNTIF($A$2:A2,A2)=1,MAX($B$1:B1)+1,VLOOKUP(A2,$A$1:B1,2,FALSE))

第 6,7,9 行之所以为空值,是因为第二行中第一次出现了该数据,因此只需查询到第一次出现数据的第二列编号即可,而 VLOOKUP 函数会自动从上往下进行查找,就能正确的找到第二列的编号。

复制工作表

按住 Ctrl 键拖动即可复制工作表

image.png

查找工作表当中的合并单元格

需求:从别人接手的工作表,很可能包含大量合并的单元格,需要将它们取消合并

1、查找,进入 格式 选择

image.png

2、勾选 合并单元格

image.png

宏绑定快捷键

如下图所示,不过不支持 Ctrl+ 数字键,那么就换成字母吧

image.png

单元格是否存在某些内容

例如,若 E 列中存在 PH值PHPH50% 这些字样,那么在 I 列中将其提取出来

image.png

1
=IF(ISNUMBER(SEARCH("pH", E4)), E4, "")

为什么要用 ISNUMBER 函数:因为 SEARCH 函数在未找到指定的字符串时会返回错误值,而不是返回 0。
比如没有找到相应的文字,那么 search 返回 false,isnumber 函数返回 false,最终就返回空值。如果找到了相应的文字,那么 search 返回对应的索引,isnumber 函数返回 true,最终返回


进一步地,将一个词的条件变为多个词,需求如下:

只要单元格存在 氢氰酸总氰游离氢氰酸游离丙酮硫酸丙酮氰醇 这几个词中的某一个,那么就判断正确(即需要在其他列中将其提取出),否则为空

1
2
3
4
5
6
7
8
9
10
11
12
=IF(
OR(
ISNUMBER(SEARCH("氢氰酸", E4)),
ISNUMBER(SEARCH("总氰", E4)),
ISNUMBER(SEARCH("游离氢氰酸", E4)),
ISNUMBER(SEARCH("游离丙酮", E4)),
ISNUMBER(SEARCH("硫酸", E4)),
ISNUMBER(SEARCH("丙酮氰醇含量", E4))
),
E4,
""
)

最小公倍数

1
=LCM(B2:B5)

image.png

合并单元格 - 宏程序

假设三个单元格都存在数据,将这连续的三个单元格合并时,默认只会保留左上角单元格的数据,其他单元格数据丢失。而如果有如下需求:需要将多个单元格数据都保留下来

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
Sub 合并选定单元格()
Dim rng As Range
Dim mergedCell As Range
Dim cell As Range
Dim text As String

' 检查是否有选定的单元格
If Selection.Cells.Count < 2 Then
MsgBox "请选择至少2个单元格进行合并。"
Exit Sub
End If

Set rng = Selection ' 获取选定的单元格范围

' 创建一个合并单元格
Set mergedCell = rng.Cells(1)

' 将其他单元格的值添加到合并单元格中
For Each cell In rng
If cell.Value <> "" Then
text = text & cell.Value & Chr(10) ' Chr(10) 表示换行符
End If
Next cell

' 去除最后一个换行符
text = Left(text, Len(text) - 1)

' 将合并后的文本赋值给合并单元格
mergedCell.Value = text

' 合并单元格
rng.Merge
End Sub

拆解合并的单元格(并填充内容)

image.png

例如第 145 和 146 行,按照常规拆解单元格流程,拆解后只有 145 行保留数据,146 行数据为空。
需求如下:如何保持每一格单元格的内容和拆解前的保持一致

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Sub1()
'
' 宏1 宏
' 取消合并行。请注意,这段代码假设你只选择了一个连续的单元格范围。如果选择的范围包含多个不连续的区域,代码可能无法正常工作。确保选择的范围是连续的单元格范围。
'

'
Dim selectedRange As Range
Set selectedRange = Selection ' 将选择的单元格范围存储到变量中

selectedRange.Select
Selection.UnMerge
selectedRange.Rows(1).AutoFill Destination:=selectedRange, Type:=xlFillCopy ' 使用第一行的内容自动填充整个范围
selectedRange.Select
selectedRange.Cells(selectedRange.Rows.Count, selectedRange.Columns.Count).Select ' 选中范围中的最后一个单元格
End Sub

计算单元格中字符出现的次数

image.png

1
=LEN(A2) - LEN(SUBSTITUTE(A2, "k", ""))

公式思路:先计算单元格中总的字符长度,然后将要找到字符替换为空字符,那么『总长度』减去『替换为空字符后的长度』,就是你要找到字符的个数
SUBSTITUTE:将 "k" 替换为空字符 ""

ROUNDUP 函数

参考资料:Fetching Title#mrdb

朝着远离 0(零)的方向将数字进行向上舍入:

  • ROUNDUP 的行为与 ROUND 相似,所不同的是它始终将数字进行向上舍入
  • 如果 num_digits 大于 0(零),则将数字向上舍入到指定的小数位数。
  • 如果 num_digits 为 0,则将数字向上舍入到最接近的整数。
  • 如果 num_digits 小于 0,则将数字向上舍入到小数点左边的相应位数。
1
2
3
4
5
6
7
8
=ROUNDUP(3.001,0) —> 4
=ROUNDUP(3.01,1) —> 3.1

注意:该函数并不考虑什么四舍五入,都是向上取整

=ROUNDUP(-3.14159, 1) —> 如果第一个参数是正数,那么返回值是3.2;是负数,返回-3.2

=ROUNDUP(31415.92654, -2) —> 将 31415.92654 向上舍入到小数点左边两位数,结果为31500

多列数据合并(自动添加分隔符)

未完待续…

粘贴时保留纯文本

参考资料:如何设置 WORD 粘贴时默认仅保留文本格式 - 百度经验

image.png

之后从浏览器中粘贴过来,就不需要在按一次 Ctrl,然后再按 T 了,直接粘贴为纯文本格式

image.png

使用自定义模板

参考资料:office 增加自定义文档模板_用户可自定义文档类型的模板,对文档模板进行规范,并可定义模板中的 “签字标记”-CSDN 博客

  1. 打开手头的 word 模板
  2. 另存为 dotx,会自动跳转到自定义 office 模板的文件夹路径,保存
  3. 再次新建 word 文档的时候,可从 个人 的模板中选择进行新建文档

image.png

image.png

Mathtype 插入公式

如果使用中文输入法,那么输出的 x 这样的字符就不是标准的数学字体,切换成英文

Word 自带的公式

有专用和线性区别,比如说如下的 Cambria Math 字体,还有两种显示模式

调整表格行高

问题:直接调整段落,设置为最小值,表格之间仍然还是有间隙

1、先手动减小这个高度

image.png

2、调整完之后,行与行之间的间隙没有了

image.png

3、此时在调整为最小值

image.png

网页文字复制

有些网站是禁止复制文字的 —> 在不使用浏览器插件的情况下,可以将网页保存为 mhtml 本地文件

参考资料

案例

1
2
3
4
5
6
7
8
9
10
11
12
gantt
title 项目进度
dateFormat YYYY-MM-DD
section 设计
任务1: a1, 2022-01-01, 3d
任务2: a2, after a1, 2d
%% 注释内容
section 开发
任务3: b1, after a2, 4d
里程碑: milestone, after b1
section 测试
任务4:c1, after b1, 2d

修改坐标轴格式

坐标轴(横坐标)默认输出日期格式为 YYYY-MM-DD,以天作为单位,如何以分钟或者秒作为横坐标的单位呢:

1
axisFormat %Y-%m-%d
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
%a - 缩写的星期几名称。
%A - 完整的星期几名称。
%b - 缩写的月份名称。
%B - 完整的月份名称。
%c - 日期和时间,格式为 "%a %b %e %H:%M:%S %Y"。
%d - 以十进制数表示的零填充的月份中的日期 [01,31]。
%e - 以十进制数表示的空格填充的月份中的日期 [ 1,31];等同于 %_d。
%H - 小时(24小时制)的十进制数表示 [00,23]。
%I - 小时(12小时制)的十进制数表示 [01,12]。
%j - 以十进制数表示的一年中的日期 [001,366]。
%m - 以十进制数表示的月份 [01,12]。
%M - 以十进制数表示的分钟 [00,59]。
%L - 以十进制数表示的毫秒数 [000, 999]。
%p - 上午(AM)或下午(PM)。
%S - 以十进制数表示的秒数 [00,61]。
%U - 以十进制数表示的一年中的周数(以星期日作为一周的第一天) [00,53]。
%w - 以十进制数表示的星期几 [0(星期日),6]。
%W - 以十进制数表示的一年中的周数(以星期一作为一周的第一天) [00,53]。
%x - 日期,格式为 "%m/%d/%Y"。
%X - 时间,格式为 "%H:%M:%S"。
%y - 以十进制数表示的不带世纪的年份 [00,99]。
%Y - 以十进制数表示的带世纪的年份。
%Z - 时区偏移,例如 "-0700"。
%% - 字面上的 "%" 字符。

例如需要将坐标轴改为小时和分钟,那么:

1
2
3
4
5
6
7
gantt
title 甘特图
+ axisFormat %H-%M
+ dateFormat HH:mm
section 样本1
任务1:a1,00:00, 70s
任务2:after a1, 100s

image.png

将坐标轴改成分钟和秒,那么:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
gantt
title 效率计算
+ axisFormat %M:%S
+ dateFormat mm:ss
%% 进样
section 流程1
步骤1: milestone,a0,0,0s
步骤2: a1,after a0,8.6s
步骤3: a2,after a1,3s
步骤4: a3,after a2,10s
section 流程2
步骤2: b1,after a2,8.6s
步骤3: b2,after b1,3s
步骤4: b3,after b2,10s

image.png

给里程碑取别名

1
2
3
4
5
6
7
gantt
dateFormat HH:mm
axisFormat %H:%M
+ Initial milestone : milestone, m1, 17:49, 2m
Task A : 10m
Task B : 5m
+ Final milestone : milestone, m2, 18:08, 4m

image.png

某效率计算

案例分析如下,对于有同步运行的动作,可通过这种时间重叠的方式,求出仪器检测样本的最快时间:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
gantt
title 水分检测效率计算
axisFormat %M-%S
dateFormat mm-ss

section 样本1
开始检测: milestone,SF1-0,0,0s
样本瓶就位:SF1-1,after SF1-0,10s
夹取注射器: SF1-2,after SF1-0,10s
开盖:SF1-3,after SF1-1,10s
润洗3次注射器: SF1-4,after SF1-2,45s
抽取样品: SF1-5,after SF1-4,10s
拧盖:SF1-6,after SF1-5,10s
称重: SF1-7,after SF1-5,20s
注入仪器:SF1-8,after SF1-7,20s
称重: SF1-9,after SF1-8,20s
注射器放托盘: SF1-10,after SF1-9,10s
水分检测:SF1-11,after SF1-8,240s

section 样本2
样本瓶就位:SF2-1,after SF1-10,10s
夹取注射器: SF2-2,after SF1-10,10s
开盖:SF2-3,after SF2-1,10s
润洗3次注射器: SF2-4,after SF2-2,45s
抽取样品: SF2-5,after SF2-4,10s
拧盖:SF2-6,after SF2-5,10s
称重: SF2-7,after SF2-5,20s
注入仪器:SF2-8,after SF2-7,20s
称重: SF2-9,after SF2-8,20s
注射器放托盘: SF2-10,after SF2-9,10s
水分检测:SF2-11,after SF2-8,240s

section 样本3
样本瓶就位:SF3-1,after SF2-10,10s
夹取注射器: SF3-2,after SF2-10,10s
开盖:SF3-3,after SF3-1,10s
润洗3次注射器: SF3-4,after SF3-2,45s
抽取样品: SF3-5,after SF3-4,10s
拧盖:SF3-6,after SF3-5,10s
称重: SF3-7,after SF3-5,20s
注入仪器:SF3-8,after SF3-7,20s
称重: SF3-9,after SF3-8,20s
注射器放托盘: SF3-10,after SF3-9,10s
水分检测:SF3-11,after SF3-8,240s

image.png

Tips:每个样本检测需要执行的操作都是一样的。第一个样本的代码写完后,后续可以通过查找替换的方式,进行批量替换。例如在样本一中将 SF1-1 第一个 1 替换为 2,即 SF2-1,样本 2 的代码便很快可以修改完毕。