docs: 添加涉密文件自检工具实施计划

This commit is contained in:
2026-06-08 13:53:24 +08:00
commit 31161d9a5f
1838 changed files with 455407 additions and 0 deletions

View File

@@ -0,0 +1,312 @@
// ==================================================
// =============== 功能页:批量文档处理 ===============
// ==================================================
import QtQuick 2.15
import QtQuick.Controls 2.15
import ".."
import "../../Widgets"
import "../../Widgets/ResultLayout"
import "../../Widgets/IgnoreArea"
/* 文档参数:
path 路径
pages 页数显示
state 状态显示
page_count 总页数
range_start 范围开始
range_end 范围结束
is_encrypted 需要密码
is_authenticate 密码正确
password 密码
*/
TabPage {
id: tabPage
// ========================= 【逻辑】 =========================
property string msnID: "" // 当前任务ID
// 异步加载一批文档路径
function addDocs(paths) {
if(paths.length <= 0) return
const isRecurrence = configsComp.getValue("mission.recurrence")
qmlapp.asynFilesLoader.run(paths,"doc",isRecurrence,onAddDocs)
}
// 完毕后,将合法表格加入表格
function onAddDocs(docs) {
if(docs.length <= 0) return
let encryptedCount = 0
for(let i in docs) {
const info = docs[i]
filesTableView.add({
// 显示:路径,状态,页范围
path: info.path,
pages: `${info.page_count}`, // 如果范围为整本,只显示总页数。否则显示 起始-结束
state: info.is_encrypted ? qsTr("加密") : "" ,
// 数据
page_count: info.page_count,
range_start: 1,
range_end: info.page_count,
is_encrypted: info.is_encrypted, // 有密码
is_authenticate: !info.is_encrypted, // 已解密(密码正确)
password: "",
})
if(info.is_encrypted) encryptedCount++
}
if(encryptedCount > 0) {
qmlapp.popup.simple(qsTr("%1个加密文档").arg(encryptedCount),
qsTr("请点击文件名填写密码"))
}
}
// 运行文档任务
function docStart() {
const fileCount = filesTableView.rowCount
if(fileCount <= 0) {
ctrlPanel.stopFinished()
return
}
// 获取信息
const docs = filesTableView.getColumnsValues([
"path","range_start", "range_end", "page_count", "is_encrypted", "is_authenticate", "password"])
// 第1次遍历检查密码填写
for(let i = 0; i < fileCount; i++) {
const d = docs[i]
if(d.is_encrypted && !d.is_authenticate) {
qmlapp.popup.message(qsTr("文档已加密"), qsTr("【%1】\n请点击文档名设置密码").arg(d.path), "warning")
ctrlPanel.stopFinished()
return
}
}
// 第2次遍历刷新信息
let allPages = 0 // 页总数
for(let i = 0; i < fileCount; i++) {
const d = docs[i]
allPages += d.range_end - d.range_start + 1
filesTableView.setProperty(d.path, "state", qsTr("排队"))
}
// 若tabPanel面板的下标没有变化过则切换到记录页
if(tabPanel.indexChangeNum < 2)
tabPanel.currentIndex = 1
// 任务进度 开始计时
ctrlPanel.runFinished(allPages)
// 提交任务
const argd = configsComp.getValueDict()
tabPage.callPy("msnDocs", docs, argd)
}
// 停止文档任务
function docStop() {
tabPage.callPy("msnStop")
// 刷新表格,清空未执行的任务的状态
let msnLength = filesTableView.rowCount
for(let i = 0; i < msnLength; i++) {
const row = filesTableView.get(i)
const s = row.state
if(s.length > 0 && s[0] !== "√" && s[0] !== "×") {
filesTableView.setProperty(i, "state", "")
}
}
ctrlPanel.stopFinished()
}
// 文件表格中单击文档
function onClickDoc(index) {
if(ctrlPanel.state_ !== "stop") return
const info = filesTableView.get(index)
if(Object.keys(info).length > 0)
previewDoc.show(info)
}
// 关闭页面
function closePage() {
if(ctrlPanel.state_ !== "stop") {
const argd = { yesText: qsTr("依然关闭") }
const callback = (flag)=>{
if(flag) {
docStop()
delPage()
}
}
qmlapp.popup.dialog("", qsTr("任务正在进行中。\n要结束任务并关闭页面吗"), callback, "warning", argd)
}
else {
delPage()
}
}
// ========================= 【python调用qml】 =========================
// 准备开始处理一个文档
function onDocStart(path) {
// 刷新表格显示
const d = filesTableView.get(path)
let state = `0/${d.range_end - d.range_start + 1}`
filesTableView.setProperty(path, "state", state)
}
// 获取一个文档的一页的结果
function onDocGet(path, page, res) {
// 刷新单个文档的信息
const d = filesTableView.get(path)
const state = `${page - d.range_start + 1}/${d.range_end - d.range_start + 1}`
filesTableView.setProperty(path, "state", state)
// 提取文字,添加到结果表格
const title = path2name(path)
res.title = `${title} - ${page}`
resultsTableView.addOcrResult(res)
ctrlPanel.msnStep(1)
}
// 一个文档处理完毕。 isAll==true 时所有文档处理完毕。
function onDocEnd(path, msg, isAll) {
const errTitle = qsTr("文档识别异常")
// 成功结束
if(msg.startsWith("[Success]")) {
filesTableView.setProperty(path, "state", "√")
msg = ""
}
// 单个文档任务失败,总体未结束
else if(!isAll) {
filesTableView.setProperty(path, "state", "× "+ qsTr("失败"))
qmlapp.popup.simple(errTitle, msg)
}
// 所有文档处理完毕
if(isAll) {
const simpleType = configsComp.getValue("other.simpleNotificationType")
qmlapp.popup.simple(qsTr("批量识别完成"), "", simpleType)
if(msg) // 如果有异常,则弹窗
qmlapp.popup.message(errTitle, msg, "error")
ctrlPanel.stopFinished()
// 任务完成后续操作
qmlapp.globalConfigs.utilsDicts.postTaskHardwareCtrl(
configsComp.getValue("postTaskActions.system")
)
}
}
// 路径转文件名
function path2name(path) {
const parts = path.split("/")
return parts[parts.length - 1]
}
// ========================= 【布局】 =========================
// 配置
configsComp: BatchDOCConfigs {}
// 主区域:可切换双栏面板
DoubleSwitchableLayout {
id: doubleLayout
saveKey: "BatchDOC_1"
anchors.fill: parent
// 面板A控制板+文件表格
itemA: Panel {
anchors.fill: parent
// 上方控制板
MissionCtrlPanel {
id: ctrlPanel
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
anchors.margins: size_.spacing
height: size_.line * 2
onRunClicked: tabPage.docStart()
onPauseClicked: {
tabPage.callPy("msnPause")
pauseFinished()
}
onResumeClicked: {
tabPage.callPy("msnResume")
resumeFinished()
}
onStopClicked: tabPage.docStop()
}
// 下方文件表格
FilesTableView {
id: filesTableView
anchors.top: ctrlPanel.bottom
anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: parent.bottom
anchors.margins: size_.spacing
anchors.topMargin: size_.smallSpacing
headers: [
{key: "path", title: qsTr("文档"), left: true, display: path2name,
btn: true, onClicked:onClickDoc},
{key: "state", title: qsTr("状态"), btn: true, onClicked:onClickDoc},
{key: "pages", title: qsTr("页数"), btn: true, onClicked:onClickDoc},
]
openBtnText: qsTr("打开文档")
clearBtnText: qsTr("清空")
defaultTips: qsTr("拖入文档或文件夹")
fileDialogTitle: qsTr("请选择文档")
fileDialogNameFilters: [qsTr("文档")+" (*.pdf *.xps *.epub *.mobi *.fb2 *.cbz)"]
isLock: ctrlPanel.state_ !== "stop"
onAddPaths: {
tabPage.addDocs(paths)
}
}
}
// 面板B文字输出 & 设置
itemB: Panel {
anchors.fill: parent
// 配置项控制板
TabPanel {
id: tabPanel
anchors.fill: parent
anchors.margins: size_.spacing
isMenuTop: doubleLayout.isRow // 左右布局时,菜单在顶部;上下布局时菜单在底部
menuHeight: size_.line * 2
// 结果面板
ResultsTableView {
id: resultsTableView
anchors.fill: parent
visible: false
}
tabsModel: [
{
"key": "configs",
"title": qsTr("设置"),
"component": configsComp.panelComponent,
},
{
"key": "ocrResult",
"title": qsTr("记录"),
"component": resultsTableView,
},
]
}
}
}
// 鼠标拖入文档
DropArea_ {
id: "addDocsDropArea"
anchors.fill: parent
callback: tabPage.addDocs
}
// 预览面板
PreviewDoc {
id: previewDoc
anchors.fill: parent
configsComp: tabPage.configsComp
updateInfo: (path, info) => {
let infoA = filesTableView.get(path)
Object.assign(infoA, info)
filesTableView.set(path, infoA)
}
}
}

View File

@@ -0,0 +1,149 @@
// ==============================================
// =============== 批量PDF的配置项 ===============
// ==============================================
import QtQuick 2.15
import "../../Configs"
Configs {
category_: "BatchPDF"
signal clickIgnoreArea() // 打开忽略区域
configDict: {
// OCR参数
"ocr": qmlapp.globalConfigs.ocrManager.deploy(this, "ocr"),
// 后处理
"tbpu": {
"title": qsTr("OCR文本后处理"),
"type": "group",
"parser": qmlapp.globalConfigs.utilsDicts.getTbpuParser(),
"btns": {
"title": "👈"+qsTr("点击表格,可设置更多内容"),
"btnsList": [],
},
"ignoreArea": {
"type": "var",
"save": false,
},
"ignoreRangeStart": { // 忽略区域范围
"default": 1,
"save": false,
},
"ignoreRangeEnd": {
"default": -1,
"save": false,
},
},
// 文档参数
"doc": {
"title": qsTr("文档处理"),
"type": "group",
"extractionMode": {
"title": qsTr("内容提取模式"),
"toolTip": qsTr("若一页文档既存在图片又存在文本,如何进行处理"),
"optionsList": [
["mixed", qsTr("混合OCR/原文本")],
["fullPage", qsTr("整页强制OCR")],
["imageOnly", qsTr("仅OCR图片")],
["textOnly", qsTr("仅拷贝原有文本")],
],
},
},
// 任务参数
"mission": {
"title": qsTr("批量任务"),
"type": "group",
"recurrence": {
"title": qsTr("递归读取子文件夹"),
"toolTip": qsTr("导入文件夹时,导入子文件夹中全部文档"),
"default": false,
},
"dirType": {
"title": qsTr("保存到"),
"optionsList": [
["source", qsTr("文档原目录")],
["specify", qsTr("指定目录")],
],
},
"dir": {
"title": qsTr("指定目录"),
"toolTip": qsTr("必须先指定“保存到指定目录”才生效"),
"type": "file",
"selectExisting": true, // 选择现有
"selectFolder": true, // 选择文件夹
"dialogTitle": qsTr("OCR结果保存目录"),
},
"fileNameFormat": {
"title": qsTr("文件名格式"),
"toolTip": qsTr("无需填写拓展名。支持插入以下占位符:\n%date 日期时间\n%name 原文档名\n%range 识别页数范围。只有识别页数小于总页数时才会显示。\n举例[OCR]_%name%range_%date\n生成[OCR]_文档A(p2-10)_20230901_1213.txt\n添加占位符可以避免旧文件被新文件覆盖。"),
"default": "[OCR]_%name%range_%date",
"advanced": true,
},
"datetimeFormat": {
"title": qsTr("日期时间格式"),
"toolTip": qsTr("文件名中 %date 的日期格式。支持插入以下占位符:\n%Y 年、 %m 月、 %d 日、 %H 小时、 \n%M 分钟、 %S 秒 、 %unix 时间戳 \n举例%Y年%m月%d日_%H-%M\n生成2023年09月01日_12-13.txt"),
"default": "%Y%m%d_%H%M",
"advanced": true,
},
"filesType": {
"title": qsTr("保存文件类型"),
"type": "group",
"enabledFold": true,
"fold": false,
"pdfLayered": {
"title": qsTr("layered.pdf 双层可搜索文档"),
"toolTip": qsTr("保留原有图片,叠加一层透明文字,可以搜索和复制"),
"default": true,
},
"pdfOneLayer": {
"title": qsTr("text.pdf 单层纯文本文档"),
"toolTip": qsTr("创建空白PDF文档只写入识别文字不含图片"),
"default": false,
},
"txt": {
"title": qsTr("txt 标准格式"),
"toolTip": qsTr("含识别文字和页数信息"),
"default": false,
},
"txtPlain": {
"title": qsTr("p.txt 纯文字格式"),
"toolTip": qsTr("输出所有识别文字"),
"default": false,
},
"csv": {
"title": qsTr("csv 表格文件(Excel)"),
"toolTip": qsTr("将页数信息和识别内容写入csv表格文件。可用Excel打开另存为xlsx格式。"),
"default": false,
},
"jsonl": {
"title": qsTr("jsonl 原始信息"),
"toolTip": qsTr("每行为一条json数据便于第三方程序读取操作"),
"default": false,
},
},
"ignoreBlank": {
"title": qsTr("忽略空白页"),
"toolTip": qsTr("若某一页没有文字或识别失败,也不会输出错误提示信息"),
"default": true,
},
},
// 任务完成后续操作
"postTaskActions": qmlapp.globalConfigs.utilsDicts.getPostTaskActions(),
"other": {
"title": qsTr("其它"),
"type": "group",
"simpleNotificationType": qmlapp.globalConfigs.utilsDicts.getSimpleNotificationType()
},
}
}

View File

@@ -0,0 +1,431 @@
// ===========================================
// =============== 文档预览面板 ===============
// ===========================================
import QtQuick 2.15
import QtQuick.Controls 2.15
import DocPreviewConnector 1.0
import "../../Widgets"
import "../../Widgets/IgnoreArea"
ModalLayer {
id: pRoot
property var updateInfo // 更新信息函数
property var configsComp: undefined // 设置组件
property string previewPath: ""
property string password: ""
property bool isEncrypted: false // 已加密
property bool isAuthenticate: false // 密码正确
property int previewPage: -1
property int pageCount: -1
property int rangeStart: -1
property int rangeEnd: -1
property int ignoreRangeStart: 1
property int ignoreRangeEnd: -1
property bool previewOCR: false // 是否预览OCR
property bool ocrRunning: false // 是否预览OCR正在执行
// 展示文档
// info: path, page_count, range_start, range_end, is_encrypted, password, is_authenticate
function show(info) {
imgViewer.clear()
visible = true
previewPath = info.path
pageCount = info.page_count
previewPage = info.range_start
rangeStart = info.range_start
rangeEnd = info.range_end
password = info.password
isEncrypted = info.is_encrypted
isAuthenticate = info.is_authenticate
// 读取忽略区域设置
let initArea = configsComp.getValue("tbpu.ignoreArea")
if(initArea && initArea.length>0) {
// 读取设置,反格式化
let ig1 = []
for(let i=0,l=initArea.length; i<l; i++) {
const b = initArea[i]
ig1.push({
"x": b[0][0],
"y": b[0][1],
"width": b[2][0] - b[0][0],
"height": b[2][1] - b[0][1],
})
}
imgViewer.ig1Boxes = ig1
}
ignoreRangeStart = configsComp.getValue("tbpu.ignoreRangeStart")
ignoreRangeEnd = configsComp.getValue("tbpu.ignoreRangeEnd")
toPreview()
}
// 返回上层,更新信息
onVisibleChanged: {
if(visible) return
// 负数转倒数
if(rangeStart < 0) rangeStart += pageCount + 1
if(rangeEnd < 0) rangeEnd += pageCount + 1
// 范围检查
if(rangeStart < 1) rangeStart = 1
if(rangeStart > pageCount) rangeStart = pageCount
if(rangeEnd < rangeStart) rangeEnd = rangeStart
if(rangeEnd > pageCount) rangeEnd = pageCount
let pages_ = `${pageCount}` // 如果范围为整本,只显示总页数。否则显示 起始-结束
if(rangeEnd - rangeStart + 1 < pageCount)
pages_ = `${rangeStart}-${rangeEnd}`
if(updateInfo) {
updateInfo(previewPath, {
pages: pages_,
state: isAuthenticate ? "" : qsTr("加密"),
range_start: rangeStart,
range_end: rangeEnd,
password: password,
is_authenticate: isAuthenticate,
})
}
// 更新忽略区域
if(imgViewer.ig1Boxes.length > 0) {
// 格式化,存入设置
let ig1 = []
for(let i=0,l=imgViewer.ig1Boxes.length; i<l; i++) {
const b = imgViewer.ig1Boxes[i]
const x = Math.round(b.x)
const y = Math.round(b.y)
const w = Math.round(b.width)
const h = Math.round(b.height)
ig1.push([[x, y], [x+w, y], [x+w, y+h], [x, y+h]])
}
configsComp.setValue("tbpu.ignoreArea", ig1)
}
else {
configsComp.setValue("tbpu.ignoreArea", undefined)
}
configsComp.setValue("tbpu.ignoreRangeStart", ignoreRangeStart)
configsComp.setValue("tbpu.ignoreRangeEnd", ignoreRangeEnd)
imgViewer.clear()
prevConn.clear() // 清除文档缓存
previewPath = ""
}
// 翻页。to直接翻页flag加减页。
function changePage(to, flag=0) {
if (typeof to === "string") {
to = parseInt(to, 10)
}
if(flag != 0) {
to = previewPage + flag
}
if(previewPage != to && to > 0 && to <= pageCount) {
previewPage = to
toPreview()
}
}
Keys.onLeftPressed: changePage(-1, -1) // 上一页
Keys.onUpPressed: changePage(-1, -1)
Keys.onRightPressed: changePage(-1, 1) // 下一页
Keys.onDownPressed: changePage(-1, 1)
// 预览一页文档
function toPreview() {
if(!previewPath) return
if(previewPage < 1) previewPage = 1
if(previewPage > pageCount) previewPage = pageCount
prevConn.preview(previewPath, previewPage, password)
if(previewOCR) { // 预览OCR
ocrRunning = true
const argd = configsComp.getValueDict()
argd["tbpu.parser"] = "None" // 去除排版解析
prevConn.ocr(previewPath, previewPage, password, argd)
}
}
// 预览连接器
DocPreviewConnector {
id: prevConn
// 图片渲染的回调
onPreviewImg: function(imgID) {
const title = qsTr("打开文档失败")
if(imgID === "[Warning] is_encrypted") {
qmlapp.popup.simple(title, qsTr("请填写正确的密码"))
isAuthenticate = false
}
else if(imgID.startsWith("[Error]")) {
qmlapp.popup.message(title, imgID, "error")
}
else {
imgViewer.showImgID(imgID)
if(!isAuthenticate) {
qmlapp.popup.simple(qsTr("密码正确"), password)
isAuthenticate = true
}
}
}
// ocr预览的回调
onPreviewOcr: function(info) {
let path = info[0], page = info[1], res = info[2]
if(res.code!=100&&res.code!=101) { // 遇到异常
qmlapp.popup.message(qsTr("文档预览异常"), res.data, "error")
return
}
if(path != previewPath || page != previewPage) {
console.warn("文档OCR预览回调不匹配")
return
}
ocrRunning = false
imgViewer.showTextBoxes(res)
}
}
property string ignoreTips: qsTr("忽略区域说明:\n右键拖拽绘制矩形区域包含在区域内的文字框将被忽略。可用于排除水印、页眉页脚。\n范围允许填写负数表示倒数第x页。如-1表示最后一页-2表示倒数第2页。\n忽略区域的设置对所有文档生效。")
contentItem: DoubleRowLayout {
anchors.fill: parent
initSplitterX: size_.line * 13
// 左:控制面板
leftItem: Panel {
anchors.fill: parent
ScrollView {
anchors.fill: parent
contentWidth: width // 内容宽度
clip: true // 溢出隐藏
Column {
anchors.left: parent.left
anchors.right: parent.right
anchors.margins: size_.spacing
spacing: size_.smallSpacing
// ===== 文件名 =====
Text_ {
text: previewPath
anchors.left: parent.left
anchors.right: parent.right
wrapMode: TextEdit.WrapAnywhere // 任意换行
maximumLineCount: 4 // 限制行数
color: theme.subTextColor
font.pixelSize: size_.smallText
}
// ===== 密码 =====
Row {
visible: isEncrypted && !isAuthenticate // 已加密,未填密码,才显示
spacing: size_.spacing
height: size_.line + size_.spacing * 2
Text_ {
color: theme.noColor
anchors.verticalCenter: parent.verticalCenter
text: qsTr("密码:")
}
TextField_ {
width: size_.line * 6
anchors.top: parent.top
anchors.bottom: parent.bottom
text: password
onTextChanged: password = text
}
IconButton {
anchors.top: parent.top
anchors.bottom: parent.bottom
width: height
icon_: "yes"
onClicked: toPreview()
}
}
// ===== 控制项 =====
Column {
visible: !isEncrypted || isAuthenticate
spacing: size_.smallSpacing
anchors.left: parent.left
anchors.right: parent.right
// ===== 页数 =====
Rectangle {
anchors.left: parent.left
anchors.right: parent.right
height: 1
color: theme.coverColor4
}
Row {
spacing: size_.line
height: size_.line
Text_ {
text: qsTr("预览页面")
anchors.verticalCenter: parent.verticalCenter
}
CheckButton {
anchors.verticalCenter: parent.verticalCenter
height: size_.line
enabledAnime: true
checked: previewOCR
textColor_: theme.subTextColor
onCheckedChanged: {
if(!previewOCR&&checked) {
previewOCR = true
toPreview()
}
else {
previewOCR = ocrRunning = false
}
}
text_: "OCR"
toolTip: qsTr("预览PDF时是否预览OCR结果")
}
}
Row {
spacing: size_.spacing
height: size_.line + size_.smallSpacing * 2
Button_ {
anchors.top: parent.top
anchors.bottom: parent.bottom
text_: "<"
onClicked: changePage(0, -1)
}
Button_ {
anchors.top: parent.top
anchors.bottom: parent.bottom
text_: ">"
onClicked: changePage(0, 1)
}
TextField_ {
width: size_.line * 3
anchors.top: parent.top
anchors.bottom: parent.bottom
validator: IntValidator{bottom: 1; top: pageCount;}
text: previewPage
onTextChanged: changePage(text)
}
Text_ {
anchors.verticalCenter: parent.verticalCenter
text: "/ "+pageCount
}
}
// ===== OCR范围 =====
Rectangle {
anchors.left: parent.left
anchors.right: parent.right
height: 1
color: theme.coverColor4
}
Text_ {
text: qsTr("OCR页数")
}
Row {
height: size_.line + size_.smallSpacing * 2
spacing: size_.spacing
Text_ {
font.pixelSize: size_.smallText
anchors.verticalCenter: parent.verticalCenter
text: qsTr("范围")
}
TextField_ {
width: size_.line * 3
anchors.top: parent.top
anchors.bottom: parent.bottom
validator: IntValidator{bottom: -pageCount; top: pageCount;}
text: rangeStart
onTextChanged: {
if(text !== "" && text !== "-") rangeStart = text
}
}
Text_ {
anchors.verticalCenter: parent.verticalCenter
text: "-"
}
TextField_ {
width: size_.line * 3
anchors.top: parent.top
anchors.bottom: parent.bottom
validator: IntValidator{bottom: -pageCount; top: pageCount;}
text: rangeEnd
onTextChanged: {
if(text !== "" && text !== "-") rangeEnd = text
}
}
}
// ===== 忽略区域 =====
Rectangle {
anchors.left: parent.left
anchors.right: parent.right
height: 1
color: theme.coverColor4
}
Text_ {
text: qsTr("忽略区域页数(全局)")
}
Row {
height: size_.line + size_.smallSpacing * 2
spacing: size_.spacing
Text_ {
font.pixelSize: size_.smallText
anchors.verticalCenter: parent.verticalCenter
text: qsTr("范围")
}
TextField_ {
width: size_.line * 3
anchors.top: parent.top
anchors.bottom: parent.bottom
validator: IntValidator {}
text: ignoreRangeStart
onTextChanged: {
if(text !== "" && text !== "-") ignoreRangeStart = text
}
}
Text_ {
anchors.verticalCenter: parent.verticalCenter
text: "-"
}
TextField_ {
width: size_.line * 3
anchors.top: parent.top
anchors.bottom: parent.bottom
validator: IntValidator {}
text: ignoreRangeEnd
onTextChanged: {
if(text !== "" && text !== "-") ignoreRangeEnd = text
}
}
}
Row {
spacing: size_.spacing
height: size_.smallLine + size_.smallSpacing * 2
Button_ {
anchors.verticalCenter: parent.verticalCenter
height: size_.smallLine + size_.smallSpacing
bgColor_: theme.coverColor1
text_: qsTr("撤销")
onClicked: imgViewer.revokeIg()
textSize: size_.smallText
}
Button_ {
anchors.verticalCenter: parent.verticalCenter
height: size_.smallLine + size_.smallSpacing
bgColor_: theme.coverColor1
textColor_: theme.noColor
text_: qsTr("清空")
onClicked: imgViewer.clearIg()
textSize: size_.smallText
}
}
Text_ {
text: ignoreTips
color: theme.subTextColor
font.pixelSize: size_.smallText
anchors.left: parent.left
anchors.right: parent.right
wrapMode: TextEdit.Wrap
}
}
}
}
}
// 右:图片查看面板
rightItem: ImageWithIgnore {
id: imgViewer
anchors.fill: parent
// 加载中 动态图标
Loading {
visible: ocrRunning
anchors.centerIn: parent
}
}
}
}