docs: 添加涉密文件自检工具实施计划
This commit is contained in:
429
UmiOCR-data/qt_res/qml/Widgets/FilesTableView.qml
Normal file
429
UmiOCR-data/qt_res/qml/Widgets/FilesTableView.qml
Normal file
@@ -0,0 +1,429 @@
|
||||
// =============== 文件表格面板 ===============
|
||||
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15
|
||||
import Qt.labs.qmlmodels 1.0 // 表格
|
||||
import QtGraphicalEffects 1.15 // 子元素圆角
|
||||
import QtQuick.Dialogs 1.3 // 文件对话框
|
||||
|
||||
Item {
|
||||
id: fTableRoot
|
||||
|
||||
// ========================= 【定义】 =========================
|
||||
|
||||
// 表头。定义每一列。
|
||||
property var headers: [
|
||||
// 第一列也作为总key(tk),不允许重复。
|
||||
{key: "path", title: "文件", },
|
||||
{key: "time", title: "耗时", },
|
||||
{key: "state", title: "状态", },
|
||||
// 可选项:
|
||||
// btn: true 启用按钮
|
||||
// onClicked: 单击函数
|
||||
// left: true 左对齐
|
||||
// display: 显示函数,输入value,返回显示文本
|
||||
]
|
||||
property string openBtnText: "选择文件"
|
||||
property string clearBtnText: "清空"
|
||||
property string defaultTips: "拖入或选择文件"
|
||||
property string fileDialogTitle: "请选择文件"
|
||||
property var fileDialogNameFilters: ["文件 (*.jpg *.jpe *.jpeg *.jfif *.png *.webp *.bmp *.tif *.tiff)"]
|
||||
property int spacing: size_.smallLine // 表项水平间隔
|
||||
property int minWidth0: size_.smallLine * 5 // 第0列最小宽度
|
||||
property bool isLock: false // 是否锁定UI操作
|
||||
|
||||
|
||||
// ========================= 【调用接口】 =========================
|
||||
|
||||
// 增:添加一项。 row:字典,key在headers中,如 { "path" "time" "state" }
|
||||
// ik:可以是表格行index(int),也可以是总key(string)
|
||||
function add(row, ik=-1) {
|
||||
const key = row[headerKey]
|
||||
if(key in dataDict) {
|
||||
console.warn(`add: ${key} 已在dataDict中!`)
|
||||
return false
|
||||
}
|
||||
if(ik === -1 || ik === rowCount) {
|
||||
dataDict[key] = rowCount
|
||||
dataModel.append(row)
|
||||
}
|
||||
else {
|
||||
const i = ik2i(ik)
|
||||
if(i < 0) {
|
||||
console.warn(`add: ik ${ik} ${i} < 0 !`)
|
||||
return false
|
||||
}
|
||||
dataDict[key] = i
|
||||
dataModel.insert(i, row)
|
||||
}
|
||||
updateWidth()
|
||||
return true
|
||||
}
|
||||
// 删:删除一项
|
||||
function del(ik) {
|
||||
const i = ik2i(ik)
|
||||
if(i < 0) {
|
||||
console.warn(`del: ik ${ik} ${i} < 0 !`)
|
||||
return false
|
||||
}
|
||||
const key = dataModel.get(i)[headerKey]
|
||||
delete dataDict[key]
|
||||
dataModel.remove(i)
|
||||
return true
|
||||
}
|
||||
// 删:清空
|
||||
function clear() {
|
||||
dataModel.clear()
|
||||
dataDict = {}
|
||||
}
|
||||
// 改:属性字典
|
||||
function set(ik, columnDict) {
|
||||
const i = ik2i(ik)
|
||||
if(i < 0) {
|
||||
console.warn(`set: ik ${ik} ${i} < 0 !`)
|
||||
return false
|
||||
}
|
||||
dataModel.set(i, columnDict)
|
||||
updateWidth()
|
||||
return true
|
||||
}
|
||||
// 改:单个属性
|
||||
function setProperty(ik, columnKey, value) {
|
||||
const i = ik2i(ik)
|
||||
if(i < 0) {
|
||||
console.warn(`setProperty: ik ${ik} ${i} < 0 !`)
|
||||
return false
|
||||
}
|
||||
dataModel.setProperty(i, columnKey, value)
|
||||
updateWidth()
|
||||
return true
|
||||
}
|
||||
// 查:ik转index。返回-1表示失败。
|
||||
function ik2i(ik) {
|
||||
if (typeof ik === "number") {
|
||||
if(ik >= 0 && ik < rowCount)
|
||||
return ik
|
||||
} else if (typeof ik === "string") {
|
||||
if(ik in dataDict)
|
||||
return dataDict[ik]
|
||||
}
|
||||
return -1
|
||||
}
|
||||
// 查:获取单个行的字典
|
||||
function get(ik) {
|
||||
const i = ik2i(ik)
|
||||
if(i < 0) {
|
||||
console.warn(`get: ik ${ik} ${i} < 0 !`)
|
||||
return {}
|
||||
}
|
||||
return dataModel.get(i)
|
||||
}
|
||||
// 查:获取key列的所有数据,返回每项为value
|
||||
function getColumnsValue(key) {
|
||||
let list = []
|
||||
for(let y = 0; y < rowCount; y++) {
|
||||
list.push( dataModel.get(y)[key] )
|
||||
}
|
||||
return list
|
||||
}
|
||||
// 查:获取多个列的数据,返回每项为字典
|
||||
function getColumnsValues(keys=[]) {
|
||||
let list = []
|
||||
if(keys.length > 0) {
|
||||
for(let y = 0; y < rowCount; y++) {
|
||||
const data = dataModel.get(y)
|
||||
const d = {}
|
||||
for(let i in keys)
|
||||
d[keys[i]] = data[keys[i]]
|
||||
list.push(d)
|
||||
}
|
||||
}
|
||||
else {
|
||||
for(let y = 0; y < rowCount; y++)
|
||||
list.push(dataModel.get(y))
|
||||
}
|
||||
return list
|
||||
}
|
||||
|
||||
// 定义信号
|
||||
signal addPaths(var paths) // 添加文件的信号
|
||||
signal click(var info) // 点击条目的信号
|
||||
|
||||
Component.onCompleted: {
|
||||
dataDict = {}
|
||||
columnCount = headers.length
|
||||
for(let i=0; i<columnCount; i++){
|
||||
headerModel.append({
|
||||
"key": headers[i].key,
|
||||
"title": headers[i].title,
|
||||
"width": 1,
|
||||
})
|
||||
}
|
||||
headerKey = headers[0].key
|
||||
updateWidth(true)
|
||||
}
|
||||
|
||||
// ========================= 【逻辑】 =========================
|
||||
|
||||
property int columnCount: 0 // 列数量, onCompleted 中初始化
|
||||
property int rowCount: dataModel.count // 行数量
|
||||
property string headerKey: "" // 自动
|
||||
// 表头, key title width
|
||||
ListModel { id: headerModel }
|
||||
// 数据, 项为headers的key
|
||||
ListModel { id: dataModel }
|
||||
property var dataDict: {} // 指向 dataModel 的 index
|
||||
onRowCountChanged: {
|
||||
headerModel.setProperty(0, "title", headers[0].title + ` (${rowCount})`)
|
||||
}
|
||||
|
||||
// 宽度更新
|
||||
Timer {
|
||||
id: updateWidthTimer
|
||||
interval: 100
|
||||
repeat: false
|
||||
onTriggered: {
|
||||
updateWidth(true)
|
||||
}
|
||||
}
|
||||
// 更新全部宽度
|
||||
function updateWidth(timer=false) {
|
||||
if(!timer) { // 启动计时器,减少调用频率
|
||||
updateWidthTimer.restart()
|
||||
return
|
||||
}
|
||||
let ws = Array(columnCount).fill(1)
|
||||
// 表头
|
||||
for(let i = 1; i < columnCount; i++) {
|
||||
let maxWidth = headerRepeater.itemAt(i).maxWidth + fTableRoot.spacing*2
|
||||
if(maxWidth > ws[i]) ws[i] = maxWidth
|
||||
}
|
||||
// 表体
|
||||
for(let y in tableView.items) {
|
||||
const repeater = tableView.items[y].repeater
|
||||
for(let x = 1; x < columnCount; x++) {
|
||||
let maxWidth = repeater.itemAt(x).maxWidth + fTableRoot.spacing*2
|
||||
if(maxWidth > ws[x]) ws[x] = maxWidth
|
||||
}
|
||||
}
|
||||
// 赋值 / 计算第0列宽度
|
||||
let w0 = tableArea.width
|
||||
for(let i = 1; i < columnCount; i++) {
|
||||
headerModel.setProperty(i, "width", ws[i])
|
||||
w0 -= ws[i]
|
||||
}
|
||||
// 更新第0列宽度
|
||||
updateWidth0(w0)
|
||||
}
|
||||
// 更新第0列宽度
|
||||
function updateWidth0(w0 = -1) {
|
||||
if(headerModel.count <= 0) return
|
||||
if(w0 < 0) {
|
||||
w0 = tableArea.width
|
||||
for(let i = 1; i < columnCount; i++)
|
||||
w0 -= headerModel.get(i).width
|
||||
}
|
||||
w0 += columnCount-10 // 避让右侧滚动条空间
|
||||
if(w0 < minWidth0) w0 = minWidth0
|
||||
headerModel.setProperty(0, "width", w0)
|
||||
}
|
||||
|
||||
// ========================= 【布局】 =========================
|
||||
|
||||
// 表格区域
|
||||
Rectangle {
|
||||
id: tableArea
|
||||
anchors.fill: parent
|
||||
color: theme.bgColor
|
||||
|
||||
Item {
|
||||
id: tableContainer
|
||||
anchors.fill: parent
|
||||
|
||||
// 上方操控版
|
||||
Item {
|
||||
id: tableTopPanel
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
height: size_.line * 2
|
||||
|
||||
// 左打开图片按钮
|
||||
IconTextButton {
|
||||
id: openBtn
|
||||
visible: parent.width > openBtn.width + clearBtn.width // 容器宽度过小时隐藏
|
||||
anchors.left: parent.left
|
||||
anchors.top: parent.top
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.margins: size_.smallSpacing * 0.5
|
||||
icon_: "folder"
|
||||
text_: openBtnText
|
||||
|
||||
onClicked: {
|
||||
if(isLock) return
|
||||
fileDialog.open()
|
||||
}
|
||||
}
|
||||
|
||||
// 右清空按钮
|
||||
IconTextButton {
|
||||
id: clearBtn
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.margins: size_.smallSpacing * 0.5
|
||||
icon_: "clear"
|
||||
text_: clearBtnText
|
||||
|
||||
onClicked: {
|
||||
if(isLock) return
|
||||
fTableRoot.clear()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 提示
|
||||
DefaultTips {
|
||||
visibleFlag: fTableRoot.rowCount
|
||||
anchors.top: tableTopPanel.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: parent.bottom
|
||||
tips: defaultTips
|
||||
}
|
||||
|
||||
// 表头
|
||||
Item {
|
||||
id: tableHeaderContainer
|
||||
visible: fTableRoot.rowCount > 0
|
||||
anchors.top: tableTopPanel.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
height: size_.line * 1.5
|
||||
onWidthChanged: updateWidth0()
|
||||
|
||||
Row {
|
||||
anchors.fill: parent
|
||||
spacing: -1
|
||||
Repeater {
|
||||
model: headerModel
|
||||
id: headerRepeater
|
||||
Rectangle {
|
||||
width: model.width
|
||||
anchors.top: parent.top
|
||||
anchors.bottom: parent.bottom
|
||||
color: theme.bgColor
|
||||
border.width: 1
|
||||
border.color: theme.coverColor2
|
||||
property alias maxWidth: hText.width
|
||||
Text_ {
|
||||
id: hText
|
||||
anchors.top: parent.top
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
verticalAlignment: Text.AlignVCenter // 垂直居中
|
||||
font.pixelSize: size_.smallText
|
||||
text: model.title
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// 表体
|
||||
TableView {
|
||||
id: tableView
|
||||
anchors.top: tableHeaderContainer.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: parent.bottom
|
||||
flickableDirection: Flickable.VerticalFlick // 只允许垂直滚动
|
||||
boundsBehavior: Flickable.StopAtBounds // 禁止flick过冲。不影响滚轮滚动的过冲
|
||||
model: dataModel
|
||||
clip: true
|
||||
property var items: tableView.children[0].children
|
||||
rowSpacing: -1
|
||||
delegate: Item {
|
||||
Component.onCompleted: updateWidth()
|
||||
TableView.onReused: updateWidth()
|
||||
implicitHeight: size_.smallLine * 1.5
|
||||
implicitWidth: 1
|
||||
property int rowIndex: index
|
||||
property var rowModel: model
|
||||
property alias repeater: repeater
|
||||
Row {
|
||||
anchors.fill: parent
|
||||
spacing: -1
|
||||
Repeater {
|
||||
id: repeater
|
||||
model: headerModel
|
||||
Rectangle {
|
||||
width: model.width
|
||||
anchors.top: parent.top
|
||||
anchors.bottom: parent.bottom
|
||||
color: theme.bgColor
|
||||
border.width: 1
|
||||
border.color: theme.coverColor2
|
||||
property alias maxWidth: hText.width
|
||||
property int columnIndex: index
|
||||
property string columnKey: model.key
|
||||
property var header: headers[columnIndex]
|
||||
clip: true
|
||||
Button_ {
|
||||
visible: header.btn?true:false
|
||||
anchors.fill: parent
|
||||
radius: 0
|
||||
onClicked: {
|
||||
if(header.onClicked) {
|
||||
header.onClicked(rowIndex)
|
||||
}
|
||||
}
|
||||
}
|
||||
Text_ {
|
||||
id: hText
|
||||
property bool isLeft: headers[columnIndex].left?true:false
|
||||
anchors.top: parent.top
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.left: isLeft? parent.left : undefined
|
||||
anchors.leftMargin: fTableRoot.spacing * 0.5
|
||||
anchors.horizontalCenter: isLeft? undefined : parent.horizontalCenter
|
||||
verticalAlignment: Text.AlignVCenter // 垂直居中
|
||||
font.pixelSize: size_.smallText
|
||||
color: (columnKey != "state"|| typeof rowModel.state != "string" || rowModel.state.length == 0) ? theme.subTextColor :
|
||||
(rowModel.state.startsWith("×") ? theme.noColor : (rowModel.state.startsWith("√") ? theme.yesColor : theme.subTextColor))
|
||||
text: header.display ? header.display(rowModel[columnKey]) : rowModel[columnKey]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// 滚动条
|
||||
ScrollBar.vertical: ScrollBar { }
|
||||
}
|
||||
}
|
||||
|
||||
// 内圆角裁切
|
||||
layer.enabled: true
|
||||
layer.effect: OpacityMask {
|
||||
maskSource: Rectangle {
|
||||
width: tableContainer.width
|
||||
height: tableContainer.height
|
||||
radius: size_.btnRadius
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 文件选择对话框
|
||||
// QT-5.15.2 会报错:“Model size of -225 is less than 0”,不影响使用。
|
||||
// QT-5.15.5 修复了这个Bug,但是PySide2尚未更新到这个版本号。只能先忍忍了
|
||||
// https://bugreports.qt.io/browse/QTBUG-92444
|
||||
FileDialog_ {
|
||||
id: fileDialog
|
||||
title: fileDialogTitle
|
||||
nameFilters: fileDialogNameFilters
|
||||
folder: shortcuts.pictures
|
||||
selectMultiple: true // 多选
|
||||
onAccepted: {
|
||||
addPaths(fileDialog.fileUrls_)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user