Anki插件Export deck to html的改造

在Anki中进行复习时,每次只能打开一条笔记。如果积累了很多笔记,有时候会有将它们集中输出成一个pdf进行阅读的想法。Anki插件Export deck to html(安装ID:1897277426)就有这个功能。但是,这个插件目前存在以下问题:

1、Anki升级为版本 ⁨24.06.3 (d678e393)⁩后(也许更早的版本就这样,我没试过),插件无法正常运行;

2、插件转pdf的效果不是很好,但转html的效果不错。考虑到html转pdf非常容易(word即可完成,多数浏览器在插件支持下或无需插件也能完成),所以插件的转pdf功能比较鸡肋;

3、笔记中的img标签,在转换为html后,除了“src”属性得以保留,其余的属性会全部丢失。

4、输出的html在每一条笔记前添加了没有用处的前缀“>y”

鉴于上述问题,所以对该插件的主文件ExportDeckToHtml.py进行了修改。具体修改的内容包括:

1、将不在兼容新版Anki的几行代码进行修改和删除,其中包括

1)dialog.exec_()修改为dialog.exec()

2)options = QFileDialog.DontUseNativeDialog删除

3)path = QFileDialog.getSaveFileName( self, "Save File", directory, "All Files (*)", options=options) 修改为path = QFileDialog.getSaveFileName(  self, "Save File", directory, "All Files (*)")

2、修改_setup_ui函数,取消界面上的保存为pdf等元素。

3、修改_export_to_html函数,在处理卡片的html中的img标签时,只将src属性中的路径修改为绝对路径,而src属性之外的其他属性保持不变。

4、修改每条笔记的html,增加笔记序号信息,删掉无用前缀。

修改后的ExportDeckToHtml.py文件内容如下:

from aqt import mw, utils
from aqt.qt import *
from os.path import expanduser, join
from pickle import load, dumpimport os
import re
import unicodedata
from .pdfkit import from_stringdelimiter = "####"ascending = "Ascending"
descending = "Descending"
config_file = "export_decks_to_html_config.cfg"class AddonDialog(QDialog):"""Main Options dialog"""def __init__(self):global config_fileQDialog.__init__(self, parent=mw)self.path = Noneself.deck = Noneself.fields = {}self.card_orders = [ascending, descending]self.order_fn = Noneself.advance_mode = Falseif os.path.exists(config_file):try:self.config = load(open(config_file, 'rb'))except:self.config = {}else:self.config = {}self._setup_ui()def _handle_button(self):dialog = OpenFileDialog()self.path = dialog.filenameif self.path is not None:utils.showInfo("Choose file successful.")def _handle_load_template(self):dialog = OpenFileDialog()self.advance_mode = Falseself.template_path = dialog.filenameif self.template_path is not None and len(self.template_path) > 0:utils.showInfo("Choose file successful.")self.template_label.setText(self.template_path)def _setup_ui(self):"""Set up widgets and layouts"""layout = QGridLayout()layout.setSpacing(10)deck_label = QLabel("Choose deck")# deck nameself.deck_selection = QComboBox()deck_names = sorted(mw.col.decks.allNames())current_deck = mw.col.decks.current()['name']deck_names.insert(0, current_deck)for i in range(len(deck_names)):if deck_names[i] == 'Default':deck_names.pop(i)breakself.deck_selection.addItems(deck_names)self.deck_selection.currentIndexChanged.connect(self._select_deck)layout.addWidget(deck_label, 1, 0, 1, 1)layout.addWidget(self.deck_selection, 1, 1, 1, 2)export_dir = self.config.get('export_dir', expanduser("~/Desktop"))self.export_dir = QLineEdit(export_dir)field_label = QLabel('Sort')self.field_selection = QComboBox()fields = self._select_fields(self.deck_selection.currentText())if self.deck_selection.currentText() in self.config:currentField = self.config[self.deck_selection.currentText()].get('field_selection', '')if len(currentField) > 0:if currentField in fields:fields.remove(currentField)fields.insert(0, currentField)self.field_selection.addItems(fields)layout.addWidget(field_label, 2, 0, 1, 1)layout.addWidget(self.field_selection, 2, 1, 1, 2)template_path = ''if self.deck_selection.currentText() in self.config:template_path = self.config[self.deck_selection.currentText()].get('template_path', '')self.template_label = QLabel(template_path)# orderorder_label = QLabel('Order')self.order_selection = QComboBox()orders = self.card_orders[:]if self.deck_selection.currentText() in self.config:currentOrder = self.config[self.deck_selection.currentText()].get("order_selection", '')if len(currentOrder) > 0:orders.remove(currentOrder)orders.insert(0, currentOrder)self.order_selection.addItems(orders)self.order_selection.currentIndexChanged.connect(self._handle_order_card)layout.addWidget(order_label, 3, 0, 1, 1)layout.addWidget(self.order_selection, 3, 1, 1, 2)self.load_template_btn = QPushButton('Load template')self.load_template_btn.clicked.connect(self._handle_load_template)layout.addWidget(self.load_template_btn, 4, 0, 1, 1)layout.addWidget(self.template_label, 4, 1, 1, 2)self.to_pdf = Falselayout.addWidget(self.export_dir, 5, 1, 1, 2)export_dir_label = QLabel("Export directory")layout.addWidget(export_dir_label, 5, 0, 1, 1)# Main button boxok_btn = QPushButton("Export")save_btn = QPushButton("Save")cancel_btn = QPushButton("Cancel")button_box = QHBoxLayout()ok_btn.clicked.connect(self._on_accept)save_btn.clicked.connect(self._on_save)cancel_btn.clicked.connect(self._on_reject)button_box.addWidget(ok_btn)button_box.addWidget(save_btn)button_box.addWidget(cancel_btn)# Main layoutmain_layout = QVBoxLayout()main_layout.addLayout(layout)main_layout.addLayout(button_box)self.setLayout(main_layout)self.setMinimumWidth(360)self.setWindowTitle('Export deck to html')def _reset_advance_mode(self):self.advance_mode = Falseself.csv_file_label.setText('')def _to_pdf(self):self.to_pdf = not self.to_pdfdef _handle_adv_mode(self):dialog = OpenFileDialog("csv")self.path = dialog.filenameif self.path is not None and len(self.path) > 0:utils.showInfo("Choose file successful.")self.advance_mode = Trueself.csv_file_label.setText(self.path)def _select_deck(self):current_deck = self.deck_selection.currentText()fields = self._select_fields(current_deck)if self.deck_selection.currentText() in self.config:currentField = self.config[current_deck].get('field_selection', '')if len(currentField) > 0:fields.remove(currentField)fields.insert(0, currentField)self.field_selection.clear()self.field_selection.addItems(fields)orders = self.card_orders[:]if current_deck in self.config:currentOrder = self.config[current_deck].get("order_selection", '')if len(currentOrder) > 0:orders.remove(currentOrder)orders.insert(0, currentOrder)self.order_selection.clear()self.order_selection.addItems(orders)template_path = ''if current_deck in self.config:template_path = self.config[current_deck].get("template_path", '')self.template_label.setText(template_path)def _on_save(self):global config_filecurrent_deck = self.deck_selection.currentText()self.config[current_deck] = {}self.config[current_deck]['template_path'] = self.template_label.text()self.config[current_deck]["field_selection"] = self.field_selection.currentText()self.config[current_deck]["order_selection"] = self.order_selection.currentText()self.config[current_deck]["to_pdf"] = self.to_pdfself.config["export_dir"] = self.export_dir.text()dump(self.config, open(config_file, 'wb'))utils.showInfo("Config saved")def _convert_to_multiple_choices(self, value):choices = value.split("|")letters = "ABCDEFGHIKLMNOP"value = "<div>"for letter, choice in zip(letters, choices):value += '<div>' + \"<span><strong>(" + letter + ")&nbsp</strong></span>" + \choice.strip() + '</div>'return value + "</div>"def _select_fields(self, deck):query = 'deck:"{}"'.format(deck)try:card_id = mw.col.findCards(query=query)[0]except:utils.showInfo("This deck has no cards.")return []card = mw.col.getCard(card_id)fields = card.note().keys()return ["Due", ] + fieldsdef _handle_order_card(self):self.order_fn = self._order_card(self.order_selection.currentText())def _order_card(self, order_by):def f(field):def g(card):try:if field == 'Due':return card.duereturn card.note()[field]except KeyError:return ''return gdef ascending_fn(cards, field):return sorted(cards, key=f(field))def descending_fn(cards, field):return sorted(cards, key=f(field), reverse=True)if order_by == ascending:return ascending_fnreturn descending_fndef _get_all_cards(self, deck_name, field, order_fn):deck_name = deck_name.replace('"', '')deck_name = unicodedata.normalize('NFC', deck_name)deck = mw.col.decks.byName(deck_name)if deck == None:returndecks = [deck_name, ]if len(mw.col.decks.children(deck['id'])) != 0:decks = [name for (name, _) in mw.col.decks.children(deck['id'])]decks = sorted(decks)all_cards = []for deck in decks:query = 'deck:"{}"'.format(deck)cids = mw.col.findCards(query=query)cards = []for cid in cids:card = mw.col.getCard(cid)cards.append(card)all_cards.extend(cards)if order_fn is not None:return order_fn(all_cards, field)return all_cardsdef _export_to_html(self, output_path, deck_name, sort_by, order, template_path, export_to_pdf=True):# html_path = self.template_label.text()if template_path is None or len(template_path) == 0:return Falseorder_fn = self._order_card(order)cards = self._get_all_cards(deck_name, sort_by, order_fn)if cards is None or len(cards) == 0:return Falsehtml_template = ''with open(template_path, 'r', encoding='utf-8') as f:html_template += f.read()header, body, has_table = self._separate_header_and_body(html_template)collection_path = mw.col.media.dir()path = output_pathtry:html = ""template = bodyfields = re.findall("\{\{[^\}]*\}\}", template)dedup = set()for i, card in enumerate(cards):card_html = templatecard_html = card_html.replace("{{id}}", str(i + 1))key = ""for field in fields:if field == "{{id}}":continuetry:value = card.note()[field[2:-2]]key += valueexcept KeyError:value = '## field ' + field + ' not found ##'card_html = card_html.replace(field, value)# 将html中的相对路径全部替换为绝对路径pattern = re.compile(r'<img.*?src="(.*?)".*?>', re.I | re.M)for match in re.finditer(pattern, card_html):relative_path = match.group(1)absolute_path = f'{collection_path}\\{relative_path}'card_html = card_html.replace(relative_path, absolute_path)if key not in dedup:html += '<span class="red">第' + str(i + 1) + '条:</span>' + card_html[2:]dedup.add(key)if not has_table:html = header + "\n<body>" + html + "</body>"else:html = header + "\n<body>\n\t<table>" + html + "\t</table>\n</body>"if not export_to_pdf:with open(path, "w", encoding="utf8") as f:f.write(html)else:options = {# 'header-left': '[webpage]',# 'header-right': '[page]/[toPage]',# 'header-line': '',# 'header-font-size': 10'margin-bottom': 15,'margin-left': 10,'margin-right': 10,'margin-top': 15,'footer-center': '[page]','footer-font-size': 8,'footer-spacing': 5,}from_string(html, path, options)except IOError as e:return Falsereturn Truedef _on_accept(self):if not self.advance_mode:dialog = SaveFileDialog(self.deck_selection.currentText(), self.export_dir.text(), self.to_pdf)file_path = dialog.filenameif file_path == None:returnif type(file_path) is tuple:file_path = file_path[0]template_path = self.template_label.text()if template_path is None or len(template_path) == 0:utils.showInfo("Cannot find template")returncan_export = self._export_to_html(join(self.export_dir.text(), file_path),self.deck_selection.currentText(),self.field_selection.currentText(),self.order_selection.currentText(),template_path,self.to_pdf)if not can_export:utils.showInfo("Cannot export")else:utils.showInfo("Exported successfully")else:with open(self.path, "r", encoding="utf-8") as f:i = 0non_exist_decks = []non_exist_files = []for line in f:if i == 0:i += 1continuedeck_name, output_dir, output_name, sort_by, order, template_path, to_pdf = \line.split(',')[:7]if output_name is None and len(output_name) == 0:output_name = deck_nameif not os.path.isfile(template_path):non_exist_files.append(template_path)continueto_pdf = True if standardize(to_pdf).lower() == 'true' else Falsecan_export = self._export_to_html(join(standardize(output_dir),standardize(output_name)),standardize(deck_name),standardize(sort_by),standardize(order),standardize(template_path),to_pdf)if not can_export:non_exist_decks.append(deck_name)if len(non_exist_decks) > 0:utils.showInfo("Non existing decks\n" +'\n'.join(non_exist_decks))returnif len(non_exist_files) > 0:utils.showInfo("Non existing files\n" +'\n'.join(non_exist_files))returnutils.showInfo("Exported successfully")def _on_reject(self):self.close()def _separate_header_and_body(self, hl):last_header = hl.find("</head>")last_header += len("</head>")body = hl[last_header:]first = body.find("<table>")last = body.rfind("</table>")if first == -1 or last == -1:first = body.find("<table>") + len("<body>")last = body.find("</body>")has_table = Falseelse:first = first + len("<table>")has_table = Truereturn hl[:last_header][:], body[first:last], has_tableclass SaveFileDialog(QDialog):def __init__(self, filename, export_dir=expanduser("~/Desktop/"), to_pdf=False):QDialog.__init__(self, mw)self.title = 'Save File'self.left = 10self.top = 10self.width = 640self.height = 480self.filename = Noneself.default_filename = filenameself.to_pdf = to_pdfself.export_dir = export_dirself._init_ui()def _init_ui(self):self.setWindowTitle(self.title)self.setGeometry(self.left, self.top, self.width, self.height)self.filename = self._get_file()def _get_file(self):# options = QFileDialog.Options()# 升级后QFileDialog不存在DontUseNativeDialog属性# options = QFileDialog.DontUseNativeDialogdefault_filename = self.default_filename.replace('::', '_')if not self.to_pdf:directory = join(self.export_dir, default_filename + ".html")else:directory = join(self.export_dir, default_filename + ".pdf")try:path = QFileDialog.getSaveFileName(# 取消options参数# self, "Save File", directory, "All Files (*)", options=options)self, "Save File", directory, "All Files (*)")if path:return pathelse:utils.showInfo("Cannot open this file.")except:utils.showInfo("Cannot open this file.")return Noneclass OpenFileDialog(QDialog):def __init__(self, file_type="html"):QDialog.__init__(self, mw)self.title = 'Open file'self.left = 10self.top = 10self.width = 640self.height = 480self.filename = Noneself.file_type = file_typeself._init_ui()def _init_ui(self):self.setWindowTitle(self.title)self.setGeometry(self.left, self.top, self.width, self.height)self.filename = self._get_file()# self.exec_()def _get_file(self):# options = QFileDialog.Options()# 升级后QFileDialog不存在DontUseNativeDialog属性# options = QFileDialog.DontUseNativeDialogdirectory = expanduser("~/Desktop")try:if self.file_type == "html":path = QFileDialog.getOpenFileName(# 取消options参数# self, "Save File", directory, "All Files (*)", options=options)self, "Save File", directory, "All Files (*)")elif self.file_type == "csv":path = QFileDialog.getOpenFileName(# 取消options参数# self, "Save File", directory, "All Files (*)", options=options)self, "Save File", directory, "All Files (*)")if path and path[0]:return path[0]else:utils.showInfo("Cannot open this file.")except:utils.showInfo("Cannot open this file.")return Nonedef display_dialog():dialog = AddonDialog()dialog.exec()# 原来方法名exec_错误,多了下划线# dialog.exec_()def standardize(word):return word.strip()action = QAction("Export deck to html", mw)
action.setShortcut("Ctrl+M")
action.triggered.connect(display_dialog)
mw.form.menuTools.addAction(action)

只需安装该插件,然后打开插件文件夹,编辑ExportDeckToHtml.py文件,将其内容全部替换为以上代码即可。在使用此插件时,需要提前准备一个html模板。我用于导出基于对兼容各操作系统的Anki选择题模板的更新——提供更方便的笔记修改功能-CSDN博客一文中的模板所编写的笔记牌组的html模板如下,可供参考:

<!DOCTYPE html>
<html>
<head><style>body{font-size:1.2em;width:19.7cm;}table {border-collapse: collapse;
}table tr:nth-child(2n+1) {background-color: #eee;
}td {padding: 5px;text-align: center;border: 2px solid green;vertical-align: middle;
}td.left {text-align: left;
}td.red {border-right: solid thick red;
}hr {border: none;height: 5px;background-color: blue;
}div {margin: 5px auto
}a,
a:visited,
a:hover,
a:link,
a:active {color: #f90;font-weight: bold;font-family:Cambria-modify,'干就完事了简','微软雅黑';
}.pink{font-family:'黑体';font-weight: bold;font-size: 1.2em;
}u,
.red {color: #f00;font-weight: bold;text-decoration: none;font-family:Cambria-modify,'干就完事了简','微软雅黑';
}.green,
i {font-weight: bold;font-style: normal;color: #3bb;font-family:Cambria-modify,'Aa奇思胖丫儿','微软雅黑';
}.blue,
b {font-weight: bold;font-style: normal;color: #39e;font-family:Cambria-modify,'微软雅黑';
}img{display:block;object-fit:scale-down;
}</style>
</head>
<body>
<div><span class='pink'>【题干】:</span>{{问题}}</div>
<span class='pink'>【选项】:</span>
<div>{{选项}}</div>
<div><span class='pink'>【答案】:</span>{{答案}}</div>
<span class='pink'>【解析】:</span>
<div>{{解析}}</div>
<hr>
</body>
</html>

基于以上模板输出html的操作过程如下:

c5cf3120ad0e498f8172b546a2185c32.png

6f7871eeeddf4dcea9ea5eedcfbeff2c.png

导出的html效果如下:

58343782896b44b6b93df542c396da97.png

顺便说一句,在试用了十数个Anki插件后,我只保留了两个:Edit field during review和Export deck to html。如果有其他便于Anki使用的插件,欢迎留言推荐,如有改造相关插件的想法,也欢迎留言,我可能会试着帮你实现。

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/458964.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

岛津分子泵软件TMP系列分子泵EI-D系列控制电源 EI Monitor(232和485控制)

岛津分子泵软件TMP系列分子泵EI-D系列控制电源 EI Monitor(232和485控制)

探索Unity:从游戏引擎到元宇宙体验,聚焦内容创作

unity是实时3D互动内容创作和运营平台&#xff0c;包括游戏开发、美术、建筑、汽车设计、影视在内的所有创作者&#xff0c;借助Unity将创意变成现实。提供一整套完善的软件解决方案&#xff0c;可用于创作、运营和变现任何实时互动的2D和3D内容&#xff0c;支持平台包括手机、…

图为大模型一体机新探索,赋能智能家居行业

在21世纪的今天&#xff0c;科技的飞速进步正以前所未有的速度重塑着我们的生活方式。从智能手机到物联网&#xff0c;从大数据到人工智能&#xff0c;每一项技术创新都在为人类带来前所未有的便利与效率。其中&#xff0c;图为AI大模型一体机作为人工智能领域的最新成果&#…

DiskGenius一键修复磁盘损坏

下午外接磁盘和U盘都出现扇区损坏&#xff0c;估计就是在开着电脑&#xff0c;可能是电脑运行的软件还在对磁盘进行读写&#xff0c;不小心按到笔记本关机键&#xff0c;重新开机读写磁盘分区变得异常卡顿&#xff0c;估摸就是这个原因导致扇区损坏。在进行读写时&#xff0c;整…

深度学习:YOLO v1网络架构、损失值及NMS极大值抑制

引言 随着深度学习的发展&#xff0c;物体检测&#xff08;Object Detection&#xff09;成为计算机视觉领域的一项重要任务。传统的物体检测方法往往依赖于手工设计的特征和滑窗搜索策略&#xff0c;效率低下且效果有限。近年来&#xff0c;基于深度学习的方法&#xff0c;尤…

leetcode-63-不同陆路径II

题解&#xff1a; 1、设dp[i][j]为到达(i,j)点的路径。当grid[i][j]1时,dp[i][j]0;否则dp[i][j]为到达(i-1,j)的最多路径与到达(i,j-1)的最多路径之和。当(i,j)位于第一行时&#xff0c;dp[i][j]dp[i][j-1]。当(i,j)位于第一列时&#xff0c;dp[i][j]dp[i-1][j]。 2、初始化M…

MATLAB锂电概率分布模型

&#x1f3af;要点 概率分布等效电路模型结合了路径相关速率能力及状态估计中滞后效应。纠正了充电状态中时间误差累积及避免开路电压中电压滞后现象。使用电流方向和电池容量相关函数描述开路电压&#xff0c;并使用微分方程描述电压滞后现象。模型结构基于一级相变的材料机制…

新华三H3CNE网络工程师认证—OSPF路由协议

OSPF是典型的链路状态路由协议&#xff0c;是目前业内使用非常广泛的IGP协议之一。本博客将对OSPF路由协议进行总结。 OSPF目前针对IPv4协议使用的是OSPFVersion2(RFC2328)&#xff1b; 针对IPv6协议使用OSPFVersion3(RFC2740)。如无特殊说明本章后续所指的OSPF均为OSPF Versi…

监督学习之逻辑回归

逻辑回归&#xff08;Logistic Regression&#xff09; 逻辑回归是一种用于二分类&#xff08;binary classification&#xff09;问题的统计模型。尽管其名称中有“回归”二字&#xff0c;但逻辑回归实际上用于分类任务。它的核心思想是通过将线性回归的输出映射到一个概率值…

【MATLAB源码-第193期】基于matlab的网络覆盖率NOA优化算法仿真对比VFINOA,VFPSO,VFNGO,VFWOA等算法。

操作环境&#xff1a; MATLAB 2022a 1、算法描述 NOA&#xff08;Network Optimization Algorithm&#xff0c;网络优化算法&#xff09;是一个针对网络覆盖率优化的算法&#xff0c;它主要通过优化网络中节点的分布和配置来提高网络的整体覆盖性能。网络覆盖率是衡量一个无…

基于STM32G0的USB PD协议学习(3)

0、前言 STM32这个平台资源确实很不错&#xff0c;但是里面的PD代码是一个lib库文件&#xff0c;没有开源。可以做来玩玩&#xff0c;但是如果要满足USB-IF认证需求的话&#xff0c;谨慎&#xff01;&#xff01;&#xff01; 这段时间较为繁忙&#xff0c;断更有点严重... …

植物健康,Spring Boot来保障

5系统详细实现 5.1 系统首页 植物健康系统需要登录才可以看到首页。具体界面的展示如图5.1所示。 图5.1 系统首页界面 5.2 咨询专家 可以在咨询专家栏目发布消息。具体界面如图5.2所示。 图5.2 咨询专家界面 5.3 普通植物检查登记 普通员工可以对普通植物检查登记信息进行添…

Linux之权限(2)

权限&#xff08;2&#xff09; 操作&#xff1a;有VS没有 只有文件的拥有者或者root&#xff0c;能修改自己的权限 Linux下能执行真的是一个可执行文件可执行权限 user,group,other和我自己进行身份对比&#xff0c;依次只对比一次 8进制数值表示方法 chmod ax /home/abc.…

基于vue、VantUI、django的程序设计

首先构建vue项目&#xff0c;构建项目点这里 安装 npm install axios axios简介 Axios 是一个基于 promise 的 HTTP 库&#xff0c;用于发起请求和接收响应&#xff0c;实现异步操作 基本使用 axios对象 请求响应拦截 在utils文件夹里新建ajax.js 创建一个axios对象并…

云智慧完成华为原生鸿蒙系统的适配, 透视宝 APM 为用户体验保驾护航

2024 年 10 月 22 日&#xff0c;首个国产移动操作系统 —— 华为原生鸿蒙操作系统 HarmonyOS NEXT 正式面世&#xff0c;成为继 iOS 和 Android 后的全球第三大移动操作系统。HarmonyOS NEXT&#xff0c;从系统内核、数据库根基&#xff0c;到编程语言创新、AI&#xff08;人工…

【WebGis开发 - Cesium】三维可视化项目教程---图层管理拓展图层透明度控制

目录 引言一、为什么要开发图层透明度控制功能二、开发思路整理1. cesium图层api查询1.1 imageryLayer 透明度1.2 primitive 透明度 三、代码编写1. 修改原有图层管理代码2. 新增页面结构3. 编写图层透明度控制方法 四、总结 引言 本教程主要是围绕Cesium这一开源三维框架开展的…

如何通过sip信令以及抓包文件分析媒体发到哪个地方

前言 问题描述&#xff1a;A的媒体没转发到B&#xff0c;B只能听到回铃音&#xff0c;没有A的说话声音&#xff0c;并且fs这边按正常的信令发送了. 分析流程 分析早期媒体发送到哪一个IP 10.19.0.1发送了一个请求给10.19.0.157这个IP&#xff0c;然而这里的SDP媒体地址&am…

react 总结+复习+应用加深

文章目录 一、React生命周期1. 挂载阶段&#xff08;Mounting&#xff09;补充2. 更新阶段&#xff08;Updating&#xff09;补充 static getDerivedStateFromProps 更新阶段应用补充 getSnapshotBeforeUpdate3. 卸载阶段&#xff08;Unmounting&#xff09; 二、React组件间的…

搭建 mongodb 副本集,很详细

搭建 mongodb 副本集&#xff0c;很详细 一、前言二、创建用户1、创建 root 用户2、创建测试用户3、修改用户密码 三、修改配置文件&#xff08;主节点&#xff09;1、开启登录认证2、加上副本集3、最终配置文件 四、副本节点1、创建副本节点目录2、编辑配置文件3、启动副本节点…

2024年四川省大学生程序设计竞赛 补题记录

文章目录 Problem A. 逆序对染色&#xff08;思维树状数组&#xff09;Problem B. 连接召唤&#xff08;贪心&#xff09;Problem E. L 型覆盖检查器&#xff08;模拟&#xff09;Problem F. 小球进洞&#xff1a;平面版&#xff08;几何&#xff09;Problem G. 函数查询Proble…