no message
|
|
@ -0,0 +1 @@
|
|||
build/
|
||||
|
|
@ -0,0 +1,84 @@
|
|||
cmake_minimum_required(VERSION 3.16)
|
||||
|
||||
project(NDP_SM_Svr VERSION 0.1 LANGUAGES CXX)
|
||||
|
||||
set(CMAKE_AUTOUIC ON)
|
||||
set(CMAKE_AUTOMOC ON)
|
||||
set(CMAKE_AUTORCC ON)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
|
||||
find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Core QuickControls2 Quick)
|
||||
find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Core QuickControls2 Quick)
|
||||
add_subdirectory(libssh2)
|
||||
|
||||
set(PROJECT_SOURCES
|
||||
main.cpp
|
||||
icon.rc
|
||||
qml.qrc
|
||||
qmltool.h qmltool.cpp
|
||||
sshmanager.h sshmanager.cpp
|
||||
FileTransfer.h
|
||||
jsonhighlighter.h jsonhighlighter.cpp
|
||||
)
|
||||
|
||||
if(${QT_VERSION_MAJOR} GREATER_EQUAL 6)
|
||||
qt_add_executable(NDP_SM_Svr
|
||||
MANUAL_FINALIZATION
|
||||
${PROJECT_SOURCES}
|
||||
)
|
||||
# Define target properties for Android with Qt 6 as:
|
||||
# set_property(TARGET NDP_SM_Svr APPEND PROPERTY QT_ANDROID_PACKAGE_SOURCE_DIR
|
||||
# ${CMAKE_CURRENT_SOURCE_DIR}/android)
|
||||
# For more information, see https://doc.qt.io/qt-6/qt-add-executable.html#target-creation
|
||||
else()
|
||||
if(ANDROID)
|
||||
add_library(NDP_SM_Svr SHARED
|
||||
${PROJECT_SOURCES}
|
||||
)
|
||||
# Define properties for Android with Qt 5 after find_package() calls as:
|
||||
# set(ANDROID_PACKAGE_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/android")
|
||||
else()
|
||||
add_executable(NDP_SM_Svr
|
||||
${PROJECT_SOURCES}
|
||||
)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
|
||||
target_include_directories(NDP_SM_Svr PRIVATE DelegateUI/include libssh2/include QXlsx/header)
|
||||
target_link_directories(NDP_SM_Svr PRIVATE DelegateUI/lib "L:/Qt/QtApp/5.15.2/msvc2015_64/lib")
|
||||
|
||||
target_link_libraries(NDP_SM_Svr
|
||||
PRIVATE Qt${QT_VERSION_MAJOR}::Core
|
||||
Qt${QT_VERSION_MAJOR}::Quick
|
||||
Qt${QT_VERSION_MAJOR}::QuickControls2
|
||||
DelegateUI
|
||||
libssh2
|
||||
)
|
||||
|
||||
|
||||
# Qt for iOS sets MACOSX_BUNDLE_GUI_IDENTIFIER automatically since Qt 6.1.
|
||||
# If you are developing for iOS or macOS you should consider setting an
|
||||
# explicit, fixed bundle identifier manually though.
|
||||
if(${QT_VERSION} VERSION_LESS 6.1.0)
|
||||
set(BUNDLE_ID_OPTION MACOSX_BUNDLE_GUI_IDENTIFIER com.example.NDP_SM_Svr)
|
||||
endif()
|
||||
set_target_properties(NDP_SM_Svr PROPERTIES
|
||||
${BUNDLE_ID_OPTION}
|
||||
MACOSX_BUNDLE_BUNDLE_VERSION ${PROJECT_VERSION}
|
||||
MACOSX_BUNDLE_SHORT_VERSION_STRING ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}
|
||||
MACOSX_BUNDLE TRUE
|
||||
WIN32_EXECUTABLE TRUE
|
||||
)
|
||||
|
||||
include(GNUInstallDirs)
|
||||
install(TARGETS NDP_SM_Svr
|
||||
BUNDLE DESTINATION .
|
||||
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR})
|
||||
|
||||
if(QT_VERSION_MAJOR EQUAL 6)
|
||||
qt_import_qml_plugins(NDP_SM_Svr)
|
||||
qt_finalize_executable(NDP_SM_Svr)
|
||||
endif()
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
// BusyIndicator.qml
|
||||
import QtQuick 2.15
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
// 自定义属性
|
||||
property color color: "#2196F3" // 指示器颜色
|
||||
property real size: 48 // 组件大小
|
||||
property real strokeWidth: size/10 // 线条宽度
|
||||
property int duration: 1000 // 旋转周期(毫秒)
|
||||
|
||||
implicitWidth: size
|
||||
implicitHeight: size
|
||||
|
||||
Canvas {
|
||||
id: canvas
|
||||
anchors.fill: parent
|
||||
antialiasing: true
|
||||
|
||||
onPaint: {
|
||||
var ctx = getContext("2d")
|
||||
ctx.clearRect(0, 0, width, height)
|
||||
|
||||
// 绘制圆弧
|
||||
ctx.beginPath()
|
||||
ctx.strokeStyle = root.color
|
||||
ctx.lineWidth = strokeWidth
|
||||
ctx.arc(width/2, height/2,
|
||||
(Math.min(width, height) - strokeWidth)/2,
|
||||
-Math.PI/2, // 起始角度(顶部)
|
||||
Math.PI/2, // 结束角度(90度缺口)
|
||||
false)
|
||||
ctx.stroke()
|
||||
}
|
||||
}
|
||||
|
||||
RotationAnimator {
|
||||
target: canvas
|
||||
running: true
|
||||
loops: Animation.Infinite
|
||||
from: 0
|
||||
to: 360
|
||||
duration: root.duration
|
||||
}
|
||||
|
||||
// 自动重新绘制当属性变化时
|
||||
onColorChanged: canvas.requestPaint()
|
||||
onSizeChanged: canvas.requestPaint()
|
||||
}
|
||||
|
|
@ -0,0 +1,99 @@
|
|||
import QtQuick 2.15
|
||||
import QtGraphicalEffects 1.15
|
||||
|
||||
Item {
|
||||
id: cardRoot
|
||||
width: 300
|
||||
height: 200
|
||||
|
||||
property alias content: contentItem.data
|
||||
property real elevation: 8
|
||||
property real hoverScale: 1.05
|
||||
property real pressedScale: 0.95
|
||||
property bool isDarkMode: false
|
||||
property color cardColor: isDarkMode ? "#2d2d2d" : "#f5f5f5"
|
||||
property color shadowColor: isDarkMode ? "#20ffffff" : "#40000000"
|
||||
property color pressedColor: isDarkMode ? "#20ffffff" : "#40000000"
|
||||
property var onHoverEnter: undefined
|
||||
property var onHoverExit: undefined
|
||||
property var onClicked: undefined
|
||||
|
||||
DropShadow {
|
||||
id: shadow
|
||||
anchors.fill: card
|
||||
source: card
|
||||
horizontalOffset: 0
|
||||
verticalOffset: elevation
|
||||
samples: 16
|
||||
color: shadowColor
|
||||
scale: card.scale
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: card
|
||||
anchors.fill: parent
|
||||
radius: 8
|
||||
color: cardColor
|
||||
antialiasing: true
|
||||
|
||||
// 按压覆盖层
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
radius: parent.radius
|
||||
color: pressedColor
|
||||
opacity: mouseArea.pressed ? 0.4 : 0
|
||||
Behavior on opacity { NumberAnimation { duration: 100 } }
|
||||
}
|
||||
|
||||
Item {
|
||||
id: contentItem
|
||||
anchors.fill: parent
|
||||
anchors.margins: 16
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: mouseArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
|
||||
onEntered: {
|
||||
cardRoot.z = 999
|
||||
if (typeof onHoverEnter === "function") Qt.callLater(onHoverEnter)
|
||||
}
|
||||
onExited: {
|
||||
cardRoot.z = 0
|
||||
if (typeof onHoverExit === "function") Qt.callLater(onHoverExit)
|
||||
}
|
||||
onClicked: {
|
||||
if (typeof cardRoot.onClicked === "function") Qt.callLater(cardRoot.onClicked)
|
||||
}
|
||||
}
|
||||
|
||||
states: [
|
||||
State {
|
||||
name: "hovered"
|
||||
when: mouseArea.containsMouse && !mouseArea.pressed
|
||||
PropertyChanges {
|
||||
target: card
|
||||
scale: hoverScale
|
||||
}
|
||||
},
|
||||
State {
|
||||
name: "pressed"
|
||||
when: mouseArea.pressed
|
||||
PropertyChanges {
|
||||
target: card
|
||||
scale: pressedScale
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
transitions: Transition {
|
||||
PropertyAnimation {
|
||||
properties: "scale"
|
||||
duration: 200
|
||||
easing.type: Easing.OutQuad
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15
|
||||
import DelegateUI 1.0
|
||||
|
||||
Item {
|
||||
id: progressBarWrapper
|
||||
width: 120
|
||||
height: 12
|
||||
|
||||
property int minimumValue: 0
|
||||
property int maximumValue: 100
|
||||
property int currentValue: 0
|
||||
|
||||
Rectangle {
|
||||
id: backgroundRect
|
||||
width: progressBarWrapper.width
|
||||
height: progressBarWrapper.height
|
||||
color: "lightgray"
|
||||
radius: 14
|
||||
|
||||
Rectangle {
|
||||
id: progressRect
|
||||
width: (progressBarWrapper.currentValue - progressBarWrapper.minimumValue) / (progressBarWrapper.maximumValue - progressBarWrapper.minimumValue) * backgroundRect.width
|
||||
height: backgroundRect.height
|
||||
color: "#66a333"
|
||||
radius: 14
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
id: progressText
|
||||
text: currentValue + "%"
|
||||
anchors.centerIn: parent
|
||||
font {
|
||||
pixelSize: 12
|
||||
family: DelTheme.Primary.fontPrimaryFamily
|
||||
bold: true
|
||||
}
|
||||
color: DelTheme.Primary.colorTextBase
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,161 @@
|
|||
import QtQuick 2.15
|
||||
import QtQuick.Window 2.15
|
||||
import QtQuick.Controls 2.15
|
||||
import QtQuick.Layouts 1.15
|
||||
import QtMultimedia 5.15
|
||||
import DelegateUI 1.0
|
||||
import QmlTool 1.0
|
||||
import "../MyGlobals" 1.0
|
||||
import "../Component" 1.0
|
||||
|
||||
Item {
|
||||
id: delegate
|
||||
height: 60
|
||||
|
||||
property string timestr: ""
|
||||
|
||||
Timer {
|
||||
id: timer
|
||||
interval: 10 // 1 秒
|
||||
running: true
|
||||
repeat: true
|
||||
onTriggered: {
|
||||
if(modelData.status === 3){
|
||||
if(!modelData.error)timestr = "已完成";
|
||||
if(modelData.error)timestr = "失败!";
|
||||
return;
|
||||
}
|
||||
|
||||
if(modelData.remainingTime === 0){
|
||||
timestr = "";
|
||||
return;
|
||||
}
|
||||
var timeDifference = (Math.floor(Date.now() / 1000) - modelData.remainingTime)
|
||||
var seconds = timeDifference
|
||||
var minutes = Math.floor(seconds / 60)
|
||||
var hours = Math.floor(seconds / 60)
|
||||
timestr = hours + ":" + minutes + ":" + seconds
|
||||
}
|
||||
}
|
||||
|
||||
DelRectangle {
|
||||
anchors.fill: parent
|
||||
color: {
|
||||
if(!DelTheme.isDark)return index % 2 === 0 ? "#eeeeee" : "#dddddd"
|
||||
else return index % 2 === 0 ? "#333333" : "#444444"
|
||||
}
|
||||
|
||||
//只有第一个任务上方有圆角
|
||||
topLeftRadius: index === 0 ? 8 : 0
|
||||
topRightRadius: index === 0 ? 8 : 0
|
||||
clip: true
|
||||
|
||||
DelRectangle{
|
||||
id:progress_color
|
||||
width: {
|
||||
if(modelData.status === 3)return parent.width
|
||||
else return Math.min(parent.width * (modelData.progress / 100.0),parent.width)
|
||||
}
|
||||
|
||||
height: parent.height
|
||||
color: {
|
||||
if(modelData.error)return "#ff3d38"
|
||||
return modelData.status === 3 ? "#54a334" : "#1677ff"
|
||||
}
|
||||
|
||||
//只有第一个任务上方有圆角
|
||||
topLeftRadius: index === 0 ? 8 : 0
|
||||
topRightRadius: index === 0 ? 8 : 0
|
||||
|
||||
// 添加宽度变化的动画行为
|
||||
Behavior on width {
|
||||
NumberAnimation {
|
||||
duration: 300 // 动画持续时间(毫秒)
|
||||
easing.type: Easing.OutQuad // 缓动曲线(先快后慢)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 5
|
||||
spacing: 15
|
||||
|
||||
// 文件图标
|
||||
DelIconText {
|
||||
iconSource: DelIcon.CopyOutlined
|
||||
iconSize: 32
|
||||
}
|
||||
|
||||
// 文件名
|
||||
DelText {
|
||||
text: modelData.fileName
|
||||
elide: Text.ElideMiddle
|
||||
font.bold: true
|
||||
font {
|
||||
pixelSize: 14
|
||||
family: DelTheme.Primary.fontPrimaryFamily
|
||||
}
|
||||
color: DelTheme.Primary.colorTextBase
|
||||
}
|
||||
|
||||
// 下载时间
|
||||
DelText {
|
||||
text: timestr
|
||||
|
||||
elide: Text.ElideMiddle
|
||||
font.bold: true
|
||||
font {
|
||||
pixelSize: 14
|
||||
family: DelTheme.Primary.fontPrimaryFamily
|
||||
}
|
||||
color: DelTheme.Primary.colorTextBase
|
||||
}
|
||||
|
||||
// 占位项,用于将状态和进度文本推到右边
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
// 进度百分比文本
|
||||
DelText {
|
||||
visible: modelData.status === 3 ? false : true
|
||||
Layout.margins: 10 // 设置右边距
|
||||
text: Math.min(modelData.progress,100) + "%"
|
||||
elide: Text.ElideMiddle
|
||||
font.bold: true
|
||||
font {
|
||||
pixelSize: 14
|
||||
family: DelTheme.Primary.fontPrimaryFamily
|
||||
}
|
||||
color: DelTheme.Primary.colorTextBase
|
||||
Layout.alignment: Qt.AlignRight
|
||||
}
|
||||
|
||||
// 状态
|
||||
DelText {
|
||||
Layout.margins: 10 // 设置右边距
|
||||
text: {
|
||||
switch(modelData.status){
|
||||
case 0 : return "等待中"
|
||||
case 1 : return "下载中"
|
||||
case 2 : return "上传中"
|
||||
case 3 : return ""
|
||||
default: return ""
|
||||
}
|
||||
}
|
||||
elide: Text.ElideMiddle
|
||||
font.bold: true
|
||||
font {
|
||||
pixelSize: 14
|
||||
family: DelTheme.Primary.fontPrimaryFamily
|
||||
}
|
||||
color: DelTheme.Primary.colorTextBase
|
||||
Layout.alignment: Qt.AlignRight
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,102 @@
|
|||
// ExcelTableView.qml
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15
|
||||
import QtQuick.Layouts 1.15
|
||||
import Excel 1.0
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
// 公共属性
|
||||
property var dataModel: [] // 表格数据
|
||||
property string filePath: "" // Excel文件路径
|
||||
property var sheet: null // 工作表(索引或名称)
|
||||
property bool autoLoad: true // 文件路径变化时自动加载
|
||||
property color headerColor: "#f0f0f0" // 表头背景色
|
||||
property color cellColor: "white" // 单元格背景色
|
||||
property int cellPadding: 8 // 单元格内边距
|
||||
property int headerFontSize: 12 // 表头字体大小
|
||||
property int cellFontSize: 11 // 单元格字体大小
|
||||
|
||||
// 信号
|
||||
signal dataChanged(var newData) // 数据修改时触发
|
||||
signal loadCompleted() // 数据加载完成时触发
|
||||
|
||||
// 表格尺寸
|
||||
property real rowHeight: 40
|
||||
property real colWidth: 120
|
||||
|
||||
// 私有属性
|
||||
QtObject {
|
||||
id: internal
|
||||
property int columns: 0
|
||||
property int rows: 0
|
||||
}
|
||||
|
||||
// 自动加载机制
|
||||
onFilePathChanged: if(autoLoad) loadData()
|
||||
|
||||
// 加载Excel数据
|
||||
function loadData() {
|
||||
var result = Excel.read(filePath, sheet)
|
||||
if(result.length > 0) {
|
||||
dataModel = result
|
||||
processDataModel()
|
||||
loadCompleted()
|
||||
}
|
||||
}
|
||||
|
||||
// 处理数据模型
|
||||
function processDataModel() {
|
||||
internal.rows = dataModel.length
|
||||
internal.columns = dataModel[0] ? dataModel[0].length : 0
|
||||
}
|
||||
|
||||
// 保存数据到Excel
|
||||
function saveToExcel(path) {
|
||||
return Excel.write(path || filePath, dataModel)
|
||||
}
|
||||
|
||||
// 表格布局
|
||||
Flickable {
|
||||
anchors.fill: parent
|
||||
contentWidth: colWidth * internal.columns
|
||||
contentHeight: rowHeight * internal.rows
|
||||
clip: true
|
||||
|
||||
Grid {
|
||||
columns: internal.columns
|
||||
rows: internal.rows
|
||||
spacing: 1
|
||||
|
||||
Repeater {
|
||||
model: dataModel
|
||||
|
||||
// 单元格模板
|
||||
Rectangle {
|
||||
width: colWidth
|
||||
height: rowHeight
|
||||
color: (index < internal.columns) ? headerColor : cellColor
|
||||
|
||||
TextInput {
|
||||
anchors.fill: parent
|
||||
anchors.margins: cellPadding
|
||||
text: modelData ? modelData : ""
|
||||
font.pixelSize: (index < internal.columns) ? headerFontSize : cellFontSize
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
|
||||
onTextChanged: {
|
||||
if(index >= internal.columns) {
|
||||
// 更新数据模型
|
||||
var row = Math.floor(index/internal.columns)
|
||||
var col = index % internal.columns
|
||||
dataModel[row][col] = text
|
||||
dataChanged(dataModel)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,180 @@
|
|||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15
|
||||
import QtQuick.Layouts 1.15
|
||||
import JsonEditor 1.0
|
||||
import DelegateUI 1.0
|
||||
|
||||
ScrollView {
|
||||
width: 600
|
||||
height: 400
|
||||
|
||||
|
||||
property var projectConfigStr: null // 初始化为空对象
|
||||
property var saveFunction: function(config){}
|
||||
property var errorLine: null
|
||||
property var errorPosition : null
|
||||
|
||||
// JSON 处理器
|
||||
JsonProcessor {
|
||||
id: jsonProcessor
|
||||
}
|
||||
|
||||
// 将 UTF-8 字节偏移量转换为 UTF-16 代码单元索引
|
||||
function utf8OffsetToUtf16Index(text, utf8Offset) {
|
||||
var utf16Index = 0;
|
||||
var utf8BytesCount = 0;
|
||||
|
||||
for (var i = 0; i < text.length; i++) {
|
||||
var charCode = text.charCodeAt(i);
|
||||
var charUtf8Length = 1;
|
||||
|
||||
if (charCode <= 0x7F) {
|
||||
charUtf8Length = 1;
|
||||
} else if (charCode <= 0x7FF) {
|
||||
charUtf8Length = 2;
|
||||
} else if (charCode <= 0xFFFF) {
|
||||
charUtf8Length = 3;
|
||||
} else {
|
||||
charUtf8Length = 4;
|
||||
}
|
||||
|
||||
if (utf8BytesCount + charUtf8Length > utf8Offset) {
|
||||
break;
|
||||
}
|
||||
|
||||
utf8BytesCount += charUtf8Length;
|
||||
utf16Index++;
|
||||
}
|
||||
|
||||
return utf16Index;
|
||||
}
|
||||
|
||||
function getErrorLine(text, offset) {
|
||||
var tbuf = text.slice(0,offset);
|
||||
var lines = tbuf.split('\n')
|
||||
if(lines.length > 0)return lines.length
|
||||
return -1
|
||||
}
|
||||
|
||||
Rectangle{
|
||||
id:header_area
|
||||
anchors.fill: parent
|
||||
anchors.bottomMargin: 40
|
||||
color:"transparent"
|
||||
radius:8
|
||||
border.color: DelTheme.isDark ? "#23272e" : "#f0f4f7"
|
||||
border.width: 2
|
||||
|
||||
ScrollView {
|
||||
id: textScrollView
|
||||
anchors.fill: parent
|
||||
clip: true
|
||||
TextArea {
|
||||
id: jsonEditor
|
||||
font.family: "Consolas"
|
||||
font.pixelSize: 14
|
||||
wrapMode: TextArea.Wrap
|
||||
placeholderText: "请输入JSON内容..."
|
||||
selectByMouse: true
|
||||
selectionColor: "#0078d4" // 选中区域颜色(Fluent 主题蓝)
|
||||
text:projectConfigStr
|
||||
|
||||
JsonHighlighter {
|
||||
document: jsonEditor.textDocument
|
||||
}
|
||||
onTextChanged: {
|
||||
var result = jsonProcessor.validateJson(jsonEditor.text)
|
||||
if (result.hasError) {
|
||||
var utf16Index = utf8OffsetToUtf16Index(jsonEditor.text, result.errorOffset);
|
||||
errorPosition = utf16Index
|
||||
// 计算错误行号
|
||||
errorLine = getErrorLine(jsonEditor.text, utf16Index)
|
||||
errorLabel.text = "❌ JSON语法错误" + `第 ${errorLine} 行出错:${result.errorMessage}`
|
||||
errorCanvas.requestPaint()
|
||||
} else {
|
||||
errorLine = -1
|
||||
errorPosition = -1
|
||||
}
|
||||
errorLabel.visible = (errorLine !== -1)
|
||||
}
|
||||
}
|
||||
|
||||
Canvas {
|
||||
id: errorCanvas
|
||||
anchors.fill: parent
|
||||
visible: errorPosition !== -1
|
||||
z: 1
|
||||
renderTarget: Canvas.Image
|
||||
|
||||
onPaint: {
|
||||
var ctx = getContext("2d")
|
||||
ctx.clearRect(0, 0, width, height)
|
||||
|
||||
if (errorPosition === -1) return
|
||||
|
||||
// 获取错误位置在TextArea中的坐标
|
||||
var rect = jsonEditor.positionToRectangle(errorPosition)
|
||||
if (!rect || rect.width === 0) return
|
||||
|
||||
// 关键步骤:映射到ScrollView的contentItem(Flickable)
|
||||
var flickablePos = jsonEditor.mapToItem(textScrollView.contentItem, rect.x, rect.y)
|
||||
|
||||
// 计算滚动偏移后的可视坐标(减去滚动量)
|
||||
var canvasX = flickablePos.x - textScrollView.contentItem.contentX
|
||||
var canvasY = flickablePos.y + textScrollView.contentItem.contentY
|
||||
|
||||
// 调整标记到文本下方
|
||||
canvasY += rect.height
|
||||
|
||||
// 绘制红色三角形(向下箭头)
|
||||
ctx.fillStyle = "#d13438"
|
||||
ctx.beginPath()
|
||||
ctx.moveTo(canvasX, canvasY) // 顶点
|
||||
ctx.lineTo(canvasX - 8, canvasY + 10) // 左下
|
||||
ctx.lineTo(canvasX + 8, canvasY + 10) // 右下
|
||||
ctx.closePath()
|
||||
ctx.fill()
|
||||
}
|
||||
|
||||
// 滚动时实时更新
|
||||
Connections {
|
||||
target: textScrollView.contentItem
|
||||
function onContentYChanged(){
|
||||
errorCanvas.requestPaint()
|
||||
}
|
||||
function onContentXChanged(){
|
||||
errorCanvas.requestPaint()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 按钮区域(底部固定高度)
|
||||
RowLayout {
|
||||
anchors.top:header_area.bottom
|
||||
anchors.topMargin: 6
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
spacing: 15
|
||||
// 错误提示
|
||||
Text {
|
||||
id: errorLabel
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
visible: false
|
||||
color: "#d13438"
|
||||
font.bold: true
|
||||
}
|
||||
|
||||
DelButton {
|
||||
text: "格式化"
|
||||
onClicked: {
|
||||
jsonEditor.text = jsonProcessor.formatJson(jsonEditor.text)
|
||||
}
|
||||
}
|
||||
|
||||
DelButton {
|
||||
text: "保存"
|
||||
onClicked: saveFunction(jsonEditor.text)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,256 @@
|
|||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15
|
||||
import QtGraphicalEffects 1.15
|
||||
|
||||
Item {
|
||||
id: root
|
||||
width: 800
|
||||
height: 400
|
||||
|
||||
property var originalModel: []
|
||||
property alias model: listView.model
|
||||
property int currentIndex: listView.currentIndex
|
||||
property int cardSpacing : -100
|
||||
property int switchInterval: 999999999
|
||||
signal clickedFunction(data:var)
|
||||
|
||||
// 初始化循环模型
|
||||
onOriginalModelChanged: {
|
||||
if(originalModel.length <= 0)return
|
||||
var loopedModel = [];
|
||||
loopedModel = loopedModel.concat(originalModel.slice(-1));
|
||||
loopedModel = loopedModel.concat(originalModel);
|
||||
loopedModel = loopedModel.concat(originalModel.slice(0, 2));
|
||||
listView.model = loopedModel;
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: autoSwitch
|
||||
interval: switchInterval
|
||||
repeat: true
|
||||
running: true
|
||||
onTriggered: {
|
||||
listView.currentIndex = listView.currentIndex + 1;
|
||||
}
|
||||
}
|
||||
|
||||
ListView {
|
||||
id: listView
|
||||
anchors.fill: parent
|
||||
orientation: ListView.Horizontal
|
||||
snapMode: ListView.SnapOneItem
|
||||
highlightRangeMode: ListView.StrictlyEnforceRange
|
||||
preferredHighlightBegin: (width - delegateWidth) / 2
|
||||
preferredHighlightEnd: preferredHighlightBegin + delegateWidth
|
||||
spacing: -100
|
||||
|
||||
leftMargin: delegateWidth * 0.2
|
||||
rightMargin: delegateWidth * 0.2
|
||||
boundsBehavior: Flickable.StopAtBounds
|
||||
flickDeceleration: 1500
|
||||
|
||||
readonly property real delegateWidth: width * 0.85
|
||||
|
||||
onCurrentIndexChanged: {
|
||||
//位移到最后跳回第一
|
||||
if (currentIndex === (originalModel.length + 1)) {
|
||||
currentIndex = 1;
|
||||
}
|
||||
//位移到最前跳回最后
|
||||
else if (currentIndex === 0) {
|
||||
currentIndex = originalModel.length;
|
||||
}
|
||||
}
|
||||
|
||||
onModelChanged: {
|
||||
if(model && model.length > 0){
|
||||
currentIndex = 1
|
||||
listView.positionViewAtIndex(1, ListView.Center)
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
|
||||
}
|
||||
|
||||
delegate: Item {
|
||||
id: delegateItem
|
||||
width: listView.delegateWidth
|
||||
height: listView.height
|
||||
|
||||
// 被动式偏移计算
|
||||
property real _rawCenterOffset: 0
|
||||
property real _stableCenterOffset: 0
|
||||
property real centerOffset: _stableCenterOffset
|
||||
property real itemScale: 0.8 + (1 - Math.abs(centerOffset)) * 0.2
|
||||
property real sideMargin: Math.abs(centerOffset) * 40
|
||||
|
||||
Timer {
|
||||
id: offsetTracker
|
||||
interval: 16
|
||||
repeat: true
|
||||
running: true
|
||||
onTriggered: {
|
||||
const itemCenter = x + width / 2
|
||||
const viewCenter = listView.contentX + listView.width / 2
|
||||
_rawCenterOffset = (itemCenter - viewCenter) / (listView.width / 2)
|
||||
if (Math.abs(_rawCenterOffset - _stableCenterOffset) > 0.001) {
|
||||
_stableCenterOffset = _rawCenterOffset
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// // 延迟定位修正
|
||||
// Timer {
|
||||
// id: positionCorrector
|
||||
// interval: 50
|
||||
// onTriggered: {
|
||||
// if (listView.currentIndex === index && Math.abs(centerOffset) > 0.1) {
|
||||
// listView.positionViewAtIndex(index, ListView.Center)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// onCenterOffsetChanged: {
|
||||
// if (index === listView.currentIndex) {
|
||||
// positionCorrector.restart()
|
||||
// }
|
||||
// }
|
||||
|
||||
Rectangle {
|
||||
id: imageContainer
|
||||
anchors {
|
||||
fill: parent
|
||||
leftMargin: delegateItem.sideMargin * 0.8
|
||||
rightMargin: delegateItem.sideMargin * 0.8
|
||||
topMargin: 20 * (1 - Math.abs(delegateItem.centerOffset))
|
||||
bottomMargin: 20 * (1 - Math.abs(delegateItem.centerOffset))
|
||||
}
|
||||
radius: 10
|
||||
border.color: "#e0e0e0"
|
||||
border.width: 2
|
||||
clip: true
|
||||
|
||||
Image {
|
||||
id: contentImage
|
||||
anchors.fill: parent
|
||||
source: modelData.img
|
||||
fillMode: Image.PreserveAspectCrop
|
||||
layer.enabled: true
|
||||
layer.effect: OpacityMask {
|
||||
maskSource: Rectangle {
|
||||
width: imageContainer.width
|
||||
height: imageContainer.height
|
||||
radius: imageContainer.radius
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
layer.enabled: true
|
||||
layer.effect: DropShadow {
|
||||
radius: 8
|
||||
samples: 16
|
||||
color: "#40000000"
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: mouse_effect
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
onClicked: {
|
||||
listView.currentIndex = index
|
||||
clickEffect.start(mouseX, mouseY)
|
||||
var realIndex = index - 1;
|
||||
if (realIndex < 0) {
|
||||
realIndex = originalModel.length - 1;
|
||||
} else if (realIndex >= originalModel.length) {
|
||||
realIndex = 0;
|
||||
}
|
||||
root.clickedFunction(originalModel[realIndex]);
|
||||
}
|
||||
|
||||
onWheel: {
|
||||
var buf = 1;
|
||||
if (wheel.angleDelta.y > 0) buf = -1
|
||||
var newIndex = listView.currentIndex + buf;
|
||||
listView.currentIndex = newIndex;
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: clickEffect
|
||||
width: 0
|
||||
height: width
|
||||
radius: width / 2
|
||||
color: "#40ffffff"
|
||||
visible: false
|
||||
x: 0
|
||||
y: 0
|
||||
|
||||
function start(x, y) {
|
||||
clickEffect.x = 0 - delegateItem.width
|
||||
clickEffect.y = 0 - delegateItem.height
|
||||
visible = true
|
||||
anim.start()
|
||||
}
|
||||
|
||||
SequentialAnimation {
|
||||
id: anim
|
||||
ParallelAnimation {
|
||||
NumberAnimation {
|
||||
target: clickEffect
|
||||
property: "width"
|
||||
from: 0
|
||||
to: Math.max(delegateItem.width, delegateItem.height) * 4
|
||||
duration: 800
|
||||
easing.type: Easing.OutQuad
|
||||
}
|
||||
NumberAnimation {
|
||||
target: clickEffect
|
||||
property: "opacity"
|
||||
from: 0.8
|
||||
to: 0
|
||||
duration: 800
|
||||
easing.type: Easing.OutQuad
|
||||
}
|
||||
}
|
||||
ScriptAction {
|
||||
script: clickEffect.visible = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
transform: Scale {
|
||||
origin.x: delegateItem.width / 2
|
||||
origin.y: delegateItem.height / 2
|
||||
xScale: itemScale
|
||||
yScale: itemScale
|
||||
}
|
||||
|
||||
Behavior on scale {
|
||||
NumberAnimation { duration: 300; easing.type: Easing.OutQuad }
|
||||
}
|
||||
}
|
||||
|
||||
layoutDirection: Qt.LeftToRight
|
||||
highlightMoveDuration: 300
|
||||
|
||||
|
||||
}
|
||||
|
||||
PageIndicator {
|
||||
anchors {
|
||||
bottom: parent.bottom
|
||||
horizontalCenter: parent.horizontalCenter
|
||||
margins: 1
|
||||
}
|
||||
count: originalModel.length
|
||||
currentIndex: {
|
||||
var index = listView.currentIndex - 1 >= 0? listView.currentIndex - 1 : originalModel.length - 1
|
||||
if(index >= originalModel.length)index = 0
|
||||
return index
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,186 @@
|
|||
import QtQuick 2.15
|
||||
import QtQuick.Window 2.15
|
||||
import QtQuick.Controls 2.15
|
||||
import DelegateUI 1.0
|
||||
import "../MyGlobals" 1.0
|
||||
import SSHManager 1.0
|
||||
|
||||
|
||||
Item {
|
||||
visible: true
|
||||
|
||||
// SSH控制台输出显示区域
|
||||
Item {
|
||||
id:consolearea
|
||||
anchors {
|
||||
fill: parent
|
||||
margins: 10
|
||||
bottomMargin: 46
|
||||
}
|
||||
|
||||
ScrollView {
|
||||
id: scrollView
|
||||
anchors.fill: parent
|
||||
clip: true
|
||||
|
||||
TextArea {
|
||||
id: consoleOutput
|
||||
selectByMouse: true
|
||||
textFormat: Text.RichText
|
||||
readOnly: true
|
||||
wrapMode: Text.WrapAnywhere
|
||||
font {
|
||||
pixelSize: 14
|
||||
family: DelTheme.Primary.fontPrimaryFamily
|
||||
}
|
||||
color: DelTheme.Primary.colorTextBase
|
||||
background: Rectangle {
|
||||
color: DelTheme.isDark ? "#2d2d2d" : "#f5f5f5"
|
||||
radius: 4
|
||||
}
|
||||
|
||||
// 自动滚动到底部
|
||||
onTextChanged: {
|
||||
if (length > 0) {
|
||||
cursorPosition = length
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DelInput {
|
||||
id: inputField
|
||||
width: consolearea.width
|
||||
iconPosition: DelInput.Position_Left
|
||||
iconSource: DelIcon.ForwardOutlined
|
||||
placeholderText: qsTr("这里可以输入服务器命令")
|
||||
anchors {
|
||||
top: consolearea.bottom
|
||||
topMargin: 6
|
||||
horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
|
||||
// 添加回车键事件
|
||||
onAccepted: {
|
||||
SSHManager.sendInput(text)
|
||||
text = ""
|
||||
}
|
||||
|
||||
// 监听 Ctrl+C
|
||||
Keys.onPressed: (event) => {
|
||||
if ((event.modifiers & Qt.ControlModifier) && event.key === Qt.Key_C) {
|
||||
SSHManager.sendInput("\x03")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ANSI转HTML的转换逻辑
|
||||
function ansiToHtml(text) {
|
||||
// 第一步:处理颜色代码
|
||||
let colored = text.replace(/\x1B\[([0-9;]*)m/g, function(match, codes) {
|
||||
let styles = [];
|
||||
for (let code of codes.split(';')) {
|
||||
switch (parseInt(code)) {
|
||||
case 0:
|
||||
return "</span>";
|
||||
case 1:
|
||||
styles.push("font-weight:bold");
|
||||
break;
|
||||
case 3:
|
||||
styles.push("font-style:italic");
|
||||
break;
|
||||
case 4:
|
||||
styles.push("text-decoration:underline");
|
||||
break;
|
||||
case 30:
|
||||
styles.push("color:black");
|
||||
break;
|
||||
case 31:
|
||||
styles.push("color:red");
|
||||
break;
|
||||
case 32:
|
||||
styles.push("color:green");
|
||||
break;
|
||||
case 33:
|
||||
styles.push("color:yellow");
|
||||
break;
|
||||
case 34:
|
||||
styles.push("color:blue");
|
||||
break;
|
||||
case 35:
|
||||
styles.push("color:magenta");
|
||||
break;
|
||||
case 36:
|
||||
styles.push("color:cyan");
|
||||
break;
|
||||
case 37:
|
||||
styles.push("color:white");
|
||||
break;
|
||||
case 40:
|
||||
styles.push("background-color:black");
|
||||
break;
|
||||
// 其他颜色代码...
|
||||
}
|
||||
}
|
||||
return styles.length ? `<span style="${styles.join(';')}">` : "";
|
||||
});
|
||||
|
||||
// 第二步:清除所有 ANSI 转义序列(包括 XTerm 标题、控制序列等)
|
||||
let cleaned = colored
|
||||
//删除 XTerm 窗口标题序列(格式:\x1B]0;标题\x07 或 \x1B]0;标题\x1B\)
|
||||
.replace(/\x1B\][^\x07]*(?:\x07|\x1B\\)/g, '')
|
||||
//删除其他 ANSI 控制序列(包括以 ? 开头的模式设置,如 ?1034h)
|
||||
.replace(/\x1B\[[1034h;?]*[A-Za-z]/g, '');
|
||||
|
||||
return cleaned;
|
||||
}
|
||||
|
||||
function connectionSuccess(){
|
||||
|
||||
}
|
||||
|
||||
function connectionFailed(error){
|
||||
consoleOutput.text += "连接服务器失败" + error + "\n"
|
||||
}
|
||||
|
||||
function disconnected(){
|
||||
consoleOutput.text += "服务器断开连接\n"
|
||||
}
|
||||
|
||||
function updateStatus(msg) {
|
||||
consoleOutput.text += (msg + "\n")
|
||||
}
|
||||
|
||||
function appendOutput(msg) {
|
||||
// 转义 HTML 特殊字符
|
||||
const safeMsg = msg.replace(/</g, "<").replace(/>/g, ">").replace(/\n/g, "<br>"); // 新增换行符转换
|
||||
const zemsg = ansiToHtml("> " + safeMsg)
|
||||
consoleOutput.text += zemsg;
|
||||
}
|
||||
|
||||
|
||||
// 初始化后自动连接示例(可选)
|
||||
Component.onCompleted: {
|
||||
SSHManager.connectionSuccess.connect(connectionSuccess)
|
||||
SSHManager.connectionFailed.connect(connectionFailed)
|
||||
SSHManager.statusMessage.connect(updateStatus)
|
||||
SSHManager.disconnected.connect(disconnected)
|
||||
|
||||
SSHManager.shellOutput.connect(appendOutput)
|
||||
consoleOutput.text = "控制台已就绪...\n"
|
||||
}
|
||||
|
||||
// 窗口关闭时自动触发清理
|
||||
Component.onDestruction: {
|
||||
// 断开所有信号连接
|
||||
SSHManager.connectionSuccess.disconnect(connectionSuccess);
|
||||
SSHManager.connectionFailed.disconnect(connectionFailed);
|
||||
SSHManager.statusMessage.disconnect(updateStatus);
|
||||
SSHManager.disconnected.disconnect(disconnected);
|
||||
SSHManager.shellOutput.disconnect(appendOutput);
|
||||
console.log("窗口关闭,已断开所有信号连接并清理资源。");
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,139 @@
|
|||
// UserInfoCard.qml
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15
|
||||
import QtGraphicalEffects 1.15
|
||||
import DelegateUI 1.0
|
||||
|
||||
Rectangle {
|
||||
id: card
|
||||
width: 300
|
||||
height: 120
|
||||
radius: 8
|
||||
color: "#ffffff"
|
||||
border.color: "#e0e0e0"
|
||||
border.width: 1
|
||||
|
||||
// 暴露可修改属性
|
||||
property alias avatarSource: avatarImg.iconSource
|
||||
property alias userName: nameText.text
|
||||
property alias userPosition: positionText.text
|
||||
property alias userEmail: emailText.text
|
||||
property alias userPhone: phoneText.text
|
||||
|
||||
// 鼠标悬停效果
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
onEntered: {
|
||||
card.color = "#f5f5f5"
|
||||
shadow.radius = 12
|
||||
}
|
||||
onExited: {
|
||||
card.color = "#ffffff"
|
||||
shadow.radius = 8
|
||||
}
|
||||
}
|
||||
|
||||
// 阴影效果
|
||||
layer.enabled: true
|
||||
layer.effect: DropShadow {
|
||||
id: shadow
|
||||
color: "#20000000"
|
||||
radius: 8
|
||||
samples: 16
|
||||
verticalOffset: 2
|
||||
}
|
||||
|
||||
// 内容布局
|
||||
Row {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 16
|
||||
spacing: 16
|
||||
|
||||
// 头像部分
|
||||
Rectangle {
|
||||
width: 80
|
||||
height: 80
|
||||
radius: width / 2
|
||||
clip: true
|
||||
|
||||
|
||||
DelAvatar {
|
||||
id: avatarImg
|
||||
anchors.fill: parent
|
||||
iconSource: DelIcon.UserOutlined
|
||||
}
|
||||
}
|
||||
|
||||
// 文字信息部分
|
||||
Column {
|
||||
width: parent.width - 80 - 16
|
||||
spacing: 6
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
// 姓名
|
||||
Text {
|
||||
id: nameText
|
||||
text: "张三"
|
||||
font.bold: true
|
||||
font.pixelSize: 18
|
||||
color: "#333333"
|
||||
}
|
||||
|
||||
// 职位
|
||||
Text {
|
||||
id: positionText
|
||||
text: "高级软件工程师"
|
||||
font.pixelSize: 12
|
||||
color: "#666666"
|
||||
topPadding: 2
|
||||
}
|
||||
|
||||
// 分隔线
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: 1
|
||||
color: "#eeeeee"
|
||||
anchors.margins: 4
|
||||
}
|
||||
|
||||
// 联系方式
|
||||
Column {
|
||||
spacing: 4
|
||||
topPadding: 8
|
||||
|
||||
Row {
|
||||
spacing: 6
|
||||
Image {
|
||||
source: "mail.svg"
|
||||
width: 12
|
||||
height: 12
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
Text {
|
||||
id: emailText
|
||||
text: "zhangsan@example.com"
|
||||
font.pixelSize: 12
|
||||
color: "#666666"
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
spacing: 6
|
||||
Image {
|
||||
source: "phone.svg"
|
||||
width: 12
|
||||
height: 12
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
Text {
|
||||
id: phoneText
|
||||
text: "+86 138-1234-5678"
|
||||
font.pixelSize: 12
|
||||
color: "#666666"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
import QtQuick 2.15
|
||||
import QtGraphicalEffects 1.15
|
||||
import DelegateUI 1.0
|
||||
|
||||
Item {
|
||||
id: control
|
||||
|
||||
property alias sourceItem: __source.sourceItem
|
||||
property alias sourceRect: __source.sourceRect
|
||||
property alias opacityNoise: __noiseImage.opacity
|
||||
property alias radiusBlur: __fastBlur.radius
|
||||
property real radiusBg: 0
|
||||
property color colorTint: "#fff"
|
||||
property real opacityTint: 0.65
|
||||
property real luminosity: 0.01
|
||||
|
||||
ShaderEffectSource {
|
||||
id: __source
|
||||
anchors.fill: parent
|
||||
visible: false
|
||||
sourceRect: Qt.rect(control.x, control.y, control.width, control.height)
|
||||
}
|
||||
|
||||
FastBlur {
|
||||
id: __fastBlur
|
||||
anchors.fill: parent
|
||||
source: __source
|
||||
radius: 32
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
color: DelThemeFunctions.alpha("#fff", luminosity)
|
||||
radius: control.radiusBg
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
color: DelThemeFunctions.alpha(colorTint, opacityTint)
|
||||
radius: control.radiusBg
|
||||
}
|
||||
|
||||
Image {
|
||||
id: __noiseImage
|
||||
anchors.fill: parent
|
||||
source: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAAEnQAABJ0Ad5mH3gAAAGHaVRYdFhNTDpjb20uYWRvYmUueG1wAAAAAAA8P3hwYWNrZXQgYmVnaW49J++7vycgaWQ9J1c1TTBNcENlaGlIenJlU3pOVGN6a2M5ZCc/Pg0KPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyI+PHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj48cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0idXVpZDpmYWY1YmRkNS1iYTNkLTExZGEtYWQzMS1kMzNkNzUxODJmMWIiIHhtbG5zOnRpZmY9Imh0dHA6Ly9ucy5hZG9iZS5jb20vdGlmZi8xLjAvIj48dGlmZjpPcmllbnRhdGlvbj4xPC90aWZmOk9yaWVudGF0aW9uPjwvcmRmOkRlc2NyaXB0aW9uPjwvcmRmOlJERj48L3g6eG1wbWV0YT4NCjw/eHBhY2tldCBlbmQ9J3cnPz4slJgLAAAMNElEQVRYR02XW1NTd9vGf0nYZEFMVha7JBAIYYihEQVSUQQVtOrUtipVsTPtTOs38KDtUQ+Y6ZfoTI88aT3o2Jk6bUfa4QFEKZWIsgm7ECKQHZu1FpiQQAx5DnyfzHv6n//Jvbl+13Vrvv/++5zRaOSPP/7giy++wGQyMTw8TCgUoquri2QySSgUorm5mcbGRh4/fozT6cTtdvPgwQMMBgPnzp0DYHR0FEmSiEajXL16leLiYh48eMDbt2/JZrMYjUaSySQFBQXs7+9TUVGBrra2tj+ZTNLY2EhdXR0//vgjTqcTURRZWFjg/fffJ5vNsr6+Tn19Pa9evSKRSOByuZifn8ftdhMIBNjd3aWjowOHw8GrV6948uQJ5eXl7OzscO3aNQwGA1arFbfbTWtrK06nk4KCAnTd3d39ZrMZURQZHBzE4/GQy+WoqKhAr9dzeHjI7OwsNpuNaDSKyWRCEAQKCwvZ2toil8vx8uVLOjo68Pv9FBYWEggE8Hg8VFVVEY/HURSFcDiMKIpEIhFSqRQzMzMAaDOZDF6vF6vVSltbG16vl8PDQ16+fEkwGKSiooJsNouqqszMzCAIAqqq8vjxYzweD6IoYjKZ0Gg0iKKITqejoKAASZIYGhqipqYGn8+HJEmMjo4SDAYZHR3F4/EgSRK6ZDLZrygK29vbJBIJBgYGsFqtSJKEw+GgtLSURCJBT08P6XQaq9XK8ePHWVlZoaysjJWVFUZGRshkMszOzuarTiQShMNhzp49y+nTp2lrayOVStHT08PMzAzHjh0jHA6j++677/r1ej2zs7PcvHkTAEVR0Ov1pNNpJiYmOHr0KCsrK7S3t/PTTz8BUFdXRyAQwGw2U1NTw5kzZxAEgXQ6nR9TOp3myJEjrKysYDQamZ6eRlEUVldXOXPmDNFoFJ3D4eg3GAyYTCYMBgOhUAiDwUA4HCaXy6HVamlsbARge3sbj8fD6uoqNTU1tLS08OjRI4qLi2lqasJut/PmzRvq6uoIBoMkEglSqRRzc3MYDAYWFhZYXV3FbDYTi8XQ6/XoXC5Xv0ajYXl5mSNHjtDR0YHRaESr1VJZWYmqqpjNZgYGBpiYmMBsNlNXV8f8/DylpaWcO3eOqakpFEVhYGAASZIoLy+nqKiIU6dO0d7eTnV1Nbu7uxQXF9Pd3Y0gCBQVFZHL5dC1tLT022w2UqkUra2t+P1+ZFlGFEUURWFubo6WlhYEQch3Q1EUqquryWQyBINBcrkcJpMJm81GQ0MDf/31F4IgsLGxwc7ODpFIhPHxcXp7e/H7/YyPj9PU1EQ6nUZbWlqK1+slm80yNDSEKIrU1dXh9/vxer14vV5GRkaQJIm9vT3W1taQZZmpqSmKi4uRZRmDwYDBYMDr9TIzM8PMzExeNSMjIzidThoaGiguLsbpdHL16lVCoRDBYBDdvXv3+u/fv8/JkydxuVwsLS3R3t5ONBrlzz//5Pr162QyGVZXV6msrKS5uZmSkhLGx8ex2+35N4vFwg8//EBTUxNVVVXU1tZSVVWFIAgMDw8DUFBQwNOnT/N8+fDDD9Fdv369XxAEFhcXKS8vB2BqaoqamhokScJqtTI9PY3ZbObNmzeUlJQgyzKbm5u0t7cTiUR4+vQpTU1NAMTjcUwmE+l0msXFRVwuF5Ik0dDQwPLyMjabDUVROHHiBH6/H53FYuk/f/48r1+/5tmzZ2xtbXHnzh3evHmDXq9ndHSURCLBBx98QFlZGT6fj0wmQ3NzM9vb22xvb2M0GjGZTDidTjY3N3G73QSDQex2O7dv36agoIDi4mLKysqYnJwkkUiwvr5OIBBA8+233+b8fj+ff/45e3t7FBQUcHh4yMOHD3E4HJSXl2M0Gnnx4gV3795ldHSUzc1NNjc3SSaTOBwO5ufnMRqNlJSUoCgKly9fJpvN8ujRI65fv04wGMTv92OxWLh9+zaxWIydnZ13XtDX19dvNptZXFwkFouRTqdpaGggGAxis9lYXl6mq6uLdDrNzs5O3hd2d3f5+OOPSafT2O123G43er0evV6PJEkoisLW1hZer5dUKoXdbieXy6GqKoWFhaysrKDRaNBeuXIlv+H/Y3VRUREWiyXvEb/++isANTU17O3tIUkS9+7dQ1VVVFWlsbGRra0tnE4n586dQxRFJEmisLCQWCyGKIoEAgEcDkfeL65du4bX60Xndrv7c7kcnZ2d6PV6GhoamJ+fZ2hoiLm5OXp7eykqKiISiWA2m9ne3qayspLx8XFGRkY4efIk2WyWv//+m4WFBba3t4nFYpSVlfHee+/R2trKs2fPsNlsdHV1MTY2xsjICFNTUwQCAXSXLl3qVxQFi8XC2NgYgiAQCoW4c+cOgiAgCAJPnz7FZrMxOTlJb28vwWCQV69e8dlnnxGNRqmqqmJjYwOPx4PBYKCmpgZZlgmFQuzv75PL5fD5fOj1egB6e3t5/vw5Ho8HrclkwufzsbS0xNraWr7ts7OzqKpKJpPB5XLlYbW3t4fP50On0+X/qKrKrVu3kCSJ2tpapqenkWU5b9+yLCPLMslkMj+erq6udyBqamrqv3HjBg0NDSQSCQKBAN3d3TQ3N2Oz2RgbG2NsbIyZmZl8ldXV1UiSRFVVFW1tbfznP/9hdXWV8vJySkpK2NjYoLGxkUQiweXLlzk4OCAUClFfX084HCYWi7G+vs6NGzfQeb3efkVR+OWXX+jp6SGVShGNRllZWSEajSIIAg6HA5vNRn19PTs7O6TTaXK5HOFwGFVVuXjxIoODg5jNZjQaDWVlZciyTDAYpLq6mkgkQiaToaenB0VRMJlMrK6uvvMCURQRRZG7d++yuLiIz+cjGo3mk0w8HqexsRGv18uFCxeora1FkiTOnDlDYWEhoigyPDzM5cuXEUWReDyeb/v58+dZWFjA5/Nx5coVYrEYPp+P2tpaMpkMsiyj02g0/Xq9Hp1Oh9/v55NPPmF+fh673U53dzdLS0sUFhby8OFDXr58ycTEBFqtFovFwsTEBPF4nPX1dSoqKohEIqytraHVajk4OCAcDhOJRPj000/x+XzEYjH6+vo4ODjgyZMndHR0oDt79my/1WrFarWys7NDKpUCIJvNEo1G2djYyMdunU6Hy+VCEAQMBgO7u7tYrVb0ej3JZBKz2YzL5eLg4ABBEDCZTLjdbiKRCLOzs/T19eVRvra2htlsRitJEqIoMjExQWdnJ8FgEIfDQTKZxOfzYbfbmZycRJIklpeXsVqteL1eZmdnsVqtAHz11VccPXoUWZaZnp7m8PAwr6apqSmcTicnTpwgkUjg8/l48eIFOp3uHZS+/vrr/qGhIQYHB3E4HPT09BCNRpmZmaGnp4fKysp8AJUkic7OTh49ekQsFqO+vh5BEJibm+P333+nsrKStrY2KisrefbsGR0dHXkfiMfjFBYW4na76erqQq/XMzw8jPb/h4RoNIrP5+PYsWNkMhlUVcVisRAKhZBlGVVVWVpayuM0m80iyzKlpaX5mJ1KpZiYmEAURfb29lhZWcHpdNLV1ZWP94qiMDs7+w7Fp0+f7rdarQiCwOvXrzk8PESn0+HxeHC5XGSzWfb395mammJ/fx+n05mXaGNjI6Ojo1gsFi5cuMDU1BQ7OzucOnWKbDbL9vY2H330Eevr64yOjiLLMkVFRbS0tLC/v/9OBcFgsD8ej9Pa2srbt28pLS3l+fPnyLLM+vo6k5OTaLVa+vr6CIVC2O12YrEYqqricDi4dOkSv/32G+FwmIaGBp48eUI2m0VRFAYHB6murkav11NbW4vD4cDpdPLPP/+wuLjI+fPn0XV0dPR7vV60Wm0ePEVFRbhcLgA0Gg2XLl3K47q5uZl0Ok1XVxdPnz4lnU7T2dnJ2NgYkiTR0tKCKIrkcjkURaGurg5FUWhubmZycpJUKkUikaClpYVIJIL2+PHjSJKEx+OhoqKCYDBIZ2cn8XgcURS5efNmfu63bt1Cp9Mh/d/V9D9fMJvNHD16FFVV8wCSZZnKykqqqqrye2I0GgFIJBKUlZXhdDrRffPNN/2qqjI8PIzf7+fLL7/k33//5eeffyaZTFJeXs7AwAAajYbp6WmWlpbyKJ6fn+fixYtIkgRAW1sb9+/fJ5fLYbVaefHiBaqqUlBQkL8xDg8PCQQCZDIZpqen+S/TdODXYD8HMAAAAABJRU5ErkJggg=="
|
||||
fillMode: Image.Tile
|
||||
opacity: 0.02
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,211 @@
|
|||
import QtQuick 2.15
|
||||
import QtQuick.Window 2.15
|
||||
import QtQuick.Templates 2.15 as T
|
||||
import DelegateUI 1.0
|
||||
|
||||
DelInput {
|
||||
id: control
|
||||
|
||||
signal search(input: string)
|
||||
signal select(option: var)
|
||||
|
||||
property var options: []
|
||||
property var filterOption: (input, option) => true
|
||||
property string textRole: 'label'
|
||||
property string valueRole: 'value'
|
||||
property bool tooltipVisible: false
|
||||
property alias clearIconSource: control.iconSource
|
||||
property alias clearIconSize: control.iconSize
|
||||
property alias clearIconPosition: control.iconPosition
|
||||
property int defaultPopupMaxHeight: 240
|
||||
property int defaultOptionSpacing: 0
|
||||
|
||||
property Component labelDelegate: Text {
|
||||
text: textData
|
||||
color: DelTheme.DelAutoComplete.colorItemText
|
||||
font {
|
||||
family: DelTheme.DelAutoComplete.fontFamily
|
||||
pixelSize: DelTheme.DelAutoComplete.fontSize
|
||||
weight: highlighted ? Font.DemiBold : Font.Normal
|
||||
}
|
||||
elide: Text.ElideRight
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
}
|
||||
property Component labelBgDelegate: Rectangle {
|
||||
radius: 2
|
||||
color: highlighted ? DelTheme.DelAutoComplete.colorItemBgActive :
|
||||
hovered ? DelTheme.DelAutoComplete.colorItemBgHover :
|
||||
DelTheme.DelAutoComplete.colorItemBg;
|
||||
}
|
||||
property Component clearIconDelegate: DelIconText {
|
||||
iconSource: control.clearIconSource
|
||||
iconSize: control.iconSize
|
||||
colorIcon: control.enabled ?
|
||||
__iconMouse.hovered ? DelTheme.DelAutoComplete.colorIconHover :
|
||||
DelTheme.DelAutoComplete.colorIcon : DelTheme.DelAutoComplete.colorIconDisabled
|
||||
|
||||
Behavior on colorIcon { enabled: control.animationEnabled; ColorAnimation { duration: DelTheme.Primary.durationFast } }
|
||||
|
||||
MouseArea {
|
||||
id: __iconMouse
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: parent.iconSource == control.clearIconSource ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||
onEntered: hovered = true;
|
||||
onExited: hovered = false;
|
||||
onClicked: control.clearInput();
|
||||
property bool hovered: false
|
||||
}
|
||||
}
|
||||
|
||||
clearIconPosition: DelInput.Position_Right
|
||||
iconDelegate: clearIconDelegate
|
||||
onOptionsChanged: {
|
||||
__popupListView.currentIndex = -1;
|
||||
__private.model = options;
|
||||
}
|
||||
onTextEdited: {
|
||||
control.search(text);
|
||||
__private.filter();
|
||||
if (__private.model.length > 0)
|
||||
control.openPopup();
|
||||
else
|
||||
control.closePopup();
|
||||
}
|
||||
|
||||
function clearInput() {
|
||||
control.clear();
|
||||
control.textEdited();
|
||||
__popupListView.currentIndex = -1;
|
||||
}
|
||||
|
||||
function openPopup() {
|
||||
if (!__popup.opened)
|
||||
__popup.open();
|
||||
}
|
||||
|
||||
function closePopup() {
|
||||
__popup.close();
|
||||
}
|
||||
|
||||
Item {
|
||||
id: __private
|
||||
property var window: Window.window
|
||||
property var model: []
|
||||
function filter() {
|
||||
__private.model = options.filter(option => filterOption(text, option) === true);
|
||||
}
|
||||
}
|
||||
|
||||
TapHandler {
|
||||
onTapped: {
|
||||
if (__private.model.length > 0)
|
||||
control.openPopup();
|
||||
}
|
||||
}
|
||||
|
||||
DelPopup {
|
||||
id: __popup
|
||||
implicitWidth: control.width
|
||||
implicitHeight: Math.min(control.defaultPopupMaxHeight, __popupListView.contentHeight) + topPadding + bottomPadding
|
||||
leftPadding: 4
|
||||
rightPadding: 4
|
||||
topPadding: 6
|
||||
bottomPadding: 6
|
||||
closePolicy: T.Popup.NoAutoClose | T.Popup.CloseOnEscape | T.Popup.CloseOnPressOutsideParent
|
||||
onAboutToShow: {
|
||||
const pos = control.mapToItem(null, 0, 0);
|
||||
x = (control.width - width) * 0.5;
|
||||
if (__private.window.height > (pos.y + control.height + implicitHeight + 6)){
|
||||
y = control.height + 6;
|
||||
} else if (pos.y > implicitHeight) {
|
||||
y = -implicitHeight - 6;
|
||||
} else {
|
||||
y = __private.window.height - (pos.y + implicitHeight + 6);
|
||||
}
|
||||
}
|
||||
enter: Transition {
|
||||
NumberAnimation {
|
||||
property: 'opacity'
|
||||
from: 0.0
|
||||
to: 1.0
|
||||
easing.type: Easing.InOutQuad
|
||||
duration: control.animationEnabled ? DelTheme.Primary.durationMid : 0
|
||||
}
|
||||
}
|
||||
exit: Transition {
|
||||
NumberAnimation {
|
||||
property: 'opacity'
|
||||
from: 1.0
|
||||
to: 0.0
|
||||
easing.type: Easing.InOutQuad
|
||||
duration: control.animationEnabled ? DelTheme.Primary.durationMid : 0
|
||||
}
|
||||
}
|
||||
contentItem: ListView {
|
||||
id: __popupListView
|
||||
clip: true
|
||||
currentIndex: -1
|
||||
model: __private.model
|
||||
boundsBehavior: Flickable.StopAtBounds
|
||||
spacing: control.defaultOptionSpacing
|
||||
delegate: T.ItemDelegate {
|
||||
id: __popupDelegate
|
||||
|
||||
required property var modelData
|
||||
required property int index
|
||||
|
||||
property var textData: modelData[control.textRole]
|
||||
property var valueData: modelData[control.valueRole] ?? textData
|
||||
|
||||
width: __popupListView.width
|
||||
height: implicitContentHeight + topPadding + bottomPadding
|
||||
leftPadding: 8
|
||||
rightPadding: 8
|
||||
topPadding: 4
|
||||
bottomPadding: 4
|
||||
highlighted: __popupListView.currentIndex === index
|
||||
contentItem: Loader {
|
||||
sourceComponent: control.labelDelegate
|
||||
property alias textData: __popupDelegate.textData
|
||||
property alias valueData: __popupDelegate.valueData
|
||||
property alias modelData: __popupDelegate.modelData
|
||||
property alias hovered: __popupDelegate.hovered
|
||||
property alias highlighted: __popupDelegate.highlighted
|
||||
}
|
||||
background: Loader {
|
||||
sourceComponent: control.labelBgDelegate
|
||||
property alias textData: __popupDelegate.textData
|
||||
property alias valueData: __popupDelegate.valueData
|
||||
property alias modelData: __popupDelegate.modelData
|
||||
property alias hovered: __popupDelegate.hovered
|
||||
property alias highlighted: __popupDelegate.highlighted
|
||||
}
|
||||
onClicked: {
|
||||
control.select(__popupDelegate.modelData);
|
||||
control.text = __popupDelegate.valueData;
|
||||
__popupListView.currentIndex = index;
|
||||
__popup.close();
|
||||
__private.filter();
|
||||
}
|
||||
|
||||
HoverHandler {
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
}
|
||||
|
||||
Loader {
|
||||
y: __popupDelegate.height
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
active: control.tooltipVisible
|
||||
sourceComponent: DelToolTip {
|
||||
arrowVisible: false
|
||||
visible: __popupDelegate.hovered && !__popupDelegate.pressed
|
||||
text: __popupDelegate.textData
|
||||
position: DelToolTip.Position_Bottom
|
||||
}
|
||||
}
|
||||
}
|
||||
T.ScrollBar.vertical: DelScrollBar { }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,151 @@
|
|||
import QtQuick 2.15
|
||||
import QtGraphicalEffects 1.15
|
||||
import DelegateUI 1.0
|
||||
|
||||
Item {
|
||||
id: control
|
||||
|
||||
width: __loader.width
|
||||
height: __loader.height
|
||||
|
||||
textFont {
|
||||
family: DelTheme.Primary.fontPrimaryFamily
|
||||
pixelSize: control.size * 0.5
|
||||
}
|
||||
|
||||
enum TextSiz {
|
||||
Size_Fixed = 0,
|
||||
Size_Auto = 1
|
||||
}
|
||||
|
||||
property int size: 30
|
||||
property int iconSource: 0
|
||||
|
||||
property url imageSource: ""
|
||||
property bool imageMipmap: false
|
||||
|
||||
property string textSource: ""
|
||||
property font textFont
|
||||
property int textSize: DelAvatar.Size_Fixed
|
||||
property int textGap: 4
|
||||
|
||||
property color colorBg: DelTheme.Primary.colorTextQuaternary
|
||||
property color colorIcon: "white"
|
||||
property color colorText: "white"
|
||||
property int radiusBg: width * 0.5
|
||||
|
||||
Component {
|
||||
id: __iconImpl
|
||||
|
||||
Rectangle {
|
||||
width: control.size
|
||||
height: control.size
|
||||
radius: control.radiusBg
|
||||
color: control.colorBg
|
||||
|
||||
DelIconText {
|
||||
id: __iconSource
|
||||
anchors.centerIn: parent
|
||||
iconSource: control.iconSource
|
||||
iconSize: control.size * 0.7
|
||||
colorIcon: control.colorIcon
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: __imageImpl
|
||||
|
||||
Rectangle {
|
||||
width: control.size
|
||||
height: control.size
|
||||
radius: control.radiusBg
|
||||
color: control.colorBg
|
||||
|
||||
Rectangle {
|
||||
id: __mask
|
||||
anchors.fill: parent
|
||||
radius: parent.radius
|
||||
layer.enabled: true
|
||||
visible: false
|
||||
}
|
||||
|
||||
Image {
|
||||
id: __imageSource
|
||||
anchors.fill: parent
|
||||
mipmap: control.imageMipmap
|
||||
source: control.imageSource
|
||||
sourceSize: Qt.size(width, height)
|
||||
layer.enabled: true
|
||||
visible: false
|
||||
}
|
||||
|
||||
OpacityMask {
|
||||
anchors.fill: parent
|
||||
maskSource: __mask
|
||||
source: __imageSource
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: __textImpl
|
||||
|
||||
Rectangle {
|
||||
id: __bg
|
||||
width: Math.max(control.size, __textSource.implicitWidth + control.textGap * 2);
|
||||
height: width
|
||||
radius: control.radiusBg
|
||||
color: control.colorBg
|
||||
Component.onCompleted: calcBestSize();
|
||||
|
||||
function calcBestSize() {
|
||||
if (control.textSize == DelAvatar.Size_Fixed) {
|
||||
__textSource.font.pixelSize = control.size * 0.5;
|
||||
} else {
|
||||
let bestSize = control.size * 0.5;
|
||||
__fontMetrics.font.pixelSize = bestSize;
|
||||
while ((__fontMetrics.advanceWidth(control.textSource) + control.textGap * 2 > control.size)) {
|
||||
bestSize -= 1;
|
||||
__fontMetrics.font.pixelSize = bestSize;
|
||||
if (bestSize <= 1) break;
|
||||
}
|
||||
__textSource.font.pixelSize = bestSize;
|
||||
}
|
||||
}
|
||||
|
||||
FontMetrics {
|
||||
id: __fontMetrics
|
||||
font.family: __textSource.font.family
|
||||
}
|
||||
|
||||
Text {
|
||||
id: __textSource
|
||||
anchors.centerIn: parent
|
||||
color: control.colorText
|
||||
text: control.textSource
|
||||
smooth: true
|
||||
font: control.textFont
|
||||
|
||||
Connections {
|
||||
target: control
|
||||
function onTextSourceChanged() {
|
||||
__bg.calcBestSize();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: __loader
|
||||
sourceComponent: {
|
||||
if (control.iconSource != 0)
|
||||
return __iconImpl;
|
||||
else if (control.imageSource != "")
|
||||
return __imageImpl;
|
||||
else
|
||||
return __textImpl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,201 @@
|
|||
import QtQuick 2.15
|
||||
import QtQuick.Templates 2.15 as T
|
||||
import DelegateUI 1.0
|
||||
|
||||
T.Button {
|
||||
id: control
|
||||
|
||||
enum Type {
|
||||
Type_Default = 0,
|
||||
Type_Outlined = 1,
|
||||
Type_Primary = 2,
|
||||
Type_Filled = 3,
|
||||
Type_Text = 4,
|
||||
Type_Link = 5
|
||||
}
|
||||
|
||||
enum Shape {
|
||||
Shape_Default = 0,
|
||||
Shape_Circle = 1
|
||||
}
|
||||
|
||||
property bool animationEnabled: DelTheme.animationEnabled
|
||||
property bool effectEnabled: true
|
||||
property int hoverCursorShape: Qt.PointingHandCursor
|
||||
property int type: DelButton.Type_Default
|
||||
property int shape: DelButton.Shape_Default
|
||||
property int radiusBg: DelTheme.DelButton.radiusBg
|
||||
property color colorText: {
|
||||
if (enabled) {
|
||||
switch(control.type)
|
||||
{
|
||||
case DelButton.Type_Default:
|
||||
return control.down ? DelTheme.DelButton.colorTextActive :
|
||||
control.hovered ? DelTheme.DelButton.colorTextHover :
|
||||
DelTheme.DelButton.colorTextDefault;
|
||||
case DelButton.Type_Outlined:
|
||||
return control.down ? DelTheme.DelButton.colorTextActive :
|
||||
control.hovered ? DelTheme.DelButton.colorTextHover :
|
||||
DelTheme.DelButton.colorText;
|
||||
case DelButton.Type_Primary: return "white";
|
||||
case DelButton.Type_Filled:
|
||||
case DelButton.Type_Text:
|
||||
case DelButton.Type_Link:
|
||||
return control.down ? DelTheme.DelButton.colorTextActive :
|
||||
control.hovered ? DelTheme.DelButton.colorTextHover :
|
||||
DelTheme.DelButton.colorText;
|
||||
default: return DelTheme.DelButton.colorText;
|
||||
}
|
||||
} else {
|
||||
return DelTheme.DelButton.colorTextDisabled;
|
||||
}
|
||||
}
|
||||
property color colorBg: {
|
||||
if (type == DelButton.Type_Link) return "transparent";
|
||||
if (enabled) {
|
||||
switch(control.type)
|
||||
{
|
||||
case DelButton.Type_Default:
|
||||
case DelButton.Type_Outlined:
|
||||
return control.down ? DelTheme.DelButton.colorBgActive :
|
||||
control.hovered ? DelTheme.DelButton.colorBgHover :
|
||||
DelTheme.DelButton.colorBg;
|
||||
case DelButton.Type_Primary:
|
||||
return control.down ? DelTheme.DelButton.colorPrimaryBgActive:
|
||||
control.hovered ? DelTheme.DelButton.colorPrimaryBgHover :
|
||||
DelTheme.DelButton.colorPrimaryBg;
|
||||
case DelButton.Type_Filled:
|
||||
if (DelTheme.isDark) {
|
||||
return control.down ? DelTheme.DelButton.colorFillBgDarkActive:
|
||||
control.hovered ? DelTheme.DelButton.colorFillBgDarkHover :
|
||||
DelTheme.DelButton.colorFillBgDark;
|
||||
} else {
|
||||
return control.down ? DelTheme.DelButton.colorFillBgActive:
|
||||
control.hovered ? DelTheme.DelButton.colorFillBgHover :
|
||||
DelTheme.DelButton.colorFillBg;
|
||||
}
|
||||
case DelButton.Type_Text:
|
||||
if (DelTheme.isDark) {
|
||||
return control.down ? DelTheme.DelButton.colorFillBgDarkActive:
|
||||
control.hovered ? DelTheme.DelButton.colorFillBgDarkHover :
|
||||
DelTheme.DelButton.colorTextBg;
|
||||
} else {
|
||||
return control.down ? DelTheme.DelButton.colorTextBgActive:
|
||||
control.hovered ? DelTheme.DelButton.colorTextBgHover :
|
||||
DelTheme.DelButton.colorTextBg;
|
||||
}
|
||||
default: return DelTheme.DelButton.colorBg;
|
||||
}
|
||||
} else {
|
||||
return DelTheme.DelButton.colorBgDisabled;
|
||||
}
|
||||
}
|
||||
property color colorBorder: {
|
||||
if (type == DelButton.Type_Link) return "transparent";
|
||||
if (enabled) {
|
||||
switch(control.type)
|
||||
{
|
||||
case DelButton.Type_Default:
|
||||
return control.down ? DelTheme.DelButton.colorBorderActive :
|
||||
control.hovered ? DelTheme.DelButton.colorBorderHover :
|
||||
DelTheme.DelButton.colorDefaultBorder;
|
||||
default:
|
||||
return control.down ? DelTheme.DelButton.colorBorderActive :
|
||||
control.hovered ? DelTheme.DelButton.colorBorderHover :
|
||||
DelTheme.DelButton.colorBorder;
|
||||
}
|
||||
} else {
|
||||
return DelTheme.DelButton.colorBorder;
|
||||
}
|
||||
}
|
||||
property string contentDescription: text
|
||||
|
||||
implicitWidth: implicitContentWidth + leftPadding + rightPadding
|
||||
implicitHeight: implicitContentHeight + topPadding + bottomPadding
|
||||
padding: 15
|
||||
topPadding: 5
|
||||
bottomPadding: 5
|
||||
font {
|
||||
family: DelTheme.DelButton.fontFamily
|
||||
pixelSize: DelTheme.DelButton.fontSize
|
||||
}
|
||||
contentItem: Text {
|
||||
text: control.text
|
||||
font: control.font
|
||||
lineHeight: DelTheme.DelButton.fontLineHeight
|
||||
color: control.colorText
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
elide: Text.ElideRight
|
||||
|
||||
Behavior on color { enabled: control.animationEnabled; ColorAnimation { duration: DelTheme.Primary.durationFast } }
|
||||
}
|
||||
background: Item {
|
||||
Rectangle {
|
||||
id: __effect
|
||||
width: __bg.width
|
||||
height: __bg.height
|
||||
radius: __bg.radius
|
||||
anchors.centerIn: parent
|
||||
visible: control.effectEnabled && control.type != DelButton.Type_Link
|
||||
color: "transparent"
|
||||
border.width: 0
|
||||
border.color: control.enabled ? DelTheme.DelButton.colorBorderHover : "transparent"
|
||||
opacity: 0.2
|
||||
|
||||
ParallelAnimation {
|
||||
id: __animation
|
||||
onFinished: __effect.border.width = 0;
|
||||
NumberAnimation {
|
||||
target: __effect; property: "width"; from: __bg.width + 3; to: __bg.width + 8;
|
||||
duration: DelTheme.Primary.durationFast
|
||||
easing.type: Easing.OutQuart
|
||||
}
|
||||
NumberAnimation {
|
||||
target: __effect; property: "height"; from: __bg.height + 3; to: __bg.height + 8;
|
||||
duration: DelTheme.Primary.durationFast
|
||||
easing.type: Easing.OutQuart
|
||||
}
|
||||
NumberAnimation {
|
||||
target: __effect; property: "opacity"; from: 0.2; to: 0;
|
||||
duration: DelTheme.Primary.durationSlow
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: control
|
||||
function onReleased() {
|
||||
if (control.animationEnabled && control.effectEnabled) {
|
||||
__effect.border.width = 8;
|
||||
__animation.restart();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Rectangle {
|
||||
id: __bg
|
||||
width: realWidth
|
||||
height: realHeight
|
||||
anchors.centerIn: parent
|
||||
radius: control.shape == DelButton.Shape_Default ? control.radiusBg : height * 0.5
|
||||
color: control.colorBg
|
||||
border.width: (control.type == DelButton.Type_Filled || control.type == DelButton.Type_Text) ? 0 : 1
|
||||
border.color: control.enabled ? control.colorBorder : "transparent"
|
||||
|
||||
property real realWidth: control.shape == DelButton.Shape_Default ? parent.width : parent.height
|
||||
property real realHeight: control.shape == DelButton.Shape_Default ? parent.height : parent.height
|
||||
|
||||
Behavior on color { enabled: control.animationEnabled; ColorAnimation { duration: DelTheme.Primary.durationMid } }
|
||||
Behavior on border.color { enabled: control.animationEnabled; ColorAnimation { duration: DelTheme.Primary.durationMid } }
|
||||
}
|
||||
}
|
||||
|
||||
HoverHandler {
|
||||
cursorShape: control.hoverCursorShape
|
||||
}
|
||||
|
||||
Accessible.role: Accessible.Button
|
||||
Accessible.name: control.text
|
||||
Accessible.description: control.contentDescription
|
||||
Accessible.onPressAction: control.clicked();
|
||||
}
|
||||
|
|
@ -0,0 +1,193 @@
|
|||
import QtQuick 2.15
|
||||
import QtQuick.Layouts 1.15
|
||||
import QtQuick.Window 2.15
|
||||
import DelegateUI 1.0
|
||||
|
||||
Rectangle {
|
||||
id: control
|
||||
|
||||
color: "transparent"
|
||||
|
||||
property var targetWindow: null
|
||||
property DelWindowAgent windowAgent: null
|
||||
|
||||
property alias layoutDirection: __row.layoutDirection
|
||||
|
||||
property string winTitle: targetWindow ? targetWindow.title : ""
|
||||
property string winIcon: ""
|
||||
property alias winIconWidth: __winIconLoader.width
|
||||
property alias winIconHeight: __winIconLoader.height
|
||||
property alias winIconVisible: __winIconLoader.visible
|
||||
|
||||
property font winTitleFont
|
||||
winTitleFont {
|
||||
family: DelTheme.Primary.fontPrimaryFamily
|
||||
pixelSize: 14
|
||||
}
|
||||
property color winTitleColor: DelTheme.Primary.colorTextBase
|
||||
property alias winTitleVisible: __winTitleLoader.visible
|
||||
|
||||
property string contentDescription: targetWindow ? targetWindow.title : ""
|
||||
|
||||
property bool returnButtonVisible: false
|
||||
property bool themeButtonVisible: false
|
||||
property bool topButtonVisible: false
|
||||
property bool minimizeButtonVisible: Qt.platform.os !== "osx"
|
||||
property bool maximizeButtonVisible: Qt.platform.os !== "osx"
|
||||
property bool closeButtonVisible: Qt.platform.os !== "osx"
|
||||
|
||||
property var returnCallback: () => { }
|
||||
property var themeCallback: () => { DelTheme.darkMode = DelTheme.isDark ? DelTheme.Light : DelTheme.Dark; }
|
||||
property var topCallback: checked => { }
|
||||
property var minimizeCallback:
|
||||
() => {
|
||||
if (targetWindow) targetWindow.showMinimized();
|
||||
}
|
||||
property var maximizeCallback: () => {
|
||||
if (!targetWindow) return;
|
||||
|
||||
if (targetWindow.visibility === Window.Maximized) targetWindow.showNormal();
|
||||
else targetWindow.showMaximized();
|
||||
}
|
||||
property var closeCallback: () => { if (targetWindow) targetWindow.close(); }
|
||||
|
||||
property Component winIconDelegate: Image {
|
||||
source: control.winIcon
|
||||
sourceSize.width: width
|
||||
sourceSize.height: height
|
||||
mipmap: true
|
||||
}
|
||||
property Component winTitleDelegate: Text {
|
||||
text: winTitle
|
||||
color: winTitleColor
|
||||
font: winTitleFont
|
||||
}
|
||||
property Component winButtonsDelegate: Row {
|
||||
Connections {
|
||||
target: control
|
||||
function onWindowAgentChanged() {
|
||||
if (windowAgent) {
|
||||
windowAgent.setHitTestVisible(__themeButton, true);
|
||||
windowAgent.setHitTestVisible(__topButton, true);
|
||||
windowAgent.setSystemButton(DelWindowAgent.Minimize, __minimizeButton);
|
||||
windowAgent.setSystemButton(DelWindowAgent.Maximize, __maximizeButton);
|
||||
windowAgent.setSystemButton(DelWindowAgent.Close, __closeButton);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DelCaptionButton {
|
||||
id: __themeButton
|
||||
visible: control.themeButtonVisible
|
||||
iconSource: DelTheme.isDark ? DelIcon.MoonOutlined : DelIcon.SunOutlined
|
||||
iconSize: 16
|
||||
onClicked: themeCallback();
|
||||
contentDescription: qsTr("明暗主题切换")
|
||||
}
|
||||
|
||||
DelCaptionButton {
|
||||
id: __topButton
|
||||
visible: control.topButtonVisible
|
||||
iconSource: DelIcon.PushpinOutlined
|
||||
iconSize: 16
|
||||
checkable: true
|
||||
onClicked: topCallback(checked);
|
||||
contentDescription: qsTr("置顶")
|
||||
}
|
||||
|
||||
DelCaptionButton {
|
||||
id: __minimizeButton
|
||||
visible: control.minimizeButtonVisible
|
||||
iconSource: DelIcon.LineOutlined
|
||||
onClicked: minimizeCallback();
|
||||
contentDescription: qsTr("最小化")
|
||||
}
|
||||
|
||||
DelCaptionButton {
|
||||
id: __maximizeButton
|
||||
visible: control.maximizeButtonVisible
|
||||
iconSource: targetWindow ? (targetWindow.visibility === Window.Maximized ? DelIcon.SwitcherOutlined : DelIcon.BorderOutlined) : 0
|
||||
onClicked: maximizeCallback();
|
||||
contentDescription: qsTr("最大化")
|
||||
}
|
||||
|
||||
DelCaptionButton {
|
||||
id: __closeButton
|
||||
visible: control.closeButtonVisible
|
||||
iconSource: DelIcon.CloseOutlined
|
||||
isError: true
|
||||
onClicked: closeCallback();
|
||||
contentDescription: qsTr("关闭")
|
||||
}
|
||||
}
|
||||
|
||||
function addInteractionItem(item) {
|
||||
if (windowAgent)
|
||||
windowAgent.setHitTestVisible(item, true);
|
||||
}
|
||||
|
||||
function removeInteractionItem(item) {
|
||||
if (windowAgent)
|
||||
windowAgent.setHitTestVisible(item, false);
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
id: __row
|
||||
anchors.fill: parent
|
||||
spacing: 0
|
||||
|
||||
DelCaptionButton {
|
||||
id: __returnButton
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
iconSource: DelIcon.ArrowLeftOutlined
|
||||
iconSize: DelTheme.DelCaptionButton.fontSize + 2
|
||||
visible: control.returnButtonVisible
|
||||
onClicked: returnCallback();
|
||||
contentDescription: qsTr("返回")
|
||||
}
|
||||
|
||||
Item {
|
||||
id: __title
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
Component.onCompleted: {
|
||||
if (windowAgent)
|
||||
windowAgent.setTitleBar(__title);
|
||||
}
|
||||
|
||||
Row {
|
||||
height: parent.height
|
||||
anchors.left: Qt.platform.os === "osx" ? undefined : parent.left
|
||||
anchors.leftMargin: Qt.platform.os === "osx" ? 0 : 8
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.horizontalCenter: Qt.platform.os === "osx" ? parent.horizontalCenter : undefined
|
||||
spacing: 5
|
||||
|
||||
Loader {
|
||||
id: __winIconLoader
|
||||
width: 20
|
||||
height: 20
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
sourceComponent: winIconDelegate
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: __winTitleLoader
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
sourceComponent: winTitleDelegate
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
width: item ? item.width : 0
|
||||
height: item ? item.height : 0
|
||||
sourceComponent: winButtonsDelegate
|
||||
}
|
||||
}
|
||||
|
||||
Accessible.role: Accessible.TitleBar
|
||||
Accessible.name: control.contentDescription
|
||||
Accessible.description: control.contentDescription
|
||||
}
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
import QtQuick 2.15
|
||||
import DelegateUI 1.0
|
||||
|
||||
DelIconButton {
|
||||
id: control
|
||||
|
||||
property bool isError: false
|
||||
|
||||
leftPadding: 12
|
||||
rightPadding: 12
|
||||
radiusBg: 0
|
||||
hoverCursorShape: Qt.ArrowCursor
|
||||
type: DelButton.Type_Text
|
||||
iconSize: DelTheme.DelCaptionButton.fontSize
|
||||
font.pixelSize: iconSize
|
||||
effectEnabled: false
|
||||
colorIcon: {
|
||||
if (enabled) {
|
||||
return checked ? DelTheme.DelCaptionButton.colorIconChecked :
|
||||
DelTheme.DelCaptionButton.colorIcon;
|
||||
} else {
|
||||
return DelTheme.DelCaptionButton.colorIconDisabled;
|
||||
}
|
||||
}
|
||||
colorBg: {
|
||||
if (enabled) {
|
||||
if (isError) {
|
||||
return control.down ? DelTheme.DelCaptionButton.colorErrorBgActive:
|
||||
control.hovered ? DelTheme.DelCaptionButton.colorErrorBgHover :
|
||||
DelTheme.DelCaptionButton.colorErrorBg;
|
||||
} else {
|
||||
return control.down ? DelTheme.DelCaptionButton.colorBgActive:
|
||||
control.hovered ? DelTheme.DelCaptionButton.colorBgHover :
|
||||
DelTheme.DelCaptionButton.colorBg;
|
||||
}
|
||||
} else {
|
||||
return DelTheme.DelCaptionButton.colorBgDisabled;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,171 @@
|
|||
import QtQuick 2.15
|
||||
import QtQuick.Layouts 1.15
|
||||
import DelegateUI 1.0
|
||||
|
||||
Rectangle {
|
||||
id: control
|
||||
|
||||
width: 300
|
||||
height: __column.height
|
||||
color: DelTheme.DelCard.colorBg
|
||||
border.color: DelTheme.isDark ? DelTheme.DelCard.colorBorderDark : DelTheme.DelCard.colorBorder
|
||||
radius: DelTheme.DelCard.radiusBg
|
||||
clip: true
|
||||
|
||||
titleFont {
|
||||
family: DelTheme.DelCard.fontFamily
|
||||
pixelSize: DelTheme.DelCard.fontSizeTitle
|
||||
weight: Font.DemiBold
|
||||
}
|
||||
|
||||
bodyTitleFont {
|
||||
family: DelTheme.DelCard.fontFamily
|
||||
pixelSize: DelTheme.DelCard.fontSizeBodyTitle
|
||||
weight: Font.DemiBold
|
||||
}
|
||||
|
||||
bodyDescriptionFont {
|
||||
family: DelTheme.DelCard.fontFamily
|
||||
pixelSize: DelTheme.DelCard.fontSizeBodyDescription
|
||||
}
|
||||
|
||||
property bool animationEnabled: DelTheme.animationEnabled
|
||||
|
||||
property string title: ""
|
||||
property font titleFont
|
||||
|
||||
property url coverSource: ""
|
||||
property int coverFillMode: Image.Stretch
|
||||
|
||||
property int bodyAvatarSize: 40
|
||||
property int bodyAvatarIcon: 0
|
||||
property string bodyAvatarSource: ""
|
||||
property string bodyAvatarText: ""
|
||||
property string bodyTitle: ""
|
||||
property font bodyTitleFont
|
||||
property string bodyDescription: ""
|
||||
property font bodyDescriptionFont
|
||||
|
||||
property color colorTitle: DelTheme.DelCard.colorTitle
|
||||
property color colorBodyAvatar: DelTheme.DelCard.colorBodyAvatar
|
||||
property color colorBodyAvatarBg: "transparent"
|
||||
property color colorBodyTitle: DelTheme.DelCard.colorBodyTitle
|
||||
property color colorBodyDescription: DelTheme.DelCard.colorBodyDescription
|
||||
|
||||
property Component titleDelegate: Item {
|
||||
height: 60
|
||||
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
anchors.topMargin: 5
|
||||
anchors.bottomMargin: 5
|
||||
anchors.leftMargin: 15
|
||||
anchors.rightMargin: 15
|
||||
|
||||
Text {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
text: control.title
|
||||
font: control.titleFont
|
||||
color: control.colorTitle
|
||||
wrapMode: Text.WrapAnywhere
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
}
|
||||
|
||||
Loader {
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
sourceComponent: extraDelegate
|
||||
}
|
||||
}
|
||||
|
||||
DelDivider {
|
||||
width: parent.width;
|
||||
height: 1
|
||||
anchors.bottom: parent.bottom
|
||||
visible: control.coverSource == ""
|
||||
}
|
||||
}
|
||||
property Component extraDelegate: Item { }
|
||||
property Component coverDelegate: Image {
|
||||
height: control.coverSource == "" ? 0 : 180
|
||||
source: control.coverSource
|
||||
fillMode: control.coverFillMode
|
||||
}
|
||||
property Component bodyDelegate: Item {
|
||||
height: 100
|
||||
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
|
||||
Item {
|
||||
Layout.preferredWidth: __avatar.visible ? 70 : 0
|
||||
Layout.fillHeight: true
|
||||
|
||||
DelAvatar {
|
||||
id: __avatar
|
||||
size: control.bodyAvatarSize
|
||||
anchors.centerIn: parent
|
||||
colorBg: control.colorBodyAvatarBg
|
||||
iconSource: control.bodyAvatarIcon
|
||||
imageSource: control.bodyAvatarSource
|
||||
textSource: control.bodyAvatarText
|
||||
colorIcon: control.colorBodyAvatar
|
||||
colorText: control.colorBodyAvatar
|
||||
visible: !(iconSource == 0 && imageSource == "" && textSource == "")
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
|
||||
Text {
|
||||
Layout.fillWidth: true
|
||||
leftPadding: __avatar.visible ? 0 : 15
|
||||
rightPadding: 15
|
||||
text: control.bodyTitle
|
||||
font: control.bodyTitleFont
|
||||
color: control.colorBodyTitle
|
||||
wrapMode: Text.WrapAnywhere
|
||||
visible: control.bodyTitle != ""
|
||||
}
|
||||
|
||||
Text {
|
||||
Layout.fillWidth: true
|
||||
leftPadding: __avatar.visible ? 0 : 15
|
||||
rightPadding: 15
|
||||
text: control.bodyDescription
|
||||
font: control.bodyDescriptionFont
|
||||
color: control.colorBodyDescription
|
||||
wrapMode: Text.WrapAnywhere
|
||||
visible: control.bodyDescription != ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
property Component actionDelegate: Item { }
|
||||
|
||||
Behavior on color { enabled: control.animationEnabled; ColorAnimation { duration: DelTheme.Primary.durationMid } }
|
||||
|
||||
Column {
|
||||
id: __column
|
||||
width: parent.width
|
||||
|
||||
Loader {
|
||||
width: parent.width
|
||||
sourceComponent: titleDelegate
|
||||
}
|
||||
Loader {
|
||||
width: parent.width
|
||||
sourceComponent: coverDelegate
|
||||
}
|
||||
Loader {
|
||||
width: parent.width
|
||||
sourceComponent: bodyDelegate
|
||||
}
|
||||
Loader {
|
||||
width: parent.width
|
||||
sourceComponent: actionDelegate
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,129 @@
|
|||
import QtQuick 2.15
|
||||
import QtQuick.Templates 2.15 as T
|
||||
import DelegateUI 1.0
|
||||
|
||||
T.CheckBox {
|
||||
id: control
|
||||
|
||||
property bool animationEnabled: DelTheme.animationEnabled
|
||||
property bool effectEnabled: true
|
||||
property int hoverCursorShape: Qt.PointingHandCursor
|
||||
property int indicatorSize: 20
|
||||
property color colorText: enabled ? DelTheme.DelCheckBox.colorText : DelTheme.DelCheckBox.colorTextDisabled
|
||||
property color colorIndicator: enabled ?
|
||||
(checkState != Qt.Unchecked) ? DelTheme.DelCheckBox.colorIndicatorChecked :
|
||||
DelTheme.DelCheckBox.colorIndicator : DelTheme.DelCheckBox.colorIndicatorDisabled
|
||||
property color colorIndicatorBorder: enabled ?
|
||||
(hovered || checked) ? DelTheme.DelCheckBox.colorIndicatorBorderChecked :
|
||||
DelTheme.DelCheckBox.colorIndicatorBorder : DelTheme.DelCheckBox.colorIndicatorDisabled
|
||||
property string contentDescription: ""
|
||||
|
||||
font {
|
||||
family: DelTheme.DelCheckBox.fontFamily
|
||||
pixelSize: DelTheme.DelCheckBox.fontSize
|
||||
}
|
||||
|
||||
implicitWidth: implicitContentWidth + leftPadding + rightPadding
|
||||
implicitHeight: Math.max(implicitContentHeight, implicitIndicatorHeight) + topPadding + bottomPadding
|
||||
spacing: 6
|
||||
indicator: Item {
|
||||
x: control.leftPadding
|
||||
implicitWidth: __bg.implicitWidth
|
||||
implicitHeight: __bg.implicitHeight
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
Rectangle {
|
||||
id: __effect
|
||||
width: __bg.implicitWidth
|
||||
height: __bg.implicitHeight
|
||||
radius: 4
|
||||
anchors.centerIn: parent
|
||||
visible: control.effectEnabled
|
||||
color: "transparent"
|
||||
border.width: 0
|
||||
border.color: control.enabled ? DelTheme.DelCheckBox.colorEffectBg : "transparent"
|
||||
opacity: 0.2
|
||||
|
||||
ParallelAnimation {
|
||||
id: __animation
|
||||
onFinished: __effect.border.width = 0;
|
||||
NumberAnimation {
|
||||
target: __effect; property: "width"; from: __bg.implicitWidth + 2; to: __bg.implicitWidth + 6;
|
||||
duration: DelTheme.Primary.durationFast
|
||||
easing.type: Easing.OutQuart
|
||||
}
|
||||
NumberAnimation {
|
||||
target: __effect; property: "height"; from: __bg.implicitHeight + 2; to: __bg.implicitHeight + 6;
|
||||
duration: DelTheme.Primary.durationFast
|
||||
easing.type: Easing.OutQuart
|
||||
}
|
||||
NumberAnimation {
|
||||
target: __effect; property: "opacity"; from: 0.2; to: 0;
|
||||
duration: DelTheme.Primary.durationSlow
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: control
|
||||
function onReleased() {
|
||||
if (control.animationEnabled && control.effectEnabled) {
|
||||
__effect.border.width = 6;
|
||||
__animation.restart();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DelIconText {
|
||||
id: __bg
|
||||
iconSize: control.indicatorSize
|
||||
iconSource: DelIcon.BorderOutlined
|
||||
anchors.centerIn: parent
|
||||
colorIcon: control.colorIndicatorBorder
|
||||
|
||||
Behavior on colorIcon { enabled: control.animationEnabled; ColorAnimation { duration: DelTheme.Primary.durationFast } }
|
||||
|
||||
/*! √ 的背景色 */
|
||||
DelIconText {
|
||||
anchors.centerIn: parent
|
||||
iconSource: DelIcon.XFilledPath1
|
||||
iconSize: parent.iconSize * 0.5
|
||||
colorIcon: "#fff"
|
||||
visible: control.checkState == Qt.Checked
|
||||
}
|
||||
|
||||
DelIconText {
|
||||
iconSource: DelIcon.CheckSquareFilled
|
||||
iconSize: parent.iconSize
|
||||
colorIcon: control.colorIndicator
|
||||
visible: control.checkState == Qt.Checked
|
||||
}
|
||||
|
||||
DelIconText {
|
||||
anchors.centerIn: parent
|
||||
iconSource: DelIcon.XFilledPath1
|
||||
iconSize: parent.iconSize * 0.5
|
||||
colorIcon: control.colorIndicator
|
||||
visible: control.checkState == Qt.PartiallyChecked
|
||||
}
|
||||
}
|
||||
}
|
||||
contentItem: Text {
|
||||
text: control.text
|
||||
font: control.font
|
||||
opacity: enabled ? 1.0 : 0.3
|
||||
color: control.colorText
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
leftPadding: control.indicator.width + control.spacing
|
||||
}
|
||||
background: Item { }
|
||||
|
||||
HoverHandler {
|
||||
cursorShape: control.hoverCursorShape
|
||||
}
|
||||
|
||||
Accessible.role: Accessible.CheckBox
|
||||
Accessible.name: control.text
|
||||
Accessible.description: control.contentDescription
|
||||
Accessible.onPressAction: control.clicked();
|
||||
}
|
||||
|
|
@ -0,0 +1,262 @@
|
|||
import QtQuick 2.15
|
||||
import QtQuick.Templates 2.15 as T
|
||||
import DelegateUI 1.0
|
||||
|
||||
Item {
|
||||
id: control
|
||||
|
||||
height: __listView.contentHeight
|
||||
|
||||
titleFont {
|
||||
family: DelTheme.DelCollapse.fontFamily
|
||||
pixelSize: DelTheme.DelCollapse.fontSizeTitle
|
||||
}
|
||||
contentFont {
|
||||
family: DelTheme.DelCollapse.fontFamily
|
||||
pixelSize: DelTheme.DelCollapse.fontSizeContent
|
||||
}
|
||||
|
||||
signal actived(key: string)
|
||||
|
||||
property bool animationEnabled: DelTheme.animationEnabled
|
||||
property int hoverCursorShape: Qt.PointingHandCursor
|
||||
property var initModel: []
|
||||
property alias count: __listModel.count
|
||||
property alias spacing: __listView.spacing
|
||||
property bool accordion: false
|
||||
property var activeKey: accordion ? "" : []
|
||||
property var defaultActiveKey: []
|
||||
property int expandIcon: DelIcon.RightOutlined
|
||||
property font titleFont
|
||||
property color colorBg: DelTheme.DelCollapse.colorBg
|
||||
property color colorIcon: DelTheme.DelCollapse.colorIcon
|
||||
property color colorTitle: DelTheme.DelCollapse.colorTitle
|
||||
property color colorTitleBg: DelTheme.DelCollapse.colorTitleBg
|
||||
property font contentFont
|
||||
property color colorContent: DelTheme.DelCollapse.colorContent
|
||||
property color colorContentBg: DelTheme.DelCollapse.colorContentBg
|
||||
property color colorBorder: DelTheme.isDark ? DelTheme.DelCollapse.colorBorderDark : DelTheme.DelCollapse.colorBorder
|
||||
property int radiusBg: 6
|
||||
property Component titleDelegate: Row {
|
||||
leftPadding: 16
|
||||
rightPadding: 16
|
||||
height: Math.max(40, __icon.height, __title.height)
|
||||
spacing: 8
|
||||
|
||||
DelIconText {
|
||||
id: __icon
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
iconSource: control.expandIcon
|
||||
colorIcon: control.colorIcon
|
||||
rotation: isActive ? 90 : 0
|
||||
|
||||
Behavior on rotation { enabled: control.animationEnabled; RotationAnimation { duration: DelTheme.Primary.durationFast } }
|
||||
}
|
||||
|
||||
Text {
|
||||
id: __title
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: model.title
|
||||
elide: Text.ElideRight
|
||||
font: control.titleFont
|
||||
color: control.colorTitle
|
||||
}
|
||||
|
||||
HoverHandler {
|
||||
cursorShape: control.hoverCursorShape
|
||||
}
|
||||
}
|
||||
property Component contentDelegate: DelCopyableText {
|
||||
padding: 16
|
||||
topPadding: 8
|
||||
bottomPadding: 8
|
||||
text: model.content
|
||||
font: control.contentFont
|
||||
wrapMode: Text.WordWrap
|
||||
color: control.colorContent
|
||||
}
|
||||
|
||||
onInitModelChanged: {
|
||||
clear();
|
||||
for (const object of initModel) {
|
||||
append(object);
|
||||
}
|
||||
}
|
||||
|
||||
function get(index) {
|
||||
return __listModel.get(index);
|
||||
}
|
||||
|
||||
function set(index, object) {
|
||||
__listModel.set(index, object);
|
||||
}
|
||||
|
||||
function setProperty(index, propertyName, value) {
|
||||
__listModel.setProperty(index, propertyName, value);
|
||||
}
|
||||
|
||||
function move(from, to, count = 1) {
|
||||
__listModel.move(from, to, count);
|
||||
}
|
||||
|
||||
function insert(index, object) {
|
||||
__listModel.insert(index, object);
|
||||
}
|
||||
|
||||
function append(object) {
|
||||
__listModel.append(object);
|
||||
}
|
||||
|
||||
function remove(index, count = 1) {
|
||||
__listModel.remove(index, count);
|
||||
}
|
||||
|
||||
function clear() {
|
||||
__listModel.clear();
|
||||
}
|
||||
|
||||
Behavior on colorBg { enabled: control.animationEnabled; ColorAnimation { duration: DelTheme.Primary.durationFast } }
|
||||
Behavior on colorTitle { enabled: control.animationEnabled; ColorAnimation { duration: DelTheme.Primary.durationFast } }
|
||||
Behavior on colorTitleBg { enabled: control.animationEnabled; ColorAnimation { duration: DelTheme.Primary.durationFast } }
|
||||
Behavior on colorContent { enabled: control.animationEnabled; ColorAnimation { duration: DelTheme.Primary.durationFast } }
|
||||
Behavior on colorContentBg { enabled: control.animationEnabled; ColorAnimation { duration: DelTheme.Primary.durationFast } }
|
||||
|
||||
QtObject {
|
||||
id: __private
|
||||
function calcActiveKey() {
|
||||
if (control.accordion) {
|
||||
for (let i = 0; i < __listView.count; i++) {
|
||||
const item = __listView.itemAtIndex(i);
|
||||
if (item && item.active) {
|
||||
control.activeKey = item.model.key;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let keys = [];
|
||||
for (let i = 0; i < __listView.count; i++) {
|
||||
const item = __listView.itemAtIndex(i);
|
||||
if (item && item.active) {
|
||||
keys.push(item.model.key);
|
||||
}
|
||||
}
|
||||
control.activeKey = keys;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ListView {
|
||||
id: __listView
|
||||
anchors.fill: parent
|
||||
interactive: false
|
||||
spacing: -1
|
||||
model: ListModel { id: __listModel }
|
||||
onContentHeightChanged: cacheBuffer = contentHeight;
|
||||
delegate: DelRectangle {
|
||||
id: __rootItem
|
||||
width: __listView.width
|
||||
height: __column.height + ((detached && active) ? 1 : 0)
|
||||
topLeftRadius: (isStart || detached) ? control.radiusBg : 0
|
||||
topRightRadius: (isStart || detached) ? control.radiusBg : 0
|
||||
bottomLeftRadius: (isEnd || detached) ? control.radiusBg : 0
|
||||
bottomRightRadius: (isEnd || detached) ? control.radiusBg : 0
|
||||
color: control.colorBg
|
||||
border.color: control.colorBorder
|
||||
border.width: detached ? 1 : 0
|
||||
clip: true
|
||||
|
||||
required property var model
|
||||
required property int index
|
||||
property bool isStart: index == 0
|
||||
property bool isEnd: (index + 1) === control.count
|
||||
property bool active: false
|
||||
property bool detached: __listView.spacing !== -1
|
||||
|
||||
Component.onCompleted: {
|
||||
if (control.defaultActiveKey.indexOf(model.key) != -1)
|
||||
active = true;
|
||||
}
|
||||
|
||||
Column {
|
||||
id: __column
|
||||
width: parent.width
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
|
||||
DelRectangle {
|
||||
width: parent.width
|
||||
height: __titleLoader.height
|
||||
topLeftRadius: (isStart || detached) ? control.radiusBg : 0
|
||||
topRightRadius: (isStart || detached) ? control.radiusBg : 0
|
||||
bottomLeftRadius: (isEnd && !active) || (detached && !active) ? control.radiusBg : 0
|
||||
bottomRightRadius: (isEnd && !active) || (detached && !active) ? control.radiusBg : 0
|
||||
color: control.colorTitleBg
|
||||
border.color: control.colorBorder
|
||||
|
||||
Loader {
|
||||
id: __titleLoader
|
||||
width: parent.width
|
||||
sourceComponent: titleDelegate
|
||||
property alias model: __rootItem.model
|
||||
property alias index: __rootItem.index
|
||||
property alias isActive: __rootItem.active
|
||||
|
||||
HoverHandler {
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
}
|
||||
|
||||
TapHandler {
|
||||
onTapped: {
|
||||
if (control.accordion) {
|
||||
for (let i = 0; i < __listView.count; i++) {
|
||||
const item = __listView.itemAtIndex(i);
|
||||
if (item && item !== __rootItem) {
|
||||
item.active = false;
|
||||
}
|
||||
}
|
||||
__rootItem.active = !__rootItem.active;
|
||||
} else {
|
||||
__rootItem.active = !__rootItem.active;
|
||||
}
|
||||
if (__rootItem.active)
|
||||
control.actived(__rootItem.model.key);
|
||||
__private.calcActiveKey();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DelRectangle {
|
||||
width: parent.width - __rootItem.border.width * 2
|
||||
height: active ? __contentLoader.height : 0
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
bottomLeftRadius: control.radiusBg
|
||||
bottomRightRadius: control.radiusBg
|
||||
color: control.colorContentBg
|
||||
clip: true
|
||||
|
||||
Behavior on height { enabled: control.animationEnabled; NumberAnimation { duration: DelTheme.Primary.durationFast } }
|
||||
|
||||
Loader {
|
||||
id: __contentLoader
|
||||
width: parent.width
|
||||
anchors.centerIn: parent
|
||||
sourceComponent: contentDelegate
|
||||
property alias model: __rootItem.model
|
||||
property alias index: __rootItem.index
|
||||
property alias isActive: __rootItem.active
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
anchors.fill: __listView
|
||||
active: spacing === -1
|
||||
sourceComponent: Rectangle {
|
||||
color: "transparent"
|
||||
border.color: control.colorBorder
|
||||
radius: control.radiusBg
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
import QtQuick 2.15
|
||||
import DelegateUI 1.0
|
||||
|
||||
TextEdit {
|
||||
id: control
|
||||
|
||||
readOnly: true
|
||||
color: DelTheme.DelCopyableText.colorText
|
||||
selectByMouse: true
|
||||
selectByKeyboard: true
|
||||
selectedTextColor: DelTheme.DelCopyableText.colorSelectedText
|
||||
selectionColor: DelTheme.DelCopyableText.colorSelection
|
||||
font {
|
||||
family: DelTheme.DelCopyableText.fontFamily
|
||||
pixelSize: DelTheme.DelCopyableText.fontSize
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,675 @@
|
|||
import QtQuick 2.15
|
||||
import QtQuick.Window 2.15
|
||||
import QtQuick.Layouts 1.15
|
||||
import QtQuick.Controls 2.15
|
||||
import Qt.labs.calendar 1.0 as T
|
||||
import DelegateUI 1.0
|
||||
|
||||
Item {
|
||||
id: control
|
||||
|
||||
width: 160
|
||||
height: 32
|
||||
|
||||
enum IconPosition {
|
||||
Position_Left = 0,
|
||||
Position_Right = 1
|
||||
}
|
||||
|
||||
enum PickerMode {
|
||||
Mode_Year = 0,
|
||||
Mode_Quarter = 1,
|
||||
Mode_Month = 2,
|
||||
Mode_Week = 3,
|
||||
Mode_Day = 4
|
||||
}
|
||||
|
||||
property bool animationEnabled: DelTheme.animationEnabled
|
||||
property alias placeholderText: __input.placeholderText
|
||||
property int iconPosition: DelDatePicker.Position_Right
|
||||
property int pickerMode: DelDatePicker.Mode_Day
|
||||
|
||||
property var initDate: undefined
|
||||
property var currentDate: new Date()
|
||||
property int currentYear: new Date().getFullYear()
|
||||
property int currentMonth: new Date().getMonth()
|
||||
property int currentDay: new Date().getDate()
|
||||
property int currentWeekNumber: DelApi.getWeekNumber(new Date())
|
||||
property int currentQuarter: Math.floor(currentMonth / 3) + 1
|
||||
|
||||
property int visualYear: control.currentYear
|
||||
property int visualMonth: control.currentMonth
|
||||
property int visualQuarter: control.currentQuarter
|
||||
|
||||
property string dateFormat: "yyyy-MM-dd"
|
||||
|
||||
property Component dayDelegate: DelButton {
|
||||
padding: 0
|
||||
implicitWidth: 28
|
||||
implicitHeight: 28
|
||||
type: DelButton.Type_Primary
|
||||
text: model.day
|
||||
font {
|
||||
family: DelTheme.DelDatePicker.fontFamily
|
||||
pixelSize: DelTheme.DelDatePicker.fontSize
|
||||
}
|
||||
effectEnabled: false
|
||||
colorBorder: model.today ? DelTheme.DelDatePicker.colorDayBorderToday : 'transparent'
|
||||
colorText: {
|
||||
if (control.pickerMode === DelDatePicker.Mode_Week) {
|
||||
return isCurrentWeek || isHoveredWeek ? 'white' : isCurrentVisualMonth ? DelTheme.DelDatePicker.colorDayText :
|
||||
DelTheme.DelDatePicker.colorDayTextNone;
|
||||
} else {
|
||||
return isCurrentDay ? 'white' : isCurrentVisualMonth ? DelTheme.DelDatePicker.colorDayText :
|
||||
DelTheme.DelDatePicker.colorDayTextNone;
|
||||
}
|
||||
}
|
||||
colorBg: {
|
||||
if (control.pickerMode === DelDatePicker.Mode_Week) {
|
||||
return 'transparent';
|
||||
} else {
|
||||
return isCurrentDay ? DelTheme.DelDatePicker.colorDayBgCurrent :
|
||||
hovered ? DelTheme.DelDatePicker.colorDayBgHover :
|
||||
DelTheme.DelDatePicker.colorDayBg;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onInitDateChanged: {
|
||||
if (initDate)
|
||||
__private.selectDate(initDate);
|
||||
}
|
||||
|
||||
function openPicker() {
|
||||
if (!__picker.opened)
|
||||
__picker.open();
|
||||
}
|
||||
|
||||
function closePicker() {
|
||||
__picker.close();
|
||||
}
|
||||
|
||||
component PageButton: DelIconButton {
|
||||
leftPadding: 8
|
||||
rightPadding: 8
|
||||
type: DelButton.Type_Link
|
||||
iconSize: 16
|
||||
colorIcon: hovered ? DelTheme.DelDatePicker.colorPageIconHover : DelTheme.DelDatePicker.colorPageIcon
|
||||
}
|
||||
|
||||
component PickerHeader: RowLayout {
|
||||
id: __pickerHeaderComp
|
||||
|
||||
property bool isPickYear: false
|
||||
property bool isPickMonth: false
|
||||
property bool isPickQuarter: control.pickerMode == DelDatePicker.Mode_Quarter
|
||||
|
||||
PageButton {
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
iconSource: DelIcon.DoubleLeftOutlined
|
||||
onClicked: {
|
||||
var prevYear = control.visualYear - (__pickerHeaderComp.isPickYear ? 10 : 1);
|
||||
if (prevYear > -9999) {
|
||||
control.visualYear = prevYear;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PageButton {
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
iconSource: DelIcon.LeftOutlined
|
||||
visible: !__pickerHeaderComp.isPickMonth && !__pickerHeaderComp.isPickMonth
|
||||
onClicked: {
|
||||
if (__pickerHeaderComp.isPickYear) {
|
||||
var prev1Year = control.visualYear - 1;
|
||||
if (prev1Year >= -9999) {
|
||||
control.visualYear = prev1Year;
|
||||
}
|
||||
} else {
|
||||
var prevMonth = control.visualMonth - 1;
|
||||
if (prevMonth < 0) {
|
||||
var prevYear = control.visualYear - 1;
|
||||
if (prevYear >= -9999) {
|
||||
control.visualYear = prevYear;
|
||||
control.visualMonth = 11;
|
||||
}
|
||||
} else {
|
||||
control.visualMonth = prevMonth;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: __centerRow.height
|
||||
|
||||
Row {
|
||||
id: __centerRow
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
|
||||
PageButton {
|
||||
text: control.visualYear + qsTr('年')
|
||||
colorText: hovered ? DelTheme.DelDatePicker.colorPageTextHover : DelTheme.DelDatePicker.colorPageText
|
||||
font.bold: true
|
||||
onClicked: {
|
||||
__pickerHeaderComp.isPickYear = true;
|
||||
__pickerHeaderComp.isPickMonth = false;
|
||||
__pickerHeaderComp.isPickQuarter = false;
|
||||
}
|
||||
}
|
||||
|
||||
PageButton {
|
||||
visible: control.pickerMode != DelDatePicker.Mode_Year &&
|
||||
control.pickerMode != DelDatePicker.Mode_Quarter &&
|
||||
!__pickerHeaderComp.isPickQuarter &&
|
||||
!__pickerHeaderComp.isPickYear
|
||||
text: (control.visualMonth + 1) + qsTr('月')
|
||||
colorText: hovered ? DelTheme.DelDatePicker.colorPageTextHover : DelTheme.DelDatePicker.colorPageText
|
||||
font.bold: true
|
||||
onClicked: {
|
||||
__pickerHeaderComp.isPickYear = false;
|
||||
__pickerHeaderComp.isPickMonth = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PageButton {
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
iconSource: DelIcon.RightOutlined
|
||||
visible: !__pickerHeaderComp.isPickMonth && !__pickerHeaderComp.isPickMonth
|
||||
onClicked: {
|
||||
if (__pickerHeaderComp.isPickYear) {
|
||||
var next1Year = control.visualYear + 1;
|
||||
if (next1Year < 9999) {
|
||||
control.visualYear = next1Year;
|
||||
}
|
||||
} else {
|
||||
var nextMonth = control.visualMonth + 1;
|
||||
if (nextMonth >= 11) {
|
||||
var nextYear = control.visualYear + 1;
|
||||
if (nextYear <= 9999) {
|
||||
control.visualYear = nextYear;
|
||||
control.visualMonth = 0;
|
||||
}
|
||||
} else {
|
||||
control.visualMonth = nextMonth;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PageButton {
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
iconSource: DelIcon.DoubleRightOutlined
|
||||
onClicked: {
|
||||
var nextYear = control.visualYear + (__pickerHeaderComp.isPickYear ? 10 : 1);
|
||||
if (nextYear < 9999) {
|
||||
control.visualYear = nextYear;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
component PickerButton: DelButton {
|
||||
padding: 20
|
||||
topPadding: 4
|
||||
bottomPadding: 4
|
||||
effectEnabled: false
|
||||
colorBorder: 'transparent'
|
||||
colorBg: checked ? DelTheme.DelDatePicker.colorDayBgCurrent :
|
||||
hovered ? DelTheme.DelDatePicker.colorDayBgHover :
|
||||
DelTheme.DelDatePicker.colorDayBg
|
||||
colorText: checked ? 'white' : DelTheme.DelDatePicker.colorDayText
|
||||
}
|
||||
|
||||
Item {
|
||||
id: __private
|
||||
property var window: Window.window
|
||||
property int hoveredWeekNumber: control.currentWeekNumber
|
||||
|
||||
function selectDate(date) {
|
||||
var month = date.getMonth();
|
||||
var weekNumber = DelApi.getWeekNumber(date);
|
||||
var quarter = Math.floor(month / 3) + 1;
|
||||
if (control.pickerMode === DelDatePicker.Mode_Week) {
|
||||
let inputDate = date;
|
||||
let weekYear = date.getFullYear();
|
||||
if (weekNumber === 1 && month === 11) {
|
||||
weekYear++;
|
||||
inputDate = new Date(weekYear + 1, 0, 0);
|
||||
}
|
||||
__input.text = Qt.formatDate(inputDate, control.dateFormat.replace('w', String(weekNumber)));
|
||||
} else if (control.pickerMode == DelDatePicker.Mode_Quarter) {
|
||||
__input.text = Qt.formatDate(date, control.dateFormat.replace('q', String(quarter)));
|
||||
} else {
|
||||
__input.text = Qt.formatDate(date, control.dateFormat);
|
||||
}
|
||||
|
||||
control.currentDate = date;
|
||||
control.currentYear = date.getFullYear();
|
||||
control.currentMonth = month;
|
||||
control.currentDay = date.getDate();
|
||||
control.currentWeekNumber = weekNumber;
|
||||
|
||||
control.closePicker();
|
||||
}
|
||||
}
|
||||
|
||||
DelInput {
|
||||
id: __input
|
||||
width: parent.width
|
||||
height: parent.height
|
||||
iconPosition: DelInput.Position_Right
|
||||
iconDelegate: DelIconText {
|
||||
anchors.left: control.iconPosition === DelDatePicker.Position_Left ? parent.left : undefined
|
||||
anchors.right: control.iconPosition === DelDatePicker.Position_Right ? parent.right : undefined
|
||||
anchors.margins: 5
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
iconSource: (__input.hovered && __input.length !== 0) ? DelIcon.CloseCircleFilled : DelIcon.CalendarOutlined
|
||||
iconSize: __input.iconSize
|
||||
colorIcon: control.enabled ?
|
||||
__iconMouse.hovered ? DelTheme.DelDatePicker.colorInputIconHover :
|
||||
DelTheme.DelDatePicker.colorInputIcon : DelTheme.DelDatePicker.colorInputIconDisabled
|
||||
|
||||
Behavior on colorIcon { enabled: control.animationEnabled; ColorAnimation { duration: DelTheme.Primary.durationFast } }
|
||||
|
||||
MouseArea {
|
||||
id: __iconMouse
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: parent.iconSource == DelIcon.CloseCircleFilled ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||
onEntered: hovered = true;
|
||||
onExited: hovered = false;
|
||||
onClicked: {
|
||||
if (initDate) {
|
||||
__private.selectDate(initDate);
|
||||
__input.clear();
|
||||
} else {
|
||||
__private.selectDate(new Date());
|
||||
__input.clear();
|
||||
}
|
||||
}
|
||||
property bool hovered: false
|
||||
}
|
||||
}
|
||||
|
||||
TapHandler {
|
||||
onTapped: {
|
||||
control.openPicker();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DelPopup {
|
||||
id: __picker
|
||||
|
||||
function adjustPosition() {
|
||||
var pos = control.mapToItem(null, 0, 0);
|
||||
var pickerX = (control.width - implicitWidth) * 0.5;
|
||||
if ((pos.x + pickerX) < 0)
|
||||
x = pickerX + Math.abs(pos.x + pickerX) + 6;
|
||||
else if (__private.window.width < (pos.x + pickerX + implicitWidth)) {
|
||||
x = __private.window.width - pos.x - implicitWidth - 6;
|
||||
} else {
|
||||
x = pickerX;
|
||||
}
|
||||
|
||||
if (onTop) {
|
||||
y = -implicitHeight - 6;
|
||||
} else {
|
||||
if (__private.window.height > (pos.y + control.height + implicitHeight + 6)){
|
||||
y = control.height + 6;
|
||||
} else if (pos.y > implicitHeight) {
|
||||
y = -implicitHeight - 6;
|
||||
onTop = true;
|
||||
} else {
|
||||
y = __private.window.height - (pos.y + implicitHeight + 6);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
property bool onTop: false
|
||||
|
||||
x: (control.width - implicitWidth) * 0.5
|
||||
y: control.height + 6
|
||||
width: implicitWidth
|
||||
height: implicitHeight
|
||||
implicitWidth: implicitContentWidth + leftPadding + rightPadding
|
||||
implicitHeight: implicitContentHeight + topPadding + bottomPadding
|
||||
padding: 10
|
||||
leftPadding: 12
|
||||
rightPadding: 12
|
||||
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutsideParent
|
||||
enter: Transition {
|
||||
NumberAnimation {
|
||||
property: 'opacity'
|
||||
from: 0.0
|
||||
to: 1.0
|
||||
easing.type: Easing.InOutQuad
|
||||
duration: control.animationEnabled ? DelTheme.Primary.durationMid : 0
|
||||
}
|
||||
}
|
||||
exit: Transition {
|
||||
NumberAnimation {
|
||||
property: 'opacity'
|
||||
from: 1.0
|
||||
to: 0.0
|
||||
easing.type: Easing.InOutQuad
|
||||
duration: control.animationEnabled ? DelTheme.Primary.durationMid : 0
|
||||
}
|
||||
}
|
||||
onAboutToShow: {
|
||||
control.visualYear = control.currentYear;
|
||||
control.visualMonth = control.currentMonth;
|
||||
control.visualQuarter = control.currentQuarter;
|
||||
|
||||
switch (control.pickerMode) {
|
||||
case DelDatePicker.Mode_Day:
|
||||
case DelDatePicker.Mode_Week:
|
||||
{
|
||||
__pickerHeader.isPickYear = false;
|
||||
__pickerHeader.isPickMonth = false;
|
||||
__pickerHeader.isPickQuarter = false;
|
||||
} break;
|
||||
case DelDatePicker.Mode_Month:
|
||||
{
|
||||
__pickerHeader.isPickYear = false;
|
||||
__pickerHeader.isPickMonth = true;
|
||||
__pickerHeader.isPickQuarter = false;
|
||||
} break;
|
||||
case DelDatePicker.Mode_Quarter:
|
||||
{
|
||||
__pickerHeader.isPickYear = false;
|
||||
__pickerHeader.isPickMonth = false;
|
||||
__pickerHeader.isPickQuarter = true;
|
||||
} break;
|
||||
case DelDatePicker.Mode_Year:
|
||||
{
|
||||
__pickerHeader.isPickYear = true;
|
||||
__pickerHeader.isPickMonth = false;
|
||||
__pickerHeader.isPickQuarter = false;
|
||||
} break;
|
||||
default:
|
||||
{
|
||||
__pickerHeader.isPickYear = false;
|
||||
__pickerHeader.isPickMonth = false;
|
||||
__pickerHeader.isPickQuarter = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
onOpened: adjustPosition();
|
||||
onHeightChanged: adjustPosition();
|
||||
contentItem: Item {
|
||||
implicitWidth: __pickerColumn.implicitWidth
|
||||
implicitHeight: __pickerColumn.implicitHeight
|
||||
|
||||
Column {
|
||||
id: __pickerColumn
|
||||
spacing: 5
|
||||
|
||||
PickerHeader {
|
||||
id: __pickerHeader
|
||||
width: parent.width
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: 1
|
||||
color: DelTheme.DelDatePicker.colorSplitLine
|
||||
}
|
||||
|
||||
T.DayOfWeekRow {
|
||||
id: __dayOfWeekRow
|
||||
visible: (control.pickerMode == DelDatePicker.Mode_Day || control.pickerMode == DelDatePicker.Mode_Week) &&
|
||||
!__pickerHeader.isPickYear && !__pickerHeader.isPickMonth
|
||||
locale: __monthGrid.locale
|
||||
spacing: 10
|
||||
delegate: Text {
|
||||
width: __dayOfWeekRow.itemWidth
|
||||
text: shortName
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
font {
|
||||
family: DelTheme.DelDatePicker.fontFamily
|
||||
pixelSize: DelTheme.DelDatePicker.fontSize
|
||||
}
|
||||
color: DelTheme.DelDatePicker.colorWeekText
|
||||
|
||||
required property string shortName
|
||||
}
|
||||
property real itemWidth: (__monthGrid.implicitWidth - 6 * spacing) / 7
|
||||
}
|
||||
|
||||
T.MonthGrid {
|
||||
id: __monthGrid
|
||||
visible: __dayOfWeekRow.visible
|
||||
padding: 0
|
||||
spacing: 0
|
||||
year: control.visualYear
|
||||
month: control.visualMonth
|
||||
locale: Qt.locale()
|
||||
delegate: Item {
|
||||
id: __dayItem
|
||||
width: __dayLoader.implicitWidth + 16
|
||||
height: __dayLoader.implicitHeight + 6
|
||||
|
||||
required property var model
|
||||
property int weekYear: (model.weekNumber === 1 && model.month === 11) ? (model.year + 1) : model.year
|
||||
property int currentYear: (control.currentWeekNumber === 1 && control.currentMonth === 11) ? (control.currentYear + 1) :
|
||||
control.currentYear
|
||||
property bool isCurrentWeek: control.currentWeekNumber === model.weekNumber && weekYear === __dayItem.currentYear
|
||||
property bool isHoveredWeek: __monthGrid.hovered && __private.hoveredWeekNumber === model.weekNumber
|
||||
property bool isCurrentMonth: control.currentYear === model.year && control.currentMonth === model.month
|
||||
property bool isCurrentVisualMonth: control.visualMonth === model.month
|
||||
property bool isCurrentDay: control.currentYear === model.year &&
|
||||
control.currentMonth === model.month &&
|
||||
control.currentDay === model.day
|
||||
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: __dayLoader.implicitHeight
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
clip: true
|
||||
color: {
|
||||
if (control.pickerMode === DelDatePicker.Mode_Week) {
|
||||
return __dayItem.isCurrentWeek ? DelTheme.DelDatePicker.colorDayItemBgCurrent :
|
||||
__dayItem.isHoveredWeek ? DelTheme.DelDatePicker.colorDayItemBgHover :
|
||||
DelTheme.DelDatePicker.colorDayItemBg;
|
||||
} else {
|
||||
return 'transparent';
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on color { enabled: control.animationEnabled; ColorAnimation { duration: DelTheme.Primary.durationMid } }
|
||||
|
||||
Loader {
|
||||
id: __dayLoader
|
||||
anchors.centerIn: parent
|
||||
sourceComponent: control.dayDelegate
|
||||
property alias model: __dayItem.model
|
||||
property alias isCurrentWeek: __dayItem.isCurrentWeek
|
||||
property alias isHoveredWeek: __dayItem.isHoveredWeek
|
||||
property alias isCurrentMonth: __dayItem.isCurrentMonth
|
||||
property alias isCurrentVisualMonth: __dayItem.isCurrentVisualMonth
|
||||
property alias isCurrentDay: __dayItem.isCurrentDay
|
||||
}
|
||||
|
||||
HoverHandler {
|
||||
id: __hoverHandler
|
||||
onHoveredChanged: {
|
||||
if (hovered) {
|
||||
__private.hoveredWeekNumber = __dayItem.model.weekNumber;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: __private.selectDate(model.date);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NumberAnimation on scale {
|
||||
running: control.animationEnabled && __monthGrid.visible
|
||||
from: 0
|
||||
to: 1
|
||||
easing.type: Easing.OutCubic
|
||||
duration: DelTheme.Primary.durationMid
|
||||
}
|
||||
}
|
||||
|
||||
Grid {
|
||||
id: __yearPicker
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
rows: 4
|
||||
columns: 3
|
||||
rowSpacing: 10
|
||||
columnSpacing: 10
|
||||
visible: __pickerHeader.isPickYear
|
||||
|
||||
NumberAnimation on scale {
|
||||
running: control.animationEnabled && __yearPicker.visible
|
||||
from: 0
|
||||
to: 1
|
||||
easing.type: Easing.OutCubic
|
||||
duration: DelTheme.Primary.durationMid
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: 12
|
||||
delegate: Item {
|
||||
width: 80
|
||||
height: 40
|
||||
|
||||
PickerButton {
|
||||
id: __yearPickerButton
|
||||
anchors.centerIn: parent
|
||||
text: year
|
||||
checked: year == control.visualYear
|
||||
onClicked: {
|
||||
control.visualYear = year;
|
||||
if (control.pickerMode == DelDatePicker.Mode_Day ||
|
||||
control.pickerMode == DelDatePicker.Mode_Week ||
|
||||
control.pickerMode == DelDatePicker.Mode_Month) {
|
||||
__pickerHeader.isPickYear = false;
|
||||
__pickerHeader.isPickMonth = true;
|
||||
} else if (control.pickerMode == DelDatePicker.Mode_Quarter) {
|
||||
__pickerHeader.isPickYear = false;
|
||||
__pickerHeader.isPickQuarter = true;
|
||||
} else if (control.pickerMode == DelDatePicker.Mode_Year) {
|
||||
__private.selectDate(new Date(control.visualYear + 1, 0, 0));
|
||||
}
|
||||
}
|
||||
property int year: control.visualYear + modelData - 4
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Grid {
|
||||
id: __monthPicker
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
rows: 4
|
||||
columns: 3
|
||||
rowSpacing: 10
|
||||
columnSpacing: 10
|
||||
visible: __pickerHeader.isPickMonth
|
||||
|
||||
NumberAnimation on scale {
|
||||
running: control.animationEnabled && __monthPicker.visible
|
||||
from: 0
|
||||
to: 1
|
||||
easing.type: Easing.OutCubic
|
||||
duration: DelTheme.Primary.durationMid
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: 12
|
||||
delegate: Item {
|
||||
width: 80
|
||||
height: 40
|
||||
|
||||
PickerButton {
|
||||
id: __monthPickerButton
|
||||
anchors.centerIn: parent
|
||||
text: (month + 1) + qsTr('月')
|
||||
checked: month == control.visualMonth
|
||||
onClicked: {
|
||||
control.visualMonth = month;
|
||||
if (control.pickerMode == DelDatePicker.Mode_Day ||
|
||||
control.pickerMode == DelDatePicker.Mode_Week) {
|
||||
__pickerHeader.isPickMonth = false;
|
||||
} else if (control.pickerMode == DelDatePicker.Mode_Month) {
|
||||
__private.selectDate(new Date(control.visualYear, control.visualMonth + 1, 0));
|
||||
}
|
||||
}
|
||||
property int month: modelData
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
id: __quarterPicker
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
visible: __pickerHeader.isPickQuarter
|
||||
spacing: 10
|
||||
|
||||
NumberAnimation on scale {
|
||||
running: control.animationEnabled && __quarterPicker.visible
|
||||
from: 0
|
||||
to: 1
|
||||
easing.type: Easing.OutCubic
|
||||
duration: DelTheme.Primary.durationMid
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: 4
|
||||
delegate: Item {
|
||||
width: 60
|
||||
height: 40
|
||||
|
||||
PickerButton {
|
||||
anchors.centerIn: parent
|
||||
text: `Q${quarter}`
|
||||
checked: quarter == control.visualQuarter
|
||||
onClicked: {
|
||||
control.visualQuarter = quarter;
|
||||
__pickerHeader.isPickYear = false;
|
||||
|
||||
if (control.pickerMode == DelDatePicker.Mode_Quarter) {
|
||||
__private.selectDate(new Date(control.visualYear, (quarter - 1) * 3 + 1, 0));
|
||||
}
|
||||
}
|
||||
property int quarter: modelData + 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
width: parent.width
|
||||
active: control.pickerMode == DelDatePicker.Mode_Day
|
||||
sourceComponent: Rectangle {
|
||||
height: 1
|
||||
color: DelTheme.DelDatePicker.colorSplitLine
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
active: control.pickerMode == DelDatePicker.Mode_Day
|
||||
sourceComponent: DelButton {
|
||||
type: DelButton.Type_Link
|
||||
text: qsTr('今天')
|
||||
onClicked: __private.selectDate(new Date());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,120 @@
|
|||
import QtQuick 2.15
|
||||
import QtQuick.Shapes 1.15
|
||||
import DelegateUI 1.0
|
||||
|
||||
Item {
|
||||
id: control
|
||||
|
||||
enum Align
|
||||
{
|
||||
Align_Left = 0,
|
||||
Align_Center = 1,
|
||||
Align_Right = 2
|
||||
}
|
||||
|
||||
enum Style
|
||||
{
|
||||
SolidLine = 0,
|
||||
DashLine = 1
|
||||
}
|
||||
|
||||
titleFont {
|
||||
family: DelTheme.DelDivider.fontFamily
|
||||
pixelSize: DelTheme.DelDivider.fontSize
|
||||
}
|
||||
|
||||
property bool animationEnabled: DelTheme.animationEnabled
|
||||
property string title: ""
|
||||
property font titleFont
|
||||
property int titleAlign: DelDivider.Align_Left
|
||||
property int titlePadding: 20
|
||||
property color colorText: DelTheme.DelDivider.colorText
|
||||
property color colorSplit: DelTheme.DelDivider.colorSplit
|
||||
property int style: DelDivider.SolidLine
|
||||
property int orientation: Qt.Horizontal
|
||||
property Component titleDelegate: Text {
|
||||
text: control.title
|
||||
font: control.titleFont
|
||||
color: control.colorText
|
||||
}
|
||||
property Component splitDelegate: Shape {
|
||||
id: __shape
|
||||
|
||||
property real lineX: __titleLoader.x + __titleLoader.implicitWidth * 0.5
|
||||
property real lineY: __titleLoader.y + __titleLoader.implicitHeight * 0.5
|
||||
|
||||
ShapePath {
|
||||
strokeStyle: control.style == DelDivider.SolidLine ? ShapePath.SolidLine : ShapePath.DashLine
|
||||
strokeColor: control.colorSplit
|
||||
strokeWidth: 1
|
||||
fillColor: "transparent"
|
||||
startX: control.orientation == Qt.Horizontal ? 0 : __shape.lineX
|
||||
startY: control.orientation == Qt.Horizontal ? __shape.lineY : 0
|
||||
PathLine {
|
||||
x: {
|
||||
if (control.orientation == Qt.Horizontal) {
|
||||
return control.title == "" ? 0 : __titleLoader.x - 10;
|
||||
} else {
|
||||
return __shape.lineX;
|
||||
}
|
||||
}
|
||||
y: control.orientation == Qt.Horizontal ? __shape.lineY : __titleLoader.y - 10
|
||||
}
|
||||
}
|
||||
|
||||
ShapePath {
|
||||
strokeStyle: control.style == DelDivider.SolidLine ? ShapePath.SolidLine : ShapePath.DashLine
|
||||
strokeColor: control.colorSplit
|
||||
strokeWidth: 1
|
||||
fillColor: "transparent"
|
||||
startX: {
|
||||
if (control.orientation == Qt.Horizontal) {
|
||||
return control.title == "" ? 0 : (__titleLoader.x + __titleLoader.implicitWidth + 10);
|
||||
} else {
|
||||
return __shape.lineX;
|
||||
}
|
||||
}
|
||||
startY: {
|
||||
if (control.orientation == Qt.Horizontal) {
|
||||
return __shape.lineY;
|
||||
} else {
|
||||
return control.title == "" ? 0 : (__titleLoader.y + __titleLoader.implicitHeight + 10);
|
||||
}
|
||||
}
|
||||
|
||||
PathLine {
|
||||
x: control.orientation == Qt.Horizontal ? control.width : __shape.lineX
|
||||
y: control.orientation == Qt.Horizontal ? __shape.lineY : control.height
|
||||
}
|
||||
}
|
||||
}
|
||||
property string contentDescription: title
|
||||
|
||||
Behavior on colorSplit { enabled: control.animationEnabled; ColorAnimation { duration: DelTheme.Primary.durationFast } }
|
||||
Behavior on colorText { enabled: control.animationEnabled; ColorAnimation { duration: DelTheme.Primary.durationFast } }
|
||||
|
||||
Loader {
|
||||
id: __splitLoader
|
||||
sourceComponent: splitDelegate
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: __titleLoader
|
||||
z: 1
|
||||
anchors.top: (control.orientation != Qt.Horizontal && control.titleAlign == DelDivider.Align_Left) ? parent.top : undefined
|
||||
anchors.topMargin: (control.orientation != Qt.Horizontal && control.titleAlign == DelDivider.Align_Left) ? control.titlePadding : 0
|
||||
anchors.bottom: (control.orientation != Qt.Horizontal && control.titleAlign == DelDivider.Align_Right) ? parent.right : undefined
|
||||
anchors.bottomMargin: (control.orientation != Qt.Horizontal && control.titleAlign == DelDivider.Align_Right) ? control.titlePadding : 0
|
||||
anchors.left: (control.orientation == Qt.Horizontal && control.titleAlign == DelDivider.Align_Left) ? parent.left : undefined
|
||||
anchors.leftMargin: (control.orientation == Qt.Horizontal && control.titleAlign == DelDivider.Align_Left) ? control.titlePadding : 0
|
||||
anchors.right: (control.orientation == Qt.Horizontal && control.titleAlign == DelDivider.Align_Right) ? parent.right : undefined
|
||||
anchors.rightMargin: (control.orientation == Qt.Horizontal && control.titleAlign == DelDivider.Align_Right) ? control.titlePadding : 0
|
||||
anchors.horizontalCenter: (control.orientation != Qt.Horizontal || control.titleAlign == DelDivider.Align_Center) ? parent.horizontalCenter : undefined
|
||||
anchors.verticalCenter: (control.orientation == Qt.Horizontal || control.titleAlign == DelDivider.Align_Center) ? parent.verticalCenter : undefined
|
||||
sourceComponent: titleDelegate
|
||||
}
|
||||
|
||||
Accessible.role: Accessible.Separator
|
||||
Accessible.name: control.title
|
||||
Accessible.description: control.contentDescription
|
||||
}
|
||||
|
|
@ -0,0 +1,105 @@
|
|||
import QtQuick 2.15
|
||||
import QtQuick.Layouts 1.15
|
||||
import QtGraphicalEffects 1.15
|
||||
import QtQuick.Templates 2.15 as T
|
||||
import DelegateUI 1.0
|
||||
|
||||
T.Drawer {
|
||||
id: control
|
||||
|
||||
titleFont {
|
||||
family: DelTheme.DelDrawer.fontFamily
|
||||
pixelSize: DelTheme.DelDrawer.fontSizeTitle
|
||||
}
|
||||
|
||||
property bool animationEnabled: DelTheme.animationEnabled
|
||||
property int drawerSize: 378
|
||||
property string title: ""
|
||||
property font titleFont
|
||||
property color colorTitle: DelTheme.DelDrawer.colorTitle
|
||||
property color colorBg: DelTheme.DelDrawer.colorBg
|
||||
property color colorOverlay: DelTheme.DelDrawer.colorOverlay
|
||||
property Component titleDelegate: Item {
|
||||
height: 56
|
||||
|
||||
Row {
|
||||
height: parent.height
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 15
|
||||
spacing: 5
|
||||
|
||||
DelCaptionButton {
|
||||
id: __close
|
||||
topPadding: 2
|
||||
bottomPadding: 2
|
||||
leftPadding: 4
|
||||
rightPadding: 4
|
||||
radiusBg: 4
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
iconSource: DelIcon.CloseOutlined
|
||||
hoverCursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
control.close();
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: control.title
|
||||
font: control.titleFont
|
||||
color: control.colorTitle
|
||||
}
|
||||
}
|
||||
|
||||
DelDivider {
|
||||
width: parent.width
|
||||
height: 1
|
||||
anchors.bottom: parent.bottom
|
||||
}
|
||||
}
|
||||
property Component contentDelegate: Item { }
|
||||
|
||||
enter: Transition { NumberAnimation { duration: control.animationEnabled ? DelTheme.Primary.durationMid : 0 } }
|
||||
exit: Transition { NumberAnimation { duration: control.animationEnabled ? DelTheme.Primary.durationMid : 0 } }
|
||||
|
||||
width: edge == Qt.LeftEdge || edge == Qt.RightEdge ? drawerSize : parent.width
|
||||
height: edge == Qt.LeftEdge || edge == Qt.RightEdge ? parent.height : drawerSize
|
||||
edge: Qt.RightEdge
|
||||
parent: T.Overlay.overlay
|
||||
modal: true
|
||||
background: Item {
|
||||
DropShadow {
|
||||
anchors.fill: __rect
|
||||
radius: 16
|
||||
samples: 17
|
||||
color: DelThemeFunctions.alpha(DelTheme.DelDrawer.colorShadow, DelTheme.isDark ? 0.1 : 0.2)
|
||||
source: __rect
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: __rect
|
||||
anchors.fill: parent
|
||||
color: control.colorBg
|
||||
}
|
||||
}
|
||||
contentItem: ColumnLayout {
|
||||
Loader {
|
||||
Layout.fillWidth: true
|
||||
sourceComponent: titleDelegate
|
||||
onLoaded: {
|
||||
/*! 无边框窗口的标题栏会阻止事件传递, 需要调这个 */
|
||||
if (captionBar)
|
||||
captionBar.addInteractionItem(item);
|
||||
}
|
||||
}
|
||||
Loader {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
sourceComponent: contentDelegate
|
||||
}
|
||||
}
|
||||
|
||||
T.Overlay.modal: Rectangle {
|
||||
color: control.colorOverlay
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
import QtQuick 2.15
|
||||
import DelegateUI 1.0
|
||||
|
||||
DelButton {
|
||||
id: control
|
||||
|
||||
enum IconPosition {
|
||||
Position_Start = 0,
|
||||
Position_End = 1
|
||||
}
|
||||
|
||||
property bool loading: false
|
||||
property int iconSource: 0
|
||||
property int iconSize: DelTheme.DelButton.fontSize
|
||||
property int iconSpacing: 5
|
||||
property int iconPosition: DelIconButton.Position_Start
|
||||
property color colorIcon: colorText
|
||||
|
||||
contentItem: Item {
|
||||
implicitWidth: __row.implicitWidth
|
||||
implicitHeight: Math.max(__icon.implicitHeight, __text.implicitHeight)
|
||||
|
||||
Row {
|
||||
id: __row
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: control.iconSpacing
|
||||
layoutDirection: control.iconPosition === DelIconButton.Position_Start ? Qt.LeftToRight : Qt.RightToLeft
|
||||
|
||||
DelIconText {
|
||||
id: __icon
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
color: control.colorIcon
|
||||
iconSize: control.iconSize
|
||||
iconSource: control.loading ? DelIcon.LoadingOutlined : control.iconSource
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
|
||||
Behavior on color { enabled: control.animationEnabled; ColorAnimation { duration: DelTheme.Primary.durationFast } }
|
||||
|
||||
NumberAnimation on rotation {
|
||||
running: control.loading
|
||||
from: 0
|
||||
to: 360
|
||||
loops: Animation.Infinite
|
||||
duration: 1000
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
id: __text
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: control.text
|
||||
font: control.font
|
||||
lineHeight: DelTheme.DelButton.fontLineHeight
|
||||
color: control.colorText
|
||||
elide: Text.ElideRight
|
||||
|
||||
Behavior on color { enabled: control.animationEnabled; ColorAnimation { duration: DelTheme.Primary.durationFast } }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
import QtQuick 2.15
|
||||
import DelegateUI 1.0
|
||||
|
||||
DelText {
|
||||
id: control
|
||||
|
||||
property int iconSource: 0
|
||||
property alias iconSize: control.font.pixelSize
|
||||
property alias colorIcon: control.color
|
||||
property string contentDescription: text
|
||||
|
||||
text: String.fromCharCode(iconSource)
|
||||
font.family: 'DelegateUI-Icons'
|
||||
font.pixelSize: DelTheme.DelIconText.fontSize
|
||||
color: DelTheme.DelIconText.colorText
|
||||
|
||||
Accessible.role: Accessible.StaticText
|
||||
Accessible.name: control.text
|
||||
Accessible.description: control.contentDescription
|
||||
}
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15 as T
|
||||
import DelegateUI 1.0
|
||||
|
||||
T.TextField {
|
||||
id: control
|
||||
|
||||
enum IconPosition {
|
||||
Position_Left = 0,
|
||||
Position_Right = 1
|
||||
}
|
||||
|
||||
property bool animationEnabled: DelTheme.animationEnabled
|
||||
readonly property bool active: hovered || activeFocus
|
||||
property int iconSource: 0
|
||||
property int iconSize: DelTheme.DelInput.fontIconSize
|
||||
property int iconPosition: DelInput.Position_Left
|
||||
property color colorIcon: colorText
|
||||
property color colorText: enabled ? DelTheme.DelInput.colorText : DelTheme.DelInput.colorTextDisabled
|
||||
property color colorBorder: enabled ?
|
||||
active ? DelTheme.DelInput.colorBorderHover :
|
||||
DelTheme.DelInput.colorBorder : DelTheme.DelInput.colorBorderDisabled
|
||||
property color colorBg: enabled ? DelTheme.DelInput.colorBg : DelTheme.DelInput.colorBgDisabled
|
||||
property int radiusBg: 6
|
||||
property string contentDescription: ""
|
||||
|
||||
property Component iconDelegate: DelIconText {
|
||||
iconSource: control.iconSource
|
||||
iconSize: control.iconSize
|
||||
colorIcon: control.colorIcon
|
||||
}
|
||||
|
||||
Behavior on colorText { enabled: control.animationEnabled; ColorAnimation { duration: DelTheme.Primary.durationFast } }
|
||||
Behavior on colorBorder { enabled: control.animationEnabled; ColorAnimation { duration: DelTheme.Primary.durationFast } }
|
||||
Behavior on colorBg { enabled: control.animationEnabled; ColorAnimation { duration: DelTheme.Primary.durationFast } }
|
||||
|
||||
objectName: "__DelInput__"
|
||||
focus: true
|
||||
padding: 5
|
||||
leftPadding: 10 + ((iconSource != 0 && iconPosition == DelInput.Position_Left) ? iconSize : 0)
|
||||
rightPadding: 10 + ((iconSource != 0 && iconPosition == DelInput.Position_Right) ? iconSize : 0)
|
||||
implicitWidth: contentWidth + leftPadding + rightPadding
|
||||
implicitHeight: contentHeight + topPadding + bottomPadding
|
||||
color: colorText
|
||||
placeholderTextColor: enabled ? DelTheme.DelInput.colorPlaceholderText : DelTheme.DelInput.colorPlaceholderTextDisabled
|
||||
selectedTextColor: DelTheme.DelInput.colorSelectedText
|
||||
selectionColor: DelTheme.DelInput.colorSelection
|
||||
font {
|
||||
family: DelTheme.DelInput.fontFamily
|
||||
pixelSize: DelTheme.DelInput.fontSize
|
||||
}
|
||||
background: Rectangle {
|
||||
color: control.colorBg
|
||||
border.color: control.colorBorder
|
||||
radius: control.radiusBg
|
||||
}
|
||||
|
||||
Loader {
|
||||
anchors.left: iconPosition == DelInput.Position_Left ? parent.left : undefined
|
||||
anchors.right: iconPosition == DelInput.Position_Right ? parent.right : undefined
|
||||
anchors.margins: 5
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
active: control.iconSize != 0
|
||||
sourceComponent: iconDelegate
|
||||
}
|
||||
|
||||
Accessible.role: Accessible.EditableText
|
||||
Accessible.editable: true
|
||||
Accessible.description: control.contentDescription
|
||||
}
|
||||
|
|
@ -0,0 +1,582 @@
|
|||
import QtQuick 2.15
|
||||
import QtQuick.Templates 2.15 as T
|
||||
import QtQuick.Window 2.15
|
||||
import DelegateUI 1.0
|
||||
|
||||
Item {
|
||||
id: control
|
||||
|
||||
width: compactMode ? compactWidth : defaultMenuWidth
|
||||
clip: true
|
||||
|
||||
signal clickMenu(deep: int, menuKey: string, menuData: var)
|
||||
|
||||
property bool animationEnabled: DelTheme.animationEnabled
|
||||
property string contentDescription: ""
|
||||
property bool showEdge: false
|
||||
property bool tooltipVisible: false
|
||||
property bool compactMode: false
|
||||
property int compactWidth: 50
|
||||
property bool popupMode: false
|
||||
property int popupWidth: 200
|
||||
property int popupMaxHeight: control.height
|
||||
property int defaultMenuIconSize: DelTheme.DelMenu.fontSize
|
||||
property int defaultMenuIconSpacing: 8
|
||||
property int defaultMenuWidth: 300
|
||||
property int defaultMenuHeight: 40
|
||||
property int defaultMenuSpacing: 4
|
||||
property var defaultSelectedKey: []
|
||||
property var initModel: []
|
||||
|
||||
Component {
|
||||
id: myContentDelegate
|
||||
|
||||
Item {
|
||||
Text {
|
||||
id: __text
|
||||
anchors.left: parent.left
|
||||
anchors.right: __tag.left
|
||||
anchors.rightMargin: 5
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: menuButton.text
|
||||
font: menuButton.font
|
||||
color: menuButton.colorText
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
DelTag {
|
||||
id: __tag
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: 'Success'
|
||||
tagState: DelTag.State_Success
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Component {
|
||||
id: __menuContentDelegate
|
||||
|
||||
Item {
|
||||
DelIconText {
|
||||
id: __icon
|
||||
x: menuButton.iconStart
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
color: menuButton.colorText
|
||||
iconSize: menuButton.iconSize
|
||||
iconSource: menuButton.iconSource
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
|
||||
Behavior on x {
|
||||
enabled: control.animationEnabled
|
||||
NumberAnimation { easing.type: Easing.OutCubic; duration: DelTheme.Primary.durationMid }
|
||||
}
|
||||
Behavior on color { enabled: control.animationEnabled; ColorAnimation { duration: DelTheme.Primary.durationFast } }
|
||||
}
|
||||
|
||||
Text {
|
||||
id: __text
|
||||
anchors.left: __icon.right
|
||||
anchors.leftMargin: menuButton.iconSpacing
|
||||
anchors.right: menuButton.expandedVisible ? __expandedIcon.left : parent.right
|
||||
anchors.rightMargin: menuButton.iconSpacing
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: menuButton.text
|
||||
font: menuButton.font
|
||||
color: menuButton.colorText
|
||||
elide: Text.ElideRight
|
||||
|
||||
Behavior on color { enabled: control.animationEnabled; ColorAnimation { duration: DelTheme.Primary.durationFast } }
|
||||
}
|
||||
|
||||
DelIconText {
|
||||
id: __expandedIcon
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
visible: menuButton.expandedVisible
|
||||
iconSource: (control.compactMode || control.popupMode) ? DelIcon.RightOutlined : DelIcon.DownOutlined
|
||||
colorIcon: menuButton.colorText
|
||||
transform: Rotation {
|
||||
origin {
|
||||
x: __expandedIcon.width * 0.5
|
||||
y: __expandedIcon.height * 0.5
|
||||
}
|
||||
axis {
|
||||
x: 1
|
||||
y: 0
|
||||
z: 0
|
||||
}
|
||||
angle: (control.compactMode || control.popupMode) ? 0 : (menuButton.expanded ? 180 : 0)
|
||||
Behavior on angle { enabled: control.animationEnabled; NumberAnimation { duration: DelTheme.Primary.durationMid } }
|
||||
}
|
||||
Behavior on color { enabled: control.animationEnabled; ColorAnimation { duration: DelTheme.Primary.durationFast } }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: __menuDelegate
|
||||
|
||||
Item {
|
||||
id: __rootItem
|
||||
width: ListView.view.width
|
||||
height: {
|
||||
switch (menuType) {
|
||||
case "item":
|
||||
case "group":
|
||||
return __layout.height;
|
||||
case "divider":
|
||||
return __dividerLoader.height;
|
||||
default:
|
||||
return __layout.height;
|
||||
}
|
||||
}
|
||||
clip: true
|
||||
Component.onCompleted: {
|
||||
if (menuType == "item" || menuType == "group") {
|
||||
layerPopup = __private.createPopupList(view.menuDeep);
|
||||
let list = []
|
||||
for (let i = 0; i < menuChildren.length; i++) {
|
||||
list.push(menuChildren[i]);
|
||||
}
|
||||
__childrenListView.model = list;
|
||||
if (control.defaultSelectedKey.length != 0) {
|
||||
if (control.defaultSelectedKey.indexOf(menuKey) != -1) {
|
||||
__rootItem.expandParent();
|
||||
__menuButton.clicked();
|
||||
}
|
||||
}
|
||||
}
|
||||
if (__rootItem.menuKey !== "" && __rootItem.menuKey === __private.gotoMenuKey) {
|
||||
__rootItem.expandParent();
|
||||
__menuButton.clicked();
|
||||
}
|
||||
}
|
||||
|
||||
required property var modelData
|
||||
property alias model: __rootItem.modelData
|
||||
property var view: ListView.view
|
||||
property string menuKey: model.key || ""
|
||||
property string menuType: model.type || "item"
|
||||
property bool menuEnabled: model.enabled === undefined ? true : model.enabled
|
||||
property string menuLabel: model.label || ""
|
||||
property int menuHeight: model.height || defaultMenuHeight
|
||||
property int menuIconSize: model.iconSize || defaultMenuIconSize
|
||||
property int menuIconSource: model.iconSource || 0
|
||||
property int menuIconSpacing: model.iconSpacing || defaultMenuIconSpacing
|
||||
property var menuChildren: model.menuChildren || []
|
||||
property int menuChildrenLength: menuChildren ? menuChildren.length : 0
|
||||
property var menuContentDelegate: model.contentDelegate ?? __menuContentDelegate
|
||||
|
||||
property var parentMenu: view.menuDeep === 0 ? null : view.parentMenu
|
||||
property bool isCurrent: __private.selectedItem === __rootItem || isCurrentParent
|
||||
property bool isCurrentParent: false
|
||||
property var layerPopup: null
|
||||
|
||||
function expandMenu() {
|
||||
if (__menuButton.expandedVisible)
|
||||
__menuButton.expanded = true;
|
||||
}
|
||||
|
||||
/*! 查找当前菜单的根菜单 */
|
||||
function findRootMenu() {
|
||||
let parent = parentMenu;
|
||||
while (parent !== null) {
|
||||
if (parent.parentMenu === null)
|
||||
return parent;
|
||||
parent = parent.parentMenu;
|
||||
}
|
||||
/*! 根菜单返回自身 */
|
||||
return __rootItem;
|
||||
}
|
||||
/*! 展开当前菜单的所有父菜单 */
|
||||
function expandParent() {
|
||||
let parent = parentMenu;
|
||||
while (parent !== null) {
|
||||
if (parent.parentMenu === null) {
|
||||
parent.expandMenu();
|
||||
return;
|
||||
}
|
||||
parent.expandMenu();
|
||||
parent = parent.parentMenu;
|
||||
}
|
||||
}
|
||||
/*! 清除当前菜单的所有子菜单 */
|
||||
function clearIsCurrentParent() {
|
||||
isCurrentParent = false;
|
||||
for (let i = 0; i < __childrenListView.count; i++) {
|
||||
let item = __childrenListView.itemAtIndex(i);
|
||||
if (item)
|
||||
item.clearIsCurrentParent();
|
||||
}
|
||||
}
|
||||
/*! 选中当前菜单的所有父菜单 */
|
||||
function selectedCurrentParentMenu() {
|
||||
for (let i = 0; i < __listView.count; i++) {
|
||||
let item = __listView.itemAtIndex(i);
|
||||
if (item)
|
||||
item.clearIsCurrentParent();
|
||||
}
|
||||
let parent = parentMenu;
|
||||
while (parent !== null) {
|
||||
parent.isCurrentParent = true;
|
||||
if (parent.parentMenu === null)
|
||||
return;
|
||||
parent = parent.parentMenu;
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: __private
|
||||
function onGotoMenu(key) {
|
||||
if (__rootItem.menuKey !== "" && __rootItem.menuKey === key) {
|
||||
__rootItem.expandParent();
|
||||
__menuButton.clicked();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: __dividerLoader
|
||||
height: 5
|
||||
width: parent.width
|
||||
active: __rootItem.menuType == "divider"
|
||||
sourceComponent: DelDivider { }
|
||||
}
|
||||
|
||||
Item {
|
||||
id: __layout
|
||||
width: parent.width
|
||||
height: __menuButton.height + ((control.compactMode || control.popupMode) ? 0 : __childrenListView.height)
|
||||
visible: menuType == "item" || menuType == "group"
|
||||
|
||||
MenuButton {
|
||||
id: __menuButton
|
||||
width: parent.width
|
||||
height: __rootItem.menuHeight
|
||||
enabled: __rootItem.menuEnabled
|
||||
text: (control.compactMode && __rootItem.view.menuDeep === 0) ? "" : __rootItem.menuLabel
|
||||
checkable: true
|
||||
iconSize: __rootItem.menuIconSize
|
||||
iconSource: __rootItem.menuIconSource
|
||||
iconSpacing: __rootItem.menuIconSpacing
|
||||
iconStart: (control.compactMode && __rootItem.view.menuDeep === 0) ? (width - iconSize - leftPadding - rightPadding) * 0.5 : 0
|
||||
expandedVisible: {
|
||||
if (__rootItem.menuType == "group" ||
|
||||
(control.compactMode && __rootItem.view.menuDeep === 0))
|
||||
return false;
|
||||
else
|
||||
return __rootItem.menuChildrenLength > 0
|
||||
}
|
||||
isCurrent: __rootItem.isCurrent
|
||||
isGroup: __rootItem.menuType == "group"
|
||||
model: __rootItem.model
|
||||
contentDelegate: __rootItem.menuContentDelegate
|
||||
onClicked: {
|
||||
if (__rootItem.menuChildrenLength == 0) {
|
||||
control.clickMenu(__rootItem.view.menuDeep, __rootItem.menuKey, model);
|
||||
__private.selectedItem = __rootItem;
|
||||
__rootItem.selectedCurrentParentMenu();
|
||||
if (control.compactMode || control.popupMode)
|
||||
__rootItem.layerPopup.closeWithParent();
|
||||
} else {
|
||||
if (control.compactMode || control.popupMode) {
|
||||
const h = __rootItem.layerPopup.topPadding +
|
||||
__rootItem.layerPopup.bottomPadding +
|
||||
__childrenListView.realHeight + 6;
|
||||
const pos = mapToItem(null, 0, 0);
|
||||
const pos2 = mapToItem(control, 0, 0);
|
||||
if ((pos.y + h) > __private.window.height) {
|
||||
__rootItem.layerPopup.y = Math.max(0, pos2.y - ((pos.y + h) - __private.window.height));
|
||||
} else {
|
||||
__rootItem.layerPopup.y = pos2.y;
|
||||
}
|
||||
__rootItem.layerPopup.current = __childrenListView;
|
||||
__rootItem.layerPopup.open();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DelToolTip {
|
||||
position: control.compactMode || control.popupMode ? DelToolTip.Position_Right : DelToolTip.Position_Bottom
|
||||
text: __rootItem.menuLabel
|
||||
visible: {
|
||||
if (control.compactMode || control.popupMode)
|
||||
return (__rootItem.layerPopup && !__rootItem.layerPopup.opened) ? parent.hovered : false;
|
||||
else {
|
||||
return control.tooltipVisible ? parent.hovered : false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ListView {
|
||||
id: __childrenListView
|
||||
visible: __rootItem.menuEnabled
|
||||
parent: {
|
||||
if (__rootItem.layerPopup && __rootItem.layerPopup.current === __childrenListView)
|
||||
return __rootItem.layerPopup.contentItem;
|
||||
else
|
||||
return __layout;
|
||||
}
|
||||
height: {
|
||||
if (__rootItem.menuType == "group" || __menuButton.expanded)
|
||||
return realHeight;
|
||||
else if (parent != __layout)
|
||||
return parent.height;
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
anchors.top: parent ? (parent == __layout ? __menuButton.bottom : parent.top) : undefined
|
||||
anchors.topMargin: parent == __layout ? control.defaultMenuSpacing : 0
|
||||
anchors.left: parent ? parent.left : undefined
|
||||
anchors.leftMargin: (control.compactMode || control.popupMode) ? 0 : __menuButton.iconSize * menuDeep
|
||||
anchors.right: parent ? parent.right : undefined
|
||||
spacing: control.defaultMenuSpacing
|
||||
boundsBehavior: Flickable.StopAtBounds
|
||||
interactive: __childrenListView.visible
|
||||
model: []
|
||||
delegate: __menuDelegate
|
||||
onContentHeightChanged: cacheBuffer = contentHeight;
|
||||
T.ScrollBar.vertical: DelScrollBar {
|
||||
id: childrenScrollBar
|
||||
visible: (control.compactMode || control.popupMode) && childrenScrollBar.size !== 1
|
||||
}
|
||||
clip: true
|
||||
/* 子 ListView 从父 ListView 的深度累加可实现自动计算 */
|
||||
property int menuDeep: __rootItem.view.menuDeep + 1
|
||||
property var parentMenu: __rootItem
|
||||
property int realHeight: (contentHeight + ((count === 0 || control.compactMode || control.popupMode) ? 0 : control.defaultMenuSpacing))
|
||||
Behavior on height {
|
||||
enabled: control.animationEnabled
|
||||
NumberAnimation { duration: DelTheme.Primary.durationFast }
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: control
|
||||
function onCompactModeChanged() {
|
||||
if (__rootItem.layerPopup) {
|
||||
__rootItem.layerPopup.current = null;
|
||||
__rootItem.layerPopup.close();
|
||||
}
|
||||
}
|
||||
function onPopupModeChanged() {
|
||||
if (__rootItem.layerPopup) {
|
||||
__rootItem.layerPopup.current = null;
|
||||
__rootItem.layerPopup.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onInitModelChanged: {
|
||||
clear();
|
||||
let list = [];
|
||||
for (let object of initModel) {
|
||||
append(object);
|
||||
}
|
||||
}
|
||||
|
||||
function gotoMenu(key) {
|
||||
__private.gotoMenuKey = key;
|
||||
__private.gotoMenu(key);
|
||||
}
|
||||
|
||||
function get(index) {
|
||||
if (index >= 0 && index < __listView.model.length) {
|
||||
return __listView.model[index];
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function set(index, object) {
|
||||
if (index >= 0 && index < __listView.model.length) {
|
||||
__private.model[index] = object;
|
||||
__listView.model = __private.model;
|
||||
}
|
||||
}
|
||||
|
||||
function setProperty(index, propertyName, value) {
|
||||
if (index >= 0 && index < __listView.model.length) {
|
||||
__private.model[index][propertyName] = value;
|
||||
__listView.model = __private.model;
|
||||
}
|
||||
}
|
||||
|
||||
function move(from, to, count = 1) {
|
||||
if (from >= 0 && from < __listView.model.length && to >= 0 && to < __listView.model.length) {
|
||||
const objects = __listView.model.splice(from, count);
|
||||
__private.model.splice(to, 0, ...objects);
|
||||
__listView.model = __private.model;
|
||||
}
|
||||
}
|
||||
|
||||
function insert(index, object) {
|
||||
__private.model.splice(index, 0, object);
|
||||
__listView.model = __private.model;
|
||||
}
|
||||
|
||||
function append(object) {
|
||||
__private.model.push(object);
|
||||
__listView.model = __private.model;
|
||||
}
|
||||
|
||||
function remove(index, count = 1) {
|
||||
if (index >= 0 && index < __listView.model.length) {
|
||||
__private.model.splice(index, count);
|
||||
__listView.model = __private.model;
|
||||
}
|
||||
}
|
||||
|
||||
function clear() {
|
||||
__private.gotoMenuKey = '';
|
||||
__listView.model = [];
|
||||
}
|
||||
|
||||
component MenuButton: DelButton {
|
||||
id: __menuButtonImpl
|
||||
property int iconSource: 0
|
||||
property int iconSize: DelTheme.DelMenu.fontSize
|
||||
property int iconSpacing: 5
|
||||
property int iconStart: 0
|
||||
property bool expanded: false
|
||||
property bool expandedVisible: false
|
||||
property bool isCurrent: false
|
||||
property bool isGroup: false
|
||||
property var model: undefined
|
||||
property var contentDelegate: null
|
||||
|
||||
onClicked: {
|
||||
if (expandedVisible)
|
||||
expanded = !expanded;
|
||||
}
|
||||
hoverCursorShape: (isGroup && !control.compactMode) ? Qt.ArrowCursor : Qt.PointingHandCursor
|
||||
effectEnabled: false
|
||||
colorBorder: "transparent"
|
||||
colorText: {
|
||||
if (enabled) {
|
||||
if (isGroup) {
|
||||
return (isCurrent && control.compactMode) ? DelTheme.DelMenu.colorTextActive : DelTheme.DelMenu.colorTextDisabled;
|
||||
} else {
|
||||
return isCurrent ? DelTheme.DelMenu.colorTextActive : DelTheme.DelMenu.colorText;
|
||||
}
|
||||
} else {
|
||||
return DelTheme.DelMenu.colorTextDisabled;
|
||||
}
|
||||
}
|
||||
colorBg: {
|
||||
if (enabled) {
|
||||
if (isGroup)
|
||||
return (isCurrent && control.compactMode) ? DelTheme.DelMenu.colorBgActive : DelTheme.DelMenu.colorBgDisabled;
|
||||
else if (isCurrent)
|
||||
return DelTheme.DelMenu.colorBgActive;
|
||||
else if (hovered) {
|
||||
return DelTheme.DelMenu.colorBgHover;
|
||||
} else {
|
||||
return DelTheme.DelMenu.colorBg;
|
||||
}
|
||||
} else {
|
||||
return DelTheme.DelMenu.colorBgDisabled;
|
||||
}
|
||||
}
|
||||
contentItem: Loader {
|
||||
sourceComponent: __menuButtonImpl.contentDelegate
|
||||
property alias model: __menuButtonImpl.model
|
||||
property alias menuButton: __menuButtonImpl
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on width {
|
||||
enabled: control.animationEnabled
|
||||
NumberAnimation {
|
||||
easing.type: Easing.OutCubic
|
||||
duration: DelTheme.Primary.durationMid
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
id: __private
|
||||
signal gotoMenu(key: string)
|
||||
property string gotoMenuKey: ''
|
||||
property var model: []
|
||||
property var window: Window.window
|
||||
property var selectedItem: null
|
||||
property var popupList: []
|
||||
function createPopupList(deep) {
|
||||
/*! 为每一层创建一个弹窗 */
|
||||
if (popupList[deep] === undefined) {
|
||||
let parentPopup = deep > 0 ? popupList[deep - 1] : null;
|
||||
popupList[deep] = __popupComponent.createObject(control, { parentPopup: parentPopup });
|
||||
}
|
||||
return popupList[deep];
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
width: 1
|
||||
height: parent.height
|
||||
anchors.right: parent.right
|
||||
active: control.showEdge
|
||||
sourceComponent: Rectangle {
|
||||
color: DelTheme.DelMenu.colorEdge
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onWheel: (wheel) => wheel.accepted = true;
|
||||
}
|
||||
|
||||
Component {
|
||||
id: __popupComponent
|
||||
|
||||
DelPopup {
|
||||
width: control.popupWidth
|
||||
height: current ? Math.min(control.popupMaxHeight, current.realHeight + topPadding + bottomPadding) : 0
|
||||
padding: 5
|
||||
onAboutToShow: {
|
||||
let toX = control.width + 4;
|
||||
if (parentPopup) {
|
||||
toX += parentPopup.width + 4;
|
||||
}
|
||||
x = toX;
|
||||
}
|
||||
property var current: null
|
||||
property var parentPopup: null
|
||||
function closeWithParent() {
|
||||
close();
|
||||
let p = parentPopup;
|
||||
while (p) {
|
||||
p.close();
|
||||
p = p.parentPopup;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ListView {
|
||||
id: __listView
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 8
|
||||
anchors.top: parent.top
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.margins: 5
|
||||
boundsBehavior: Flickable.StopAtBounds
|
||||
spacing: control.defaultMenuSpacing
|
||||
delegate: __menuDelegate
|
||||
onContentHeightChanged: cacheBuffer = contentHeight;
|
||||
T.ScrollBar.vertical: DelScrollBar {
|
||||
anchors.rightMargin: -8
|
||||
policy: control.compactMode ? DelScrollBar.AsNeeded : DelScrollBar.AlwaysOn
|
||||
}
|
||||
property int menuDeep: 0
|
||||
}
|
||||
|
||||
Accessible.role: Accessible.Tree
|
||||
Accessible.description: control.contentDescription
|
||||
}
|
||||
|
|
@ -0,0 +1,279 @@
|
|||
import QtQuick 2.15
|
||||
import QtQuick.Layouts 1.15
|
||||
import QtQuick.Templates 2.15 as T
|
||||
import QtGraphicalEffects 1.15
|
||||
import DelegateUI 1.0
|
||||
|
||||
Item {
|
||||
id: control
|
||||
|
||||
enum MessageType {
|
||||
Type_None = 0,
|
||||
Type_Success = 1,
|
||||
Type_Warning = 2,
|
||||
Type_Message = 3,
|
||||
Type_Error = 4
|
||||
}
|
||||
|
||||
messageFont {
|
||||
family: DelTheme.DelMessage.fontFamily
|
||||
pixelSize: DelTheme.DelMessage.fontSize
|
||||
}
|
||||
|
||||
signal messageClosed(key: string)
|
||||
|
||||
property bool animationEnabled: DelTheme.animationEnabled
|
||||
property bool closeButtonVisible: false
|
||||
property int bgTopPadding: 12
|
||||
property int bgBottomPadding: 12
|
||||
property int bgLeftPadding: 12
|
||||
property int bgRightPadding: 12
|
||||
property color colorBg: DelTheme.isDark ? DelTheme.DelMessage.colorBgDark : DelTheme.DelMessage.colorBg
|
||||
property color colorBgShadow: DelTheme.DelMessage.colorBgShadow
|
||||
property int radiusBg: DelTheme.DelMessage.radiusBg
|
||||
|
||||
property font messageFont
|
||||
property color colorMessage: DelTheme.DelMessage.colorMessage
|
||||
property int messageSpacing: 8
|
||||
|
||||
function info(message: string, duration = 3000) {
|
||||
open({
|
||||
'message': message,
|
||||
'type': DelMessage.Type_Message,
|
||||
'duration': duration
|
||||
});
|
||||
}
|
||||
|
||||
function success(message: string, duration = 3000) {
|
||||
open({
|
||||
'message': message,
|
||||
'type': DelMessage.Type_Success,
|
||||
'duration': duration
|
||||
});
|
||||
}
|
||||
|
||||
function error(message: string, duration = 3000) {
|
||||
open({
|
||||
'message': message,
|
||||
'type': DelMessage.Type_Error,
|
||||
'duration': duration
|
||||
});
|
||||
}
|
||||
|
||||
function warning(message: string, duration = 3000) {
|
||||
open({
|
||||
'message': message,
|
||||
'type': DelMessage.Type_Warning,
|
||||
'duration': duration
|
||||
});
|
||||
}
|
||||
|
||||
function loading(message: string, duration = 3000) {
|
||||
open({
|
||||
'loading': true,
|
||||
'message': message,
|
||||
'type': DelMessage.Type_Message,
|
||||
'duration': duration
|
||||
});
|
||||
}
|
||||
|
||||
function open(object) {
|
||||
__listModel.append(__private.initObject(object));
|
||||
}
|
||||
|
||||
function close(key: string) {
|
||||
for (let i = 0; i < __listModel.count; i++) {
|
||||
let object = __listModel.get(i);
|
||||
if (object.key && object.key === key) {
|
||||
let item = repeater.itemAt(i);
|
||||
if (item)
|
||||
item.removeSelf();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function setProperty(key, property, value) {
|
||||
for (let i = 0; i < __listModel.count; i++) {
|
||||
let object = __listModel.get(i);
|
||||
if (object.key && object.key === key) {
|
||||
__listModel.setProperty(i, property, value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on colorBg { enabled: control.animationEnabled; ColorAnimation { duration: DelTheme.Primary.durationMid } }
|
||||
Behavior on colorMessage { enabled: control.animationEnabled; ColorAnimation { duration: DelTheme.Primary.durationMid } }
|
||||
|
||||
QtObject {
|
||||
id: __private
|
||||
function initObject(object) {
|
||||
if (!object.hasOwnProperty("colorNode")) object.colorNode = String(control.colorNode);
|
||||
if (!object.hasOwnProperty('key')) object.key = '';
|
||||
if (!object.hasOwnProperty('loading')) object.loading = false;
|
||||
if (!object.hasOwnProperty('message')) object.message = '';
|
||||
if (!object.hasOwnProperty('type')) object.type = DelMessage.Type_None;
|
||||
if (!object.hasOwnProperty('duration')) object.duration = 3000;
|
||||
if (!object.hasOwnProperty('iconSource')) object.iconSource = 0;
|
||||
|
||||
if (!object.hasOwnProperty('colorIcon')) object.colorIcon = '';
|
||||
else object.colorIcon = String(object.colorIcon);
|
||||
|
||||
return object;
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: 10
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
spacing: 10
|
||||
|
||||
Repeater {
|
||||
id: repeater
|
||||
model: ListModel { id: __listModel }
|
||||
delegate: Item {
|
||||
id: __rootItem
|
||||
width: __content.width
|
||||
height: __content.height
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
|
||||
required property int index
|
||||
required property string key
|
||||
required property bool loading
|
||||
required property string message
|
||||
required property int type
|
||||
required property int duration
|
||||
required property int iconSource
|
||||
required property string colorIcon
|
||||
|
||||
function removeSelf() {
|
||||
__removeAniamtion.restart();
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: __timer
|
||||
running: true
|
||||
interval: __rootItem.duration
|
||||
onTriggered: {
|
||||
__removeAniamtion.restart();
|
||||
}
|
||||
}
|
||||
|
||||
DropShadow {
|
||||
anchors.fill: __rootItem
|
||||
radius: 16
|
||||
samples: 17
|
||||
color: DelThemeFunctions.alpha(control.colorBgShadow, DelTheme.isDark ? 0.1 : 0.2)
|
||||
source: __bgRect
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: __bgRect
|
||||
anchors.fill: parent
|
||||
radius: control.radiusBg
|
||||
color: control.colorBg
|
||||
visible: false
|
||||
}
|
||||
|
||||
Item {
|
||||
id: __content
|
||||
width: __rowLayout.width + control.bgLeftPadding + control.bgRightPadding
|
||||
height: 0
|
||||
opacity: 0
|
||||
clip: true
|
||||
|
||||
Component.onCompleted: {
|
||||
opacity = 1;
|
||||
height = __rowLayout.height + control.bgTopPadding + control.bgBottomPadding;
|
||||
}
|
||||
|
||||
Behavior on opacity { enabled: control.animationEnabled; NumberAnimation { duration: DelTheme.Primary.durationMid } }
|
||||
Behavior on height { enabled: control.animationEnabled; NumberAnimation { duration: DelTheme.Primary.durationMid } }
|
||||
|
||||
NumberAnimation on height {
|
||||
id: __removeAniamtion
|
||||
to: 0
|
||||
running: false
|
||||
alwaysRunToEnd: true
|
||||
onFinished: {
|
||||
control.messageClosed(__rootItem.key);
|
||||
__listModel.remove(__rootItem.index);
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
id: __rowLayout
|
||||
width: Math.min(implicitWidth, control.width - control.bgLeftPadding - control.bgRightPadding)
|
||||
anchors.centerIn: parent
|
||||
spacing: control.messageSpacing
|
||||
|
||||
DelIconText {
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
iconSize: 18
|
||||
iconSource: {
|
||||
if (__rootItem.loading) return DelIcon.LoadingOutlined;
|
||||
if (__rootItem.iconSource != 0) return __rootItem.iconSource;
|
||||
switch (type) {
|
||||
case DelMessage.Type_Success: return DelIcon.CheckCircleFilled;
|
||||
case DelMessage.Type_Warning: return DelIcon.ExclamationCircleFilled;
|
||||
case DelMessage.Type_Message: return DelIcon.ExclamationCircleFilled;
|
||||
case DelMessage.Type_Error: return DelIcon.CloseCircleFilled;
|
||||
default: return 0;
|
||||
}
|
||||
}
|
||||
colorIcon: {
|
||||
if (__rootItem.loading) return DelTheme.Primary.colorInfo;
|
||||
if (__rootItem.colorIcon !== '') return __rootItem.colorIcon;
|
||||
switch ((type)) {
|
||||
case DelMessage.Type_Success: return DelTheme.Primary.colorSuccess;
|
||||
case DelMessage.Type_Warning: return DelTheme.Primary.colorWarning;
|
||||
case DelMessage.Type_Message: return DelTheme.Primary.colorInfo;
|
||||
case DelMessage.Type_Error: return DelTheme.Primary.colorError;
|
||||
default: return DelTheme.Primary.colorInfo;
|
||||
}
|
||||
}
|
||||
|
||||
NumberAnimation on rotation {
|
||||
running: __rootItem.loading
|
||||
from: 0
|
||||
to: 360
|
||||
loops: Animation.Infinite
|
||||
duration: 1000
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
Layout.fillWidth: true
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
font: control.messageFont
|
||||
color: control.colorMessage
|
||||
text: __rootItem.message
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
wrapMode: Text.WrapAnywhere
|
||||
}
|
||||
|
||||
Loader {
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
active: control.closeButtonVisible
|
||||
sourceComponent: DelCaptionButton {
|
||||
topPadding: 0
|
||||
bottomPadding: 0
|
||||
leftPadding: 2
|
||||
rightPadding: 2
|
||||
hoverCursorShape: Qt.PointingHandCursor
|
||||
iconSource: DelIcon.CloseOutlined
|
||||
colorIcon: hovered ? DelTheme.DelMessage.colorCloseHover : DelTheme.DelMessage.colorClose
|
||||
onClicked: {
|
||||
__timer.stop();
|
||||
__removeAniamtion.restart();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
import QtQuick 2.15
|
||||
|
||||
MouseArea {
|
||||
id: root
|
||||
|
||||
property var target: undefined
|
||||
property real minimumX: Number.NaN
|
||||
property real maximumX: Number.NaN
|
||||
property real minimumY: Number.NaN
|
||||
property real maximumY: Number.NaN
|
||||
|
||||
QtObject {
|
||||
id: __private
|
||||
property point startPos: Qt.point(0, 0)
|
||||
property point offsetPos: Qt.point(0, 0)
|
||||
}
|
||||
|
||||
onClicked: (mouse) => mouse.accepted = false;
|
||||
onPressed:
|
||||
(mouse) => {
|
||||
__private.startPos = Qt.point(mouse.x, mouse.y);
|
||||
cursorShape = Qt.SizeAllCursor;
|
||||
}
|
||||
onReleased:
|
||||
(mouse) => {
|
||||
__private.startPos = Qt.point(mouse.x, mouse.y);
|
||||
cursorShape = Qt.ArrowCursor;
|
||||
}
|
||||
onPositionChanged:
|
||||
(mouse) => {
|
||||
if (pressed) {
|
||||
__private.offsetPos = Qt.point(mouse.x - __private.startPos.x, mouse.y - __private.startPos.y);
|
||||
if (target) {
|
||||
// x
|
||||
if (minimumX != Number.NaN && minimumX > (target.x + __private.offsetPos.x)) {
|
||||
target.x = minimumX;
|
||||
} else if (maximumX != Number.NaN && maximumX < (target.x + __private.offsetPos.x)) {
|
||||
target.x = maximumX;
|
||||
} else {
|
||||
target.x = target.x + __private.offsetPos.x;
|
||||
}
|
||||
// y
|
||||
if (minimumY != Number.NaN && minimumY > (target.y + __private.offsetPos.y)) {
|
||||
target.y = minimumY;
|
||||
} else if (maximumY != Number.NaN && maximumY < (target.y + __private.offsetPos.y)) {
|
||||
target.y = maximumY;
|
||||
} else {
|
||||
target.y = target.y + __private.offsetPos.y;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
import QtQuick 2.15
|
||||
import QtQuick.Templates 2.15 as T
|
||||
import DelegateUI 1.0
|
||||
|
||||
Item {
|
||||
id: control
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,158 @@
|
|||
import QtQuick 2.15
|
||||
import DelegateUI 1.0
|
||||
|
||||
Item {
|
||||
id: control
|
||||
|
||||
width: __row.width
|
||||
height: __row.height
|
||||
|
||||
signal finished(input: string)
|
||||
|
||||
property bool animationEnabled: DelTheme.animationEnabled
|
||||
property int length: 6
|
||||
property int characterLength: 1
|
||||
property int currentIndex: 0
|
||||
property string currentInput: ''
|
||||
property int itemWidth: 45
|
||||
property int itemHeight: 32
|
||||
property alias itemSpacing: __row.spacing
|
||||
property var itemValidator: IntValidator { top: 9; bottom: 0 }
|
||||
property int itemInputMethodHints: Qt.ImhHiddenText
|
||||
property bool itemPassword: false
|
||||
property string itemPasswordCharacter: ''
|
||||
property var formatter: (text) => text
|
||||
|
||||
property color colorItemText: enabled ? DelTheme.DelInput.colorText : DelTheme.DelInput.colorTextDisabled
|
||||
property color colorItemBorder: enabled ? DelTheme.DelInput.colorBorder : DelTheme.DelInput.colorBorderDisabled
|
||||
property color colorItemBorderActive: enabled ? DelTheme.DelInput.colorBorderHover : DelTheme.DelInput.colorBorderDisabled
|
||||
property color colorItemBg: enabled ? DelTheme.DelInput.colorBg : DelTheme.DelInput.colorBgDisabled
|
||||
property int radiusBg: 6
|
||||
|
||||
property Component dividerDelegate: Item { }
|
||||
|
||||
onCurrentIndexChanged: {
|
||||
const item = __repeater.itemAt(currentIndex << 1);
|
||||
if (item && item.index % 2 == 0)
|
||||
item.item.selectThis();
|
||||
}
|
||||
|
||||
function setInput(inputs) {
|
||||
for (let i = 0; i < inputs.length; i++) {
|
||||
setInputAtIndex(i, input);
|
||||
}
|
||||
}
|
||||
|
||||
function setInputAtIndex(index, input) {
|
||||
const item = __repeater.itemAt(index << 1);
|
||||
if (item) {
|
||||
currentIndex = index;
|
||||
item.item.text = formatter(input);
|
||||
}
|
||||
}
|
||||
|
||||
function getInput() {
|
||||
let input = '';
|
||||
for (let i = 0; i < __repeater.count; i++) {
|
||||
const item = __repeater.itemAt(i);
|
||||
if (item && item.index % 2 == 0) {
|
||||
input += item.item.text;
|
||||
}
|
||||
}
|
||||
return input;
|
||||
}
|
||||
|
||||
function getInputAtIndex(index) {
|
||||
const item = __repeater.itemAt(index << 1);
|
||||
if (item) {
|
||||
return item.item.text;
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
Component {
|
||||
id: __inputDelegate
|
||||
|
||||
DelInput {
|
||||
id: __rootItem
|
||||
width: control.itemWidth
|
||||
height: control.itemHeight
|
||||
verticalAlignment: DelInput.AlignVCenter
|
||||
horizontalAlignment: DelInput.AlignHCenter
|
||||
enabled: control.enabled
|
||||
colorText: control.colorItemText
|
||||
colorBorder: active ? control.colorItemBorderActive : control.colorItemBorder
|
||||
colorBg: control.colorItemBg
|
||||
radiusBg: control.radiusBg
|
||||
validator: control.itemValidator
|
||||
inputMethodHints: control.itemInputMethodHints
|
||||
echoMode: control.itemPassword ? DelInput.Password : DelInput.Normal
|
||||
passwordCharacter:control.itemPasswordCharacter
|
||||
onReleased: __timer.restart();
|
||||
onTextEdited: {
|
||||
text = control.formatter(text);
|
||||
const isFull = length >= control.characterLength;
|
||||
if (isFull) selectAll();
|
||||
|
||||
if (isBackspace) isBackspace = false;
|
||||
|
||||
const input = control.getInput();
|
||||
control.currentInput = input;
|
||||
|
||||
if (isFull) {
|
||||
if (control.currentIndex < (control.length - 1))
|
||||
control.currentIndex++;
|
||||
else
|
||||
control.finished(input);
|
||||
}
|
||||
}
|
||||
|
||||
property int __index: index
|
||||
property bool isBackspace: false
|
||||
|
||||
function selectThis() {
|
||||
forceActiveFocus();
|
||||
selectAll();
|
||||
}
|
||||
|
||||
Keys.onPressed: function(event) {
|
||||
if (event.key === Qt.Key_Backspace) {
|
||||
clear();
|
||||
const input = control.getInput();
|
||||
control.currentInput = input;
|
||||
isBackspace = true;
|
||||
if (control.currentIndex != 0)
|
||||
control.currentIndex--;
|
||||
} else if (event.key === Qt.Key_Enter || event.key === Qt.Key_Return) {
|
||||
if (control.currentIndex < (control.length - 1))
|
||||
control.currentIndex++;
|
||||
else
|
||||
control.finished(control.getInput());
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: __timer
|
||||
interval: 100
|
||||
onTriggered: {
|
||||
control.currentIndex = __rootItem.__index >> 1;
|
||||
__rootItem.selectAll();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
id: __row
|
||||
spacing: 8
|
||||
|
||||
Repeater {
|
||||
id: __repeater
|
||||
model: control.length * 2 - 1
|
||||
delegate: Loader {
|
||||
sourceComponent: index % 2 == 0 ? __inputDelegate : dividerDelegate
|
||||
required property int index
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,293 @@
|
|||
import QtQuick 2.15
|
||||
import DelegateUI 1.0
|
||||
|
||||
Item {
|
||||
id: control
|
||||
|
||||
width: __row.width
|
||||
height: __row.height
|
||||
|
||||
property bool animationEnabled: DelTheme.animationEnabled
|
||||
property int defaultButtonWidth: 32
|
||||
property int defaultButtonHeight: 32
|
||||
property int defaultButtonSpacing: 8
|
||||
property bool showQuickJumper: false
|
||||
property int currentPageIndex: 0
|
||||
property int total: 0
|
||||
property int pageTotal: pageSize > 0 ? Math.ceil(total / pageSize) : 0
|
||||
property int pageButtonMaxCount: 7
|
||||
property int pageSize: 10
|
||||
property var pageSizeModel: []
|
||||
property string prevButtonTooltip: qsTr("上一页")
|
||||
property string nextButtonTooltip: qsTr("下一页")
|
||||
property Component prevButtonDelegate: ActionButton {
|
||||
iconSource: DelIcon.LeftOutlined
|
||||
tooltipText: control.prevButtonTooltip
|
||||
disabled: control.currentPageIndex == 0
|
||||
onClicked: control.gotoPrevPage();
|
||||
}
|
||||
property Component nextButtonDelegate: ActionButton {
|
||||
iconSource: DelIcon.RightOutlined
|
||||
tooltipText: control.nextButtonTooltip
|
||||
disabled: control.currentPageIndex == (control.pageTotal - 1)
|
||||
onClicked: control.gotoNextPage();
|
||||
}
|
||||
property Component quickJumperDelegate: Row {
|
||||
height: control.defaultButtonHeight
|
||||
spacing: control.defaultButtonSpacing
|
||||
|
||||
Text {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: qsTr("跳至")
|
||||
font {
|
||||
family: DelTheme.DelCopyableText.fontFamily
|
||||
pixelSize: DelTheme.DelCopyableText.fontSize
|
||||
}
|
||||
color: DelTheme.Primary.colorTextBase
|
||||
}
|
||||
|
||||
DelInput {
|
||||
width: 48
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
horizontalAlignment: DelInput.AlignHCenter
|
||||
animationEnabled: control.animationEnabled
|
||||
enabled: control.enabled
|
||||
validator: IntValidator { top: 99999; bottom: 0 }
|
||||
onEditingFinished: {
|
||||
control.gotoPageIndex(parseInt(text) - 1);
|
||||
clear();
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: qsTr("页")
|
||||
font {
|
||||
family: DelTheme.Primary.fontPrimaryFamily
|
||||
pixelSize: DelTheme.Primary.fontPrimarySize
|
||||
}
|
||||
color: DelTheme.Primary.colorTextBase
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: currentPageIndexChanged();
|
||||
|
||||
onPageSizeChanged: {
|
||||
const __pageTotal = (pageSize > 0 ? Math.ceil(total / pageSize) : 0);
|
||||
if (currentPageIndex > __pageTotal) {
|
||||
currentPageIndex = __pageTotal - 1;
|
||||
}
|
||||
}
|
||||
|
||||
function gotoPageIndex(index: int) {
|
||||
if (index <= 0)
|
||||
control.currentPageIndex = 0;
|
||||
else if (index < pageTotal)
|
||||
control.currentPageIndex = index;
|
||||
else
|
||||
control.currentPageIndex = (pageTotal - 1);
|
||||
}
|
||||
|
||||
function gotoPrevPage() {
|
||||
if (currentPageIndex > 0)
|
||||
currentPageIndex--;
|
||||
}
|
||||
|
||||
function gotoPrev5Page() {
|
||||
if (currentPageIndex > 5)
|
||||
currentPageIndex -= 5;
|
||||
else
|
||||
currentPageIndex = 0;
|
||||
}
|
||||
|
||||
function gotoNextPage() {
|
||||
if (currentPageIndex < pageTotal)
|
||||
currentPageIndex++;
|
||||
}
|
||||
|
||||
function gotoNext5Page() {
|
||||
if ((currentPageIndex + 5) < pageTotal)
|
||||
currentPageIndex += 5;
|
||||
else
|
||||
currentPageIndex = pageTotal - 1;
|
||||
}
|
||||
|
||||
component PaginationButton: DelButton {
|
||||
padding: 0
|
||||
width: control.defaultButtonWidth
|
||||
height: control.defaultButtonHeight
|
||||
animationEnabled: false
|
||||
effectEnabled: false
|
||||
enabled: control.enabled
|
||||
text: (pageIndex + 1)
|
||||
checked: control.currentPageIndex == pageIndex
|
||||
font.bold: checked
|
||||
colorText: {
|
||||
if (enabled)
|
||||
return checked ? DelTheme.DelPagination.colorButtonTextActive : DelTheme.DelPagination.colorButtonText;
|
||||
else
|
||||
return DelTheme.DelPagination.colorButtonTextDisabled;
|
||||
}
|
||||
colorBg: {
|
||||
if (enabled) {
|
||||
if (checked)
|
||||
return DelTheme.DelPagination.colorButtonBg;
|
||||
else
|
||||
return down ? DelTheme.DelPagination.colorButtonBgActive :
|
||||
hovered ? DelTheme.DelPagination.colorButtonBgHover :
|
||||
DelTheme.DelPagination.colorButtonBg;
|
||||
} else {
|
||||
return checked ? DelTheme.DelPagination.colorButtonBgDisabled : "transparent";
|
||||
}
|
||||
}
|
||||
colorBorder: checked ? DelTheme.DelPagination.colorBorderActive : "transparent"
|
||||
onClicked: {
|
||||
control.currentPageIndex = pageIndex;
|
||||
}
|
||||
property int pageIndex: 0
|
||||
|
||||
DelToolTip {
|
||||
arrowVisible: false
|
||||
text: parent.text
|
||||
visible: parent.hovered && parent.enabled
|
||||
}
|
||||
}
|
||||
|
||||
component PaginationMoreButton: DelIconButton {
|
||||
padding: 0
|
||||
width: control.defaultButtonWidth
|
||||
height: control.defaultButtonHeight
|
||||
animationEnabled: false
|
||||
effectEnabled: false
|
||||
enabled: control.enabled
|
||||
colorBg: "transparent"
|
||||
colorBorder: "transparent"
|
||||
text: (enabled && (down || hovered)) ? "" : "•••"
|
||||
iconSource: (enabled && (down || hovered)) ? (isPrev ? DelIcon.DoubleLeftOutlined : DelIcon.DoubleRightOutlined) : 0
|
||||
property bool isPrev: false
|
||||
property alias tooltipText: __moreTooltip.text
|
||||
|
||||
DelToolTip {
|
||||
id: __moreTooltip
|
||||
arrowVisible: false
|
||||
visible: parent.enabled && parent.hovered && text !== ""
|
||||
}
|
||||
}
|
||||
|
||||
component ActionButton: Item {
|
||||
id: __actionRoot
|
||||
width: __actionButton.width
|
||||
height: __actionButton.height
|
||||
|
||||
signal clicked()
|
||||
property bool disabled: false
|
||||
property alias iconSource: __actionButton.iconSource
|
||||
property alias tooltipText: __tooltip.text
|
||||
|
||||
DelIconButton {
|
||||
id: __actionButton
|
||||
padding: 0
|
||||
width: control.defaultButtonWidth
|
||||
height: control.defaultButtonHeight
|
||||
animationEnabled: control.animationEnabled
|
||||
enabled: control.enabled && !__actionRoot.disabled
|
||||
effectEnabled: false
|
||||
colorBorder: "transparent"
|
||||
colorBg: enabled ? (down ? DelTheme.DelPagination.colorButtonBgActive :
|
||||
hovered ? DelTheme.DelPagination.colorButtonBgHover : "transparent") : "transparent"
|
||||
onClicked: __actionRoot.clicked();
|
||||
|
||||
DelToolTip {
|
||||
id: __tooltip
|
||||
arrowVisible: false
|
||||
visible: parent.hovered && parent.enabled && text !== ""
|
||||
}
|
||||
}
|
||||
|
||||
HoverHandler {
|
||||
enabled: __actionRoot.disabled
|
||||
cursorShape: Qt.ForbiddenCursor
|
||||
}
|
||||
}
|
||||
|
||||
QtObject {
|
||||
id: __private
|
||||
property int pageButtonHalfCount: Math.ceil(control.pageButtonMaxCount * 0.5)
|
||||
}
|
||||
|
||||
Row {
|
||||
id: __row
|
||||
spacing: control.defaultButtonSpacing
|
||||
|
||||
Loader {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
sourceComponent: control.prevButtonDelegate
|
||||
}
|
||||
|
||||
PaginationButton {
|
||||
pageIndex: 0
|
||||
visible: control.pageTotal > 0
|
||||
}
|
||||
|
||||
PaginationMoreButton {
|
||||
isPrev: true
|
||||
tooltipText: qsTr("向前5页")
|
||||
visible: control.pageTotal > control.pageButtonMaxCount && (control.currentPageIndex + 1) > __private.pageButtonHalfCount
|
||||
onClicked: control.gotoPrev5Page();
|
||||
}
|
||||
|
||||
Repeater {
|
||||
id: __repeater
|
||||
model: (control.pageTotal < 2) ? 0 :
|
||||
(control.pageTotal >= control.pageButtonMaxCount) ? (control.pageButtonMaxCount - 2) :
|
||||
(control.pageTotal - 2)
|
||||
delegate: Loader {
|
||||
sourceComponent: PaginationButton {
|
||||
pageIndex: {
|
||||
if ((control.currentPageIndex + 1) <= __private.pageButtonHalfCount)
|
||||
return index + 1;
|
||||
else if (control.pageTotal - (control.currentPageIndex + 1) <= (control.pageButtonMaxCount - __private.pageButtonHalfCount))
|
||||
return (control.pageTotal - __repeater.count + index - 1);
|
||||
else
|
||||
return (control.currentPageIndex + index + 2 - __private.pageButtonHalfCount);
|
||||
}
|
||||
}
|
||||
required property int index
|
||||
}
|
||||
}
|
||||
|
||||
PaginationMoreButton {
|
||||
isPrev: false
|
||||
tooltipText: qsTr("向后5页")
|
||||
visible: control.pageTotal > control.pageButtonMaxCount &&
|
||||
(control.pageTotal - (control.currentPageIndex + 1) > (control.pageButtonMaxCount - __private.pageButtonHalfCount))
|
||||
onClicked: control.gotoNext5Page();
|
||||
}
|
||||
|
||||
PaginationButton {
|
||||
pageIndex: control.pageTotal - 1
|
||||
visible: control.pageTotal > 1
|
||||
}
|
||||
|
||||
Loader {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
sourceComponent: control.nextButtonDelegate
|
||||
}
|
||||
|
||||
DelSelect {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
animationEnabled: control.animationEnabled
|
||||
model: control.pageSizeModel
|
||||
visible: count > 0
|
||||
onActivated:
|
||||
(index) => {
|
||||
control.pageSize = currentValue;
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
sourceComponent: control.showQuickJumper ? control.quickJumperDelegate : null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,77 @@
|
|||
import QtQuick 2.15
|
||||
import QtGraphicalEffects 1.15
|
||||
import QtQuick.Templates 2.15 as T
|
||||
import DelegateUI 1.0
|
||||
|
||||
T.Popup {
|
||||
id: control
|
||||
|
||||
property bool animationEnabled: DelTheme.animationEnabled
|
||||
property bool movable: false
|
||||
property bool resizable: false
|
||||
property real minimumX: Number.NaN
|
||||
property real maximumX: Number.NaN
|
||||
property real minimumY: Number.NaN
|
||||
property real maximumY: Number.NaN
|
||||
property real minimumWidth: 0
|
||||
property real maximumWidth: Number.NaN
|
||||
property real minimumHeight: 0
|
||||
property real maximumHeight: Number.NaN
|
||||
property color colorShadow: DelTheme.DelPopup.colorShadow
|
||||
property color colorBg: DelTheme.isDark ? DelTheme.DelPopup.colorBgDark : DelTheme.DelPopup.colorBg
|
||||
property int radiusBg: DelTheme.DelPopup.radiusBg
|
||||
|
||||
implicitWidth: implicitContentWidth + leftPadding + rightPadding
|
||||
implicitHeight: implicitContentHeight + topPadding + bottomPadding
|
||||
enter: Transition {
|
||||
NumberAnimation {
|
||||
property: "opacity";
|
||||
from: 0.0
|
||||
to: 1.0
|
||||
duration: control.animationEnabled ? DelTheme.Primary.durationMid : 0
|
||||
}
|
||||
}
|
||||
exit: Transition {
|
||||
NumberAnimation {
|
||||
property: "opacity"
|
||||
from: 1.0
|
||||
to: 0
|
||||
duration: control.animationEnabled ? DelTheme.Primary.durationMid : 0
|
||||
}
|
||||
}
|
||||
background: Item {
|
||||
DropShadow {
|
||||
anchors.fill: __popupRect
|
||||
radius: 16
|
||||
samples: 17
|
||||
color: DelThemeFunctions.alpha(control.colorShadow, DelTheme.isDark ? 0.1 : 0.2)
|
||||
source: __popupRect
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: __popupRect
|
||||
anchors.fill: parent
|
||||
color: control.colorBg
|
||||
radius: control.radiusBg
|
||||
}
|
||||
Loader {
|
||||
active: control.movable || control.resizable
|
||||
sourceComponent: DelResizeMouseArea {
|
||||
anchors.fill: parent
|
||||
target: control
|
||||
movable: control.movable
|
||||
resizable: control.resizable
|
||||
minimumX: control.minimumX
|
||||
maximumX: control.maximumX
|
||||
minimumY: control.minimumY
|
||||
maximumY: control.maximumY
|
||||
minimumWidth: control.minimumWidth
|
||||
maximumWidth: control.maximumWidth
|
||||
minimumHeight: control.minimumHeight
|
||||
maximumHeight: control.maximumHeight
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on colorBg { enabled: control.animationEnabled; ColorAnimation { duration: DelTheme.Primary.durationMid } }
|
||||
}
|
||||
|
|
@ -0,0 +1,312 @@
|
|||
import QtQuick 2.15
|
||||
import DelegateUI 1.0
|
||||
|
||||
Item {
|
||||
id: control
|
||||
|
||||
width: parent.width
|
||||
height: 16
|
||||
|
||||
enum Type {
|
||||
Type_Line = 0,
|
||||
Type_Circle = 1,
|
||||
Type_Dashboard = 2
|
||||
}
|
||||
|
||||
enum Status {
|
||||
Status_Normal = 0,
|
||||
Status_Success = 1,
|
||||
Status_Exception = 2,
|
||||
Status_Active = 3
|
||||
}
|
||||
|
||||
property bool animationEnabled: DelTheme.animationEnabled
|
||||
property int type: DelProgress.Type_Line
|
||||
property int status: DelProgress.Status_Normal
|
||||
property real percent: 0
|
||||
property real barThickness: 8
|
||||
property string strokeLineCap: 'round'
|
||||
property int steps: 0
|
||||
property int currentStep: 0
|
||||
property real gap: 4
|
||||
property real gapDegree: 60
|
||||
property bool useGradient: false
|
||||
property var gradientStops: ({
|
||||
'0%': control.colorBar,
|
||||
'100%': control.colorBar
|
||||
})
|
||||
|
||||
property bool showInfo: true
|
||||
property int precision: 0
|
||||
property var formatter: function() {
|
||||
switch (control.status) {
|
||||
case DelProgress.Status_Success:
|
||||
return control.type === DelProgress.Type_Line ? DelIcon.CheckCircleFilled : DelIcon.CheckOutlined;
|
||||
case DelProgress.Status_Exception:
|
||||
return control.type === DelProgress.Type_Line ? DelIcon.CloseCircleFilled : DelIcon.CloseOutlined;
|
||||
default: return `${control.percent.toFixed(control.precision)}%`;
|
||||
}
|
||||
}
|
||||
property color colorBar: {
|
||||
switch (control.status) {
|
||||
case DelProgress.Status_Success: return DelTheme.DelProgress.colorBarSuccess;
|
||||
case DelProgress.Status_Exception: return DelTheme.DelProgress.colorBarException;
|
||||
case DelProgress.Status_Normal: return DelTheme.DelProgress.colorBarNormal;
|
||||
case DelProgress.Status_Active : return DelTheme.DelProgress.colorBarNormal;
|
||||
default: return DelTheme.DelProgress.colorBarNormal;
|
||||
}
|
||||
}
|
||||
property color colorTrack: DelTheme.DelProgress.colorTrack
|
||||
property color colorInfo: {
|
||||
switch (control.status) {
|
||||
case DelProgress.Status_Success: return DelTheme.DelProgress.colorInfoSuccess;
|
||||
case DelProgress.Status_Exception: return DelTheme.DelProgress.colorInfoException;
|
||||
default: return DelTheme.DelProgress.colorInfoNormal;
|
||||
}
|
||||
}
|
||||
property Component infoDelegate: DelIconText {
|
||||
color: control.colorInfo
|
||||
font.family: DelTheme.DelProgress.fontFamily
|
||||
font.pixelSize: type === DelProgress.Type_Line ? DelTheme.DelProgress.fontSize + (!isIcon ? 0 : 2) :
|
||||
DelTheme.DelProgress.fontSize + (!isIcon ? 8 : 16)
|
||||
text: isIcon ? String.fromCharCode(formatText) : formatText
|
||||
property var formatText: control.formatter()
|
||||
property bool isIcon: typeof formatText == 'number'
|
||||
}
|
||||
|
||||
onPercentChanged: __canvas.requestPaint();
|
||||
onStepsChanged: __canvas.requestPaint();
|
||||
onCurrentStepChanged: __canvas.requestPaint();
|
||||
onBarThicknessChanged: __canvas.requestPaint();
|
||||
onStrokeLineCapChanged: __canvas.requestPaint();
|
||||
onGapChanged: __canvas.requestPaint();
|
||||
onGapDegreeChanged: __canvas.requestPaint();
|
||||
onUseGradientChanged: __canvas.requestPaint();
|
||||
onGradientStopsChanged: __canvas.requestPaint();
|
||||
onColorBarChanged: __canvas.requestPaint();
|
||||
onColorTrackChanged: __canvas.requestPaint();
|
||||
onColorInfoChanged: __canvas.requestPaint();
|
||||
|
||||
Behavior on percent { enabled: control.animationEnabled; NumberAnimation { duration: DelTheme.Primary.durationMid } }
|
||||
|
||||
Behavior on colorBar { enabled: control.animationEnabled; ColorAnimation { duration: DelTheme.Primary.durationMid } }
|
||||
Behavior on colorTrack { enabled: control.animationEnabled; ColorAnimation { duration: DelTheme.Primary.durationMid } }
|
||||
Behavior on colorInfo { enabled: control.animationEnabled; ColorAnimation { duration: DelTheme.Primary.durationMid } }
|
||||
|
||||
Canvas {
|
||||
id: __canvas
|
||||
height: parent.height
|
||||
anchors.left: parent.left
|
||||
anchors.right: control.type === DelProgress.Type_Line ? __infoLoader.left : parent.right
|
||||
anchors.rightMargin: control.type === DelProgress.Type_Line ? 5 : 0
|
||||
antialiasing: true
|
||||
onWidthChanged: requestPaint();
|
||||
onHeightChanged: requestPaint();
|
||||
onActiveWidthChanged: requestPaint();
|
||||
|
||||
property color activeColor: DelThemeFunctions.alpha(DelTheme.Primary.colorBgBase, 0.15)
|
||||
property real activeWidth: 0
|
||||
property real progressWidth: control.percent * 0.01 * width
|
||||
|
||||
NumberAnimation on activeWidth {
|
||||
running: control.type == DelProgress.Type_Line && control.status == DelProgress.Status_Active
|
||||
from: 0
|
||||
to: __canvas.progressWidth
|
||||
loops: Animation.Infinite
|
||||
duration: 2000
|
||||
easing.type: Easing.OutQuint
|
||||
}
|
||||
|
||||
function createGradient(ctx) {
|
||||
let gradient = ctx.createLinearGradient(0, 0, width, height);
|
||||
Object.keys(control.gradientStops).forEach(
|
||||
stop => {
|
||||
const percentage = parseFloat(stop) / 100;
|
||||
gradient.addColorStop(percentage, control.gradientStops[stop]);
|
||||
});
|
||||
|
||||
return gradient;
|
||||
}
|
||||
|
||||
function getCurrentColor(ctx) {
|
||||
return control.useGradient ? createGradient(ctx) : control.colorBar;
|
||||
}
|
||||
|
||||
function drawStrokeWithRadius(ctx, x, y, radius, startAngle, endAngle, color) {
|
||||
ctx.beginPath();
|
||||
ctx.arc(x, y, radius, startAngle, endAngle);
|
||||
ctx.lineWidth = control.barThickness;
|
||||
ctx.strokeStyle = color;
|
||||
ctx.stroke();
|
||||
}
|
||||
|
||||
function drawRoundLine(ctx, x, y, width, height, radius, color) {
|
||||
ctx.beginPath();
|
||||
if (control.strokeLineCap === 'butt') {
|
||||
ctx.moveTo(x, y + height * 0.5);
|
||||
ctx.lineTo(x + width, y + height * 0.5);
|
||||
} else {
|
||||
ctx.moveTo(x + radius, y + height * 0.5);
|
||||
ctx.lineTo(x + width - radius * 2, y + radius);
|
||||
}
|
||||
ctx.lineWidth = control.barThickness;
|
||||
ctx.lineCap = control.strokeLineCap;
|
||||
ctx.strokeStyle = color;
|
||||
ctx.stroke();
|
||||
}
|
||||
|
||||
function drawLine(ctx) {
|
||||
const color = getCurrentColor(ctx);
|
||||
if (control.steps > 0) {
|
||||
const stepWidth = (width - ((control.steps - 1) * control.gap)) / control.steps;
|
||||
const stepHeight = control.barThickness;
|
||||
const stepY = (__canvas.height - stepHeight) * 0.5;
|
||||
|
||||
for (let i = 0; i < control.steps; i++) {
|
||||
const stepX = i * control.gap + i * stepWidth;
|
||||
ctx.fillStyle = control.colorTrack;
|
||||
ctx.fillRect(stepX, stepY, stepWidth, stepHeight);
|
||||
}
|
||||
|
||||
for (let ii = 0; ii < control.currentStep; ii++) {
|
||||
const stepX = ii * control.gap + ii * stepWidth;
|
||||
ctx.fillStyle = color;
|
||||
ctx.fillRect(stepX, stepY, stepWidth, stepHeight);
|
||||
}
|
||||
} else {
|
||||
const x = 0;
|
||||
const y = (height - control.barThickness) * 0.5;
|
||||
const progressWidth = control.percent * 0.01 * width;
|
||||
const radius = control.strokeLineCap === 'round' ? control.barThickness * 0.5 : 0;
|
||||
|
||||
drawRoundLine(ctx, x, y, width, control.barThickness, radius, control.colorTrack);
|
||||
|
||||
if (progressWidth > 0) {
|
||||
drawRoundLine(ctx, x, y, progressWidth, control.barThickness, radius, color);
|
||||
/*! 绘制激活状态动画 */
|
||||
if (control.status == DelProgress.Status_Active) {
|
||||
drawRoundLine(ctx, x, y, __canvas.activeWidth, control.barThickness, radius, __canvas.activeColor);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function drawCircle(ctx, centerX, centerY, radius) {
|
||||
/*! 确保绘制不会超出边界 */
|
||||
radius = Math.min(radius, Math.min(width, height) * 0.5 - control.barThickness);
|
||||
const color = getCurrentColor(ctx);
|
||||
if (control.steps > 0) {
|
||||
/*! 计算每个步骤的弧长,考虑圆角影响 */
|
||||
const gap = control.gap;
|
||||
const circumference = Math.PI * 2 * radius;
|
||||
const totalGapLength = gap * control.steps;
|
||||
const availableLength = circumference - totalGapLength;
|
||||
const stepLength = availableLength / control.steps;
|
||||
|
||||
/*! 绘制背景圆环段 */
|
||||
for (let i = 0; i < control.steps; i++) {
|
||||
const gapDistance = (gap * i) / radius;
|
||||
const stepAngle = stepLength / radius;
|
||||
const startAngle = (i * stepAngle) + gapDistance - Math.PI / 2;
|
||||
const endAngle = startAngle + stepLength / radius;
|
||||
|
||||
drawStrokeWithRadius(ctx, centerX, centerY, radius, startAngle, endAngle, control.colorTrack);
|
||||
}
|
||||
|
||||
/*! 绘制已完成的步骤 */
|
||||
for (let ii = 0; ii < control.currentStep; ii++) {
|
||||
const gapDistance = (gap * ii) / radius;
|
||||
const stepAngle = stepLength / radius;
|
||||
const startAngle = (ii * stepAngle) + gapDistance - Math.PI / 2;
|
||||
const endAngle = startAngle + stepLength / radius;
|
||||
|
||||
drawStrokeWithRadius(ctx, centerX, centerY, radius, startAngle, endAngle, color);
|
||||
}
|
||||
} else {
|
||||
/*! 非步骤条需要使用线帽 */
|
||||
ctx.lineCap = control.strokeLineCap;
|
||||
|
||||
/*! 绘制轨道 */
|
||||
drawStrokeWithRadius(ctx, centerX, centerY, radius, 0, Math.PI * 2, control.colorTrack);
|
||||
|
||||
/*! 绘制进度 */
|
||||
const progress = control.percent * 0.01 * Math.PI * 2;
|
||||
drawStrokeWithRadius(ctx, centerX, centerY, radius, -Math.PI / 2, progress - Math.PI / 2, color);
|
||||
}
|
||||
}
|
||||
|
||||
function drawDashboard(ctx, centerX, centerY, radius) {
|
||||
radius = Math.min(radius, Math.min(width, height) * 0.5 - control.barThickness);
|
||||
/* ! 计算开始和结束角度 */
|
||||
const gapRad = Math.min(Math.max(control.gapDegree, 0), 295) * Math.PI / 180;
|
||||
const startAngle = Math.PI * 0.5 + gapRad * 0.5;
|
||||
const endAngle = Math.PI * 2.5 - gapRad * 0.5;
|
||||
const color = getCurrentColor(ctx);
|
||||
|
||||
if (control.steps > 0) {
|
||||
/*! 计算每个步骤的弧长,考虑仪表盘缺口和步进间隔 */
|
||||
const gap = control.gap;
|
||||
const availableAngle = endAngle - startAngle;
|
||||
const totalGapAngle = (gap / radius) * (control.steps - 1);
|
||||
const stepAngle = (availableAngle - totalGapAngle) / control.steps;
|
||||
|
||||
/*! 绘制背景圆环段 */
|
||||
for (let i = 0; i < control.steps; i++) {
|
||||
const stepStartAngle = startAngle + i * (stepAngle + gap / radius);
|
||||
const stepEndAngle = stepStartAngle + stepAngle;
|
||||
drawStrokeWithRadius(ctx, centerX, centerY, radius, stepStartAngle, stepEndAngle, control.colorTrack);
|
||||
}
|
||||
|
||||
/*! 绘制已完成的步骤 */
|
||||
for (let ii = 0; ii < control.currentStep; ii++) {
|
||||
const stepStartAngle = startAngle + ii * (stepAngle + gap / radius);
|
||||
const stepEndAngle = stepStartAngle + stepAngle;
|
||||
drawStrokeWithRadius(ctx, centerX, centerY, radius, stepStartAngle, stepEndAngle, color);
|
||||
}
|
||||
} else {
|
||||
/*! 非步骤条需要使用线帽 */
|
||||
ctx.lineCap = control.strokeLineCap;
|
||||
|
||||
/*!绘制背景轨道 */
|
||||
drawStrokeWithRadius(ctx, centerX, centerY, radius, startAngle, endAngle, control.colorTrack);
|
||||
|
||||
/*计算进度条角度 */
|
||||
const progressRange = endAngle - startAngle;
|
||||
const progress = control.percent * 0.01 * progressRange;
|
||||
|
||||
/*绘制进度 */
|
||||
drawStrokeWithRadius(ctx, centerX, centerY, radius, startAngle, startAngle + progress, color);
|
||||
}
|
||||
}
|
||||
|
||||
onPaint: {
|
||||
let ctx = getContext('2d');
|
||||
|
||||
let centerX = width / 2;
|
||||
let centerY = height / 2;
|
||||
let radius = Math.min(width, height) / 2 - control.barThickness;
|
||||
|
||||
/*! 清除画布 */
|
||||
ctx.clearRect(0, 0, width, height);
|
||||
|
||||
switch (control.type) {
|
||||
case DelProgress.Type_Line:
|
||||
drawLine(ctx); break;
|
||||
case DelProgress.Type_Circle:
|
||||
drawCircle(ctx, centerX, centerY, radius); break;
|
||||
case DelProgress.Type_Dashboard:
|
||||
drawDashboard(ctx, centerX, centerY, radius); break;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: __infoLoader
|
||||
active: control.showInfo
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.horizontalCenter: control.type === DelProgress.Type_Line ? undefined : parent.horizontalCenter
|
||||
anchors.right: control.type === DelProgress.Type_Line ? parent.right : undefined
|
||||
sourceComponent: control.infoDelegate
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,117 @@
|
|||
import QtQuick 2.15
|
||||
import QtQuick.Templates 2.15 as T
|
||||
import DelegateUI 1.0
|
||||
|
||||
T.RadioButton {
|
||||
id: control
|
||||
|
||||
property bool animationEnabled: DelTheme.animationEnabled
|
||||
property bool effectEnabled: true
|
||||
property int hoverCursorShape: Qt.PointingHandCursor
|
||||
property int radiusIndicator: 8
|
||||
property color colorText: enabled ? DelTheme.DelRadio.colorText : DelTheme.DelRadio.colorTextDisabled
|
||||
property color colorIndicator: enabled ?
|
||||
checked ? DelTheme.DelRadio.colorIndicatorChecked :
|
||||
DelTheme.DelRadio.colorIndicator : DelTheme.DelRadio.colorIndicatorDisabled
|
||||
property color colorIndicatorBorder: (enabled && (hovered || checked)) ? DelTheme.DelRadio.colorIndicatorBorderChecked :
|
||||
DelTheme.DelRadio.colorIndicatorBorder
|
||||
property string contentDescription: ""
|
||||
|
||||
font {
|
||||
family: DelTheme.DelRadio.fontFamily
|
||||
pixelSize: DelTheme.DelRadio.fontSize
|
||||
}
|
||||
|
||||
implicitWidth: implicitContentWidth + leftPadding + rightPadding
|
||||
implicitHeight: Math.max(implicitContentHeight, implicitIndicatorHeight) + topPadding + bottomPadding
|
||||
spacing: 8
|
||||
indicator: Item {
|
||||
x: control.leftPadding
|
||||
implicitWidth: __bg.width
|
||||
implicitHeight: __bg.height
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
Rectangle {
|
||||
id: __effect
|
||||
width: __bg.width
|
||||
height: __bg.height
|
||||
radius: width * 0.5
|
||||
anchors.centerIn: parent
|
||||
visible: control.effectEnabled
|
||||
color: "transparent"
|
||||
border.width: 0
|
||||
border.color: control.enabled ? DelTheme.DelRadio.colorEffectBg : "transparent"
|
||||
opacity: 0.2
|
||||
|
||||
ParallelAnimation {
|
||||
id: __animation
|
||||
onFinished: __effect.border.width = 0;
|
||||
NumberAnimation {
|
||||
target: __effect; property: "width"; from: __bg.width + 3; to: __bg.width + 8;
|
||||
duration: DelTheme.Primary.durationFast
|
||||
easing.type: Easing.OutQuart
|
||||
}
|
||||
NumberAnimation {
|
||||
target: __effect; property: "height"; from: __bg.height + 3; to: __bg.height + 8;
|
||||
duration: DelTheme.Primary.durationFast
|
||||
easing.type: Easing.OutQuart
|
||||
}
|
||||
NumberAnimation {
|
||||
target: __effect; property: "opacity"; from: 0.2; to: 0;
|
||||
duration: DelTheme.Primary.durationSlow
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: control
|
||||
function onReleased() {
|
||||
if (control.animationEnabled && control.effectEnabled) {
|
||||
__effect.border.width = 8;
|
||||
__animation.restart();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: __bg
|
||||
width: control.radiusIndicator * 2
|
||||
height: width
|
||||
anchors.centerIn: parent
|
||||
radius: height * 0.5
|
||||
color: control.colorIndicator
|
||||
border.color: control.colorIndicatorBorder
|
||||
border.width: control.checked ? 0 : 1
|
||||
|
||||
Behavior on color { enabled: control.animationEnabled; ColorAnimation { duration: DelTheme.Primary.durationFast } }
|
||||
Behavior on border.color { enabled: control.animationEnabled; ColorAnimation { duration: DelTheme.Primary.durationFast } }
|
||||
|
||||
Rectangle {
|
||||
width: control.checked ? control.radiusIndicator - 2 : 0
|
||||
height: width
|
||||
anchors.centerIn: parent
|
||||
radius: width * 0.5
|
||||
|
||||
Behavior on width { enabled: control.animationEnabled; NumberAnimation { duration: DelTheme.Primary.durationMid } }
|
||||
}
|
||||
}
|
||||
}
|
||||
contentItem: Text {
|
||||
text: control.text
|
||||
font: control.font
|
||||
opacity: enabled ? 1.0 : 0.3
|
||||
color: control.colorText
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
leftPadding: control.indicator.width + control.spacing
|
||||
}
|
||||
background: Item { }
|
||||
|
||||
HoverHandler {
|
||||
cursorShape: control.hoverCursorShape
|
||||
}
|
||||
|
||||
Accessible.role: Accessible.RadioButton
|
||||
Accessible.name: control.text
|
||||
Accessible.description: control.contentDescription
|
||||
Accessible.onPressAction: control.clicked();
|
||||
}
|
||||
|
|
@ -0,0 +1,197 @@
|
|||
import QtQuick 2.15
|
||||
import QtQuick.Templates 2.15 as T
|
||||
import DelegateUI 1.0
|
||||
|
||||
Item {
|
||||
id: control
|
||||
|
||||
implicitWidth: __loader.width
|
||||
implicitHeight: __loader.height
|
||||
|
||||
font {
|
||||
family: DelTheme.DelRadio.fontFamily
|
||||
pixelSize: DelTheme.DelRadio.fontSize
|
||||
}
|
||||
|
||||
enum Type {
|
||||
Type_Filled = 0,
|
||||
Type_Outlined = 1
|
||||
}
|
||||
|
||||
enum Size {
|
||||
Size_Auto = 0,
|
||||
Size_Fixed = 1
|
||||
}
|
||||
|
||||
signal clicked(radioData: var)
|
||||
|
||||
property bool animationEnabled: DelTheme.animationEnabled
|
||||
property bool effectEnabled: true
|
||||
property int hoverCursorShape: Qt.PointingHandCursor
|
||||
property var model: []
|
||||
property int count: model.length
|
||||
property int initCheckedIndex: -1
|
||||
property int currentCheckedIndex: -1
|
||||
property var currentCheckedValue: undefined
|
||||
property int type: DelRadioBlock.Type_Filled
|
||||
property int size: DelRadioBlock.Size_Auto
|
||||
property int radioWidth: 120
|
||||
property int radioHeight: 30
|
||||
property font font
|
||||
property int radiusBg: 6
|
||||
property Component radioDelegate: DelButton {
|
||||
id: __rootItem
|
||||
|
||||
required property var modelData
|
||||
required property int index
|
||||
|
||||
T.ButtonGroup.group: __buttonGroup
|
||||
Component.onCompleted: {
|
||||
if (control.initCheckedIndex == index) {
|
||||
checked = true;
|
||||
__buttonGroup.clicked(__rootItem);
|
||||
}
|
||||
}
|
||||
|
||||
animationEnabled: control.animationEnabled
|
||||
effectEnabled: control.effectEnabled
|
||||
hoverCursorShape: control.hoverCursorShape
|
||||
implicitWidth: control.size == DelRadioBlock.Size_Auto ? (implicitContentWidth + leftPadding + rightPadding) :
|
||||
control.radioWidth
|
||||
implicitHeight: control.size == DelRadioBlock.Size_Auto ? (implicitContentHeight + topPadding + bottomPadding) :
|
||||
control.radioHeight
|
||||
z: (hovered || checked) ? 1 : 0
|
||||
enabled: control.enabled && (modelData.enabled === undefined ? true : modelData.enabled)
|
||||
font: control.font
|
||||
type: DelButton.Type_Default
|
||||
text: modelData.label
|
||||
colorBorder: (enabled && checked) ? DelTheme.DelRadio.colorBlockBorderChecked :
|
||||
DelTheme.DelRadio.colorBlockBorder;
|
||||
colorText: {
|
||||
if (enabled) {
|
||||
if (control.type == DelRadioBlock.Type_Filled) {
|
||||
return checked ? DelTheme.DelRadio.colorBlockTextFilledChecked :
|
||||
hovered ? DelTheme.DelRadio.colorBlockTextChecked :
|
||||
DelTheme.DelRadio.colorBlockText;
|
||||
} else {
|
||||
return (checked || hovered) ? DelTheme.DelRadio.colorBlockTextChecked :
|
||||
DelTheme.DelRadio.colorBlockText;
|
||||
}
|
||||
} else {
|
||||
return DelTheme.DelRadio.colorTextDisabled;
|
||||
}
|
||||
}
|
||||
colorBg: {
|
||||
if (enabled) {
|
||||
if (control.type == DelRadioBlock.Type_Filled) {
|
||||
return down ? (checked ? DelTheme.DelRadio.colorBlockBgActive : DelTheme.DelRadio.colorBlockBg) :
|
||||
hovered ? (checked ? DelTheme.DelRadio.colorBlockBgHover : DelTheme.DelRadio.colorBlockBg) :
|
||||
checked ? DelTheme.DelRadio.colorBlockBgChecked :
|
||||
DelTheme.DelRadio.colorBlockBg;
|
||||
} else {
|
||||
return DelTheme.DelRadio.colorBlockBg;
|
||||
}
|
||||
} else {
|
||||
return checked ? DelTheme.DelRadio.colorBlockBgCheckedDisabled : DelTheme.DelRadio.colorBlockBgDisabled;
|
||||
}
|
||||
}
|
||||
checkable: true
|
||||
background: Item {
|
||||
Rectangle {
|
||||
id: __effect
|
||||
width: __bg.width
|
||||
height: __bg.height
|
||||
anchors.centerIn: parent
|
||||
visible: __rootItem.effectEnabled
|
||||
color: "transparent"
|
||||
border.width: 0
|
||||
border.color: __rootItem.enabled ? DelTheme.DelRadio.colorBlockEffectBg : "transparent"
|
||||
opacity: 0.2
|
||||
|
||||
ParallelAnimation {
|
||||
id: __animation
|
||||
onFinished: __effect.border.width = 0;
|
||||
NumberAnimation {
|
||||
target: __effect; property: "width"; from: __bg.width + 3; to: __bg.width + 8;
|
||||
duration: DelTheme.Primary.durationFast
|
||||
easing.type: Easing.OutQuart
|
||||
}
|
||||
NumberAnimation {
|
||||
target: __effect; property: "height"; from: __bg.height + 3; to: __bg.height + 8;
|
||||
duration: DelTheme.Primary.durationFast
|
||||
easing.type: Easing.OutQuart
|
||||
}
|
||||
NumberAnimation {
|
||||
target: __effect; property: "opacity"; from: 0.2; to: 0;
|
||||
duration: DelTheme.Primary.durationSlow
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: __rootItem
|
||||
function onReleased() {
|
||||
if (__rootItem.animationEnabled && __rootItem.effectEnabled) {
|
||||
__effect.border.width = 8;
|
||||
__animation.restart();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DelRectangle {
|
||||
id: __bg
|
||||
width: parent.width
|
||||
height: parent.height
|
||||
anchors.centerIn: parent
|
||||
color: __rootItem.colorBg
|
||||
topLeftRadius: index == 0 ? control.radiusBg : 0
|
||||
bottomLeftRadius: index == 0 ? control.radiusBg : 0
|
||||
topRightRadius: index === (count - 1) ? control.radiusBg : 0
|
||||
bottomRightRadius: index === (count - 1) ? control.radiusBg : 0
|
||||
border.width: 1
|
||||
border.color: __rootItem.colorBorder
|
||||
|
||||
Behavior on color { enabled: __rootItem.animationEnabled; ColorAnimation { duration: DelTheme.Primary.durationMid } }
|
||||
Behavior on border.color { enabled: __rootItem.animationEnabled; ColorAnimation { duration: DelTheme.Primary.durationMid } }
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: control
|
||||
function onCurrentCheckedIndexChanged() {
|
||||
if (__rootItem.index == control.currentCheckedIndex) {
|
||||
__rootItem.checked = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
property string contentDescription: ""
|
||||
|
||||
Loader {
|
||||
id: __loader
|
||||
sourceComponent: Row {
|
||||
spacing: -1
|
||||
|
||||
Repeater {
|
||||
id: __repeater
|
||||
model: control.model
|
||||
delegate: radioDelegate
|
||||
}
|
||||
}
|
||||
|
||||
T.ButtonGroup {
|
||||
id: __buttonGroup
|
||||
onClicked:
|
||||
button => {
|
||||
control.currentCheckedIndex = button.index;
|
||||
control.currentCheckedValue = button.modelData.value;
|
||||
control.clicked(button.modelData);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Accessible.role: Accessible.RadioButton
|
||||
Accessible.name: control.contentDescription
|
||||
Accessible.description: control.contentDescription
|
||||
Accessible.onPressAction: control.clicked();
|
||||
}
|
||||
|
|
@ -0,0 +1,230 @@
|
|||
import QtQuick 2.15
|
||||
import QtGraphicalEffects 1.15
|
||||
import DelegateUI 1.0
|
||||
|
||||
Item {
|
||||
id: control
|
||||
|
||||
implicitWidth: __mouseArea.width
|
||||
implicitHeight: __mouseArea.height
|
||||
toolTipFont {
|
||||
family: DelTheme.DelRate.fontFamily
|
||||
pixelSize: DelTheme.DelRate.fontSize
|
||||
}
|
||||
|
||||
property bool animationEnabled: DelTheme.animationEnabled
|
||||
property int hoverCursorShape: Qt.PointingHandCursor
|
||||
property int count: 5
|
||||
property real initValue: -1
|
||||
property real value: 0
|
||||
property alias spacing: __row.spacing
|
||||
property int iconSize: 24
|
||||
/* 文字提示 */
|
||||
property font toolTipFont
|
||||
property bool toolTipVisible: false
|
||||
property var toolTipTexts: []
|
||||
property color colorFill: DelTheme.DelRate.colorFill
|
||||
property color colorEmpty: DelTheme.DelRate.colorEmpty
|
||||
property color colorHalf: DelTheme.DelRate.colorHalf
|
||||
property color colorToolTipText: DelTheme.DelRate.colorToolTipText
|
||||
property color colorToolTipBg: DelTheme.isDark ? DelTheme.DelRate.colorToolTipBgDark : DelTheme.DelRate.colorToolTipBg
|
||||
/* 允许半星 */
|
||||
property bool allowHalf: false
|
||||
property bool isDone: false
|
||||
property int fillIcon: DelIcon.StarFilled
|
||||
property int emptyIcon: DelIcon.StarFilled
|
||||
property int halfIcon: DelIcon.StarFilled
|
||||
property Component fillDelegate: DelIconText {
|
||||
colorIcon: control.colorFill
|
||||
iconSource: control.fillIcon
|
||||
iconSize: control.iconSize
|
||||
}
|
||||
property Component emptyDelegate: DelIconText {
|
||||
colorIcon: control.colorEmpty
|
||||
iconSource: control.emptyIcon
|
||||
iconSize: control.iconSize
|
||||
}
|
||||
property Component halfDelegate: DelIconText {
|
||||
colorIcon: control.colorEmpty
|
||||
iconSource: control.emptyIcon
|
||||
iconSize: control.iconSize
|
||||
|
||||
DelIconText {
|
||||
id: __source
|
||||
colorIcon: control.colorHalf
|
||||
iconSource: control.halfIcon
|
||||
iconSize: control.iconSize
|
||||
layer.enabled: true
|
||||
layer.effect: halfRateHelper
|
||||
}
|
||||
}
|
||||
property Component toolTipDelegate: Item {
|
||||
width: 12
|
||||
height: 6
|
||||
opacity: hovered ? 1 : 0
|
||||
|
||||
Behavior on opacity { enabled: control.animationEnabled; NumberAnimation { duration: DelTheme.Primary.durationMid } }
|
||||
|
||||
DropShadow {
|
||||
anchors.fill: __item
|
||||
radius: 16
|
||||
samples: 17
|
||||
color: DelThemeFunctions.alpha(control.colorToolTipText, DelTheme.isDark ? 0.1 : 0.2)
|
||||
source: __item
|
||||
}
|
||||
|
||||
Item {
|
||||
id: __item
|
||||
width: __toolTipBg.width
|
||||
height: __arrow.height + __toolTipBg.height - 1
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.bottom: parent.bottom
|
||||
|
||||
Rectangle {
|
||||
id: __toolTipBg
|
||||
width: __toolTipText.implicitWidth + 14
|
||||
height: __toolTipText.implicitHeight + 12
|
||||
anchors.bottom: __arrow.top
|
||||
anchors.bottomMargin: -1
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
color: control.colorToolTipBg
|
||||
radius: 4
|
||||
|
||||
Text {
|
||||
id: __toolTipText
|
||||
color: control.colorToolTipText
|
||||
text: control.toolTipTexts[index]
|
||||
font: control.toolTipFont
|
||||
anchors.centerIn: parent
|
||||
}
|
||||
}
|
||||
|
||||
Canvas {
|
||||
id: __arrow
|
||||
width: 12
|
||||
height: 6
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
onColorBgChanged: requestPaint();
|
||||
onPaint: {
|
||||
const ctx = getContext("2d");
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(0, 0);
|
||||
ctx.lineTo(width, 0);
|
||||
ctx.lineTo(width * 0.5, height);
|
||||
ctx.closePath();
|
||||
ctx.fillStyle = colorBg;
|
||||
ctx.fill();
|
||||
}
|
||||
property color colorBg: control.colorToolTipBg
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
property Component halfRateHelper: ShaderEffect {
|
||||
fragmentShader: "qrc:/DelegateUI/shaders/delrate.frag"
|
||||
}
|
||||
|
||||
onInitValueChanged: {
|
||||
__private.doneValue = value = initValue;
|
||||
isDone = true;
|
||||
}
|
||||
|
||||
/* 结束 */
|
||||
signal done(int value);
|
||||
|
||||
QtObject {
|
||||
id: __private
|
||||
property real doneValue: 0
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: __mouseArea
|
||||
width: __row.width
|
||||
height: control.iconSize
|
||||
hoverEnabled: true
|
||||
enabled: control.enabled
|
||||
onExited: {
|
||||
if (control.isDone) {
|
||||
control.value = __private.doneValue;
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
id: __row
|
||||
spacing: 4
|
||||
|
||||
Repeater {
|
||||
id: __repeater
|
||||
|
||||
property int fillCount: Math.floor(control.value)
|
||||
property int emptyStartIndex: Math.round(control.value)
|
||||
property bool hasHalf: control.value - fillCount > 0
|
||||
|
||||
model: control.count
|
||||
delegate: MouseArea {
|
||||
id: __rootItem
|
||||
width: control.iconSize
|
||||
height: control.iconSize
|
||||
hoverEnabled: true
|
||||
cursorShape: hovered ? control.hoverCursorShape : Qt.ArrowCursor
|
||||
enabled: control.enabled
|
||||
onEntered: hovered = true;
|
||||
onExited: hovered = false;
|
||||
onClicked: {
|
||||
control.isDone = !control.isDone;
|
||||
if (control.isDone) {
|
||||
__private.doneValue = control.value;
|
||||
control.done(__private.doneValue);
|
||||
}
|
||||
}
|
||||
onPositionChanged: function(mouse) {
|
||||
if (control.allowHalf) {
|
||||
if (mouse.x > (width * 0.5)) {
|
||||
control.value = index + 1;
|
||||
} else {
|
||||
control.value = index + 0.5;
|
||||
}
|
||||
|
||||
} else {
|
||||
control.value = index + 1;
|
||||
}
|
||||
}
|
||||
required property int index
|
||||
property bool hovered: false
|
||||
|
||||
Loader {
|
||||
active: index < __repeater.fillCount
|
||||
sourceComponent: control.fillDelegate
|
||||
property int index: __rootItem.index
|
||||
property bool hovered: __rootItem.hovered
|
||||
}
|
||||
|
||||
Loader {
|
||||
active: __repeater.hasHalf && index === (__repeater.emptyStartIndex - 1)
|
||||
sourceComponent: control.halfDelegate
|
||||
property int index: __rootItem.index
|
||||
property bool hovered: __rootItem.hovered
|
||||
}
|
||||
|
||||
Loader {
|
||||
active: index >= __repeater.emptyStartIndex
|
||||
sourceComponent: control.emptyDelegate
|
||||
property int index: __rootItem.index
|
||||
property bool hovered: __rootItem.hovered
|
||||
}
|
||||
|
||||
Loader {
|
||||
x: (parent.width - width) * 0.5
|
||||
y: -height - 4
|
||||
z: 10
|
||||
active: control.toolTipVisible
|
||||
sourceComponent: control.toolTipDelegate
|
||||
property int index: __rootItem.index
|
||||
property bool hovered: __rootItem.hovered
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,354 @@
|
|||
import QtQuick 2.15
|
||||
|
||||
/*
|
||||
↑ ↑ ↑
|
||||
←|1| |2| |3|→
|
||||
←|4| |5| |6|→
|
||||
←|7| |8| |9|→
|
||||
↓ ↓ ↓
|
||||
分8个缩放区域
|
||||
|5|为移动区域{MoveMouseArea}
|
||||
target 缩放目标
|
||||
__private.startPos 鼠标起始点
|
||||
__private.fixedPos 用于固定目标的点
|
||||
每一个area 大小 areaMarginSize x areaMarginSize
|
||||
*/
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
property var target: undefined
|
||||
property real areaMarginSize: 8
|
||||
property bool resizable: true
|
||||
property real minimumWidth: 0
|
||||
property real maximumWidth: Number.NaN
|
||||
property real minimumHeight: 0
|
||||
property real maximumHeight: Number.NaN
|
||||
|
||||
property alias movable: area5.enabled
|
||||
property alias minimumX: area5.minimumX
|
||||
property alias maximumX: area5.maximumX
|
||||
property alias minimumY: area5.minimumY
|
||||
property alias maximumY: area5.maximumY
|
||||
|
||||
QtObject {
|
||||
id: __private
|
||||
property point startPos: Qt.point(0, 0)
|
||||
property point fixedPos: Qt.point(0, 0)
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: area1
|
||||
x: 0
|
||||
y: 0
|
||||
width: areaMarginSize
|
||||
height: areaMarginSize
|
||||
enabled: resizable
|
||||
hoverEnabled: true
|
||||
onEntered: cursorShape = Qt.SizeFDiagCursor;
|
||||
onExited: cursorShape = Qt.ArrowCursor;
|
||||
onPressed: (mouse) => __private.startPos = Qt.point(mouseX, mouseY);
|
||||
onPositionChanged:
|
||||
(mouse) => {
|
||||
if (pressed && target) {
|
||||
let offsetX = mouse.x - __private.startPos.x;
|
||||
let offsetY = mouse.y - __private.startPos.y;
|
||||
//如果本次调整小于最小限制,则调整为最小,大于最大则调整为最大
|
||||
if (maximumWidth != Number.NaN && (target.width - offsetX) > maximumWidth) {
|
||||
target.x += (target.width - maximumWidth);
|
||||
target.width = maximumWidth;
|
||||
} else if ((target.width - offsetX) < minimumWidth) {
|
||||
target.x += (target.width - minimumWidth);
|
||||
target.width = minimumWidth;
|
||||
} else {
|
||||
target.x += offsetX;
|
||||
target.width -= offsetX;
|
||||
}
|
||||
|
||||
if (maximumHeight != Number.NaN && (target.height - offsetY) > maximumHeight) {
|
||||
target.y += (target.height - maximumHeight);
|
||||
target.height = maximumHeight;
|
||||
} else if ((target.height - offsetY) < minimumHeight) {
|
||||
target.y += (target.height - minimumHeight);
|
||||
target.height = minimumHeight;
|
||||
} else {
|
||||
target.y += offsetY;
|
||||
target.height -= offsetY;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: area2
|
||||
x: areaMarginSize
|
||||
y: 0
|
||||
width: target.width - areaMarginSize * 2
|
||||
height: areaMarginSize
|
||||
enabled: resizable
|
||||
hoverEnabled: true
|
||||
onEntered: cursorShape = Qt.SizeVerCursor;
|
||||
onExited: cursorShape = Qt.ArrowCursor;
|
||||
onPressed: (mouse) => __private.startPos = Qt.point(mouseX, mouseY);
|
||||
onPositionChanged:
|
||||
(mouse) => {
|
||||
if (pressed && target) {
|
||||
let offsetY = mouse.y - __private.startPos.y;
|
||||
if (maximumHeight != Number.NaN && (target.height - offsetY) > maximumHeight) {
|
||||
target.y += (target.height - maximumHeight);
|
||||
target.height = maximumHeight;
|
||||
} else if ((target.height - offsetY) < minimumHeight) {
|
||||
target.y += (target.height - minimumHeight);
|
||||
target.height = minimumHeight;
|
||||
} else {
|
||||
target.y += offsetY;
|
||||
target.height -= offsetY;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: area3
|
||||
x: target.width - areaMarginSize
|
||||
y: 0
|
||||
width: areaMarginSize
|
||||
height: areaMarginSize
|
||||
enabled: resizable
|
||||
hoverEnabled: true
|
||||
onEntered: cursorShape = Qt.SizeBDiagCursor;
|
||||
onExited: cursorShape = Qt.ArrowCursor;
|
||||
onPressed:
|
||||
(mouse) => {
|
||||
if (root.target) {
|
||||
__private.startPos = Qt.point(mouseX, mouseY);
|
||||
__private.fixedPos = Qt.point(target.x, target.y);
|
||||
}
|
||||
}
|
||||
onPositionChanged:
|
||||
(mouse) => {
|
||||
if (pressed && target) {
|
||||
let offsetX = mouse.x - __private.startPos.x;
|
||||
let offsetY = mouse.y - __private.startPos.y;
|
||||
target.x = __private.fixedPos.x;
|
||||
if (maximumWidth != Number.NaN && (target.width + offsetX) > maximumWidth) {
|
||||
target.width = maximumWidth;
|
||||
} else if ((target.width + offsetX) < minimumWidth) {
|
||||
target.width = minimumWidth;
|
||||
} else {
|
||||
target.width += offsetX;
|
||||
}
|
||||
|
||||
if (maximumHeight != Number.NaN && (target.height - offsetY) > maximumHeight) {
|
||||
target.y += (target.height - maximumHeight);
|
||||
target.height = maximumHeight;
|
||||
} else if ((target.height - offsetY) < minimumHeight) {
|
||||
target.y += (target.height - minimumHeight);
|
||||
target.height = minimumHeight;
|
||||
} else {
|
||||
target.y += offsetY;
|
||||
target.height -= offsetY;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: area4
|
||||
x: 0
|
||||
y: areaMarginSize
|
||||
width: areaMarginSize
|
||||
height: target.height - areaMarginSize * 2
|
||||
enabled: resizable
|
||||
hoverEnabled: true
|
||||
onEntered: cursorShape = Qt.SizeHorCursor;
|
||||
onExited: cursorShape = Qt.ArrowCursor;
|
||||
onPressed:
|
||||
(mouse) => {
|
||||
__private.startPos = Qt.point(mouseX, mouseY);
|
||||
}
|
||||
onPositionChanged:
|
||||
(mouse) => {
|
||||
if (pressed && target) {
|
||||
let offsetX = mouse.x - __private.startPos.x;
|
||||
if (maximumWidth != Number.NaN && (target.width - offsetX) > maximumWidth) {
|
||||
target.x += (target.width - maximumWidth);
|
||||
target.width = maximumWidth;
|
||||
} else if ((target.width - offsetX) < minimumWidth) {
|
||||
target.x += (target.width - minimumWidth);
|
||||
target.width = minimumWidth;
|
||||
} else {
|
||||
target.x += offsetX;
|
||||
target.width -= offsetX;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DelMoveMouseArea {
|
||||
id: area5
|
||||
x: areaMarginSize
|
||||
y: areaMarginSize
|
||||
width: root.target.width - areaMarginSize * 2
|
||||
height: root.target.height - areaMarginSize * 2
|
||||
enabled: false
|
||||
target: root.target
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: area6
|
||||
x: target.width - areaMarginSize
|
||||
y: areaMarginSize
|
||||
width: areaMarginSize
|
||||
height: target.height - areaMarginSize * 2
|
||||
enabled: resizable
|
||||
hoverEnabled: true
|
||||
property real fixedX: 0
|
||||
onEntered: cursorShape = Qt.SizeHorCursor;
|
||||
onExited: cursorShape = Qt.ArrowCursor;
|
||||
onPressed:
|
||||
(mouse) => {
|
||||
if (target) {
|
||||
__private.startPos = Qt.point(mouseX, mouseY);
|
||||
__private.fixedPos = Qt.point(target.x, target.y);
|
||||
}
|
||||
}
|
||||
onPositionChanged:
|
||||
(mouse) => {
|
||||
if (pressed && target) {
|
||||
let offsetX = mouse.x - __private.startPos.x;
|
||||
target.x = __private.fixedPos.x;
|
||||
if (maximumWidth != Number.NaN && (target.width + offsetX) > maximumWidth) {
|
||||
target.width = maximumWidth;
|
||||
} else if ((target.width + offsetX) < minimumWidth) {
|
||||
target.width = minimumWidth;
|
||||
} else {
|
||||
target.width += offsetX;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: area7
|
||||
x: 0
|
||||
y: target.height - areaMarginSize
|
||||
width: areaMarginSize
|
||||
height: target.height - areaMarginSize * 2
|
||||
enabled: resizable
|
||||
hoverEnabled: true
|
||||
property real fixedX: 0
|
||||
onEntered: cursorShape = Qt.SizeBDiagCursor;
|
||||
onExited: cursorShape = Qt.ArrowCursor;
|
||||
onPressed:
|
||||
(mouse) => {
|
||||
if (target) {
|
||||
__private.startPos = Qt.point(mouseX, mouseY);
|
||||
__private.fixedPos = Qt.point(target.x, target.y);
|
||||
}
|
||||
}
|
||||
onPositionChanged:
|
||||
(mouse) => {
|
||||
if (pressed && target) {
|
||||
let offsetX = mouse.x - __private.startPos.x;
|
||||
let offsetY = mouse.y - __private.startPos.y;
|
||||
if (maximumWidth != Number.NaN && (target.width - offsetX) > maximumWidth) {
|
||||
target.x += (target.width - maximumWidth);
|
||||
target.width = maximumWidth;
|
||||
} else if ((target.width - offsetX) < minimumWidth) {
|
||||
target.x += (target.width - minimumWidth);
|
||||
target.width = minimumWidth;
|
||||
} else {
|
||||
target.x += offsetX;
|
||||
target.width -= offsetX;
|
||||
}
|
||||
|
||||
target.y = __private.fixedPos.y;
|
||||
if (maximumHeight != Number.NaN && (target.height + offsetY) > maximumHeight) {
|
||||
target.height = maximumHeight;
|
||||
} else if ((target.height + offsetY) < minimumHeight) {
|
||||
target.height = minimumHeight;
|
||||
} else {
|
||||
target.height += offsetY;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: area8
|
||||
x: areaMarginSize
|
||||
y: target.height - areaMarginSize
|
||||
width: target.height - areaMarginSize * 2
|
||||
height: areaMarginSize
|
||||
enabled: resizable
|
||||
hoverEnabled: true
|
||||
property real fixedX: 0
|
||||
onEntered: cursorShape = Qt.SizeVerCursor;
|
||||
onExited: cursorShape = Qt.ArrowCursor;
|
||||
onPressed:
|
||||
(mouse) => {
|
||||
if (target) {
|
||||
__private.startPos = Qt.point(mouseX, mouseY);
|
||||
__private.fixedPos = Qt.point(target.x, target.y);
|
||||
}
|
||||
}
|
||||
onPositionChanged:
|
||||
(mouse) => {
|
||||
if (pressed && target) {
|
||||
let offsetY = mouse.y - __private.startPos.y;
|
||||
target.y = __private.fixedPos.y;
|
||||
if (maximumHeight != Number.NaN && (target.height + offsetY) > maximumHeight) {
|
||||
target.height = maximumHeight;
|
||||
} else if ((target.height + offsetY) < minimumHeight) {
|
||||
target.height = minimumHeight;
|
||||
} else {
|
||||
target.height += offsetY;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: area9
|
||||
x: target.width - areaMarginSize
|
||||
y: target.height - areaMarginSize
|
||||
width: areaMarginSize
|
||||
height: areaMarginSize
|
||||
enabled: resizable
|
||||
hoverEnabled: true
|
||||
onEntered: cursorShape = Qt.SizeFDiagCursor;
|
||||
onExited: cursorShape = Qt.ArrowCursor;
|
||||
onPressed:
|
||||
(mouse) => {
|
||||
if (target) {
|
||||
__private.startPos = Qt.point(mouseX, mouseY);
|
||||
__private.fixedPos = Qt.point(target.x, target.y);
|
||||
}
|
||||
}
|
||||
onPositionChanged:
|
||||
(mouse) => {
|
||||
if (pressed && target) {
|
||||
let offsetX = mouse.x - __private.startPos.x;
|
||||
let offsetY = mouse.y - __private.startPos.y;
|
||||
target.x = __private.fixedPos.x;
|
||||
if (maximumWidth != Number.NaN && (target.width + offsetX) > maximumWidth) {
|
||||
target.width = maximumWidth;
|
||||
} else if ((target.width + offsetX) < minimumWidth) {
|
||||
target.width = minimumWidth;
|
||||
} else {
|
||||
target.width += offsetX;
|
||||
}
|
||||
|
||||
target.y = __private.fixedPos.y;
|
||||
if (maximumHeight != Number.NaN && (target.height + offsetY) > maximumHeight) {
|
||||
target.height = maximumHeight;
|
||||
} else if ((target.height + offsetY) < minimumHeight) {
|
||||
target.height = minimumHeight;
|
||||
} else {
|
||||
target.height += offsetY;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,138 @@
|
|||
import QtQuick 2.15
|
||||
import QtQuick.Templates 2.15 as T
|
||||
import DelegateUI 1.0
|
||||
|
||||
T.ScrollBar {
|
||||
id: control
|
||||
|
||||
property bool animationEnabled: DelTheme.animationEnabled
|
||||
property color colorBar: control.pressed ? DelTheme.DelScrollBar.colorBarActive :
|
||||
control.hovered ? DelTheme.DelScrollBar.colorBarHover :
|
||||
DelTheme.DelScrollBar.colorBar
|
||||
property color colorBg: control.pressed ? DelTheme.DelScrollBar.colorBgActive :
|
||||
control.hovered ? DelTheme.DelScrollBar.colorBgHover :
|
||||
DelTheme.DelScrollBar.colorBg
|
||||
property string contentDescription: ""
|
||||
|
||||
QtObject {
|
||||
id: __private
|
||||
property bool visible: control.hovered || control.pressed
|
||||
}
|
||||
|
||||
width: control.orientation == Qt.Vertical ? 10 : parent.width
|
||||
height: control.orientation == Qt.Horizontal ? 10 : parent.height
|
||||
anchors.right: control.orientation == Qt.Vertical ? parent.right : undefined
|
||||
anchors.bottom: control.orientation == Qt.Horizontal ? parent.bottom : undefined
|
||||
leftPadding: control.orientation == Qt.Horizontal ? (leftInset + 10) : leftInset
|
||||
rightPadding: control.orientation == Qt.Horizontal ? (rightInset + 10) : rightInset
|
||||
topPadding: control.orientation == Qt.Vertical ? (topInset + 10) : topInset
|
||||
bottomPadding: control.orientation == Qt.Vertical ? (bottomInset + 10) : bottomInset
|
||||
policy: T.ScrollBar.AlwaysOn
|
||||
visible: (control.policy != T.ScrollBar.AlwaysOff) && control.size !== 1
|
||||
contentItem: Item {
|
||||
Rectangle {
|
||||
width: {
|
||||
if (control.orientation == Qt.Vertical) {
|
||||
return __private.visible ? 6 : 2;
|
||||
} else {
|
||||
return parent.width;
|
||||
}
|
||||
}
|
||||
height: {
|
||||
if (control.orientation == Qt.Vertical) {
|
||||
return parent.height;
|
||||
} else {
|
||||
return __private.visible ? 6 : 2;
|
||||
}
|
||||
}
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
radius: control.orientation == Qt.Vertical ? width * 0.5 : height * 0.5
|
||||
color: control.colorBar
|
||||
opacity: {
|
||||
if (control.policy == T.ScrollBar.AlwaysOn) {
|
||||
return 1;
|
||||
} else if (control.policy == T.ScrollBar.AsNeeded) {
|
||||
return __private.visible ? 1 : 0;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on width { enabled: control.animationEnabled; NumberAnimation { duration: DelTheme.Primary.durationFast } }
|
||||
Behavior on height { enabled: control.animationEnabled; NumberAnimation { duration: DelTheme.Primary.durationFast } }
|
||||
Behavior on opacity { enabled: control.animationEnabled; NumberAnimation { duration: DelTheme.Primary.durationFast } }
|
||||
}
|
||||
}
|
||||
background: Rectangle {
|
||||
color: control.colorBg
|
||||
opacity: __private.visible ? 1 : 0
|
||||
|
||||
Behavior on opacity { enabled: control.animationEnabled; NumberAnimation { duration: DelTheme.Primary.durationFast } }
|
||||
}
|
||||
|
||||
component HoverIcon: DelIconText {
|
||||
signal clicked()
|
||||
property bool hovered: false
|
||||
|
||||
colorIcon: hovered ? DelTheme.DelScrollBar.colorIconHover : DelTheme.DelScrollBar.colorIcon
|
||||
opacity: __private.visible ? 1 : 0
|
||||
|
||||
Behavior on opacity { enabled: control.animationEnabled; NumberAnimation { duration: DelTheme.Primary.durationFast } }
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
onEntered: parent.hovered = true;
|
||||
onExited: parent.hovered = false;
|
||||
onClicked: parent.clicked();
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
active: control.orientation == Qt.Vertical
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.top: parent.top
|
||||
sourceComponent: HoverIcon {
|
||||
iconSize: parent.width
|
||||
iconSource: DelIcon.CaretUpOutlined
|
||||
onClicked: control.decrease();
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
active: control.orientation == Qt.Vertical
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.bottom: parent.bottom
|
||||
sourceComponent: HoverIcon {
|
||||
iconSize: parent.width
|
||||
iconSource: DelIcon.CaretDownOutlined
|
||||
onClicked: control.increase();
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
active: control.orientation == Qt.Horizontal
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.left: parent.left
|
||||
sourceComponent: HoverIcon {
|
||||
iconSize: parent.height
|
||||
iconSource: DelIcon.CaretLeftOutlined
|
||||
onClicked: control.decrease();
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
active: control.orientation == Qt.Horizontal
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.right: parent.right
|
||||
sourceComponent: HoverIcon {
|
||||
iconSize: parent.height
|
||||
iconSource: DelIcon.CaretRightOutlined
|
||||
onClicked: control.increase();
|
||||
}
|
||||
}
|
||||
|
||||
Accessible.role: Accessible.ScrollBar
|
||||
Accessible.description: control.contentDescription
|
||||
}
|
||||
|
|
@ -0,0 +1,196 @@
|
|||
import QtQuick 2.15
|
||||
import QtQuick.Templates 2.15 as T
|
||||
import DelegateUI 1.0
|
||||
|
||||
T.ComboBox {
|
||||
id: control
|
||||
|
||||
property bool animationEnabled: true
|
||||
property int hoverCursorShape: Qt.PointingHandCursor
|
||||
property bool tooltipVisible: false
|
||||
property bool loading: false
|
||||
property int defaulPopupMaxHeight: 240
|
||||
property color colorText: enabled ?
|
||||
popup.visible ? DelTheme.DelSelect.colorTextActive :
|
||||
DelTheme.DelSelect.colorText : DelTheme.DelSelect.colorTextDisabled
|
||||
property color colorBorder: enabled ?
|
||||
hovered ? DelTheme.DelSelect.colorBorderHover :
|
||||
DelTheme.DelSelect.colorBorder : DelTheme.DelSelect.colorBorderDisabled
|
||||
property color colorBg: enabled ? DelTheme.DelSelect.colorBg : DelTheme.DelSelect.colorBgDisabled
|
||||
|
||||
property int radiusBg: 6
|
||||
property int radiusPopupBg: 6
|
||||
property string contentDescription: ""
|
||||
|
||||
property Component indicatorDelegate: DelIconText {
|
||||
iconSize: 12
|
||||
iconSource: control.loading ? DelIcon.LoadingOutlined : DelIcon.DownOutlined
|
||||
|
||||
NumberAnimation on rotation {
|
||||
running: control.loading
|
||||
from: 0
|
||||
to: 360
|
||||
loops: Animation.Infinite
|
||||
duration: 1000
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on colorText { enabled: control.animationEnabled; ColorAnimation { duration: DelTheme.Primary.durationFast } }
|
||||
Behavior on colorBorder { enabled: control.animationEnabled; ColorAnimation { duration: DelTheme.Primary.durationFast } }
|
||||
Behavior on colorBg { enabled: control.animationEnabled; ColorAnimation { duration: DelTheme.Primary.durationFast } }
|
||||
|
||||
rightPadding: 8
|
||||
topPadding: 5
|
||||
bottomPadding: 5
|
||||
implicitWidth: implicitContentWidth + implicitIndicatorWidth + leftPadding + rightPadding
|
||||
implicitHeight: implicitContentHeight + topPadding + bottomPadding
|
||||
textRole: "label"
|
||||
valueRole: "value"
|
||||
objectName: "__DelSelect__"
|
||||
font {
|
||||
family: DelTheme.DelSelect.fontFamily
|
||||
pixelSize: DelTheme.DelSelect.fontSize
|
||||
}
|
||||
delegate: T.ItemDelegate { }
|
||||
indicator: Loader {
|
||||
x: control.width - width - control.rightPadding
|
||||
y: control.topPadding + (control.availableHeight - height) / 2
|
||||
sourceComponent: indicatorDelegate
|
||||
}
|
||||
contentItem: Text {
|
||||
leftPadding: 8
|
||||
rightPadding: control.indicator.width + control.spacing
|
||||
text: control.displayText
|
||||
font: control.font
|
||||
color: control.colorText
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
background: Rectangle {
|
||||
color: control.colorBg
|
||||
border.color: control.colorBorder
|
||||
border.width: control.visualFocus ? 2 : 1
|
||||
radius: control.radiusBg
|
||||
}
|
||||
popup: DelPopup {
|
||||
id: __popup
|
||||
y: control.height + 2
|
||||
implicitWidth: control.width
|
||||
implicitHeight: implicitContentHeight + topPadding + bottomPadding
|
||||
leftPadding: 4
|
||||
rightPadding: 4
|
||||
topPadding: 6
|
||||
bottomPadding: 6
|
||||
enter: Transition {
|
||||
NumberAnimation {
|
||||
property: 'opacity'
|
||||
from: 0.0
|
||||
to: 1.0
|
||||
easing.type: Easing.InOutQuad
|
||||
duration: control.animationEnabled ? DelTheme.Primary.durationMid : 0
|
||||
}
|
||||
NumberAnimation {
|
||||
property: 'height'
|
||||
from: 0
|
||||
to: __popup.implicitHeight
|
||||
easing.type: Easing.InOutQuad
|
||||
duration: control.animationEnabled ? DelTheme.Primary.durationMid : 0
|
||||
}
|
||||
}
|
||||
exit: Transition {
|
||||
NumberAnimation {
|
||||
property: 'opacity'
|
||||
from: 1.0
|
||||
to: 0.0
|
||||
easing.type: Easing.InOutQuad
|
||||
duration: control.animationEnabled ? DelTheme.Primary.durationMid : 0
|
||||
}
|
||||
NumberAnimation {
|
||||
property: 'height'
|
||||
to: 0
|
||||
easing.type: Easing.InOutQuad
|
||||
duration: control.animationEnabled ? DelTheme.Primary.durationMid : 0
|
||||
}
|
||||
}
|
||||
contentItem: ListView {
|
||||
id: __popupListView
|
||||
implicitHeight: Math.min(control.defaulPopupMaxHeight, contentHeight)
|
||||
clip: true
|
||||
model: control.popup.visible ? control.model : null
|
||||
currentIndex: control.highlightedIndex
|
||||
boundsBehavior: Flickable.StopAtBounds
|
||||
delegate: T.ItemDelegate {
|
||||
id: __popupDelegate
|
||||
|
||||
required property var modelData
|
||||
required property int index
|
||||
property alias model: __popupDelegate.modelData
|
||||
|
||||
width: __popupListView.width
|
||||
height: implicitContentHeight + topPadding + bottomPadding
|
||||
leftPadding: 8
|
||||
rightPadding: 8
|
||||
topPadding: 4
|
||||
bottomPadding: 4
|
||||
enabled: model.enabled ?? true
|
||||
contentItem: Text {
|
||||
text: __popupDelegate.model[control.textRole]
|
||||
color: __popupDelegate.enabled ? DelTheme.DelSelect.colorItemText : DelTheme.DelSelect.colorItemTextDisabled;
|
||||
font {
|
||||
family: DelTheme.DelSelect.fontFamily
|
||||
pixelSize: DelTheme.DelSelect.fontSize
|
||||
weight: highlighted ? Font.DemiBold : Font.Normal
|
||||
}
|
||||
elide: Text.ElideRight
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
}
|
||||
background: Rectangle {
|
||||
radius: 2
|
||||
color: {
|
||||
if (__popupDelegate.enabled)
|
||||
return highlighted ? DelTheme.DelSelect.colorItemBgActive :
|
||||
hovered ? DelTheme.DelSelect.colorItemBgHover :
|
||||
DelTheme.DelSelect.colorItemBg;
|
||||
else
|
||||
return DelTheme.DelSelect.colorItemBgDisabled;
|
||||
}
|
||||
|
||||
Behavior on color { enabled: control.animationEnabled; ColorAnimation { duration: DelTheme.Primary.durationFast } }
|
||||
}
|
||||
highlighted: control.highlightedIndex === index
|
||||
onClicked: {
|
||||
control.currentIndex = index;
|
||||
control.activated(index);
|
||||
control.popup.close();
|
||||
}
|
||||
|
||||
HoverHandler {
|
||||
cursorShape: control.hoverCursorShape
|
||||
}
|
||||
|
||||
Loader {
|
||||
y: __popupDelegate.height
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
active: control.tooltipVisible
|
||||
sourceComponent: DelToolTip {
|
||||
arrowVisible: false
|
||||
visible: __popupDelegate.hovered
|
||||
text: __popupDelegate.model[control.textRole]
|
||||
position: DelToolTip.Position_Bottom
|
||||
}
|
||||
}
|
||||
}
|
||||
T.ScrollBar.vertical: DelScrollBar { }
|
||||
|
||||
Behavior on height { enabled: control.animationEnabled; NumberAnimation { duration: DelTheme.Primary.durationFast } }
|
||||
}
|
||||
}
|
||||
|
||||
HoverHandler {
|
||||
cursorShape: control.hoverCursorShape
|
||||
}
|
||||
|
||||
Accessible.role: Accessible.ComboBox
|
||||
Accessible.name: control.displayText
|
||||
Accessible.description: control.contentDescription
|
||||
}
|
||||
|
|
@ -0,0 +1,273 @@
|
|||
import QtQuick 2.15
|
||||
import QtQuick.Templates 2.15 as T
|
||||
import DelegateUI 1.0
|
||||
|
||||
Item {
|
||||
id: control
|
||||
|
||||
enum SnapMode
|
||||
{
|
||||
NoSnap = 0,
|
||||
SnapAlways = 1,
|
||||
SnapOnRelease = 2
|
||||
}
|
||||
|
||||
signal firstMoved()
|
||||
signal firstReleased()
|
||||
signal secondMoved()
|
||||
signal secondReleased()
|
||||
|
||||
property bool animationEnabled: DelTheme.animationEnabled
|
||||
property int hoverCursorShape: Qt.PointingHandCursor
|
||||
property real min: 0
|
||||
property real max: 100
|
||||
property real stepSize: 0.0
|
||||
property var value: range ? [0, 0] : 0
|
||||
readonly property var currentValue: {
|
||||
if (__sliderLoader.item) {
|
||||
return range ? [__sliderLoader.item.first.value, __sliderLoader.item.second.value] : __sliderLoader.item.value;
|
||||
} else {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
property bool range: false
|
||||
property bool hovered: __sliderLoader.item ? __sliderLoader.item.hovered : false
|
||||
property int snapMode: DelSlider.NoSnap
|
||||
property int orientation: Qt.Horizontal
|
||||
property int radiusBg: 6
|
||||
property color colorBg: (enabled && hovered) ? DelTheme.DelSlider.colorBgHover : DelTheme.DelSlider.colorBg
|
||||
property color colorHandle: DelTheme.DelSlider.colorHandle
|
||||
property color colorTrack: {
|
||||
if (!enabled) return DelTheme.DelSlider.colorTrackDisabled;
|
||||
|
||||
if (DelTheme.isDark)
|
||||
return hovered ? DelTheme.DelSlider.colorTrackHoverDark : DelTheme.DelSlider.colorTrackDark;
|
||||
else
|
||||
return hovered ? DelTheme.DelSlider.colorTrackHover : DelTheme.DelSlider.colorTrack;
|
||||
}
|
||||
property Component handleToolTipDelegate: Item { }
|
||||
property Component handleDelegate: Rectangle {
|
||||
id: __handleItem
|
||||
x: {
|
||||
if (control.orientation == Qt.Horizontal) {
|
||||
return slider.leftPadding + visualPosition * (slider.availableWidth - width);
|
||||
} else {
|
||||
return slider.topPadding + (slider.availableWidth - width) * 0.5;
|
||||
}
|
||||
}
|
||||
y: {
|
||||
if (control.orientation == Qt.Horizontal) {
|
||||
return slider.topPadding + (slider.availableHeight - height) * 0.5;
|
||||
} else {
|
||||
return slider.leftPadding + visualPosition * (slider.availableHeight - height);
|
||||
}
|
||||
}
|
||||
implicitWidth: active ? 18 : 14
|
||||
implicitHeight: active ? 18 : 14
|
||||
radius: height * 0.5
|
||||
color: control.colorHandle
|
||||
border.color: {
|
||||
if (control.enabled) {
|
||||
if (DelTheme.isDark)
|
||||
return active ? DelTheme.DelSlider.colorHandleBorderHoverDark : DelTheme.DelSlider.colorHandleBorderDark;
|
||||
else
|
||||
return active ? DelTheme.DelSlider.colorHandleBorderHover : DelTheme.DelSlider.colorHandleBorder;
|
||||
} else {
|
||||
return DelTheme.DelSlider.colorHandleBorderDisabled;
|
||||
}
|
||||
}
|
||||
border.width: active ? 4 : 2
|
||||
|
||||
property bool down: pressed
|
||||
property bool active: __hoverHandler.hovered || down
|
||||
|
||||
Behavior on implicitWidth { enabled: control.animationEnabled; NumberAnimation { duration: DelTheme.Primary.durationFast } }
|
||||
Behavior on implicitHeight { enabled: control.animationEnabled; NumberAnimation { duration: DelTheme.Primary.durationFast } }
|
||||
Behavior on border.width { enabled: control.animationEnabled; NumberAnimation { duration: DelTheme.Primary.durationFast } }
|
||||
Behavior on color { enabled: control.animationEnabled; ColorAnimation { duration: DelTheme.Primary.durationFast } }
|
||||
Behavior on border.color { enabled: control.animationEnabled; ColorAnimation { duration: DelTheme.Primary.durationFast } }
|
||||
|
||||
HoverHandler {
|
||||
id: __hoverHandler
|
||||
cursorShape: control.hoverCursorShape
|
||||
}
|
||||
|
||||
Loader {
|
||||
sourceComponent: handleToolTipDelegate
|
||||
onLoaded: item.parent = __handleItem;
|
||||
property alias handleHovered: __hoverHandler.hovered
|
||||
property alias handlePressed: __handleItem.down
|
||||
}
|
||||
}
|
||||
property Component bgDelegate: Item {
|
||||
Rectangle {
|
||||
width: control.orientation == Qt.Horizontal ? parent.width : 4
|
||||
height: control.orientation == Qt.Horizontal ? 4 : parent.height
|
||||
anchors.horizontalCenter: control.orientation == Qt.Horizontal ? undefined : parent.horizontalCenter
|
||||
anchors.verticalCenter: control.orientation == Qt.Horizontal ? parent.verticalCenter : undefined
|
||||
radius: control.radiusBg
|
||||
color: control.colorBg
|
||||
|
||||
Behavior on color { enabled: control.animationEnabled; ColorAnimation { duration: DelTheme.Primary.durationFast } }
|
||||
|
||||
Rectangle {
|
||||
x: {
|
||||
if (control.orientation == Qt.Horizontal)
|
||||
return range ? (slider.first.visualPosition * parent.width) : 0;
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
y: {
|
||||
if (control.orientation == Qt.Horizontal)
|
||||
return 0;
|
||||
else
|
||||
return range ? (slider.second.visualPosition * parent.height) : slider.visualPosition * parent.height;
|
||||
}
|
||||
width: {
|
||||
if (control.orientation == Qt.Horizontal)
|
||||
return range ? (slider.second.visualPosition * parent.width - x) : slider.visualPosition * parent.width;
|
||||
else
|
||||
return parent.width;
|
||||
}
|
||||
height: {
|
||||
if (control.orientation == Qt.Horizontal)
|
||||
return parent.height;
|
||||
else
|
||||
return range ? (slider.first.visualPosition * parent.height - y) : ((1.0 - slider.visualPosition) * parent.height);
|
||||
}
|
||||
color: colorTrack
|
||||
radius: parent.radius
|
||||
|
||||
Behavior on color { enabled: control.animationEnabled; ColorAnimation { duration: DelTheme.Primary.durationFast } }
|
||||
}
|
||||
}
|
||||
}
|
||||
property string contentDescription: ""
|
||||
|
||||
onValueChanged: __fromValueUpdate();
|
||||
|
||||
function decrease(first = true) {
|
||||
if (__sliderLoader.item) {
|
||||
if (range) {
|
||||
if (first)
|
||||
__sliderLoader.item.first.decrease();
|
||||
else
|
||||
__sliderLoader.item.second.decrease();
|
||||
} else {
|
||||
__sliderLoader.item.decrease();
|
||||
}
|
||||
}
|
||||
}
|
||||
function increase(first = true) {
|
||||
if (range) {
|
||||
if (first)
|
||||
__sliderLoader.item.first.increase();
|
||||
else
|
||||
__sliderLoader.item.second.increase();
|
||||
} else {
|
||||
__sliderLoader.item.decrease();
|
||||
}
|
||||
}
|
||||
function __fromValueUpdate() {
|
||||
if (__sliderLoader.item) {
|
||||
if (range) {
|
||||
__sliderLoader.item.setValues(...value);
|
||||
} else {
|
||||
__sliderLoader.item.value = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: __sliderComponent
|
||||
|
||||
T.Slider {
|
||||
id: __control
|
||||
from: min
|
||||
to: max
|
||||
stepSize: control.stepSize
|
||||
orientation: control.orientation
|
||||
snapMode: {
|
||||
switch (control.snapMode) {
|
||||
case DelSlider.SnapAlways: return T.Slider.SnapAlways;
|
||||
case DelSlider.SnapOnRelease: return T.Slider.SnapOnRelease;
|
||||
default: return T.Slider.NoSnap;
|
||||
}
|
||||
}
|
||||
handle: Loader {
|
||||
sourceComponent: handleDelegate
|
||||
property alias slider: __control
|
||||
property alias visualPosition: __control.visualPosition
|
||||
property alias pressed: __control.pressed
|
||||
}
|
||||
background: Loader {
|
||||
sourceComponent: bgDelegate
|
||||
property alias slider: __control
|
||||
property alias visualPosition: __control.visualPosition
|
||||
}
|
||||
onMoved: control.firstMoved();
|
||||
onPressedChanged: {
|
||||
if (!pressed)
|
||||
control.firstReleased();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: __rangeSliderComponent
|
||||
|
||||
T.RangeSlider {
|
||||
id: __control
|
||||
from: min
|
||||
to: max
|
||||
stepSize: control.stepSize
|
||||
snapMode: {
|
||||
switch (control.snapMode) {
|
||||
case DelSlider.SnapAlways: return T.RangeSlider.SnapAlways;
|
||||
case DelSlider.SnapOnRelease: return T.RangeSlider.SnapOnRelease;
|
||||
default: return T.RangeSlider.NoSnap;
|
||||
}
|
||||
}
|
||||
orientation: control.orientation
|
||||
first.handle: Loader {
|
||||
sourceComponent: handleDelegate
|
||||
property alias slider: __control
|
||||
property alias visualPosition: __control.first.visualPosition
|
||||
property alias pressed: __control.first.pressed
|
||||
}
|
||||
first.onMoved: control.firstMoved();
|
||||
first.onPressedChanged: {
|
||||
if (!first.pressed)
|
||||
control.firstReleased();
|
||||
}
|
||||
second.handle: Loader {
|
||||
sourceComponent: handleDelegate
|
||||
property alias slider: __control
|
||||
property alias visualPosition: __control.second.visualPosition
|
||||
property alias pressed: __control.second.pressed
|
||||
}
|
||||
second.onMoved: control.secondMoved();
|
||||
second.onPressedChanged: {
|
||||
if (!second.pressed)
|
||||
control.secondReleased();
|
||||
}
|
||||
background: Loader {
|
||||
sourceComponent: bgDelegate
|
||||
property alias slider: __control
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: __sliderLoader
|
||||
anchors.fill: parent
|
||||
sourceComponent: control.range ? __rangeSliderComponent : __sliderComponent
|
||||
onLoaded: __fromValueUpdate();
|
||||
}
|
||||
|
||||
Accessible.role: Accessible.Slider
|
||||
Accessible.name: control.contentDescription
|
||||
Accessible.description: control.contentDescription
|
||||
Accessible.onIncreaseAction: increase();
|
||||
Accessible.onDecreaseAction: decrease();
|
||||
}
|
||||
|
|
@ -0,0 +1,193 @@
|
|||
import QtQuick 2.15
|
||||
import QtQuick.Templates 2.15 as T
|
||||
import DelegateUI 1.0
|
||||
|
||||
T.Switch {
|
||||
id: control
|
||||
|
||||
property bool animationEnabled: DelTheme.animationEnabled
|
||||
property bool effectEnabled: true
|
||||
property int hoverCursorShape: Qt.PointingHandCursor
|
||||
property bool loading: false
|
||||
property string checkedText: ""
|
||||
property string uncheckedText: ""
|
||||
property int checkedIconSource: 0
|
||||
property int uncheckedIconSource: 0
|
||||
property string contentDescription: ""
|
||||
property int radiusBg: __bg.height * 0.5
|
||||
property color colorHandle: DelTheme.DelSwitch.colorHandle
|
||||
property color colorBg: {
|
||||
if (!enabled)
|
||||
return checked ? DelTheme.DelSwitch.colorCheckedBgDisabled : DelTheme.DelSwitch.colorBgDisabled;
|
||||
|
||||
if (checked)
|
||||
return control.down ? DelTheme.DelSwitch.colorCheckedBgActive :
|
||||
control.hovered ? DelTheme.DelSwitch.colorCheckedBgHover :
|
||||
DelTheme.DelSwitch.colorCheckedBg;
|
||||
else
|
||||
return control.down ? DelTheme.DelSwitch.colorBgActive :
|
||||
control.hovered ? DelTheme.DelSwitch.colorBgHover :
|
||||
DelTheme.DelSwitch.colorBg;
|
||||
}
|
||||
property Component handleDelegate: Rectangle {
|
||||
radius: height * 0.5
|
||||
color: control.colorHandle
|
||||
|
||||
DelIconText {
|
||||
anchors.centerIn: parent
|
||||
iconSize: parent.height - 4
|
||||
iconSource: DelIcon.LoadingOutlined
|
||||
colorIcon: control.colorBg
|
||||
visible: control.loading
|
||||
transformOrigin: Item.Center
|
||||
|
||||
NumberAnimation on rotation {
|
||||
running: control.loading
|
||||
from: 0
|
||||
to: 360
|
||||
loops: Animation.Infinite
|
||||
duration: 1000
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
width: implicitIndicatorWidth + leftPadding + rightPadding
|
||||
height: implicitIndicatorHeight + topPadding + bottomPadding
|
||||
font {
|
||||
family: DelTheme.DelSwitch.fontFamily
|
||||
pixelSize: DelTheme.DelSwitch.fontSize - 2
|
||||
}
|
||||
indicator: Item {
|
||||
implicitWidth: __bg.width
|
||||
implicitHeight: __bg.height
|
||||
|
||||
Rectangle {
|
||||
id: __effect
|
||||
width: __bg.width
|
||||
height: __bg.height
|
||||
radius: __bg.radius
|
||||
anchors.centerIn: parent
|
||||
visible: control.effectEnabled
|
||||
color: "transparent"
|
||||
border.width: 0
|
||||
border.color: control.enabled ? DelTheme.DelSwitch.colorBgHover : "transparent"
|
||||
opacity: 0.2
|
||||
|
||||
ParallelAnimation {
|
||||
id: __animation
|
||||
onFinished: __effect.border.width = 0;
|
||||
NumberAnimation {
|
||||
target: __effect; property: "width"; from: __bg.width + 3; to: __bg.width + 8;
|
||||
duration: DelTheme.Primary.durationFast
|
||||
easing.type: Easing.OutQuart
|
||||
}
|
||||
NumberAnimation {
|
||||
target: __effect; property: "height"; from: __bg.height + 3; to: __bg.height + 8;
|
||||
duration: DelTheme.Primary.durationFast
|
||||
easing.type: Easing.OutQuart
|
||||
}
|
||||
NumberAnimation {
|
||||
target: __effect; property: "opacity"; from: 0.2; to: 0;
|
||||
duration: DelTheme.Primary.durationSlow
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: control
|
||||
function onReleased() {
|
||||
if (control.animationEnabled && control.effectEnabled) {
|
||||
__effect.border.width = 8;
|
||||
__animation.restart();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Rectangle {
|
||||
id: __bg
|
||||
width: Math.max(Math.max(checkedWidth, uncheckedWidth) + __handle.width, height * 2)
|
||||
height: hasText ? Math.max(checkedHeight, uncheckedHeight, 22) : 22
|
||||
anchors.centerIn: parent
|
||||
radius: control.radiusBg
|
||||
color: control.colorBg
|
||||
clip: true
|
||||
|
||||
property bool hasText: control.checkedIconSource !== 0 || control.uncheckedIconSource !== 0 ||
|
||||
control.checkedText.length !== 0 || control.uncheckedText.length !== 0
|
||||
property real checkedWidth: control.checkedIconSource == 0 ? __checkedText.width + 6 : __checkedIcon.width + 6
|
||||
property real uncheckedWidth: control.checkedIconSource == 0 ? __uncheckedText.width + 6 : __uncheckedIcon.width + 6
|
||||
property real checkedHeight: control.checkedIconSource == 0 ? __checkedText.height + 4 : __checkedIcon.height + 4
|
||||
property real uncheckedHeight: control.checkedIconSource == 0 ? __uncheckedText.height + 4 : __uncheckedIcon.height + 4
|
||||
|
||||
Behavior on color { enabled: control.animationEnabled; ColorAnimation { duration: DelTheme.Primary.durationMid } }
|
||||
|
||||
Text {
|
||||
id: __checkedText
|
||||
width: text.length === 0 ? 0 : Math.max(implicitWidth + 8, __uncheckedText.implicitWidth + 8)
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.right: __handle.left
|
||||
font: control.font
|
||||
text: control.checkedText
|
||||
color: control.colorHandle
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
visible: !__checkedIcon.visible
|
||||
}
|
||||
|
||||
Text {
|
||||
id: __uncheckedText
|
||||
width: text.length === 0 ? 0 : Math.max(implicitWidth + 8, __checkedText.implicitWidth + 8)
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.left: __handle.right
|
||||
font: control.font
|
||||
text: control.uncheckedText
|
||||
color: control.colorHandle
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
visible: !__uncheckedIcon.visible
|
||||
}
|
||||
|
||||
DelIconText {
|
||||
id: __checkedIcon
|
||||
width: text.length === 0 ? 0 : implicitWidth + 8
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.right: __handle.left
|
||||
iconSize: control.font.pixelSize
|
||||
iconSource: control.checkedIconSource
|
||||
colorIcon: control.colorHandle
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
visible: iconSource != 0
|
||||
}
|
||||
|
||||
DelIconText {
|
||||
id: __uncheckedIcon
|
||||
width: text.length === 0 ? 0 : implicitWidth + 8
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.left: __handle.right
|
||||
iconSize: control.font.pixelSize
|
||||
iconSource: control.uncheckedIconSource
|
||||
colorIcon: control.colorHandle
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
visible: iconSource != 0
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: __handle
|
||||
x: control.checked ? (parent.width - (control.pressed ? height + 6 : height) - 2) : 2
|
||||
width: control.pressed ? height + 6 : height
|
||||
height: parent.height - 4
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
sourceComponent: handleDelegate
|
||||
|
||||
Behavior on width { enabled: control.animationEnabled; NumberAnimation { duration: DelTheme.Primary.durationMid } }
|
||||
Behavior on x { enabled: control.animationEnabled; NumberAnimation { duration: DelTheme.Primary.durationMid } }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
HoverHandler {
|
||||
cursorShape: control.hoverCursorShape
|
||||
}
|
||||
|
||||
Accessible.role: Accessible.CheckBox
|
||||
Accessible.name: control.checked ? control.checkedText : control.uncheckedText
|
||||
Accessible.description: control.contentDescription
|
||||
Accessible.onToggleAction: control.toggle();
|
||||
}
|
||||
|
|
@ -0,0 +1,710 @@
|
|||
import QtQuick 2.15
|
||||
import QtQuick.Layouts 1.15
|
||||
import QtQuick.Templates 2.15 as T
|
||||
import DelegateUI 1.0
|
||||
|
||||
Item {
|
||||
id: control
|
||||
clip: true
|
||||
|
||||
enum TabPosition
|
||||
{
|
||||
Position_Top = 0,
|
||||
Position_Bottom = 1,
|
||||
Position_Left = 2,
|
||||
Position_Right = 3
|
||||
}
|
||||
|
||||
enum TabType
|
||||
{
|
||||
Type_Default = 0,
|
||||
Type_Card = 1,
|
||||
Type_CardEditable = 2
|
||||
}
|
||||
|
||||
enum TabSize
|
||||
{
|
||||
Size_Auto = 0,
|
||||
Size_Fixed = 1
|
||||
}
|
||||
|
||||
property bool animationEnabled: DelTheme.animationEnabled
|
||||
property var initModel: []
|
||||
property alias count: __tabModel.count
|
||||
property alias currentIndex: __tabView.currentIndex
|
||||
property int tabType: DelTabView.Type_Default
|
||||
property int tabSize: DelTabView.Size_Auto
|
||||
property int tabPosition: DelTabView.Position_Top
|
||||
property bool tabCentered: false
|
||||
property bool tabCardMovable: true
|
||||
property int defaultTabWidth: 80
|
||||
property int defaultTabHeight: DelTheme.DelTabView.fontSize + 16
|
||||
property int defaultTabSpacing: 2
|
||||
property int defaultTabBgRadius: DelTheme.DelTabView.radiusTabBg
|
||||
property int defaultHighlightWidth: __private.isHorizontal ? 30 : 20
|
||||
property var addTabCallback:
|
||||
() => {
|
||||
append({ title: `New Tab ${__tabView.count + 1}` });
|
||||
positionViewAtEnd();
|
||||
}
|
||||
property Component addButtonDelegate: DelCaptionButton {
|
||||
id: __addButton
|
||||
iconSize: DelTheme.DelTabView.fontSize
|
||||
iconSource: DelIcon.PlusOutlined
|
||||
colorIcon: DelTheme.DelTabView.colorTabCloseHover
|
||||
background: Rectangle {
|
||||
radius: DelTheme.DelTabView.radiusButton
|
||||
color: __addButton.colorBg
|
||||
}
|
||||
onClicked: addTabCallback();
|
||||
}
|
||||
property Component tabDelegate: tabType == DelTabView.Type_Default ? __defaultTabDelegate : __cardTabDelegate
|
||||
property Component contentDelegate: Item { }
|
||||
property Component highlightDelegate: Item {
|
||||
Loader {
|
||||
id: __highlight
|
||||
width: __private.isHorizontal ? defaultHighlightWidth : 2
|
||||
height: __private.isHorizontal ? 2 : defaultHighlightWidth
|
||||
anchors {
|
||||
bottom: control.tabPosition == DelTabView.Position_Top ? parent.bottom : undefined
|
||||
right: control.tabPosition == DelTabView.Position_Left ? parent.right : undefined
|
||||
}
|
||||
state: __content.state
|
||||
states: [
|
||||
State {
|
||||
name: "top"
|
||||
AnchorChanges {
|
||||
target: __highlight
|
||||
anchors.top: undefined
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.left: undefined
|
||||
anchors.right: undefined
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.verticalCenter: undefined
|
||||
}
|
||||
},
|
||||
State {
|
||||
name: "bottom"
|
||||
AnchorChanges {
|
||||
target: __highlight
|
||||
anchors.top: parent.top
|
||||
anchors.bottom: undefined
|
||||
anchors.left: undefined
|
||||
anchors.right: undefined
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.verticalCenter: undefined
|
||||
}
|
||||
},
|
||||
State {
|
||||
name: "left"
|
||||
AnchorChanges {
|
||||
target: __highlight
|
||||
anchors.top: undefined
|
||||
anchors.bottom: undefined
|
||||
anchors.left: undefined
|
||||
anchors.right: parent.right
|
||||
anchors.horizontalCenter: undefined
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
},
|
||||
State {
|
||||
name: "right"
|
||||
AnchorChanges {
|
||||
target: __highlight
|
||||
anchors.top: undefined
|
||||
anchors.bottom: undefined
|
||||
anchors.left: parent.left
|
||||
anchors.right: undefined
|
||||
anchors.horizontalCenter: undefined
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
]
|
||||
active: control.tabType === DelTabView.Type_Default
|
||||
sourceComponent: Rectangle {
|
||||
color: DelTheme.isDark ? DelTheme.DelTabView.colorHightlightDark : DelTheme.DelTabView.colorHightlight
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onInitModelChanged: {
|
||||
clear();
|
||||
for (const object of initModel) {
|
||||
append(object);
|
||||
}
|
||||
}
|
||||
|
||||
function flick(xVelocity: real, yVelocity: real) {
|
||||
__tabView.flick(xVelocity, yVelocity);
|
||||
}
|
||||
|
||||
function positionViewAtBeginning() {
|
||||
__tabView.positionViewAtBeginning();
|
||||
}
|
||||
|
||||
function positionViewAtIndex(index, mode) {
|
||||
__tabView.positionViewAtIndex(index, mode);
|
||||
}
|
||||
|
||||
function positionViewAtEnd() {
|
||||
__tabView.positionViewAtEnd();
|
||||
}
|
||||
|
||||
function get(index) {
|
||||
return __tabModel.get(index);
|
||||
}
|
||||
|
||||
function set(index, object) {
|
||||
//默认为true
|
||||
if (object.editable === undefined)
|
||||
object.editable = true;
|
||||
__tabModel.set(index, object);
|
||||
}
|
||||
|
||||
function setProperty(index, propertyName, value) {
|
||||
__tabModel.setProperty(index, propertyName, value);
|
||||
}
|
||||
|
||||
function move(from, to, count = 1) {
|
||||
__tabModel.move(from, to, count);
|
||||
}
|
||||
|
||||
function insert(index, object) {
|
||||
//默认为true
|
||||
if (object.editable === undefined)
|
||||
object.editable = true;
|
||||
__tabModel.insert(index, object);
|
||||
}
|
||||
|
||||
function append(object) {
|
||||
//默认为true
|
||||
if (object.editable === undefined)
|
||||
object.editable = true;
|
||||
__tabModel.append(object);
|
||||
}
|
||||
|
||||
function remove(index, count = 1) {
|
||||
__tabModel.remove(index, count);
|
||||
}
|
||||
|
||||
function clear() {
|
||||
__tabModel.clear();
|
||||
}
|
||||
|
||||
Component {
|
||||
id: __defaultTabDelegate
|
||||
|
||||
DelIconButton {
|
||||
id: __tabItem
|
||||
width: (!__private.isHorizontal && control.tabSize == DelTabView.Size_Auto) ? Math.max(__private.tabMaxWidth, tabWidth) : tabWidth
|
||||
height: tabHeight
|
||||
leftPadding: 5
|
||||
rightPadding: 5
|
||||
iconSize: tabIconSize
|
||||
iconSource: tabIcon
|
||||
text: tabTitle
|
||||
effectEnabled: false
|
||||
colorBg: "transparent"
|
||||
colorBorder: "transparent"
|
||||
colorText: {
|
||||
if (isCurrent) {
|
||||
return DelTheme.isDark ? DelTheme.DelTabView.colorHightlightDark : DelTheme.DelTabView.colorHightlight;
|
||||
} else {
|
||||
return down ? DelTheme.DelTabView.colorTabActive :
|
||||
hovered ? DelTheme.DelTabView.colorTabHover :
|
||||
DelTheme.DelTabView.colorTab;
|
||||
}
|
||||
}
|
||||
font {
|
||||
family: DelTheme.DelTabView.fontFamily
|
||||
pixelSize: DelTheme.DelTabView.fontSize
|
||||
}
|
||||
contentItem: Item {
|
||||
implicitWidth: control.tabSize == DelTabView.Size_Auto ? (__text.width + calcIconWidth) : __tabItem.tabFixedWidth
|
||||
implicitHeight: Math.max(__icon.implicitHeight, __text.implicitHeight)
|
||||
|
||||
property int calcIconWidth: iconSource == 0 ? 0 : (__icon.implicitWidth + __tabItem.tabIconSpacing)
|
||||
|
||||
DelIconText {
|
||||
id: __icon
|
||||
width: iconSource == 0 ? 0 : implicitWidth
|
||||
anchors.left: parent.left
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
color: __tabItem.colorIcon
|
||||
iconSize: __tabItem.iconSize
|
||||
iconSource: __tabItem.iconSource
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
|
||||
Behavior on color { enabled: control.animationEnabled; ColorAnimation { duration: DelTheme.Primary.durationFast } }
|
||||
}
|
||||
|
||||
Text {
|
||||
id: __text
|
||||
width: control.tabSize == DelTabView.Size_Auto ? implicitWidth :
|
||||
Math.max(0, __tabItem.tabFixedWidth - 5 - parent.calcIconWidth)
|
||||
anchors.left: __icon.right
|
||||
anchors.leftMargin: __icon.iconSource == 0 ? 0 : __tabItem.tabIconSpacing
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: __tabItem.text
|
||||
font: __tabItem.font
|
||||
color: __tabItem.colorText
|
||||
elide: Text.ElideRight
|
||||
|
||||
Behavior on color { enabled: control.animationEnabled; ColorAnimation { duration: DelTheme.Primary.durationFast } }
|
||||
}
|
||||
}
|
||||
onTabWidthChanged: {
|
||||
if (index == 0)
|
||||
__private.tabMaxWidth = 0;
|
||||
__private.tabMaxWidth = Math.max(__private.tabMaxWidth, __tabItem.tabWidth);
|
||||
}
|
||||
onClicked: __tabView.currentIndex = index;
|
||||
|
||||
required property int index
|
||||
required property var model
|
||||
property alias modelData: __tabItem.model
|
||||
property bool isCurrent: __tabView.currentIndex === index
|
||||
property string tabKey: modelData.key || ""
|
||||
property int tabIcon: modelData.icon || 0
|
||||
property int tabIconSize: modelData.iconSize || DelTheme.DelTabView.fontSize
|
||||
property int tabIconSpacing: modelData.iconSpacing || 5
|
||||
property string tabTitle: modelData.title || ""
|
||||
property int tabFixedWidth: modelData.tabWidth || defaultTabWidth
|
||||
property int tabWidth: control.tabSize == DelTabView.Size_Auto ? (implicitContentWidth + leftPadding + rightPadding) :
|
||||
implicitContentWidth
|
||||
property int tabHeight: modelData.tabHeight || defaultTabHeight
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: __cardTabDelegate
|
||||
|
||||
Item {
|
||||
id: __tabContainer
|
||||
width: __tabItem.width
|
||||
height: __tabItem.height
|
||||
|
||||
required property int index
|
||||
required property var model
|
||||
property alias modelData: __tabContainer.model
|
||||
property alias tabItem: __tabItem
|
||||
property bool isCurrent: __tabView.currentIndex === index
|
||||
property string tabKey: modelData.key || ""
|
||||
property int tabIcon: modelData.icon || 0
|
||||
property int tabIconSize: modelData.iconSize || DelTheme.DelTabView.fontSize
|
||||
property int tabIconSpacing: modelData.iconSpacing || 5
|
||||
property string tabTitle: modelData.title || ""
|
||||
property int tabFixedWidth: modelData.tabWidth || defaultTabWidth
|
||||
property int tabWidth: __tabItem.calcWidth
|
||||
property int tabHeight: modelData.tabHeight || defaultTabHeight
|
||||
|
||||
property bool tabEditable: modelData.editable && control.tabType == DelTabView.Type_CardEditable
|
||||
|
||||
onTabWidthChanged: {
|
||||
if (__private.needResetTabMaxWidth) {
|
||||
__private.needResetTabMaxWidth = false;
|
||||
__private.tabMaxWidth = 0;
|
||||
}
|
||||
__private.tabMaxWidth = Math.max(__private.tabMaxWidth, __tabItem.calcWidth);
|
||||
}
|
||||
|
||||
DelRectangle {
|
||||
id: __tabItem
|
||||
width: (!__private.isHorizontal && control.tabSize == DelTabView.Size_Auto) ? Math.max(__private.tabMaxWidth, tabWidth) : tabWidth
|
||||
height: tabHeight
|
||||
z: __dragHandler.drag.active ? 1 : 0
|
||||
color: {
|
||||
if (DelTheme.isDark)
|
||||
return isCurrent ? DelTheme.DelTabView.colorTabCardBgCheckedDark : DelTheme.DelTabView.colorTabCardBgDark;
|
||||
else
|
||||
return isCurrent ? DelTheme.DelTabView.colorTabCardBgChecked : DelTheme.DelTabView.colorTabCardBg;
|
||||
}
|
||||
border.color: DelTheme.DelTabView.colorTabCardBorder
|
||||
topLeftRadius: control.tabPosition == DelTabView.Position_Top || control.tabPosition == DelTabView.Position_Left ? defaultTabBgRadius : 0
|
||||
topRightRadius: control.tabPosition == DelTabView.Position_Top || control.tabPosition == DelTabView.Position_Right ? defaultTabBgRadius : 0
|
||||
bottomLeftRadius: control.tabPosition == DelTabView.Position_Bottom || control.tabPosition == DelTabView.Position_Left ? defaultTabBgRadius : 0
|
||||
bottomRightRadius: control.tabPosition == DelTabView.Position_Bottom || control.tabPosition == DelTabView.Position_Right ? defaultTabBgRadius : 0
|
||||
|
||||
property int calcIconWidth: __icon.iconSource == 0 ? 0 : (__icon.implicitWidth + __tabContainer.tabIconSpacing)
|
||||
property int calcCloseWidth: __close.visible ? (__close.implicitWidth + 5) : 0
|
||||
property real calcWidth: control.tabSize == DelTabView.Size_Auto ? (__text.width + calcIconWidth + calcCloseWidth + 10)
|
||||
: __tabContainer.tabFixedWidth
|
||||
property real calcHeight: Math.max(__icon.implicitHeight, __text.implicitHeight, __close.height)
|
||||
property color colorText: {
|
||||
if (isCurrent) {
|
||||
return DelTheme.isDark ? DelTheme.DelTabView.colorHightlightDark : DelTheme.DelTabView.colorHightlight;
|
||||
} else {
|
||||
return down ? DelTheme.DelTabView.colorTabActive :
|
||||
hovered ? DelTheme.DelTabView.colorTabHover :
|
||||
DelTheme.DelTabView.colorTab;
|
||||
}
|
||||
}
|
||||
property bool down: false
|
||||
property bool hovered: false
|
||||
|
||||
Behavior on color { enabled: control.animationEnabled; ColorAnimation { duration: DelTheme.Primary.durationMid } }
|
||||
|
||||
MouseArea {
|
||||
id: __dragHandler
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
drag.target: control.tabCardMovable ? __tabItem : null
|
||||
drag.axis: __private.isHorizontal ? Drag.XAxis : Drag.YAxis
|
||||
onEntered: __tabItem.hovered = true;
|
||||
onExited: __tabItem.hovered = false;
|
||||
onClicked: __tabView.currentIndex = index;
|
||||
onPressed:
|
||||
(mouse) => {
|
||||
__tabView.currentIndex = index;
|
||||
__tabItem.down = true;
|
||||
|
||||
if (control.tabCardMovable) {
|
||||
const pos = __tabView.mapFromItem(__tabContainer, 0, 0);
|
||||
__tabItem.parent = __tabView;
|
||||
__tabItem.x = pos.x;
|
||||
__tabItem.y = pos.y;
|
||||
}
|
||||
}
|
||||
onPositionChanged: move();
|
||||
onReleased: {
|
||||
__keepMovingTimer.stop();
|
||||
__tabItem.down = false;
|
||||
__tabItem.parent = __tabContainer;
|
||||
__tabItem.x = __tabItem.y = 0;
|
||||
__tabView.forceLayout();
|
||||
}
|
||||
|
||||
function move() {
|
||||
if (pressed && control.tabCardMovable) {
|
||||
if (!__keepMovingTimer.running)
|
||||
__keepMovingTimer.restart();
|
||||
const pos = __tabView.mapFromItem(__tabItem, width * 0.5, height * 0.5);
|
||||
const item = __tabView.itemAt(pos.x + __tabView.contentX + 1, pos.y + __tabView.contentY + 1);
|
||||
if (item) {
|
||||
if (item.index !== __tabContainer.index) {
|
||||
__tabModel.move(item.index, __tabContainer.index, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: __keepMovingTimer
|
||||
repeat: true
|
||||
interval: 100
|
||||
onTriggered: __dragHandler.move();
|
||||
}
|
||||
}
|
||||
|
||||
DelIconText {
|
||||
id: __icon
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 5
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
color: __tabItem.colorText
|
||||
iconSize: tabIconSize
|
||||
iconSource: tabIcon
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
|
||||
Behavior on color { enabled: control.animationEnabled; ColorAnimation { duration: DelTheme.Primary.durationFast } }
|
||||
}
|
||||
|
||||
Text {
|
||||
id: __text
|
||||
width: control.tabSize == DelTabView.Size_Auto ? implicitWidth :
|
||||
Math.max(0, __tabContainer.tabFixedWidth - 5 - __tabItem.calcIconWidth - __tabItem.calcCloseWidth)
|
||||
anchors.left: __icon.right
|
||||
anchors.leftMargin: __icon.iconSource == 0 ? 0 : __tabContainer.tabIconSpacing
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: tabTitle
|
||||
font {
|
||||
family: DelTheme.DelTabView.fontFamily
|
||||
pixelSize: DelTheme.DelTabView.fontSize
|
||||
}
|
||||
color: __tabItem.colorText
|
||||
elide: Text.ElideRight
|
||||
|
||||
Behavior on color { enabled: control.animationEnabled; ColorAnimation { duration: DelTheme.Primary.durationFast } }
|
||||
}
|
||||
|
||||
DelCaptionButton {
|
||||
id: __close
|
||||
visible: __tabContainer.tabEditable
|
||||
enabled: visible
|
||||
implicitWidth: visible ? (implicitContentWidth + leftPadding + rightPadding) : 0
|
||||
topPadding: 0
|
||||
bottomPadding: 0
|
||||
leftPadding: 2
|
||||
rightPadding: 2
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 5
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
iconSize: tabIconSize
|
||||
iconSource: DelIcon.CloseOutlined
|
||||
colorIcon: hovered ? DelTheme.DelTabView.colorTabCloseHover : DelTheme.DelTabView.colorTabClose
|
||||
onClicked: {
|
||||
control.remove(index);
|
||||
}
|
||||
|
||||
HoverHandler {
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: control
|
||||
function onTabTypeChanged() {
|
||||
__private.needResetTabMaxWidth = true;
|
||||
}
|
||||
function onTabSizeChanged() {
|
||||
__private.needResetTabMaxWidth = true;
|
||||
}
|
||||
}
|
||||
|
||||
QtObject {
|
||||
id: __private
|
||||
property bool isHorizontal: control.tabPosition == DelTabView.Position_Top || control.tabPosition == DelTabView.Position_Bottom
|
||||
property int tabMaxWidth: 0
|
||||
property bool needResetTabMaxWidth: false
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: __tabView
|
||||
onWheel:
|
||||
(wheel) => {
|
||||
if (__private.isHorizontal)
|
||||
__tabView.flick(wheel.angleDelta.y * 6.5, 0);
|
||||
else
|
||||
__tabView.flick(0, wheel.angleDelta.y * 6.5);
|
||||
}
|
||||
}
|
||||
|
||||
ListView {
|
||||
id: __tabView
|
||||
width: {
|
||||
if (__private.isHorizontal) {
|
||||
const maxWidth = control.width - (__addButtonLoader.width + 5) * (control.tabCentered ? 2 : 1);
|
||||
return (control.tabCentered ? Math.min(contentWidth, maxWidth) : maxWidth);
|
||||
} else {
|
||||
return __private.tabMaxWidth;
|
||||
}
|
||||
}
|
||||
height: {
|
||||
if (__private.isHorizontal) {
|
||||
return defaultTabHeight;
|
||||
} else {
|
||||
const maxHeight = control.height - (__addButtonLoader.height + 5) * (control.tabCentered ? 2 : 1);
|
||||
return (control.tabCentered ? Math.min(contentHeight, maxHeight) : maxHeight)
|
||||
}
|
||||
}
|
||||
clip: true
|
||||
onContentWidthChanged: if (__private.isHorizontal) cacheBuffer = contentWidth;
|
||||
onContentHeightChanged: if (!__private.isHorizontal) cacheBuffer = contentHeight;
|
||||
spacing: defaultTabSpacing
|
||||
move: Transition {
|
||||
NumberAnimation { properties: "x,y"; duration: control.animationEnabled ? DelTheme.Primary.durationMid : 0 }
|
||||
}
|
||||
model: ListModel { id: __tabModel }
|
||||
delegate: tabDelegate
|
||||
highlight: highlightDelegate
|
||||
highlightMoveDuration: control.animationEnabled ? DelTheme.Primary.durationMid : 0
|
||||
orientation: __private.isHorizontal ? Qt.Horizontal : Qt.Vertical
|
||||
boundsBehavior: Flickable.StopAtBounds
|
||||
state: control.tabCentered ? (__content.state + "Center") : __content.state
|
||||
states: [
|
||||
State {
|
||||
name: "top"
|
||||
AnchorChanges {
|
||||
target: __tabView
|
||||
anchors.top: control.top
|
||||
anchors.bottom: undefined
|
||||
anchors.left: control.left
|
||||
anchors.right: undefined
|
||||
anchors.horizontalCenter: undefined
|
||||
anchors.verticalCenter: undefined
|
||||
}
|
||||
},
|
||||
State {
|
||||
name: "topCenter"
|
||||
AnchorChanges {
|
||||
target: __tabView
|
||||
anchors.top: control.top
|
||||
anchors.bottom: undefined
|
||||
anchors.left: undefined
|
||||
anchors.right: undefined
|
||||
anchors.horizontalCenter: control.horizontalCenter
|
||||
anchors.verticalCenter: undefined
|
||||
}
|
||||
},
|
||||
State {
|
||||
name: "bottom"
|
||||
AnchorChanges {
|
||||
target: __tabView
|
||||
anchors.top: undefined
|
||||
anchors.bottom: control.bottom
|
||||
anchors.left: control.left
|
||||
anchors.right: undefined
|
||||
anchors.horizontalCenter: undefined
|
||||
anchors.verticalCenter: undefined
|
||||
}
|
||||
},
|
||||
State {
|
||||
name: "bottomCenter"
|
||||
AnchorChanges {
|
||||
target: __tabView
|
||||
anchors.top: undefined
|
||||
anchors.bottom: control.bottom
|
||||
anchors.left: undefined
|
||||
anchors.right: undefined
|
||||
anchors.horizontalCenter: control.horizontalCenter
|
||||
anchors.verticalCenter: undefined
|
||||
}
|
||||
},
|
||||
State {
|
||||
name: "left"
|
||||
AnchorChanges {
|
||||
target: __tabView
|
||||
anchors.top: control.top
|
||||
anchors.bottom: undefined
|
||||
anchors.left: control.left
|
||||
anchors.right: undefined
|
||||
anchors.horizontalCenter: undefined
|
||||
anchors.verticalCenter: undefined
|
||||
}
|
||||
},
|
||||
State {
|
||||
name: "leftCenter"
|
||||
AnchorChanges {
|
||||
target: __tabView
|
||||
anchors.top: undefined
|
||||
anchors.bottom: undefined
|
||||
anchors.left: control.left
|
||||
anchors.right: undefined
|
||||
anchors.horizontalCenter: undefined
|
||||
anchors.verticalCenter: control.verticalCenter
|
||||
}
|
||||
},
|
||||
State {
|
||||
name: "right"
|
||||
AnchorChanges {
|
||||
target: __tabView
|
||||
anchors.top: control.top
|
||||
anchors.bottom: undefined
|
||||
anchors.left: undefined
|
||||
anchors.right: control.right
|
||||
anchors.horizontalCenter: undefined
|
||||
anchors.verticalCenter: undefined
|
||||
}
|
||||
},
|
||||
State {
|
||||
name: "rightCenter"
|
||||
AnchorChanges {
|
||||
target: __tabView
|
||||
anchors.top: undefined
|
||||
anchors.bottom: undefined
|
||||
anchors.left: undefined
|
||||
anchors.right: control.right
|
||||
anchors.horizontalCenter: undefined
|
||||
anchors.verticalCenter: control.verticalCenter
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: __addButtonLoader
|
||||
x: {
|
||||
switch (tabPosition) {
|
||||
case DelTabView.Position_Top:
|
||||
case DelTabView.Position_Bottom:
|
||||
return Math.min(__tabView.x + __tabView.contentWidth + 5, control.width - width);
|
||||
case DelTabView.Position_Left:
|
||||
return Math.max(0, __tabView.width - width);
|
||||
case DelTabView.Position_Right:
|
||||
return Math.max(0, __tabView.x);
|
||||
}
|
||||
}
|
||||
y: {
|
||||
switch (tabPosition) {
|
||||
case DelTabView.Position_Top:
|
||||
return Math.max(0, __tabView.y + __tabView.height - height);
|
||||
case DelTabView.Position_Bottom:
|
||||
return __tabView.y;
|
||||
case DelTabView.Position_Left:
|
||||
case DelTabView.Position_Right:
|
||||
return Math.min(__tabView.y + __tabView.contentHeight + 5, control.height - height);
|
||||
}
|
||||
}
|
||||
z: 10
|
||||
sourceComponent: addButtonDelegate
|
||||
}
|
||||
|
||||
Item {
|
||||
id: __content
|
||||
state: {
|
||||
switch (tabPosition) {
|
||||
case DelTabView.Position_Top: return "top";
|
||||
case DelTabView.Position_Bottom: return "bottom";
|
||||
case DelTabView.Position_Left: return "left";
|
||||
case DelTabView.Position_Right: return "right";
|
||||
}
|
||||
}
|
||||
states: [
|
||||
State {
|
||||
name: "top"
|
||||
AnchorChanges {
|
||||
target: __content
|
||||
anchors.top: __tabView.bottom
|
||||
anchors.bottom: control.bottom
|
||||
anchors.left: control.left
|
||||
anchors.right: control.right
|
||||
}
|
||||
},
|
||||
State {
|
||||
name: "bottom"
|
||||
AnchorChanges {
|
||||
target: __content
|
||||
anchors.top: control.top
|
||||
anchors.bottom: __tabView.top
|
||||
anchors.left: control.left
|
||||
anchors.right: control.right
|
||||
}
|
||||
},
|
||||
State {
|
||||
name: "left"
|
||||
AnchorChanges {
|
||||
target: __content
|
||||
anchors.top: control.top
|
||||
anchors.bottom: control.bottom
|
||||
anchors.left: __tabView.right
|
||||
anchors.right: control.right
|
||||
}
|
||||
},
|
||||
State {
|
||||
name: "right"
|
||||
AnchorChanges {
|
||||
target: __content
|
||||
anchors.top: control.top
|
||||
anchors.bottom: control.bottom
|
||||
anchors.left: control.left
|
||||
anchors.right: __tabView.left
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
Repeater {
|
||||
model: __tabModel
|
||||
delegate: Loader {
|
||||
id: __contentLoader
|
||||
anchors.fill: parent
|
||||
sourceComponent: contentDelegate
|
||||
visible: __tabView.currentIndex === index
|
||||
required property int index
|
||||
required property var model
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,986 @@
|
|||
import QtQuick 2.15
|
||||
import QtQuick.Templates 2.15 as T
|
||||
import Qt.labs.qmlmodels 1.0
|
||||
import DelegateUI 1.0
|
||||
|
||||
DelRectangle {
|
||||
id: control
|
||||
|
||||
clip: true
|
||||
color: DelTheme.Primary.colorBgBase
|
||||
topLeftRadius : 6
|
||||
topRightRadius: 6
|
||||
|
||||
columnHeaderTitleFont {
|
||||
family: DelTheme.DelTableView.fontFamily
|
||||
pixelSize: DelTheme.DelTableView.fontSize
|
||||
}
|
||||
rowHeaderTitleFont {
|
||||
family: DelTheme.DelTableView.fontFamily
|
||||
pixelSize: DelTheme.DelTableView.fontSize
|
||||
}
|
||||
|
||||
property bool animationEnabled: DelTheme.animationEnabled
|
||||
property bool alternatingRow: false
|
||||
property int defaultColumnHeaderWidth: 100
|
||||
property int defaultColumnHeaderHeight: 40
|
||||
property int defaultRowHeaderWidth: 40
|
||||
property int defaultRowHeaderHeight: 40
|
||||
property bool columnGridVisible: false
|
||||
property bool rowGridVisible: false
|
||||
property real minimumRowHeight: 40
|
||||
property real maximumRowHeight: Number.NaN
|
||||
property var initModel: []
|
||||
property var columns: []
|
||||
property var checkedKeys: []
|
||||
|
||||
property color colorGridLine: DelTheme.DelTableView.colorGridLine
|
||||
|
||||
property bool columnHeaderVisible: true
|
||||
property font columnHeaderTitleFont
|
||||
property color colorColumnHeaderTitle: DelTheme.DelTableView.colorColumnTitle
|
||||
property color colorColumnHeaderBg: DelTheme.DelTableView.colorColumnHeaderBg
|
||||
|
||||
property bool rowHeaderVisible: true
|
||||
property font rowHeaderTitleFont
|
||||
property color colorRowHeaderTitle: DelTheme.DelTableView.colorRowTitle
|
||||
property color colorRowHeaderBg: DelTheme.DelTableView.colorRowHeaderBg
|
||||
|
||||
property color colorResizeBlockBg: DelTheme.DelTableView.colorResizeBlockBg
|
||||
|
||||
property Component columnHeaderDelegate: Item {
|
||||
id: __columnHeaderDelegate
|
||||
property string align: headerData.align ?? 'center'
|
||||
property string selectionType: headerData.selectionType ?? ''
|
||||
property var sorter: headerData.sorter
|
||||
property var sortDirections: headerData.sortDirections ?? []
|
||||
property var onFilter: headerData.onFilter
|
||||
|
||||
Text {
|
||||
anchors {
|
||||
left: __checkBoxLoader.active ? __checkBoxLoader.right : parent.left
|
||||
leftMargin: __checkBoxLoader.active ? 0 : 10
|
||||
right: parent.right
|
||||
rightMargin: 10
|
||||
top: parent.top
|
||||
topMargin: 4
|
||||
bottom: parent.bottom
|
||||
bottomMargin: 4
|
||||
}
|
||||
font: control.columnHeaderTitleFont
|
||||
text: headerData.title
|
||||
color: control.colorColumnHeaderTitle
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
horizontalAlignment: {
|
||||
if (__columnHeaderDelegate.align == 'left')
|
||||
return Text.AlignLeft;
|
||||
else if (__columnHeaderDelegate.align == 'right')
|
||||
return Text.AlignRight;
|
||||
else
|
||||
return Text.AlignHCenter;
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
enabled: __sorterLoader.active
|
||||
hoverEnabled: true
|
||||
height: parent.height
|
||||
anchors.left: __checkBoxLoader.right
|
||||
anchors.right: __sorterLoader.right
|
||||
onEntered: cursorShape = Qt.PointingHandCursor;
|
||||
onExited: cursorShape = Qt.ArrowCursor;
|
||||
onClicked: {
|
||||
control.sort(column);
|
||||
__sorterLoader.updateIcon();
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: __checkBoxLoader
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 10
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
active: __columnHeaderDelegate.selectionType == 'checkbox'
|
||||
sourceComponent: DelCheckBox {
|
||||
id: __parentBox
|
||||
|
||||
Component.onCompleted: {
|
||||
__parentBox.checkState = __private.parentCheckState;
|
||||
}
|
||||
|
||||
onToggled: {
|
||||
if (checkState == Qt.Unchecked) {
|
||||
__private.model.forEach(
|
||||
object => {
|
||||
__private.checkedKeysMap.delete(object.key);
|
||||
});
|
||||
__private.checkedKeysMapChanged();
|
||||
} else {
|
||||
__private.model.forEach(
|
||||
object => {
|
||||
__private.checkedKeysMap.set(object.key, true);
|
||||
});
|
||||
__private.checkedKeysMapChanged();
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: __private
|
||||
function onParentCheckStateChanged() {
|
||||
__parentBox.checkState = __private.parentCheckState;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: __sorterLoader
|
||||
anchors.right: __filterLoader.active ? __filterLoader.left : parent.right
|
||||
anchors.rightMargin: 8
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
active: sorter !== undefined
|
||||
sourceComponent: columnHeaderSorterIconDelegate
|
||||
onLoaded: {
|
||||
if (sortDirections.length === 0) return;
|
||||
|
||||
let ref = control.columns[column];
|
||||
if (!ref.hasOwnProperty('activeSorter')) {
|
||||
ref.activeSorter = false;
|
||||
}
|
||||
if (!ref.hasOwnProperty('sortIndex')) {
|
||||
ref.sortIndex = -1;
|
||||
}
|
||||
if (!ref.hasOwnProperty('sortMode')) {
|
||||
ref.sortMode = 'false';
|
||||
}
|
||||
updateIcon();
|
||||
}
|
||||
property int column: model.column
|
||||
property alias sorter: __columnHeaderDelegate.sorter
|
||||
property alias sortDirections: __columnHeaderDelegate.sortDirections
|
||||
property string sortMode: 'false'
|
||||
|
||||
function updateIcon() {
|
||||
if (sortDirections.length === 0) return;
|
||||
|
||||
let ref = control.columns[column];
|
||||
if (ref.activeSorter) {
|
||||
sortMode = ref.sortMode;
|
||||
} else {
|
||||
sortMode = 'false';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: __filterLoader
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 8
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
active: onFilter !== undefined
|
||||
sourceComponent: columnHeaderFilterIconDelegate
|
||||
property int column: model.column
|
||||
property alias onFilter: __columnHeaderDelegate.onFilter
|
||||
}
|
||||
}
|
||||
property Component rowHeaderDelegate: Item {
|
||||
Text {
|
||||
anchors {
|
||||
left: parent.left
|
||||
leftMargin: 8
|
||||
right: parent.right
|
||||
rightMargin: 8
|
||||
top: parent.top
|
||||
topMargin: 4
|
||||
bottom: parent.bottom
|
||||
bottomMargin: 4
|
||||
}
|
||||
font: control.rowHeaderTitleFont
|
||||
text: (row + 1)
|
||||
color: control.colorRowHeaderTitle
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
}
|
||||
}
|
||||
property Component columnHeaderSorterIconDelegate: Item {
|
||||
id: __sorterIconDelegate
|
||||
width: __sorterIconColumn.width
|
||||
height: __sorterIconColumn.height + 12
|
||||
|
||||
Column {
|
||||
id: __sorterIconColumn
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: -2
|
||||
|
||||
DelIconText {
|
||||
visible: sortDirections.indexOf('ascend') !== -1
|
||||
colorIcon: sortMode === 'ascend' ? DelTheme.DelTableView.colorIconHover :
|
||||
DelTheme.DelTableView.colorIcon
|
||||
iconSource: DelIcon.CaretUpOutlined
|
||||
iconSize: DelTheme.DelTableView.fontSize - 2
|
||||
}
|
||||
|
||||
DelIconText {
|
||||
visible: sortDirections.indexOf('descend') !== -1
|
||||
colorIcon: sortMode === 'descend' ? DelTheme.DelTableView.colorIconHover :
|
||||
DelTheme.DelTableView.colorIcon
|
||||
iconSource: DelIcon.CaretDownOutlined
|
||||
iconSize: DelTheme.DelTableView.fontSize - 2
|
||||
}
|
||||
}
|
||||
}
|
||||
property Component columnHeaderFilterIconDelegate: Item {
|
||||
width: __headerFilterIcon.width
|
||||
height: __headerFilterIcon.height + 12
|
||||
|
||||
HoverIcon {
|
||||
id: __headerFilterIcon
|
||||
anchors.centerIn: parent
|
||||
iconSource: DelIcon.SearchOutlined
|
||||
colorIcon: hovered ? DelTheme.DelTableView.colorIconHover : DelTheme.DelTableView.colorIcon
|
||||
onClicked: {
|
||||
__filterPopup.open();
|
||||
}
|
||||
}
|
||||
|
||||
DelPopup {
|
||||
id: __filterPopup
|
||||
x: -width * 0.5
|
||||
y: parent.height
|
||||
padding: 5
|
||||
animationEnabled: false
|
||||
contentItem: Column {
|
||||
spacing: 5
|
||||
|
||||
DelInput {
|
||||
id: __searchInput
|
||||
width: parent.width
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
placeholderText: qsTr('Search ') + control.columns[column].dataIndex
|
||||
onEditingFinished: __searchButton.clicked();
|
||||
Component.onCompleted: {
|
||||
let ref = control.columns[column];
|
||||
if (ref.hasOwnProperty('filterInput')) {
|
||||
text = ref.filterInput;
|
||||
} else {
|
||||
ref.filterInput = '';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
spacing: 5
|
||||
|
||||
DelIconButton {
|
||||
id: __searchButton
|
||||
text: qsTr('Search')
|
||||
iconSource: DelIcon.SearchOutlined
|
||||
type: DelButton.Type_Primary
|
||||
onClicked: {
|
||||
__filterPopup.close();
|
||||
control.columns[column].filterInput = __searchInput.text;
|
||||
control.filter();
|
||||
}
|
||||
}
|
||||
|
||||
DelButton {
|
||||
text: qsTr('Reset')
|
||||
onClicked: {
|
||||
__filterPopup.close();
|
||||
control.columns[column].filterInput = '';
|
||||
control.filter();
|
||||
}
|
||||
}
|
||||
|
||||
DelButton {
|
||||
text: qsTr('Close')
|
||||
type: DelButton.Type_Link
|
||||
onClicked: {
|
||||
__filterPopup.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onColumnsChanged: {
|
||||
let headerColumns = [];
|
||||
let headerRow = {};
|
||||
for (const object of columns) {
|
||||
let column = Qt.createQmlObject('import Qt.labs.qmlmodels 1.0; TableModelColumn {}', __columnHeaderModel);
|
||||
column.display = object.dataIndex;
|
||||
headerColumns.push(column);
|
||||
headerRow[object.dataIndex] = object;
|
||||
}
|
||||
|
||||
__columnHeaderModel.clear();
|
||||
if (columnHeaderVisible) {
|
||||
__columnHeaderModel.columns = headerColumns;
|
||||
__columnHeaderModel.rows = [headerRow];
|
||||
}
|
||||
|
||||
let cellColumns = [];
|
||||
for (let i = 0; i < columns.length; i++) {
|
||||
let column = Qt.createQmlObject('import Qt.labs.qmlmodels 1.0; TableModelColumn {}', __cellModel);
|
||||
column.display = `__data${i}`;
|
||||
cellColumns.push(column);
|
||||
}
|
||||
__cellModel.columns = cellColumns;
|
||||
}
|
||||
|
||||
onInitModelChanged: {
|
||||
clearSort();
|
||||
filter();
|
||||
}
|
||||
|
||||
function checkForRows(rows) {
|
||||
rows.forEach(
|
||||
row => {
|
||||
if (row >= 0 && row < __private.model.length) {
|
||||
const key = __private.model[row].key;
|
||||
__private.checkedKeysMap.set(key, true);
|
||||
}
|
||||
});
|
||||
__private.checkedKeysMapChanged();
|
||||
}
|
||||
|
||||
function checkForKeys(keys) {
|
||||
keys.forEach(key => __private.checkedKeysMap.set(object.key, true));
|
||||
__private.checkedKeysMapChanged();
|
||||
}
|
||||
|
||||
function getCheckedKeys() {
|
||||
return [...__private.checkedKeysMap.keys()];
|
||||
}
|
||||
|
||||
function clearAllCheckedKeys() {
|
||||
__private.checkedKeysMap.clear();
|
||||
__private.checkedKeysMapChanged();
|
||||
__private.parentCheckState = Qt.Unchecked;
|
||||
__private.parentCheckStateChanged();
|
||||
}
|
||||
|
||||
function sort(column) {
|
||||
/*! 仅需设置排序相关属性, 真正的排序在 filter() 中完成 */
|
||||
if (columns[column].hasOwnProperty('sorter')) {
|
||||
columns.forEach(
|
||||
(object, index) => {
|
||||
if (object.hasOwnProperty('sorter')) {
|
||||
if (column === index) {
|
||||
object.activeSorter = true;
|
||||
object.sortIndex = (object.sortIndex + 1) % object.sortDirections.length;
|
||||
object.sortMode = object.sortDirections[object.sortIndex];
|
||||
} else {
|
||||
object.activeSorter = false;
|
||||
object.sortIndex = -1;
|
||||
object.sortMode = 'false';
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
filter();
|
||||
}
|
||||
|
||||
function clearSort() {
|
||||
columns.forEach(
|
||||
object => {
|
||||
if (object.sortDirections && object.sortDirections.length !== 0) {
|
||||
object.activeSorter = false;
|
||||
object.sortIndex = -1;
|
||||
object.sortMode = 'false';
|
||||
}
|
||||
});
|
||||
__private.model = [...initModel];
|
||||
}
|
||||
|
||||
function filter() {
|
||||
let changed = false;
|
||||
let model = [...initModel];
|
||||
columns.forEach(
|
||||
object => {
|
||||
if (object.hasOwnProperty('onFilter') && object.hasOwnProperty('filterInput')) {
|
||||
model = model.filter((record, index) => object.onFilter(object.filterInput, record));
|
||||
changed = true;
|
||||
}
|
||||
});
|
||||
if (changed)
|
||||
__private.model = model;
|
||||
|
||||
/*! 根据 activeSorter 列排序 */
|
||||
columns.forEach(
|
||||
object => {
|
||||
if (object.activeSorter === true) {
|
||||
if (object.sortMode === 'ascend') {
|
||||
/*! sorter 作为上升处理 */
|
||||
__private.model.sort(object.sorter);
|
||||
__private.modelChanged();
|
||||
} else if (object.sortMode === 'descend') {
|
||||
/*! 返回 ascend 相反结果即可 */
|
||||
__private.model.sort((a, b) => object.sorter(b, a));
|
||||
__private.modelChanged();
|
||||
} else {
|
||||
/*! 还原 */
|
||||
__private.model = model;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function clearFilter() {
|
||||
columns.forEach(
|
||||
object => {
|
||||
if (object.hasOwnProperty('onFilter') || object.hasOwnProperty('filterInput'))
|
||||
object.filterInput = '';
|
||||
});
|
||||
__private.model = [...initModel];
|
||||
}
|
||||
|
||||
function clear() {
|
||||
__private.model = initModel = [];
|
||||
__cellModel.clear();
|
||||
columns.forEach(
|
||||
object => {
|
||||
if (object.sortDirections && object.sortDirections.length !== 0) {
|
||||
object.activeSorter = false;
|
||||
object.sortIndex = -1;
|
||||
object.sortMode = 'false';
|
||||
}
|
||||
if (object.hasOwnProperty('onFilter') || object.hasOwnProperty('filterInput')) {
|
||||
object.filterInput = '';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function appendRow(object) {
|
||||
__private.model.push(object);
|
||||
__cellModel.appendRow(__private.toCellObject(object));
|
||||
}
|
||||
|
||||
function getRow(rowIndex) {
|
||||
if (rowIndex >= 0 && rowIndex < __private.model.length) {
|
||||
return __private.model[rowIndex];
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function insertRow(rowIndex, object) {
|
||||
__private.model.splice(rowIndex, 0, object);
|
||||
__cellModel.insertRow(rowIndex, __private.toCellObject(object));
|
||||
}
|
||||
|
||||
function moveRow(fromRowIndex, toRowIndex, count = 1) {
|
||||
if (fromRowIndex >= 0 && fromRowIndex < __private.model.length &&
|
||||
toRowIndex >= 0 && toRowIndex < __private.model.length) {
|
||||
const objects = __private.model.splice(from, count);
|
||||
__private.model.splice(to, 0, ...objects);
|
||||
__cellModel.moveRow(fromRowIndex, toRowIndex, count);
|
||||
}
|
||||
}
|
||||
|
||||
function removeRow(rowIndex, count = 1) {
|
||||
if (rowIndex >= 0 && rowIndex < __private.model.length) {
|
||||
__private.model.splice(rowIndex, count);
|
||||
__cellModel.removeRow(rowIndex, count);
|
||||
}
|
||||
}
|
||||
|
||||
function setRow(rowIndex, object) {
|
||||
if (rowIndex >= 0 && rowIndex < __private.model.length) {
|
||||
__private.model[rowIndex] = object;
|
||||
__cellModel.setRow(rowIndex, __private.toCellObject(object));
|
||||
}
|
||||
}
|
||||
|
||||
component HoverIcon: DelIconText {
|
||||
signal clicked()
|
||||
property alias hovered: __hoverHandler.hovered
|
||||
|
||||
HoverHandler {
|
||||
id: __hoverHandler
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
}
|
||||
|
||||
TapHandler {
|
||||
onTapped: parent.clicked();
|
||||
}
|
||||
}
|
||||
|
||||
component ResizeArea: MouseArea {
|
||||
property bool isHorizontal: true
|
||||
property var target: __columnHeaderItem
|
||||
property point startPos: Qt.point(0, 0)
|
||||
property real minimumWidth: 0
|
||||
property real maximumWidth: Number.NaN
|
||||
property real minimumHeight: 0
|
||||
property real maximumHeight: Number.NaN
|
||||
property var resizeCallback: (result) => { }
|
||||
|
||||
preventStealing: true
|
||||
hoverEnabled: true
|
||||
onEntered: cursorShape = isHorizontal ? Qt.SplitHCursor : Qt.SplitVCursor;
|
||||
onPressed:
|
||||
(mouse) => {
|
||||
if (target) {
|
||||
startPos = Qt.point(mouseX, mouseY);
|
||||
}
|
||||
}
|
||||
onPositionChanged:
|
||||
(mouse) => {
|
||||
if (pressed && target) {
|
||||
if (isHorizontal) {
|
||||
var resultWidth = 0;
|
||||
var offsetX = mouse.x - startPos.x;
|
||||
if (maximumWidth != Number.NaN && (target.width + offsetX) > maximumWidth) {
|
||||
resultWidth = maximumWidth;
|
||||
} else if ((target.width + offsetX) < minimumWidth) {
|
||||
resultWidth = minimumWidth;
|
||||
} else {
|
||||
resultWidth = target.width + offsetX;
|
||||
}
|
||||
resizeCallback(resultWidth);
|
||||
} else {
|
||||
var resultHeight = 0;
|
||||
var offsetY = mouse.y - startPos.y;
|
||||
if (maximumHeight != Number.NaN && (target.height + offsetY) > maximumHeight) {
|
||||
resultHeight = maximumHeight;
|
||||
} else if ((target.height + offsetY) < minimumHeight) {
|
||||
resultHeight = minimumHeight;
|
||||
} else {
|
||||
resultHeight = target.height + offsetY;
|
||||
}
|
||||
resizeCallback(resultHeight);
|
||||
}
|
||||
mouse.accepted = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on color { enabled: control.animationEnabled; ColorAnimation { duration: DelTheme.Primary.durationMid } }
|
||||
|
||||
QtObject {
|
||||
id: __private
|
||||
property var model: []
|
||||
property int parentCheckState: Qt.Unchecked
|
||||
property var checkedKeysMap: new Map
|
||||
|
||||
function updateParentCheckBox() {
|
||||
let checkCount = 0;
|
||||
model.forEach(
|
||||
object => {
|
||||
if (checkedKeysMap.has(object.key)) {
|
||||
checkCount++;
|
||||
}
|
||||
});
|
||||
parentCheckState = checkCount == 0 ? Qt.Unchecked : checkCount == model.length ? Qt.Checked : Qt.PartiallyChecked;
|
||||
parentCheckStateChanged();
|
||||
}
|
||||
|
||||
function updateCheckedKeys() {
|
||||
control.checkedKeys = [...checkedKeysMap.keys()];
|
||||
}
|
||||
|
||||
function toCellObject(object) {
|
||||
let dataObject = new Object;
|
||||
for (let i = 0; i < control.columns.length; i++) {
|
||||
const dataIndex = control.columns[i].dataIndex ?? '';
|
||||
if (object.hasOwnProperty(dataIndex)) {
|
||||
dataObject[`__data${i}`] = object[dataIndex];
|
||||
} else {
|
||||
dataObject[`__data${i}`] = null;
|
||||
}
|
||||
}
|
||||
return dataObject;
|
||||
}
|
||||
|
||||
onModelChanged: {
|
||||
__cellView.contentY = 0;
|
||||
__cellModel.clear();
|
||||
|
||||
let cellRows = [];
|
||||
model.forEach(
|
||||
(object, index) => {
|
||||
let data = { };
|
||||
for (let i = 0; i < columns.length; i++) {
|
||||
const dataIndex = columns[i].dataIndex ?? '';
|
||||
if (object.hasOwnProperty(dataIndex)) {
|
||||
data[`__data${i}`] = object[dataIndex];
|
||||
} else {
|
||||
data[`__data${i}`] = null;
|
||||
}
|
||||
}
|
||||
cellRows.push(data);
|
||||
});
|
||||
__cellModel.rows = cellRows;
|
||||
|
||||
__cellView.rowHeights = Array.from({ length: model.length }, () => control.defaultRowHeaderHeight);
|
||||
__rowHeaderModel.rows = model;
|
||||
|
||||
updateParentCheckBox();
|
||||
}
|
||||
onParentCheckStateChanged: updateCheckedKeys();
|
||||
onCheckedKeysMapChanged: updateCheckedKeys();
|
||||
}
|
||||
|
||||
DelRectangle {
|
||||
id: __columnHeaderViewBg
|
||||
height: control.defaultColumnHeaderHeight
|
||||
anchors.left: control.rowHeaderVisible ? __rowHeaderViewBg.right : parent.left
|
||||
anchors.right: parent.right
|
||||
topLeftRadius: control.rowHeaderVisible ? 0 : 6
|
||||
topRightRadius: 6
|
||||
color: control.colorColumnHeaderBg
|
||||
visible: control.columnHeaderVisible
|
||||
|
||||
TableView {
|
||||
id: __columnHeaderView
|
||||
anchors.fill: parent
|
||||
syncDirection: Qt.Horizontal
|
||||
syncView: __cellModel.rowCount == 0 ? null : __cellView
|
||||
columnWidthProvider: __cellView.columnWidthProvider
|
||||
boundsBehavior: Flickable.StopAtBounds
|
||||
clip: true
|
||||
model: TableModel {
|
||||
id: __columnHeaderModel
|
||||
}
|
||||
delegate: Item {
|
||||
id: __columnHeaderItem
|
||||
implicitHeight: control.defaultColumnHeaderHeight
|
||||
clip: true
|
||||
|
||||
required property var model
|
||||
required property var display
|
||||
property int row: model.row
|
||||
property int column: model.column
|
||||
property string selectionType: display.selectionType ?? ''
|
||||
property bool editable: display.editable ?? false
|
||||
property real minimumWidth: display.minimumWidth ?? 40
|
||||
property real maximumWidth: display.maximumWidth ?? Number.NaN
|
||||
|
||||
TableView.onReused: {
|
||||
if (selectionType == 'checkbox')
|
||||
__private.updateParentCheckBox();
|
||||
}
|
||||
|
||||
Loader {
|
||||
anchors.fill: parent
|
||||
sourceComponent: control.columnHeaderDelegate
|
||||
property alias model: __columnHeaderItem.model
|
||||
property var headerData: control.columns[column]
|
||||
property alias column: __columnHeaderItem.column
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
z: 2
|
||||
width: 1
|
||||
color: control.colorGridLine
|
||||
height: parent.height * 0.5
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
ResizeArea {
|
||||
width: 8
|
||||
height: parent.height
|
||||
minimumWidth: __columnHeaderItem.minimumWidth
|
||||
maximumWidth: __columnHeaderItem.maximumWidth
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: -width * 0.5
|
||||
target: __columnHeaderItem
|
||||
isHorizontal: true
|
||||
resizeCallback: result => __cellView.setColumnWidth(__columnHeaderItem.column, result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: 1
|
||||
anchors.bottom: parent.bottom
|
||||
color: control.colorGridLine
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: __rowHeaderViewBg
|
||||
width: control.defaultRowHeaderWidth
|
||||
anchors.top: control.columnHeaderVisible ? __columnHeaderViewBg.bottom : __cellMouseArea.top
|
||||
anchors.bottom: __cellMouseArea.bottom
|
||||
color: control.colorRowHeaderBg
|
||||
visible: control.rowHeaderVisible
|
||||
|
||||
TableView {
|
||||
id: __rowHeaderView
|
||||
anchors.fill: parent
|
||||
syncDirection: Qt.Vertical
|
||||
syncView: __cellView
|
||||
boundsBehavior: Flickable.StopAtBounds
|
||||
clip: true
|
||||
model: TableModel {
|
||||
id: __rowHeaderModel
|
||||
TableModelColumn { }
|
||||
}
|
||||
delegate: Item {
|
||||
id: __rowHeaderItem
|
||||
implicitWidth: control.defaultRowHeaderWidth
|
||||
clip: true
|
||||
|
||||
required property var model
|
||||
property int row: model.row
|
||||
|
||||
Loader {
|
||||
anchors.fill: parent
|
||||
sourceComponent: control.rowHeaderDelegate
|
||||
property alias model: __rowHeaderItem.model
|
||||
property alias row: __rowHeaderItem.row
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
z: 2
|
||||
width: parent.width * 0.5
|
||||
color: control.colorGridLine
|
||||
height: 1
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
|
||||
ResizeArea {
|
||||
width: parent.width
|
||||
height: 8
|
||||
minimumHeight: control.minimumRowHeight
|
||||
maximumHeight: control.maximumRowHeight
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.bottomMargin: -height * 0.5
|
||||
target: __rowHeaderItem
|
||||
isHorizontal: false
|
||||
resizeCallback: result => __cellView.setRowHeight(__rowHeaderItem.row, result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: 1
|
||||
height: parent.height
|
||||
anchors.right: parent.right
|
||||
color: control.colorGridLine
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: __cellMouseArea
|
||||
anchors.top: control.columnHeaderVisible ? __columnHeaderViewBg.bottom : parent.top
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.left: __columnHeaderViewBg.left
|
||||
anchors.right: __columnHeaderViewBg.right
|
||||
hoverEnabled: true
|
||||
onExited: __cellView.currentHoverRow = -1;
|
||||
onWheel: wheel => wheel.accepted = true;
|
||||
|
||||
TableView {
|
||||
id: __cellView
|
||||
|
||||
property int currentHoverRow: -1
|
||||
property var rowHeights: []
|
||||
|
||||
function setRowHeight(row, rowHeight) {
|
||||
rowHeights[row] = rowHeight;
|
||||
forceLayout();
|
||||
}
|
||||
|
||||
function setColumnWidth(column, columnWidth) {
|
||||
control.columns[column].width = columnWidth;
|
||||
__columnHeaderView.forceLayout()
|
||||
forceLayout();
|
||||
}
|
||||
|
||||
rowHeightProvider: row => rowHeights[row];
|
||||
columnWidthProvider:
|
||||
column => {
|
||||
let object = control.columns[column];
|
||||
if (object.hasOwnProperty('width'))
|
||||
return object.width;
|
||||
else
|
||||
return control.defaultColumnHeaderWidth;
|
||||
}
|
||||
anchors.fill: parent
|
||||
boundsBehavior: Flickable.StopAtBounds
|
||||
T.ScrollBar.horizontal: __hScrollBar
|
||||
T.ScrollBar.vertical: __vScrollBar
|
||||
clip: true
|
||||
reuseItems: false /*! 重用有未知BUG */
|
||||
model: TableModel {
|
||||
id: __cellModel
|
||||
}
|
||||
delegate: Rectangle {
|
||||
id: __rootItem
|
||||
implicitHeight: control.defaultRowHeaderWidth
|
||||
clip: true
|
||||
color: {
|
||||
if (__private.checkedKeysMap.has(key)) {
|
||||
if (row == __cellView.currentHoverRow)
|
||||
return DelTheme.isDark ? DelTheme.DelTableView.colorCellBgDarkHoverChecked :
|
||||
DelTheme.DelTableView.colorCellBgHoverChecked;
|
||||
else
|
||||
return DelTheme.isDark ? DelTheme.DelTableView.colorCellBgDarkChecked :
|
||||
DelTheme.DelTableView.colorCellBgChecked;
|
||||
} else {
|
||||
return row == __cellView.currentHoverRow ? DelTheme.DelTableView.colorCellBgHover :
|
||||
control.alternatingRow && __rootItem.row % 2 !== 0 ?
|
||||
DelTheme.DelTableView.colorCellBgHover : DelTheme.DelTableView.colorCellBg;
|
||||
}
|
||||
}
|
||||
|
||||
TableView.onReused: {
|
||||
checked = __private.checkedKeysMap.has(key);
|
||||
if (__childCheckBoxLoader.item) {
|
||||
__childCheckBoxLoader.item.checked = checked;
|
||||
}
|
||||
}
|
||||
|
||||
required property var model
|
||||
required property var index
|
||||
required property var display
|
||||
|
||||
property int row: model.row
|
||||
property int column: model.column
|
||||
property string key: __private.model[row] ? (__private.model[row].key ?? '') : ''
|
||||
property string selectionType: control.columns[column].selectionType ?? ''
|
||||
property string dataIndex: control.columns[column].dataIndex ?? ''
|
||||
property string filterInput: control.columns[column].filterInput ?? ''
|
||||
property alias cellData: __rootItem.display
|
||||
property bool checked: false
|
||||
|
||||
Loader {
|
||||
active: control.rowGridVisible
|
||||
width: parent.width
|
||||
height: 1
|
||||
anchors.bottom: parent.bottom
|
||||
sourceComponent: Rectangle { color: control.colorGridLine }
|
||||
}
|
||||
|
||||
Loader {
|
||||
active: control.columnGridVisible
|
||||
width: 1
|
||||
height: parent.height
|
||||
anchors.right: parent.right
|
||||
sourceComponent: Rectangle { color: control.colorGridLine }
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
onEntered: __cellView.currentHoverRow = __rootItem.row;
|
||||
|
||||
Loader {
|
||||
id: __childCheckBoxLoader
|
||||
active: selectionType == 'checkbox'
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 10
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
sourceComponent: DelCheckBox {
|
||||
id: __childBox
|
||||
|
||||
Component.onCompleted: {
|
||||
__childBox.checked = __rootItem.checked = __private.checkedKeysMap.has(key);
|
||||
}
|
||||
|
||||
onToggled: {
|
||||
if (checkState == Qt.Checked) {
|
||||
__private.checkedKeysMap.set(__rootItem.key, true);
|
||||
__rootItem.checked = true;
|
||||
} else {
|
||||
__private.checkedKeysMap.delete(__rootItem.key);
|
||||
__rootItem.checked = false;
|
||||
}
|
||||
__private.updateParentCheckBox();
|
||||
__cellView.currentHoverRowChanged();
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: __private
|
||||
function onCheckedKeysMapChanged() {
|
||||
__childBox.checked = __rootItem.checked = __private.checkedKeysMap.has(__rootItem.key);
|
||||
}
|
||||
}
|
||||
}
|
||||
property alias key: __rootItem.key
|
||||
}
|
||||
|
||||
Loader {
|
||||
anchors.left: __childCheckBoxLoader.active ? __childCheckBoxLoader.right : parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
anchors.bottom: parent.bottom
|
||||
sourceComponent: {
|
||||
if (control.columns[__rootItem.column].delegate) {
|
||||
return control.columns[__rootItem.column].delegate;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
property alias row: __rootItem.row
|
||||
property alias column: __rootItem.column
|
||||
property alias cellData: __rootItem.cellData
|
||||
property alias cellIndex: __rootItem.index
|
||||
property alias dataIndex: __rootItem.dataIndex
|
||||
property alias filterInput: __rootItem.filterInput
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on contentY { NumberAnimation {}}
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: __resizeRectLoader
|
||||
z: 10
|
||||
width: __rowHeaderViewBg.width
|
||||
height: __columnHeaderViewBg.height
|
||||
active: control.rowHeaderVisible && control.columnHeaderVisible
|
||||
sourceComponent: DelRectangle {
|
||||
color: control.colorResizeBlockBg
|
||||
topLeftRadius: 6
|
||||
|
||||
ResizeArea {
|
||||
width: parent.width
|
||||
height: 8
|
||||
minimumHeight: control.defaultColumnHeaderHeight
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.bottomMargin: -height * 0.5
|
||||
target: __columnHeaderViewBg
|
||||
isHorizontal: false
|
||||
resizeCallback: result => __columnHeaderViewBg.height = result;
|
||||
}
|
||||
|
||||
ResizeArea {
|
||||
width: 8
|
||||
height: parent.height
|
||||
minimumWidth: control.defaultRowHeaderWidth
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: -width * 0.5
|
||||
target: __rowHeaderViewBg
|
||||
isHorizontal: true
|
||||
resizeCallback: result => __rowHeaderViewBg.width = result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DelScrollBar {
|
||||
id: __hScrollBar
|
||||
z: 11
|
||||
anchors.left: control.rowHeaderVisible ? __rowHeaderViewBg.right : __cellMouseArea.left
|
||||
anchors.right: __cellMouseArea.right
|
||||
anchors.bottom: __cellMouseArea.bottom
|
||||
}
|
||||
|
||||
DelScrollBar {
|
||||
id: __vScrollBar
|
||||
z: 12
|
||||
anchors.right: __cellMouseArea.right
|
||||
anchors.top: control.columnHeaderVisible ? __columnHeaderViewBg.bottom : __cellMouseArea.top
|
||||
anchors.bottom: __cellMouseArea.bottom
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,152 @@
|
|||
import QtQuick 2.15
|
||||
import DelegateUI 1.0
|
||||
|
||||
Rectangle {
|
||||
id: control
|
||||
|
||||
enum State {
|
||||
State_Default = 0,
|
||||
State_Success = 1,
|
||||
State_Processing = 2,
|
||||
State_Error = 3,
|
||||
State_Warning = 4
|
||||
}
|
||||
|
||||
signal close()
|
||||
|
||||
property bool animationEnabled: DelTheme.animationEnabled
|
||||
property int tagState: DelTag.State_Default
|
||||
property string text: ""
|
||||
property font font
|
||||
property bool rotating: false
|
||||
property int iconSource: 0
|
||||
property int iconSize: DelTheme.DelButton.fontSize
|
||||
property int closeIconSource: 0
|
||||
property int closeIconSize: DelTheme.DelButton.fontSize
|
||||
property alias spacing: __row.spacing
|
||||
property string presetColor: ""
|
||||
property color colorText: presetColor == "" ? DelTheme.DelTag.colorDefaultText : __private.isCustom ? "#fff" : __private.colorArray[5]
|
||||
property color colorBg: presetColor == "" ? DelTheme.DelTag.colorDefaultBg : __private.isCustom ? presetColor : __private.colorArray[0]
|
||||
property color colorBorder: presetColor == "" ? DelTheme.DelTag.colorDefaultBorder : __private.isCustom ? "transparent" : __private.colorArray[2]
|
||||
property color colorIcon: colorText
|
||||
|
||||
onTagStateChanged: {
|
||||
switch (tagState) {
|
||||
case DelTag.State_Success: presetColor = "#52c41a"; break;
|
||||
case DelTag.State_Processing: presetColor = "#1677ff"; break;
|
||||
case DelTag.State_Error: presetColor = "#ff4d4f"; break;
|
||||
case DelTag.State_Warning: presetColor = "#faad14"; break;
|
||||
case DelTag.State_Default:
|
||||
default: presetColor = "";
|
||||
}
|
||||
}
|
||||
|
||||
onPresetColorChanged: {
|
||||
let preset = -1;
|
||||
switch (presetColor) {
|
||||
case "red": preset = DelColorGenerator.Preset_Red; break;
|
||||
case "volcano": preset = DelColorGenerator.Preset_Volcano; break;
|
||||
case "orange": preset = DelColorGenerator.Preset_Orange; break;
|
||||
case "gold": preset = DelColorGenerator.Preset_Gold; break;
|
||||
case "yellow": preset = DelColorGenerator.Preset_Yellow; break;
|
||||
case "lime": preset = DelColorGenerator.Preset_Lime; break;
|
||||
case "green": preset = DelColorGenerator.Preset_Green; break;
|
||||
case "cyan": preset = DelColorGenerator.Preset_Cyan; break;
|
||||
case "blue": preset = DelColorGenerator.Preset_Blue; break;
|
||||
case "geekblue": preset = DelColorGenerator.Preset_Geekblue; break;
|
||||
case "purple": preset = DelColorGenerator.Preset_Purple; break;
|
||||
case "magenta": preset = DelColorGenerator.Preset_Magenta; break;
|
||||
}
|
||||
|
||||
if (tagState == DelTag.State_Default) {
|
||||
__private.isCustom = preset == -1 ? true : false;
|
||||
__private.presetColor = preset == -1 ? "#000" : delColorGenerator.presetToColor(preset);
|
||||
} else {
|
||||
__private.isCustom = false;
|
||||
__private.presetColor = presetColor;
|
||||
}
|
||||
}
|
||||
|
||||
implicitWidth: __row.implicitWidth + 16
|
||||
implicitHeight: Math.max(__icon.implicitHeight, __text.implicitHeight, __closeIcon.implicitHeight) + 8
|
||||
font.family: DelTheme.DelTag.fontFamily
|
||||
font.pixelSize: DelTheme.DelTag.fontSize - 2
|
||||
color: colorBg
|
||||
border.color: colorBorder
|
||||
radius: 4
|
||||
|
||||
Behavior on color { enabled: control.animationEnabled; ColorAnimation { duration: DelTheme.Primary.durationFast } }
|
||||
|
||||
DelColorGenerator {
|
||||
id: delColorGenerator
|
||||
}
|
||||
|
||||
QtObject {
|
||||
id: __private
|
||||
property bool isCustom: false
|
||||
property color presetColor: "#000"
|
||||
property var colorArray: DelThemeFunctions.genColorString(presetColor, !DelTheme.isDark, DelTheme.Primary.colorBgBase)
|
||||
}
|
||||
|
||||
Row {
|
||||
id: __row
|
||||
anchors.centerIn: parent
|
||||
spacing: 5
|
||||
|
||||
DelIconText {
|
||||
id: __icon
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
color: control.colorIcon
|
||||
iconSize: control.iconSize
|
||||
iconSource: control.iconSource
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
visible: iconSource != 0
|
||||
|
||||
Behavior on color { enabled: control.animationEnabled; ColorAnimation { duration: DelTheme.Primary.durationFast } }
|
||||
|
||||
NumberAnimation on rotation {
|
||||
id: __animation
|
||||
running: control.rotating
|
||||
from: 0
|
||||
to: 360
|
||||
loops: Animation.Infinite
|
||||
duration: 1000
|
||||
}
|
||||
}
|
||||
|
||||
DelCopyableText {
|
||||
id: __text
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: control.text
|
||||
font: control.font
|
||||
color: control.colorText
|
||||
|
||||
Behavior on color { enabled: control.animationEnabled; ColorAnimation { duration: DelTheme.Primary.durationFast } }
|
||||
}
|
||||
|
||||
DelIconText {
|
||||
id: __closeIcon
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
color: hovered ? DelTheme.DelTag.colorCloseIconHover : DelTheme.DelTag.colorCloseIcon
|
||||
iconSize: control.closeIconSize
|
||||
iconSource: control.closeIconSource
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
visible: iconSource != 0
|
||||
|
||||
property alias hovered: __hoverHander.hovered
|
||||
property alias down: __tapHander.pressed
|
||||
|
||||
Behavior on color { enabled: control.animationEnabled; ColorAnimation { duration: DelTheme.Primary.durationFast } }
|
||||
|
||||
HoverHandler {
|
||||
id: __hoverHander
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
}
|
||||
|
||||
TapHandler {
|
||||
id: __tapHander
|
||||
onTapped: control.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
import QtQuick 2.15
|
||||
import DelegateUI 1.0
|
||||
|
||||
Text {
|
||||
id: control
|
||||
|
||||
renderType: DelTheme.textRenderType
|
||||
color: DelTheme.Primary.colorTextBase
|
||||
font {
|
||||
family: DelTheme.Primary.fontPrimaryFamily
|
||||
pixelSize: DelTheme.Primary.fontPrimarySize
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,469 @@
|
|||
import QtQuick 2.15
|
||||
import QtGraphicalEffects 1.15
|
||||
import QtQuick.Controls 2.15 as T
|
||||
import QtQuick.Window 2.15
|
||||
import DelegateUI 1.0
|
||||
|
||||
T.TextField {
|
||||
id: control
|
||||
|
||||
enum IconPosition {
|
||||
Position_Left = 0,
|
||||
Position_Right = 1
|
||||
}
|
||||
|
||||
enum TimeFormat {
|
||||
Format_HHMMSS = 0,
|
||||
Format_HHMM = 1,
|
||||
Format_MMSS = 2
|
||||
}
|
||||
|
||||
popupFont {
|
||||
family: DelTheme.DelTimePicker.fontFamily
|
||||
pixelSize: DelTheme.DelTimePicker.fontSize
|
||||
}
|
||||
|
||||
signal acceptedTime(time: string)
|
||||
|
||||
property bool animationEnabled: DelTheme.animationEnabled
|
||||
readonly property bool active: hovered || activeFocus
|
||||
property int format: DelTimePicker.Format_HHMMSS
|
||||
property int iconSize: DelTheme.DelTimePicker.fontIconSize
|
||||
property int iconPosition: DelTimePicker.Position_Right
|
||||
property color colorText: enabled ? DelTheme.DelTimePicker.colorText : DelTheme.DelTimePicker.colorTextDisabled
|
||||
property color colorBorder: enabled ?
|
||||
active ? DelTheme.DelTimePicker.colorBorderHover :
|
||||
DelTheme.DelTimePicker.colorBorder : DelTheme.DelTimePicker.colorBorderDisabled
|
||||
property color colorBg: enabled ? DelTheme.DelTimePicker.colorBg : DelTheme.DelTimePicker.colorBgDisabled
|
||||
property color colorPopupText: DelTheme.DelTimePicker.colorPopupText
|
||||
property font popupFont
|
||||
property int radiusBg: 6
|
||||
property int radiusPopupBg: 6
|
||||
property string contentDescription: ''
|
||||
|
||||
function clearTime() {
|
||||
if (__private.cleared) {
|
||||
control.text = '';
|
||||
} else {
|
||||
control.text = __private.getTime();
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on colorText { enabled: control.animationEnabled; ColorAnimation { duration: DelTheme.Primary.durationFast } }
|
||||
Behavior on colorBorder { enabled: control.animationEnabled; ColorAnimation { duration: DelTheme.Primary.durationFast } }
|
||||
Behavior on colorBg { enabled: control.animationEnabled; ColorAnimation { duration: DelTheme.Primary.durationFast } }
|
||||
Behavior on placeholderTextColor { enabled: control.animationEnabled; ColorAnimation { duration: DelTheme.Primary.durationFast } }
|
||||
|
||||
objectName: '__DelTimePicker__'
|
||||
focus: __picker.opened
|
||||
padding: 5
|
||||
leftPadding: 10 + (iconPosition == DelTimePicker.Position_Left ? iconSize : 0)
|
||||
rightPadding: 10 + (iconPosition == DelTimePicker.Position_Right ? iconSize : 0)
|
||||
width: 130
|
||||
implicitWidth: contentWidth + leftPadding + rightPadding
|
||||
implicitHeight: contentHeight + topPadding + bottomPadding
|
||||
placeholderText: qsTr('请选择时间')
|
||||
color: colorText
|
||||
placeholderTextColor: enabled ? DelTheme.DelTimePicker.colorPlaceholderText : DelTheme.DelTimePicker.colorPlaceholderTextDisabled
|
||||
selectedTextColor: DelTheme.DelTimePicker.colorSelectedText
|
||||
selectionColor: DelTheme.DelTimePicker.colorSelection
|
||||
font {
|
||||
family: DelTheme.DelTimePicker.fontFamily
|
||||
pixelSize: DelTheme.DelTimePicker.fontSize
|
||||
}
|
||||
background: Rectangle {
|
||||
color: control.colorBg
|
||||
border.color: control.colorBorder
|
||||
radius: control.radiusBg
|
||||
}
|
||||
onActiveFocusChanged: {
|
||||
if (activeFocus)
|
||||
__picker.open();
|
||||
}
|
||||
onTextEdited: {
|
||||
__private.commit();
|
||||
}
|
||||
onEditingFinished: {
|
||||
clearTime();
|
||||
}
|
||||
|
||||
Keys.onPressed: function(event) {
|
||||
if (event.key === Qt.Key_Enter || event.key === Qt.Key_Return) {
|
||||
__confirmButton.clicked();
|
||||
}
|
||||
}
|
||||
|
||||
component TimeListView: MouseArea {
|
||||
id: __rootItem
|
||||
|
||||
property string value: '00'
|
||||
property string checkValue: '00'
|
||||
property string tempValue: '00'
|
||||
property alias model: __listView.model
|
||||
|
||||
function clearCheck() {
|
||||
value = checkValue = tempValue = '00';
|
||||
if (__buttonGroup.checkedButton != null)
|
||||
__buttonGroup.checkedButton.checked = false;
|
||||
var item = __listView.itemAtIndex(0);
|
||||
if (item)
|
||||
item.checked = true;
|
||||
__listView.positionViewAtBeginning();
|
||||
}
|
||||
|
||||
function initValue(v) {
|
||||
value = checkValue = tempValue = v;
|
||||
}
|
||||
|
||||
function checkIndex(index) {
|
||||
checkValue = tempValue = (String(index).padStart(2, '0'));
|
||||
var item = __listView.itemAtIndex(index);
|
||||
if (item) {
|
||||
item.checked = true;
|
||||
item.clicked();
|
||||
}
|
||||
__listView.positionViewAtIndex(index, ListView.Beginning);
|
||||
}
|
||||
|
||||
function positionViewAtIndex(index, mode) {
|
||||
__listView.positionViewAtIndex(index, mode);
|
||||
}
|
||||
|
||||
width: 50
|
||||
height: parent.height
|
||||
hoverEnabled: true
|
||||
onExited: {
|
||||
tempValue = checkValue;
|
||||
control.text = __private.getCheckTime();
|
||||
}
|
||||
|
||||
ListView {
|
||||
id: __listView
|
||||
height: parent.height
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.margins: 2
|
||||
clip: true
|
||||
boundsBehavior: Flickable.StopAtBounds
|
||||
delegate: T.AbstractButton {
|
||||
width: __listView.width
|
||||
height: 28
|
||||
checkable: true
|
||||
contentItem: Text {
|
||||
id: __viewText
|
||||
font: control.popupFont
|
||||
text: String(index).padStart(2, '0')
|
||||
color: control.colorPopupText
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
}
|
||||
background: Rectangle {
|
||||
radius: 4
|
||||
color: hovered ? DelTheme.DelTimePicker.colorButtonBgHover :
|
||||
checked ? DelTheme.DelTimePicker.colorButtonBgActive : 'transparent'
|
||||
}
|
||||
T.ButtonGroup.group: __buttonGroup
|
||||
onClicked: {
|
||||
__rootItem.checkValue = __viewText.text;
|
||||
}
|
||||
onHoveredChanged: {
|
||||
if (hovered) {
|
||||
__rootItem.tempValue = __viewText.text;
|
||||
control.text = __private.getTempTime();
|
||||
}
|
||||
}
|
||||
Component.onCompleted: checked = (index == 0);
|
||||
}
|
||||
onContentHeightChanged: cacheBuffer = contentHeight;
|
||||
T.ScrollBar.vertical: DelScrollBar { policy: T.ScrollBar.AsNeeded }
|
||||
|
||||
T.ButtonGroup {
|
||||
id: __buttonGroup
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
id: __private
|
||||
property var window: Window.window
|
||||
property bool cleared: true
|
||||
function getTime() {
|
||||
switch (control.format) {
|
||||
case DelTimePicker.Format_HHMMSS:
|
||||
return`${__hourListView.value}:${__minuteListView.value}:${__secondListView.value}`;
|
||||
case DelTimePicker.Format_HHMM:
|
||||
return`${__hourListView.value}:${__minuteListView.value}`;
|
||||
case DelTimePicker.Format_MMSS:
|
||||
return`${__minuteListView.value}:${__secondListView.value}`;
|
||||
}
|
||||
}
|
||||
function getCheckTime() {
|
||||
switch (control.format) {
|
||||
case DelTimePicker.Format_HHMMSS:
|
||||
return`${__hourListView.checkValue}:${__minuteListView.checkValue}:${__secondListView.checkValue}`;
|
||||
case DelTimePicker.Format_HHMM:
|
||||
return`${__hourListView.checkValue}:${__minuteListView.checkValue}`;
|
||||
case DelTimePicker.Format_MMSS:
|
||||
return`${__minuteListView.checkValue}:${__secondListView.checkValue}`;
|
||||
}
|
||||
}
|
||||
function getTempTime() {
|
||||
switch (control.format) {
|
||||
case DelTimePicker.Format_HHMMSS:
|
||||
return`${__hourListView.tempValue}:${__minuteListView.tempValue}:${__secondListView.tempValue}`;
|
||||
case DelTimePicker.Format_HHMM:
|
||||
return`${__hourListView.tempValue}:${__minuteListView.tempValue}`;
|
||||
case DelTimePicker.Format_MMSS:
|
||||
return`${__minuteListView.tempValue}:${__secondListView.tempValue}`;
|
||||
}
|
||||
}
|
||||
function testValid() {
|
||||
let reg;
|
||||
switch (control.format) {
|
||||
case DelTimePicker.Format_HHMMSS:
|
||||
reg = /^([0-1]\d|2[0-3]):([0-5]\d):([0-5]\d)$/;
|
||||
break;
|
||||
case DelTimePicker.Format_HHMM:
|
||||
reg = /^([0-1]\d|2[0-3]):([0-5]\d)$/;
|
||||
break;
|
||||
case DelTimePicker.Format_MMSS:
|
||||
reg = /^([0-5]\d):([0-5]\d)$/;
|
||||
break;
|
||||
}
|
||||
return reg.test(control.text);
|
||||
}
|
||||
|
||||
function commit() {
|
||||
let hour = '';
|
||||
let minute = '';
|
||||
let second = '';
|
||||
|
||||
if (testValid()) {
|
||||
switch (control.format) {
|
||||
case DelTimePicker.Format_HHMMSS:
|
||||
hour = control.getText(0, 2);
|
||||
minute = control.getText(3, 5);
|
||||
second = control.getText(6, 8);
|
||||
break;
|
||||
case DelTimePicker.Format_HHMM:
|
||||
hour = control.getText(0, 2);
|
||||
minute = control.getText(3, 5);
|
||||
break;
|
||||
case DelTimePicker.Format_MMSS:
|
||||
minute = control.getText(0, 2);
|
||||
second = control.getText(3, 5);
|
||||
break;
|
||||
}
|
||||
|
||||
if (hour.length === 2) {
|
||||
const index = parseInt(hour);
|
||||
__hourListView.value = hour;
|
||||
__hourListView.checkIndex(index);
|
||||
}
|
||||
if (minute.length === 2) {
|
||||
const index = parseInt(minute);
|
||||
__minuteListView.value = minute;
|
||||
__minuteListView.checkIndex(index);
|
||||
}
|
||||
if (second.length === 2) {
|
||||
const index = parseInt(second);
|
||||
__secondListView.value = second;
|
||||
__secondListView.checkIndex(index);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TapHandler {
|
||||
onTapped: {
|
||||
__picker.open();
|
||||
}
|
||||
}
|
||||
|
||||
DelIconText {
|
||||
anchors.left: control.iconPosition == DelTimePicker.Position_Left ? parent.left : undefined
|
||||
anchors.right: control.iconPosition == DelTimePicker.Position_Right ? parent.right : undefined
|
||||
anchors.margins: 5
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
iconSource: (control.hovered && control.text.length !== 0) ? DelIcon.CloseCircleFilled : DelIcon.ClockCircleOutlined
|
||||
iconSize: control.iconSize
|
||||
colorIcon: control.enabled ?
|
||||
__iconMouse.hovered ? DelTheme.DelTimePicker.colorIconHover :
|
||||
DelTheme.DelTimePicker.colorIcon : DelTheme.DelTimePicker.colorIconDisabled
|
||||
|
||||
Behavior on colorIcon { enabled: control.animationEnabled; ColorAnimation { duration: DelTheme.Primary.durationFast } }
|
||||
|
||||
MouseArea {
|
||||
id: __iconMouse
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: parent.iconSource == DelIcon.CloseCircleFilled ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||
onEntered: hovered = true;
|
||||
onExited: hovered = false;
|
||||
property bool hovered: false
|
||||
onClicked: {
|
||||
__hourListView.clearCheck();
|
||||
__minuteListView.clearCheck();
|
||||
__secondListView.clearCheck();
|
||||
__private.cleared = true;
|
||||
control.text = '';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DelPopup {
|
||||
id: __picker
|
||||
implicitWidth: implicitContentWidth + leftPadding + rightPadding
|
||||
implicitHeight: implicitContentHeight + topPadding + bottomPadding
|
||||
leftPadding: 2
|
||||
rightPadding: 2
|
||||
topPadding: 6
|
||||
bottomPadding: 6
|
||||
closePolicy: T.Popup.CloseOnEscape | T.Popup.CloseOnPressOutsideParent
|
||||
enter: Transition {
|
||||
NumberAnimation {
|
||||
property: 'opacity'
|
||||
from: 0.0
|
||||
to: 1.0
|
||||
easing.type: Easing.InOutQuad
|
||||
duration: control.animationEnabled ? DelTheme.Primary.durationMid : 0
|
||||
}
|
||||
}
|
||||
exit: Transition {
|
||||
NumberAnimation {
|
||||
property: 'opacity'
|
||||
from: 1.0
|
||||
to: 0.0
|
||||
easing.type: Easing.InOutQuad
|
||||
duration: control.animationEnabled ? DelTheme.Primary.durationMid : 0
|
||||
}
|
||||
}
|
||||
onAboutToShow: {
|
||||
const pos = control.mapToItem(null, 0, 0);
|
||||
x = (control.width - width) * 0.5;
|
||||
if (__private.window.height > (pos.y + control.height + height + 6)){
|
||||
y = control.height + 6;
|
||||
} else if (pos.y > height) {
|
||||
y = -height - 6;
|
||||
} else {
|
||||
y = __private.window.height - (pos.y + height + 6);
|
||||
}
|
||||
__private.commit();
|
||||
}
|
||||
onAboutToHide: {
|
||||
control.editingFinished();
|
||||
}
|
||||
contentItem: Item {
|
||||
implicitWidth: __row.width
|
||||
implicitHeight: 250
|
||||
|
||||
Row {
|
||||
id: __row
|
||||
height: parent.height - 30
|
||||
|
||||
TimeListView {
|
||||
id: __hourListView
|
||||
model: 24
|
||||
visible: control.format == DelTimePicker.Format_HHMMSS ||
|
||||
control.format == DelTimePicker.Format_HHMM
|
||||
|
||||
DelDivider {
|
||||
width: 1
|
||||
height: parent.height
|
||||
anchors.right: parent.right
|
||||
orientation: Qt.Vertical
|
||||
}
|
||||
}
|
||||
|
||||
TimeListView {
|
||||
id: __minuteListView
|
||||
model: 60
|
||||
visible: control.format == DelTimePicker.Format_HHMMSS ||
|
||||
control.format == DelTimePicker.Format_HHMM ||
|
||||
control.format == DelTimePicker.Format_MMSS
|
||||
|
||||
DelDivider {
|
||||
width: 1
|
||||
height: parent.height
|
||||
anchors.right: parent.right
|
||||
orientation: Qt.Vertical
|
||||
visible: control.format == DelTimePicker.Format_HHMMSS ||
|
||||
control.format == DelTimePicker.Format_MMSS
|
||||
}
|
||||
}
|
||||
|
||||
TimeListView {
|
||||
id: __secondListView
|
||||
model: 60
|
||||
visible: control.format == DelTimePicker.Format_HHMMSS ||
|
||||
control.format == DelTimePicker.Format_MMSS
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
width: parent.width
|
||||
anchors.top: __row.bottom
|
||||
anchors.bottom: parent.bottom
|
||||
|
||||
DelDivider {
|
||||
width: parent.width
|
||||
height: 1
|
||||
}
|
||||
|
||||
DelButton {
|
||||
padding: 2
|
||||
topPadding: 2
|
||||
bottomPadding: 2
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 5
|
||||
anchors.bottom: parent.bottom
|
||||
type: DelButton.Type_Text
|
||||
text: qsTr('此刻')
|
||||
colorBg: 'transparent'
|
||||
onClicked: {
|
||||
const now = new Date();
|
||||
__hourListView.initValue(String(now.getHours()).padStart(2, '0'));
|
||||
__hourListView.checkIndex(now.getHours());
|
||||
__minuteListView.initValue(String(now.getMinutes()).padStart(2, '0'));
|
||||
__minuteListView.checkIndex(now.getMinutes());
|
||||
__secondListView.initValue(String(now.getSeconds()).padStart(2, '0'));
|
||||
__secondListView.checkIndex(now.getSeconds());
|
||||
|
||||
__private.cleared = false;
|
||||
__picker.close();
|
||||
|
||||
control.acceptedTime(__private.getTime());
|
||||
control.text = __private.getTime();
|
||||
}
|
||||
}
|
||||
|
||||
DelButton {
|
||||
id: __confirmButton
|
||||
topPadding: 2
|
||||
bottomPadding: 2
|
||||
leftPadding: 10
|
||||
rightPadding: 10
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 5
|
||||
anchors.bottom: parent.bottom
|
||||
type: DelButton.Type_Primary
|
||||
text: qsTr('确定')
|
||||
onClicked: {
|
||||
__hourListView.initValue(__hourListView.tempValue);
|
||||
__minuteListView.initValue(__minuteListView.tempValue);
|
||||
__secondListView.initValue(__secondListView.tempValue);
|
||||
__private.cleared = false;
|
||||
__picker.close();
|
||||
|
||||
control.acceptedTime(__private.getTime());
|
||||
control.text = __private.getTime();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Accessible.role: Accessible.EditableText
|
||||
Accessible.editable: true
|
||||
Accessible.description: control.contentDescription
|
||||
}
|
||||
|
|
@ -0,0 +1,307 @@
|
|||
import QtQuick 2.15
|
||||
import QtQuick.Templates 2.15 as T
|
||||
import DelegateUI 1.0
|
||||
|
||||
Item {
|
||||
id: control
|
||||
|
||||
enum Mode {
|
||||
Mode_Left = 0,
|
||||
Mode_Right = 1,
|
||||
Mode_Alternate = 2
|
||||
}
|
||||
|
||||
timeFont {
|
||||
family: DelTheme.DelTimeline.fontFamily
|
||||
pixelSize: DelTheme.DelTimeline.fontSize
|
||||
}
|
||||
contentFont {
|
||||
family: DelTheme.DelTimeline.fontFamily
|
||||
pixelSize: DelTheme.DelTimeline.fontSize
|
||||
}
|
||||
|
||||
property bool animationEnabled: DelTheme.animationEnabled
|
||||
property var initModel: []
|
||||
property int mode: DelTimeline.Mode_Left
|
||||
property bool reverse: false
|
||||
property int defaultNodeSize: 11
|
||||
property int defaultLineWidth: 1
|
||||
property string defaultTimeFormat: "yyyy-MM-dd"
|
||||
property int defaultContentFormat: Text.AutoText
|
||||
property color colorNode: DelTheme.DelTimeline.colorNode
|
||||
property color colorNodeBg: DelTheme.DelTimeline.colorNodeBg
|
||||
property color colorLine: DelTheme.DelTimeline.colorLine
|
||||
property font timeFont
|
||||
property color colorTimeText: DelTheme.DelTimeline.colorTimeText
|
||||
property font contentFont
|
||||
property color colorContentText: DelTheme.DelTimeline.colorContentText
|
||||
property Component nodeDelegate: Component {
|
||||
Item {
|
||||
height: __loading.active ? __loading.height : __icon.active ? __icon.height : defaultNodeSize
|
||||
|
||||
Loader {
|
||||
id: __dot
|
||||
width: parent.height
|
||||
height: parent.height
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
active: !__icon.active && !__loading.active
|
||||
sourceComponent: Rectangle {
|
||||
radius: width >> 1
|
||||
color: control.colorNodeBg
|
||||
border.color: model.colorNode
|
||||
border.width: radius * 0.5
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: __icon
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
active: !__loading.active && model.icon !== 0
|
||||
sourceComponent: DelIconText {
|
||||
iconSource: model.icon
|
||||
iconSize: model.iconSize
|
||||
colorIcon: model.colorNode
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: __loading
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
active: model.loading
|
||||
sourceComponent: DelIconText {
|
||||
iconSize: model.iconSize
|
||||
iconSource: DelIcon.LoadingOutlined
|
||||
colorIcon: model.colorNode
|
||||
|
||||
NumberAnimation on rotation {
|
||||
running: model.loading
|
||||
from: 0
|
||||
to: 360
|
||||
loops: Animation.Infinite
|
||||
duration: 1000
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
property Component lineDelegate: Component {
|
||||
Rectangle {
|
||||
color: control.colorLine
|
||||
}
|
||||
}
|
||||
property Component timeDelegate: Component {
|
||||
Text {
|
||||
id: __timeText
|
||||
color: control.colorTimeText
|
||||
font: control.timeFont
|
||||
text: {
|
||||
if (!isNaN(model.time))
|
||||
return model.time.toLocaleString(Qt.locale(), model.timeFormat);
|
||||
else
|
||||
return "";
|
||||
}
|
||||
horizontalAlignment: onLeft ? Text.AlignRight : Text.AlignLeft
|
||||
}
|
||||
}
|
||||
property Component contentDelegate: Component {
|
||||
Text {
|
||||
id: __contentText
|
||||
color: control.colorContentText
|
||||
font: control.contentFont
|
||||
text: model.content
|
||||
textFormat: model.contentFormat
|
||||
wrapMode: Text.WrapAnywhere
|
||||
horizontalAlignment: onLeft ? Text.AlignRight : Text.AlignLeft
|
||||
}
|
||||
}
|
||||
|
||||
onInitModelChanged: {
|
||||
clear();
|
||||
for (const object of initModel) {
|
||||
append(object);
|
||||
}
|
||||
}
|
||||
|
||||
function flick(xVelocity: real, yVelocity: real) {
|
||||
__listView.flick(xVelocity, yVelocity);
|
||||
}
|
||||
|
||||
function positionViewAtBeginning() {
|
||||
__listView.positionViewAtBeginning();
|
||||
}
|
||||
|
||||
function positionViewAtIndex(index: int, mode: int) {
|
||||
__listView.positionViewAtIndex(index, mode);
|
||||
}
|
||||
|
||||
function positionViewAtEnd() {
|
||||
__listView.positionViewAtEnd();
|
||||
}
|
||||
|
||||
function get(index) {
|
||||
return __listModel.get(index);
|
||||
}
|
||||
|
||||
function set(index, object) {
|
||||
__listModel.set(index, __private.initObject(object));
|
||||
}
|
||||
|
||||
function setProperty(index, propertyName, value) {
|
||||
if (propertyName === "time")
|
||||
__private.noTime = false;
|
||||
__listModel.setProperty(index, propertyName, value);
|
||||
}
|
||||
|
||||
function move(from, to, count = 1) {
|
||||
__listModel.move(from, to, count);
|
||||
}
|
||||
|
||||
function insert(index, object) {
|
||||
__listModel.insert(index, __private.initObject(object));
|
||||
}
|
||||
|
||||
function remove(index, count = 1) {
|
||||
__listModel.remove(index, count);
|
||||
for (let i = 0; i < __listModel.count; i++) {
|
||||
if (__listModel.get(i).hasOwnProperty("time")) {
|
||||
__private.noTime = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function append(object) {
|
||||
__listModel.append(__private.initObject(object));
|
||||
}
|
||||
|
||||
function clear() {
|
||||
__private.noTime = true;
|
||||
__listModel.clear();
|
||||
}
|
||||
|
||||
QtObject {
|
||||
id: __private
|
||||
property bool noTime: true
|
||||
function initObject(object) {
|
||||
/*! 静态角色类型下会有颜色不兼容问题, 统一转换为string即可 */
|
||||
if (object.hasOwnProperty("colorNode")) {
|
||||
object.colorNode = String(object.colorNode);
|
||||
}
|
||||
|
||||
if (!object.hasOwnProperty("colorNode")) object.colorNode = String(control.colorNode);
|
||||
if (!object.hasOwnProperty("icon")) object.icon = 0;
|
||||
if (!object.hasOwnProperty("iconSize")) object.iconSize = control.defaultNodeSize;
|
||||
if (!object.hasOwnProperty("loading")) object.loading = false;
|
||||
|
||||
if (!object.hasOwnProperty("time")) object.time = new Date(undefined);
|
||||
if (!object.hasOwnProperty("timeFormat")) object.timeFormat = control.defaultTimeFormat;
|
||||
|
||||
if (!object.hasOwnProperty("content")) object.content = "";
|
||||
if (!object.hasOwnProperty("contentFormat")) object.contentFormat = control.defaultContentFormat;
|
||||
|
||||
/*! 判断是否存在有效时间 */
|
||||
if (__private.noTime && object.hasOwnProperty("time") && !isNaN(object.time))
|
||||
__private.noTime = false;
|
||||
|
||||
return object;
|
||||
}
|
||||
}
|
||||
|
||||
ListView {
|
||||
id: __listView
|
||||
anchors.fill: parent
|
||||
clip: true
|
||||
verticalLayoutDirection: control.reverse ? ListView.BottomToTop : ListView.TopToBottom
|
||||
model: ListModel { id: __listModel }
|
||||
T.ScrollBar.vertical: DelScrollBar { }
|
||||
add: Transition {
|
||||
NumberAnimation { property: "opacity"; from: 0; to: 1; duration: control.animationEnabled ? DelTheme.Primary.durationMid : 0 }
|
||||
}
|
||||
remove: Transition {
|
||||
NumberAnimation { property: "opacity"; from: 1; to: 0; duration: control.animationEnabled ? DelTheme.Primary.durationMid : 0 }
|
||||
}
|
||||
delegate: Item {
|
||||
id: __rootItem
|
||||
width: __listView.width
|
||||
height: contentLoader.height + 25
|
||||
|
||||
required property var model
|
||||
required property int index
|
||||
property bool timeOnLeft: {
|
||||
if (control.mode == DelTimeline.Mode_Right)
|
||||
return false;
|
||||
else if (control.mode == DelTimeline.Mode_Alternate)
|
||||
return index % 2 == 0;
|
||||
else
|
||||
return true;
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: lineLoader
|
||||
active: {
|
||||
if (control.reverse)
|
||||
return __rootItem.index != 0;
|
||||
else
|
||||
__rootItem.index !== (__listModel.count - 1);
|
||||
}
|
||||
width: defaultLineWidth
|
||||
height: parent.height - nodeLoader.height
|
||||
anchors.horizontalCenter: nodeLoader.horizontalCenter
|
||||
anchors.top: nodeLoader.bottom
|
||||
sourceComponent: lineDelegate
|
||||
property alias model: __rootItem.model
|
||||
property alias index: __rootItem.index
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: nodeLoader
|
||||
x: {
|
||||
if (__private.noTime && control.mode != DelTimeline.Mode_Alternate)
|
||||
return control.mode == DelTimeline.Mode_Left ? 0 : parent.width - width;
|
||||
else
|
||||
return (__rootItem.width - width) * 0.5;
|
||||
}
|
||||
width: 30
|
||||
sourceComponent: nodeDelegate
|
||||
property alias model: __rootItem.model
|
||||
property alias index: __rootItem.index
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: timeLoader
|
||||
y: (nodeLoader.height - __timeFontMetrics.height) * 0.5
|
||||
anchors.left: __rootItem.timeOnLeft ? parent.left : nodeLoader.right
|
||||
anchors.leftMargin: __rootItem.timeOnLeft ? 0 : 5
|
||||
anchors.right: __rootItem.timeOnLeft ? nodeLoader.left : parent.right
|
||||
anchors.rightMargin: __rootItem.timeOnLeft ? 5 : 0
|
||||
sourceComponent: timeDelegate
|
||||
property alias model: __rootItem.model
|
||||
property alias index: __rootItem.index
|
||||
property bool onLeft: __rootItem.timeOnLeft
|
||||
|
||||
FontMetrics {
|
||||
id: __timeFontMetrics
|
||||
font: control.timeFont
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: contentLoader
|
||||
y: (nodeLoader.height - __contentFontMetrics.height) * 0.5
|
||||
anchors.left: !__rootItem.timeOnLeft ? parent.left : nodeLoader.right
|
||||
anchors.leftMargin: !__rootItem.timeOnLeft ? 0 : 5
|
||||
anchors.right: !__rootItem.timeOnLeft ? nodeLoader.left : parent.right
|
||||
anchors.rightMargin: !__rootItem.timeOnLeft ? 5 : 0
|
||||
sourceComponent: contentDelegate
|
||||
property alias model: __rootItem.model
|
||||
property alias index: __rootItem.index
|
||||
property bool onLeft: !__rootItem.timeOnLeft
|
||||
|
||||
FontMetrics {
|
||||
id: __contentFontMetrics
|
||||
font: control.contentFont
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,165 @@
|
|||
import QtQuick 2.15
|
||||
import QtGraphicalEffects 1.15
|
||||
import QtQuick.Templates 2.15 as T
|
||||
import DelegateUI 1.0
|
||||
|
||||
T.ToolTip {
|
||||
id: control
|
||||
|
||||
enum Position
|
||||
{
|
||||
Position_Top = 0,
|
||||
Position_Bottom = 1,
|
||||
Position_Left = 2,
|
||||
Position_Right = 3
|
||||
}
|
||||
|
||||
property bool animationEnabled: DelTheme.animationEnabled
|
||||
property bool arrowVisible: false
|
||||
property int position: DelToolTip.Position_Top
|
||||
property color colorText: DelTheme.DelToolTip.colorText
|
||||
property color colorBg: DelTheme.isDark ? DelTheme.DelToolTip.colorBgDark : DelTheme.DelToolTip.colorBg
|
||||
|
||||
component Arrow: Canvas {
|
||||
onWidthChanged: requestPaint();
|
||||
onHeightChanged: requestPaint();
|
||||
onColorBgChanged: requestPaint();
|
||||
onPaint: {
|
||||
var ctx = getContext("2d");
|
||||
ctx.fillStyle = colorBg;
|
||||
ctx.beginPath();
|
||||
switch (position) {
|
||||
case DelToolTip.Position_Top: {
|
||||
ctx.moveTo(0, 0);
|
||||
ctx.lineTo(width, 0);
|
||||
ctx.lineTo(width * 0.5, height);
|
||||
} break;
|
||||
case DelToolTip.Position_Bottom: {
|
||||
ctx.moveTo(0, height);
|
||||
ctx.lineTo(width, height);
|
||||
ctx.lineTo(width * 0.5, 0);
|
||||
} break;
|
||||
case DelToolTip.Position_Left: {
|
||||
ctx.moveTo(0, 0);
|
||||
ctx.lineTo(0, height);
|
||||
ctx.lineTo(width, height * 0.5);
|
||||
} break;
|
||||
case DelToolTip.Position_Right: {
|
||||
ctx.moveTo(width, 0);
|
||||
ctx.lineTo(width, height);
|
||||
ctx.lineTo(0, height * 0.5);
|
||||
} break;
|
||||
}
|
||||
ctx.closePath();
|
||||
ctx.fill();
|
||||
}
|
||||
property color colorBg: control.colorBg
|
||||
}
|
||||
|
||||
x: {
|
||||
switch (position) {
|
||||
case DelToolTip.Position_Top:
|
||||
case DelToolTip.Position_Bottom:
|
||||
return (__private.controlParentWidth - implicitWidth) * 0.5;
|
||||
case DelToolTip.Position_Left:
|
||||
return -implicitWidth - 4;
|
||||
case DelToolTip.Position_Right:
|
||||
return __private.controlParentWidth + 4;
|
||||
}
|
||||
}
|
||||
y: {
|
||||
switch (position) {
|
||||
case DelToolTip.Position_Top:
|
||||
return -implicitHeight - 4;
|
||||
case DelToolTip.Position_Bottom:
|
||||
return __private.controlParentHeight + 4;
|
||||
case DelToolTip.Position_Left:
|
||||
case DelToolTip.Position_Right:
|
||||
return (__private.controlParentHeight - implicitHeight) * 0.5;
|
||||
}
|
||||
}
|
||||
|
||||
enter: Transition {
|
||||
NumberAnimation { property: "opacity"; from: 0.0; to: 1.0; duration: control.animationEnabled ? DelTheme.Primary.durationMid : 0 }
|
||||
}
|
||||
exit: Transition {
|
||||
NumberAnimation { property: "opacity"; from: 1.0; to: 0.0; duration: control.animationEnabled ? DelTheme.Primary.durationMid : 0 }
|
||||
}
|
||||
|
||||
delay: 300
|
||||
padding: 0
|
||||
implicitWidth: implicitContentWidth
|
||||
implicitHeight: implicitContentHeight
|
||||
font {
|
||||
family: DelTheme.DelToolTip.fontFamily
|
||||
pixelSize: DelTheme.DelToolTip.fontSize
|
||||
}
|
||||
closePolicy: T.Popup.CloseOnEscape | T.Popup.CloseOnPressOutsideParent | T.Popup.CloseOnReleaseOutsideParent
|
||||
contentItem: Item {
|
||||
implicitWidth: __bg.width + (__private.isHorizontal ? 0 : __arrow.width)
|
||||
implicitHeight: __bg.height + (__private.isHorizontal ? __arrow.height : 0)
|
||||
|
||||
DropShadow {
|
||||
anchors.fill: __item
|
||||
radius: 16
|
||||
samples: 17
|
||||
color: DelThemeFunctions.alpha(control.colorText, DelTheme.isDark ? 0.1 : 0.2)
|
||||
source: __item
|
||||
}
|
||||
|
||||
Item {
|
||||
id: __item
|
||||
anchors.fill: parent
|
||||
|
||||
Arrow {
|
||||
id: __arrow
|
||||
x: __private.isHorizontal ? (-control.x + (__private.controlParentWidth - width) * 0.5) : 0
|
||||
y: __private.isHorizontal ? 0 : (-control.y + (__private.controlParentHeight - height)) * 0.5
|
||||
width: __private.arrowSize.width
|
||||
height: __private.arrowSize.height
|
||||
anchors.top: control.position == DelToolTip.Position_Bottom ? parent.top : undefined
|
||||
anchors.bottom: control.position == DelToolTip.Position_Top ? parent.bottom : undefined
|
||||
anchors.left: control.position == DelToolTip.Position_Right ? parent.left : undefined
|
||||
anchors.right: control.position == DelToolTip.Position_Left ? parent.right : undefined
|
||||
|
||||
Connections {
|
||||
target: control
|
||||
function onPositionChanged() {
|
||||
__arrow.requestPaint();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: __bg
|
||||
width: __text.implicitWidth + 14
|
||||
height: __text.implicitHeight + 12
|
||||
anchors.top: control.position == DelToolTip.Position_Top ? parent.top : undefined
|
||||
anchors.bottom: control.position == DelToolTip.Position_Bottom ? parent.bottom : undefined
|
||||
anchors.left: control.position == DelToolTip.Position_Left ? parent.left : undefined
|
||||
anchors.right: control.position == DelToolTip.Position_Right ? parent.right : undefined
|
||||
anchors.margins: 1
|
||||
radius: 4
|
||||
color: control.colorBg
|
||||
|
||||
Text {
|
||||
id: __text
|
||||
text: control.text
|
||||
font: control.font
|
||||
color: control.colorText
|
||||
wrapMode: Text.Wrap
|
||||
anchors.centerIn: parent
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
background: Item { }
|
||||
|
||||
QtObject {
|
||||
id: __private
|
||||
property bool isHorizontal: control.position == DelToolTip.Position_Top || control.position == DelToolTip.Position_Bottom
|
||||
property size arrowSize: control.arrowVisible ? (isHorizontal ? Qt.size(12, 6) : Qt.size(6, 12)) : Qt.size(0, 0)
|
||||
property real controlParentWidth: control.parent ? control.parent.width : 0
|
||||
property real controlParentHeight: control.parent ? control.parent.height : 0
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
import QtQuick 2.15
|
||||
import QtQuick.Templates 2.15 as T
|
||||
import DelegateUI 1.0
|
||||
|
||||
T.Popup {
|
||||
id: control
|
||||
|
||||
property Item target: null
|
||||
property color colorOverlay: DelTheme.DelTour.colorOverlay
|
||||
property real focusMargin: 5
|
||||
|
||||
onAboutToShow: __private.recalcPosition();
|
||||
|
||||
QtObject {
|
||||
id: __private
|
||||
property real focusX: 0
|
||||
property real focusY: 0
|
||||
property real focusWidth: control.target ? (control.target.width + control.focusMargin * 2) : 0
|
||||
property real focusHeight: control.target ? (control.target.height + control.focusMargin * 2) : 0
|
||||
function recalcPosition() {
|
||||
if (!control.target) return;
|
||||
const pos = control.target.mapToItem(null, 0, 0);
|
||||
focusX = pos.x - control.focusMargin;
|
||||
focusY = pos.y - control.focusMargin;
|
||||
}
|
||||
}
|
||||
|
||||
T.Overlay.modal: Item {
|
||||
onWidthChanged: __private.recalcPosition();
|
||||
onHeightChanged: __private.recalcPosition();
|
||||
|
||||
Rectangle {
|
||||
id: source
|
||||
color: control.colorOverlay
|
||||
anchors.fill: parent
|
||||
layer.enabled: true
|
||||
layer.effect: ShaderEffect {
|
||||
property real xMin: __private.focusX / source.width
|
||||
property real xMax: (__private.focusX + __private.focusWidth) / source.width
|
||||
property real yMin: __private.focusY / source.height
|
||||
property real yMax: (__private.focusY + __private.focusHeight) / source.height
|
||||
fragmentShader: "qrc:/DelegateUI/shaders/deltour.frag"
|
||||
}
|
||||
}
|
||||
}
|
||||
parent: T.Overlay.overlay
|
||||
modal: true
|
||||
background: Item { }
|
||||
}
|
||||
|
|
@ -0,0 +1,284 @@
|
|||
import QtQuick 2.15
|
||||
import QtQuick.Templates 2.15 as T
|
||||
import DelegateUI 1.0
|
||||
|
||||
T.Popup {
|
||||
id: control
|
||||
|
||||
x: __private.focusX - (stepCardWidth - focusWidth) * 0.5
|
||||
y: __private.focusY + focusHeight + 5
|
||||
|
||||
stepTitleFont {
|
||||
bold: true
|
||||
family: DelTheme.DelTour.fontFamily
|
||||
pixelSize: DelTheme.DelTour.fontSizeTitle
|
||||
}
|
||||
stepDescriptionFont {
|
||||
family: DelTheme.DelTour.fontFamily
|
||||
pixelSize: DelTheme.DelTour.fontSizeDescription
|
||||
}
|
||||
indicatorFont {
|
||||
family: DelTheme.DelTour.fontFamily
|
||||
pixelSize: DelTheme.DelTour.fontSizeIndicator
|
||||
}
|
||||
buttonFont {
|
||||
family: DelTheme.DelTour.fontFamily
|
||||
pixelSize: DelTheme.DelTour.fontSizeButton
|
||||
}
|
||||
|
||||
property bool animationEnabled: DelTheme.animationEnabled
|
||||
property var stepModel: []
|
||||
property Item currentTarget: null
|
||||
property int currentStep: 0
|
||||
property color colorOverlay: DelTheme.DelTour.colorOverlay
|
||||
property bool showArrow: true
|
||||
property real arrowWidth: 20
|
||||
property real arrowHeight: 10
|
||||
property real focusMargin: 5
|
||||
property real focusWidth: currentTarget ? (currentTarget.width + focusMargin * 2) : 0
|
||||
property real focusHeight: currentTarget ? (currentTarget.height + focusMargin * 2) : 0
|
||||
property real stepCardWidth: 250
|
||||
property real radiusStepCard: DelTheme.DelTour.radiusCard
|
||||
property color colorStepCard: DelTheme.DelTour.colorBg
|
||||
property font stepTitleFont
|
||||
property color colorStepTitle: DelTheme.DelTour.colorText
|
||||
property font stepDescriptionFont
|
||||
property color colorStepDescription: DelTheme.DelTour.colorText
|
||||
property font indicatorFont
|
||||
property color colorIndicator: DelTheme.DelTour.colorText
|
||||
property font buttonFont
|
||||
property Component arrowDelegate: Canvas {
|
||||
id: __arrowDelegate
|
||||
width: arrowWidth
|
||||
height: arrowHeight
|
||||
onWidthChanged: requestPaint();
|
||||
onHeightChanged: requestPaint();
|
||||
onFillStyleChanged: requestPaint();
|
||||
onPaint: {
|
||||
const ctx = getContext("2d");
|
||||
ctx.fillStyle = fillStyle;
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(0, height);
|
||||
ctx.lineTo(width * 0.5, 0);
|
||||
ctx.lineTo(width, height);
|
||||
ctx.closePath();
|
||||
ctx.fill();
|
||||
}
|
||||
property color fillStyle: control.colorStepCard
|
||||
|
||||
Connections {
|
||||
target: control
|
||||
function onCurrentTargetChanged() {
|
||||
if (control.stepModel.length > control.currentStep) {
|
||||
const stepData = control.stepModel[control.currentStep];
|
||||
__arrowDelegate.fillStyle = Qt.binding(() => stepData.cardColor ? stepData.cardColor : control.colorStepCard);
|
||||
}
|
||||
__arrowDelegate.requestPaint();
|
||||
}
|
||||
}
|
||||
}
|
||||
property Component stepCardDelegate: Rectangle {
|
||||
id: __stepCardDelegate
|
||||
width: stepData.cardWidth ? stepData.cardWidth : control.stepCardWidth
|
||||
height: stepData.cardHeight ? stepData.cardHeight : (__stepCardColumn.height + 20)
|
||||
color: stepData.cardColor ? stepData.cardColor : control.colorStepCard
|
||||
radius: stepData.cardRadius ? stepData.cardRadius : control.radiusStepCard
|
||||
clip: true
|
||||
|
||||
property var stepData: new Object
|
||||
|
||||
Connections {
|
||||
target: control
|
||||
function onCurrentTargetChanged() {
|
||||
if (control.stepModel.length > control.currentStep)
|
||||
stepData = control.stepModel[control.currentStep];
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
id: __stepCardColumn
|
||||
width: parent.width
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: 10
|
||||
|
||||
Text {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
text: stepData.title ? stepData.title : ""
|
||||
color: stepData.titleColor ? stepData.titleColor : control.colorStepTitle
|
||||
font: control.stepTitleFont
|
||||
}
|
||||
|
||||
Text {
|
||||
width: parent.width - 20
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
wrapMode: Text.WrapAnywhere
|
||||
text: stepData.description || ""
|
||||
visible: text.length !== 0
|
||||
color: stepData.descriptionColor ? stepData.descriptionColor : control.colorStepDescription
|
||||
font: control.stepDescriptionFont
|
||||
}
|
||||
|
||||
Item {
|
||||
width: parent.width
|
||||
height: 30
|
||||
|
||||
Loader {
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 15
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
sourceComponent: control.indicatorDelegate
|
||||
}
|
||||
|
||||
DelButton {
|
||||
id: __prevButton
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.right: __nextButton.left
|
||||
anchors.rightMargin: 15
|
||||
anchors.bottom: __nextButton.bottom
|
||||
visible: control.currentStep != 0
|
||||
text: qsTr("上一步")
|
||||
font: control.buttonFont
|
||||
type: DelButton.Type_Outlined
|
||||
onClicked: {
|
||||
if (control.currentStep > 0) {
|
||||
control.currentStep -= 1;
|
||||
__stepCardDelegate.stepData = control.stepModel[control.currentStep];
|
||||
control.currentTarget = __stepCardDelegate.stepData.target;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DelButton {
|
||||
id: __nextButton
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 15
|
||||
anchors.bottom: parent.bottom
|
||||
text: (control.currentStep + 1 == control.stepModel.length) ? qsTr("结束导览") : qsTr("下一步")
|
||||
font: control.buttonFont
|
||||
type: DelButton.Type_Primary
|
||||
onClicked: {
|
||||
if ((control.currentStep + 1 == control.stepModel.length)) {
|
||||
control.resetStep();
|
||||
control.close();
|
||||
} else if (control.currentStep + 1 < control.stepModel.length) {
|
||||
control.currentStep += 1;
|
||||
__stepCardDelegate.stepData = control.stepModel[control.currentStep];
|
||||
control.currentTarget = __stepCardDelegate.stepData.target;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DelCaptionButton {
|
||||
radiusBg: 4
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 2
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: 2
|
||||
iconSource: DelIcon.CloseOutlined
|
||||
onClicked: {
|
||||
control.resetStep();
|
||||
control.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
property Component indicatorDelegate: Text {
|
||||
text: (control.currentStep + 1) + " / " + control.stepModel.length
|
||||
font: control.indicatorFont
|
||||
color: control.colorIndicator
|
||||
}
|
||||
|
||||
function resetStep() {
|
||||
control.currentStep = 0;
|
||||
if (control.stepModel.length > control.currentStep) {
|
||||
const stepData = control.stepModel[control.currentStep];
|
||||
currentTarget = stepData.target;
|
||||
}
|
||||
}
|
||||
|
||||
function appendStep(object) {
|
||||
stepModel.push(object);
|
||||
}
|
||||
|
||||
onStepModelChanged: __private.recalcPosition();
|
||||
onCurrentTargetChanged: __private.recalcPosition();
|
||||
onAboutToShow: __private.recalcPosition();
|
||||
|
||||
Behavior on x { enabled: control.animationEnabled; NumberAnimation { duration: DelTheme.Primary.durationMid } }
|
||||
Behavior on y { enabled: control.animationEnabled; NumberAnimation { duration: DelTheme.Primary.durationMid } }
|
||||
|
||||
QtObject {
|
||||
id: __private
|
||||
property bool first: true
|
||||
property real focusX: 0
|
||||
property real focusY: 0
|
||||
function recalcPosition() {
|
||||
/*! 需要延时计算 */
|
||||
__privateTimer.restart();
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: __privateTimer
|
||||
interval: 40
|
||||
onTriggered: {
|
||||
if (!control.currentTarget) return;
|
||||
const pos = control.currentTarget.mapToItem(null, 0, 0);
|
||||
__private.focusX = pos.x - control.focusMargin;
|
||||
__private.focusY = pos.y - control.focusMargin;
|
||||
}
|
||||
}
|
||||
|
||||
T.Overlay.modal: Item {
|
||||
id: __overlayItem
|
||||
onWidthChanged: __private.recalcPosition();
|
||||
onHeightChanged: __private.recalcPosition();
|
||||
|
||||
Rectangle {
|
||||
id: source
|
||||
color: colorOverlay
|
||||
anchors.fill: parent
|
||||
layer.enabled: true
|
||||
layer.effect: ShaderEffect {
|
||||
property real xMin: __private.focusX / __overlayItem.width
|
||||
property real xMax: (__private.focusX + focusWidth) / __overlayItem.width
|
||||
property real yMin: __private.focusY / __overlayItem.height
|
||||
property real yMax: (__private.focusY + focusHeight) / __overlayItem.height
|
||||
|
||||
Behavior on xMin { enabled: control.animationEnabled; NumberAnimation { duration: DelTheme.Primary.durationMid } }
|
||||
Behavior on xMax { enabled: control.animationEnabled; NumberAnimation { duration: DelTheme.Primary.durationMid } }
|
||||
Behavior on yMin { enabled: control.animationEnabled; NumberAnimation { duration: DelTheme.Primary.durationMid } }
|
||||
Behavior on yMax { enabled: control.animationEnabled; NumberAnimation { duration: DelTheme.Primary.durationMid } }
|
||||
|
||||
fragmentShader: "qrc:/DelegateUI/shaders/deltour.frag"
|
||||
}
|
||||
}
|
||||
}
|
||||
closePolicy: T.Popup.CloseOnEscape
|
||||
parent: T.Overlay.overlay
|
||||
focus: true
|
||||
modal: true
|
||||
background: Item {
|
||||
width: stepLoader.item == null ? control.arrowWidth : Math.max(control.arrowWidth, stepLoader.item.width)
|
||||
height: stepLoader.item == null ? control.arrowHeight : (control.arrowHeight + stepLoader.item.height - 1)
|
||||
|
||||
Loader {
|
||||
id: arrowLoader
|
||||
width: control.arrowWidth
|
||||
height: control.arrowHeight
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
sourceComponent: control.arrowDelegate
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: stepLoader
|
||||
anchors.top: arrowLoader.bottom
|
||||
anchors.topMargin: -1
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
sourceComponent: control.stepCardDelegate
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,160 @@
|
|||
import QtQuick 2.15
|
||||
import QtQuick.Window 2.15
|
||||
import DelegateUI 1.0
|
||||
|
||||
Window {
|
||||
id: window
|
||||
|
||||
enum SpecialEffect
|
||||
{
|
||||
None = 0,
|
||||
|
||||
Win_DwmBlur = 1,
|
||||
Win_AcrylicMaterial = 2,
|
||||
Win_Mica = 3,
|
||||
Win_MicaAlt = 4,
|
||||
|
||||
Mac_BlurEffect = 10
|
||||
}
|
||||
|
||||
property real contentHeight: height - captionBar.height
|
||||
property alias captionBar: __captionBar
|
||||
property alias windowAgent: __windowAgent
|
||||
property bool followThemeSwitch: true
|
||||
property bool initialized: false
|
||||
property int specialEffect: DelWindow.None
|
||||
|
||||
visible: true
|
||||
objectName: '__DelWindow__'
|
||||
Component.onCompleted: {
|
||||
initialized = true;
|
||||
setWindowMode(DelTheme.isDark);
|
||||
__captionBar.windowAgent = __windowAgent;
|
||||
if (followThemeSwitch)
|
||||
__connections.onIsDarkChanged();
|
||||
}
|
||||
|
||||
function setMacSystemButtonsVisble(visible) {
|
||||
if (Qt.platform.os === 'osx') {
|
||||
windowAgent.setWindowAttribute('no-system-buttons', !visible);
|
||||
}
|
||||
}
|
||||
|
||||
function setWindowMode(isDark) {
|
||||
if (window.initialized)
|
||||
return windowAgent.setWindowAttribute('dark-mode', isDark);
|
||||
return false;
|
||||
}
|
||||
|
||||
function setSpecialEffect(specialEffect) {
|
||||
if (Qt.platform.os === 'windows') {
|
||||
switch (specialEffect)
|
||||
{
|
||||
case DelWindow.Win_DwmBlur:
|
||||
windowAgent.setWindowAttribute('acrylic-material', false);
|
||||
windowAgent.setWindowAttribute('mica', false);
|
||||
windowAgent.setWindowAttribute('mica-alt', false);
|
||||
if (windowAgent.setWindowAttribute('dwm-blur', true)) {
|
||||
window.specialEffect = DelWindow.Win_DwmBlur;
|
||||
window.color = 'transparent'
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
case DelWindow.Win_AcrylicMaterial:
|
||||
windowAgent.setWindowAttribute('dwm-blur', false);
|
||||
windowAgent.setWindowAttribute('mica', false);
|
||||
windowAgent.setWindowAttribute('mica-alt', false);
|
||||
if (windowAgent.setWindowAttribute('acrylic-material', true)) {
|
||||
window.specialEffect = DelWindow.Win_AcrylicMaterial;
|
||||
window.color = 'transparent';
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
case DelWindow.Win_Mica:
|
||||
windowAgent.setWindowAttribute('dwm-blur', false);
|
||||
windowAgent.setWindowAttribute('acrylic-material', false);
|
||||
windowAgent.setWindowAttribute('mica-alt', false);
|
||||
if (windowAgent.setWindowAttribute('mica', true)) {
|
||||
window.specialEffect = DelWindow.Win_Mica;
|
||||
window.color = 'transparent';
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
case DelWindow.Win_MicaAlt:
|
||||
windowAgent.setWindowAttribute('dwm-blur', false);
|
||||
windowAgent.setWindowAttribute('acrylic-material', false);
|
||||
windowAgent.setWindowAttribute('mica', false);
|
||||
if (windowAgent.setWindowAttribute('mica-alt', true)) {
|
||||
window.specialEffect = DelWindow.Win_MicaAlt;
|
||||
window.color = 'transparent';
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
case DelWindow.None:
|
||||
default:
|
||||
windowAgent.setWindowAttribute('dwm-blur', false);
|
||||
windowAgent.setWindowAttribute('acrylic-material', false);
|
||||
windowAgent.setWindowAttribute('mica', false);
|
||||
windowAgent.setWindowAttribute('mica-alt', false);
|
||||
window.specialEffect = DelWindow.None;
|
||||
break;
|
||||
}
|
||||
} else if (Qt.platform.os === 'osx') {
|
||||
switch (specialEffect)
|
||||
{
|
||||
case DelWindow.Mac_BlurEffect:
|
||||
if (windowAgent.setWindowAttribute('blur-effect', DelTheme.isDark ? 'dark' : 'light')) {
|
||||
window.specialEffect = DelWindow.Mac_BlurEffect;
|
||||
window.color = 'transparent'
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
case DelWindow.None:
|
||||
default:
|
||||
windowAgent.setWindowAttribute('blur-effect', 'none');
|
||||
window.specialEffect = DelWindow.None;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: DelTheme
|
||||
enabled: Qt.platform.os === 'osx' /*! 需额外为 MACOSX 处理*/
|
||||
function onIsDarkChanged() {
|
||||
if (window.specialEffect === DelWindow.Mac_BlurEffect)
|
||||
windowAgent.setWindowAttribute('blur-effect', DelTheme.isDark ? 'dark' : 'light');
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
id: __connections
|
||||
target: DelTheme
|
||||
enabled: window.followThemeSwitch
|
||||
function onIsDarkChanged() {
|
||||
if (window.specialEffect == DelWindow.None)
|
||||
window.color = DelTheme.Primary.colorBgBase;
|
||||
window.setWindowMode(DelTheme.isDark);
|
||||
}
|
||||
}
|
||||
|
||||
DelWindowAgent {
|
||||
id: __windowAgent
|
||||
}
|
||||
|
||||
DelCaptionBar {
|
||||
id: __captionBar
|
||||
z: 65535
|
||||
width: parent.width
|
||||
height: 30
|
||||
anchors.top: parent.top
|
||||
targetWindow: window
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
module DelegateUI
|
||||
plugin DelegateUI
|
||||
classname DelegateUIPlugin
|
||||
designersupported
|
||||
typeinfo plugins.qmltypes
|
||||
|
||||
DelButton 1.0 Controls/DelButton.qml
|
||||
DelIconButton 1.0 Controls/DelIconButton.qml
|
||||
DelCaptionButton 1.0 Controls/DelCaptionButton.qml
|
||||
DelTourFocus 1.0 Controls/DelTourFocus.qml
|
||||
DelTourStep 1.0 Controls/DelTourStep.qml
|
||||
DelIconText 1.0 Controls/DelIconText.qml
|
||||
DelCopyableText 1.0 Controls/DelCopyableText.qml
|
||||
DelCaptionBar 1.0 Controls/DelCaptionBar.qml
|
||||
DelWindow 1.0 Controls/DelWindow.qml
|
||||
DelMenu 1.0 Controls/DelMenu.qml
|
||||
DelDivider 1.0 Controls/DelDivider.qml
|
||||
DelSwitch 1.0 Controls/DelSwitch.qml
|
||||
DelScrollBar 1.0 Controls/DelScrollBar.qml
|
||||
DelResizeMouseArea 1.0 Controls/DelResizeMouseArea.qml
|
||||
DelMoveMouseArea 1.0 Controls/DelMoveMouseArea.qml
|
||||
DelAcrylic 1.0 Controls/DelAcrylic.qml
|
||||
DelSlider 1.0 Controls/DelSlider.qml
|
||||
DelTabView 1.0 Controls/DelTabView.qml
|
||||
DelToolTip 1.0 Controls/DelToolTip.qml
|
||||
DelSelect 1.0 Controls/DelSelect.qml
|
||||
DelInput 1.0 Controls/DelInput.qml
|
||||
DelOTPInput 1.0 Controls/DelOTPInput.qml
|
||||
DelRate 1.0 Controls/DelRate.qml
|
||||
DelRadio 1.0 Controls/DelRadio.qml
|
||||
DelRadioBlock 1.0 Controls/DelRadioBlock.qml
|
||||
DelCheckBox 1.0 Controls/DelCheckBox.qml
|
||||
DelTimePicker 1.0 Controls/DelTimePicker.qml
|
||||
DelDrawer 1.0 Controls/DelDrawer.qml
|
||||
DelCollapse 1.0 Controls/DelCollapse.qml
|
||||
DelAvatar 1.0 Controls/DelAvatar.qml
|
||||
DelCard 1.0 Controls/DelCard.qml
|
||||
DelPagination 1.0 Controls/DelPagination.qml
|
||||
DelPopup 1.0 Controls/DelPopup.qml
|
||||
DelTimeline 1.0 Controls/DelTimeline.qml
|
||||
DelTag 1.0 Controls/DelTag.qml
|
||||
DelTableView 1.0 Controls/DelTableView.qml
|
||||
DelMessage 1.0 Controls/DelMessage.qml
|
||||
DelAutoComplete 1.0 Controls/DelAutoComplete.qml
|
||||
DelText 1.0 Controls/DelText.qml
|
||||
DelDatePicker 1.0 Controls/DelDatePicker.qml
|
||||
DelProgress 1.0 Controls/DelProgress.qml
|
||||
|
|
@ -0,0 +1,204 @@
|
|||
#ifndef FILETRANSFER_H
|
||||
#define FILETRANSFER_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QNetworkReply>
|
||||
#include <QFile>
|
||||
#include <QDir>
|
||||
#include <QFileInfo>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QHttpMultiPart>
|
||||
|
||||
class FileTransfer : public QObject {
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(double downloadProgress READ downloadProgress NOTIFY downloadProgressChanged)
|
||||
Q_PROPERTY(double uploadProgress READ uploadProgress NOTIFY uploadProgressChanged)
|
||||
|
||||
public:
|
||||
static FileTransfer* instance() {
|
||||
static FileTransfer _instance;
|
||||
return &_instance;
|
||||
}
|
||||
|
||||
Q_INVOKABLE void postDownload(const QString& url,
|
||||
const QString& savePath,
|
||||
const QVariantMap& postData,
|
||||
const QString& additionalobj) {
|
||||
if (m_currentDownload) {
|
||||
emit downloadCompleted(false, u8"已有进行中的下载");
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取文件所在的目录路径
|
||||
QFileInfo fileInfo(savePath);
|
||||
QString dirPath = fileInfo.absolutePath();
|
||||
|
||||
// 检查目录是否存在,如果不存在则创建
|
||||
QDir dir;
|
||||
if (!dir.exists(dirPath)) {
|
||||
if (!dir.mkpath(dirPath)) {
|
||||
emit downloadCompleted(false, u8"无法创建目录");
|
||||
cleanup();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 创建目标文件
|
||||
m_downloadFile = new QFile(savePath, this);
|
||||
if (!m_downloadFile->open(QIODevice::WriteOnly)) {
|
||||
emit downloadCompleted(false, u8"无法创建文件");
|
||||
cleanup();
|
||||
return;
|
||||
}
|
||||
|
||||
QUrl u(url);
|
||||
// 配置 HTTPS
|
||||
QNetworkRequest request(u);
|
||||
QSslConfiguration sslConfig = request.sslConfiguration();
|
||||
sslConfig.setPeerVerifyMode(QSslSocket::VerifyNone); // 跳过证书验证
|
||||
request.setSslConfiguration(sslConfig);
|
||||
|
||||
// 构建 POST 数据(JSON 格式示例)
|
||||
QJsonDocument jsonDoc(QJsonObject::fromVariantMap (postData));
|
||||
QByteArray postBody = jsonDoc.toJson();
|
||||
|
||||
// 发送请求
|
||||
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||
m_currentDownload = manager.post(request, postBody);
|
||||
|
||||
// 连接信号
|
||||
connect(m_currentDownload, &QNetworkReply::readyRead, [this](){
|
||||
m_downloadFile->write(m_currentDownload->readAll());
|
||||
});
|
||||
|
||||
connect(m_currentDownload, &QNetworkReply::downloadProgress,
|
||||
[this,additionalobj](qint64 bytesReceived, qint64 bytesTotal){
|
||||
m_downloadProgress = bytesTotal > 0
|
||||
? bytesReceived / (double)bytesTotal
|
||||
: 0;
|
||||
emit downloadProgressChanged(m_downloadProgress,additionalobj);
|
||||
});
|
||||
|
||||
connect(m_currentDownload, &QNetworkReply::finished, [this,additionalobj](){
|
||||
if (m_downloadFile) {
|
||||
m_downloadFile->flush();
|
||||
m_downloadFile->close();
|
||||
}
|
||||
|
||||
bool success = !m_currentDownload->error();
|
||||
QString message = success ? additionalobj : m_currentDownload->errorString();
|
||||
|
||||
if (success) {
|
||||
// 验证文件完整性
|
||||
QFileInfo fi(m_downloadFile->fileName());
|
||||
qint64 expectedSize = m_currentDownload->header(
|
||||
QNetworkRequest::ContentLengthHeader).toLongLong();
|
||||
if (fi.size() != expectedSize && expectedSize > 0) {
|
||||
success = false;
|
||||
message = "文件不完整";
|
||||
}
|
||||
}
|
||||
cleanup();
|
||||
emit downloadCompleted(success, message.toUtf8());
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
Q_INVOKABLE void postUpload(const QString& url,
|
||||
const QString& filePath,
|
||||
const QString& remotePath,
|
||||
const QString& additionalobj) {
|
||||
if (m_currentDownload) { // 复用同一个网络管理器,保持单请求
|
||||
emit uploadCompleted(false, u8"已有进行中的传输");
|
||||
return;
|
||||
}
|
||||
|
||||
// 准备要上传的文件
|
||||
QFile *uploadFile = new QFile(filePath, this);
|
||||
if (!uploadFile->open(QIODevice::ReadOnly)) {
|
||||
emit uploadCompleted(false, u8"无法打开文件");
|
||||
uploadFile->deleteLater();
|
||||
return;
|
||||
}
|
||||
|
||||
// 创建多部分数据
|
||||
QHttpMultiPart *multiPart = new QHttpMultiPart(QHttpMultiPart::FormDataType, this);
|
||||
|
||||
// 添加文件部分
|
||||
QHttpPart filePart;
|
||||
filePart.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("application/octet-stream"));
|
||||
filePart.setHeader(QNetworkRequest::ContentDispositionHeader,
|
||||
QVariant(QString("form-data; name=\"file\"; filename=\"%1\"")
|
||||
.arg(remotePath + QFileInfo(filePath).fileName())));
|
||||
filePart.setBodyDevice(uploadFile);
|
||||
uploadFile->setParent(multiPart); // 文件对象由multiPart管理生命周期
|
||||
multiPart->append(filePart);
|
||||
|
||||
QUrl u(url);
|
||||
QNetworkRequest request(u);
|
||||
|
||||
// 配置HTTPS
|
||||
QSslConfiguration sslConfig = request.sslConfiguration();
|
||||
sslConfig.setPeerVerifyMode(QSslSocket::VerifyNone);
|
||||
request.setSslConfiguration(sslConfig);
|
||||
|
||||
// 发送请求
|
||||
m_currentDownload = manager.post(request, multiPart);
|
||||
multiPart->setParent(m_currentDownload); // 由reply管理multiPart生命周期
|
||||
|
||||
// 连接进度信号
|
||||
connect(m_currentDownload, &QNetworkReply::uploadProgress,
|
||||
[this, additionalobj](qint64 bytesSent, qint64 bytesTotal){
|
||||
m_uploadProgress = bytesTotal > 0
|
||||
? bytesSent / (double)bytesTotal
|
||||
: 1;
|
||||
emit uploadProgressChanged(m_uploadProgress, additionalobj); // 带附加参数
|
||||
});
|
||||
|
||||
// 处理完成信号
|
||||
connect(m_currentDownload, &QNetworkReply::finished, [this, additionalobj](){
|
||||
bool success = (m_currentDownload->error() == QNetworkReply::NoError);
|
||||
QString message = success ? u8"上传成功" : m_currentDownload->errorString();
|
||||
cleanup();
|
||||
emit uploadCompleted(success, additionalobj);
|
||||
});
|
||||
}
|
||||
|
||||
double downloadProgress() const { return m_downloadProgress; }
|
||||
double uploadProgress() const { return m_uploadProgress; }
|
||||
|
||||
signals:
|
||||
// 下载相关信号
|
||||
void downloadProgressChanged(double progress,const QString& additional);
|
||||
void downloadCompleted(bool success, const QString& message);
|
||||
|
||||
// 上传相关信号(原有)
|
||||
void uploadProgressChanged(double progress ,const QString& additional);
|
||||
void uploadCompleted(bool success, const QString& message);
|
||||
|
||||
private:
|
||||
explicit FileTransfer(QObject* parent = nullptr)
|
||||
: QObject(parent), m_downloadProgress(0), m_uploadProgress(0),
|
||||
m_currentDownload(nullptr), m_downloadFile(nullptr) {}
|
||||
|
||||
QNetworkAccessManager manager;
|
||||
double m_downloadProgress;
|
||||
double m_uploadProgress;
|
||||
QNetworkReply* m_currentDownload;
|
||||
QFile* m_downloadFile;
|
||||
|
||||
void cleanup() {
|
||||
if (m_downloadFile) {
|
||||
m_downloadFile->deleteLater();
|
||||
m_downloadFile = nullptr;
|
||||
}
|
||||
if (m_currentDownload) {
|
||||
m_currentDownload->deleteLater();
|
||||
m_currentDownload = nullptr;
|
||||
}
|
||||
m_downloadProgress = 0;
|
||||
}
|
||||
};
|
||||
#endif // FILETRANSFER_H
|
||||
|
|
@ -0,0 +1,374 @@
|
|||
pragma Singleton
|
||||
import QtQuick 2.15
|
||||
import QmlTool 1.0
|
||||
|
||||
|
||||
QtObject {
|
||||
//是否处于开发模式
|
||||
property bool devModel : false
|
||||
property string tool_version:"1.9"
|
||||
property var all_Version : null
|
||||
|
||||
|
||||
//请求的服务器url
|
||||
// property string server_url: "http://192.168.200.20:8651"
|
||||
property string server_url: "https://dpsm.senzo.online"
|
||||
// property string server_url: "http://43.160.206.158:8651"
|
||||
|
||||
|
||||
//界面对象
|
||||
property var main_Window : null
|
||||
property var page_home : null
|
||||
property var tab_shop : null
|
||||
property var tab_server : null
|
||||
property var tab_personal:null
|
||||
property var downlad_quest_window:null
|
||||
//全局消息提示对象
|
||||
property var msg_control : null
|
||||
//服务器控制台输出窗口
|
||||
property var serverConsoleWindow: null
|
||||
|
||||
//客户端Token
|
||||
property string token: "Bearer eyJhbGciOiJIUzI1NiJ9.eyJhdXRob3JpdGllcyI6WyJST0xFX0FMTCJdLCJ1c2VybmFtZSI6IjEyMyIsInN1YiI6IjEyMyIsImlhdCI6MTc0NDkwMTYwMiwiZXhwIjoxNzUyMTAxNjAyfQ.SlJxDukrOV4QbHXn0EBXLQhk8kEC2R0Ka431AGgWEe8"
|
||||
//活跃人数
|
||||
property string active_count : "666"
|
||||
//服务器插件项目
|
||||
property var serverPluginList : null
|
||||
//服务器插件项目Map
|
||||
property var serverPluginMap : null
|
||||
//服务器双端插件项目Map
|
||||
property var serverPluginexMap : null
|
||||
//服务器所有广告
|
||||
property var serverAdvertisement : null
|
||||
//账号下的所有服务器
|
||||
property var accServerList : null
|
||||
//账号信息
|
||||
property var accInfo : null
|
||||
|
||||
//当前ssh连接的服务器
|
||||
property string sshConnectServer:""
|
||||
//当前选择的要操作的服务器
|
||||
property int selectServer : 0
|
||||
//我的服务器插件项目
|
||||
property var myServerPluginList : null
|
||||
//我的服务器插件项目Map
|
||||
property var myServerPluginMap : null
|
||||
//我的服务器双端插件项目Map
|
||||
property var myServerExPluginMap : null
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
//公共方法
|
||||
function sendPostRequest(url, jsonData, callback) {
|
||||
// 创建请求对象
|
||||
var xhr = new XMLHttpRequest()
|
||||
xhr.open("POST", server_url + url)
|
||||
// 设置请求头,表明发送的数据是 JSON 格式
|
||||
xhr.setRequestHeader("Content-Type", "application/json")
|
||||
if (token.length > 0) {
|
||||
xhr.setRequestHeader("Authorization", token);
|
||||
}
|
||||
// 定义回调函数
|
||||
xhr.onreadystatechange = function() {
|
||||
if (xhr.readyState === XMLHttpRequest.DONE) {
|
||||
if (xhr.status === 200) {
|
||||
// console.log(xhr.responseText)
|
||||
callback(null, JSON.parse(xhr.responseText))
|
||||
} else {
|
||||
// console.log(xhr.responseText)
|
||||
callback(new Error("请求失败,状态码: " + xhr.status), xhr.responseText)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 将 JSON 对象转换为字符串
|
||||
var jsonString = JSON.stringify(jsonData)
|
||||
|
||||
// 发送包含 JSON 数据的请求
|
||||
xhr.send(jsonString)
|
||||
}
|
||||
|
||||
//公共方法
|
||||
function sendPostRequestByIp(ip, url, jsonData, callback) {
|
||||
// 创建请求对象
|
||||
var xhr = new XMLHttpRequest()
|
||||
xhr.open("POST", ip + url)
|
||||
// 设置请求头,表明发送的数据是 JSON 格式
|
||||
xhr.setRequestHeader("Content-Type", "application/json")
|
||||
// 定义回调函数
|
||||
xhr.onreadystatechange = function() {
|
||||
if (xhr.readyState === XMLHttpRequest.DONE) {
|
||||
if (xhr.status === 200) {
|
||||
// console.log(xhr.responseText)
|
||||
callback(null, JSON.parse(xhr.responseText))
|
||||
} else {
|
||||
callback(new Error("请求失败,状态码: " + xhr.status), xhr.responseText)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(jsonData){
|
||||
// 将 JSON 对象转换为字符串
|
||||
var jsonString = JSON.stringify(jsonData)
|
||||
// 发送包含 JSON 数据的请求
|
||||
xhr.send(jsonString)
|
||||
}
|
||||
else{
|
||||
xhr.send()
|
||||
}
|
||||
}
|
||||
|
||||
//获取版本信息
|
||||
function getAllVersion(){
|
||||
sendPostRequest("/rindro/getversion",{},function(error, response) {
|
||||
if (error) {
|
||||
msg_control.error("获取版本失败!");
|
||||
} else {
|
||||
all_Version = response;
|
||||
//先判断一下工具版本
|
||||
if(parseFloat(all_Version.toolVer) > parseFloat(tool_version)){
|
||||
QmlTool.runExe("DelUpdate.exe " + server_url)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
//获取市场所有插件信息
|
||||
function getShopAllProject(){
|
||||
sendPostRequest("/dps/getlist",{},function(error, response) {
|
||||
if (error) {
|
||||
msg_control.error("获取服务器插件列表失败!");
|
||||
serverPluginList = null
|
||||
serverPluginMap = null
|
||||
} else {
|
||||
serverPluginList = response.list;
|
||||
serverPluginMap = {}
|
||||
// 遍历对象的属性
|
||||
for (var key in serverPluginList) {
|
||||
var obj = serverPluginList[key];
|
||||
serverPluginMap[obj.ProjectName] = obj
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
//获取市场所有插件信息
|
||||
function getShopAllProjectex(){
|
||||
sendPostRequest("/rindro/getProjectInfoList",{},function(error, response) {
|
||||
if (error) {
|
||||
msg_control.error("获取服务器双端插件列表失败!");
|
||||
serverPluginexMap = null
|
||||
} else {
|
||||
serverPluginexMap = response.data;
|
||||
// 遍历对象的属性
|
||||
for (var key in serverPluginexMap) {
|
||||
var obj = serverPluginexMap[key]
|
||||
obj.ProjectName = key
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
//获取市场所有广告信息
|
||||
function getAllAdvertisement(callBack){
|
||||
sendPostRequest("/dps/getadvertisement",{},function(error, response) {
|
||||
if (error) {
|
||||
msg_control.error("获取服务器广告列表失败!");
|
||||
} else {
|
||||
callBack(response)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
//购买双端插件
|
||||
function buyExPlugin(Name,Ip){
|
||||
sendPostRequest("/rindro/buy",{projectName : Name,ip: Ip},function(error, response) {
|
||||
if (error) {
|
||||
msg_control.error("购买失败!");
|
||||
} else {
|
||||
if(response.code !== 200){
|
||||
msg_control.error("购买失败!\t" + response.message);
|
||||
}
|
||||
else{
|
||||
msg_control.success("购买成功!\t到期时间: " + response.data.endTime);
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
//获取账号下的所有服务器
|
||||
function getAccServerList(){
|
||||
sendPostRequest("/rindro/getServerList",{},function(error, response) {
|
||||
if (error) {
|
||||
msg_control.error("获取账号服务器列表失败!");
|
||||
} else {
|
||||
accServerList = response.data;
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
//获取账号个人信息
|
||||
function getAccInfo(){
|
||||
sendPostRequest("/rindro/getInfo",{},function(error, response) {
|
||||
if (error) {
|
||||
msg_control.error("获取账号信息失败!");
|
||||
} else {
|
||||
accInfo = response.data;
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
//删除账号下的服务器
|
||||
function removeServer(remove_ip){
|
||||
sendPostRequest("/rindro/deleteServer",{ip : remove_ip},function(error, response) {
|
||||
if (error) {
|
||||
msg_control.error("删除服务器失败!");
|
||||
} else {
|
||||
msg_control.success("删除服务器成功!");
|
||||
getAccServerList();
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
//新增或修改服务器
|
||||
function addServer(obj){
|
||||
sendPostRequest("/rindro/addServer",obj,function(error, response) {
|
||||
if (error) {
|
||||
msg_control.error("操作服务器失败!");
|
||||
} else {
|
||||
msg_control.success("操作服务器成功!");
|
||||
getAccServerList();
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
//向网关获取版本
|
||||
function getServerInfo(ip,callback){
|
||||
sendPostRequestByIp(ip , "/getdpsinfo",{},function(error, response) {
|
||||
if (error) {
|
||||
callback(false,0,0);
|
||||
} else {
|
||||
if(response.info !== null)
|
||||
callback(true,0,response.info);
|
||||
else
|
||||
callback(false,0,0);
|
||||
}
|
||||
})
|
||||
sendPostRequestByIp(ip , "/getjavapinfo",{},function(error, response) {
|
||||
if (error) {
|
||||
callback(false,1,0);
|
||||
} else {
|
||||
if(response.info !== null)
|
||||
callback(true,1,response.info);
|
||||
else
|
||||
callback(false,1,0);
|
||||
}
|
||||
})
|
||||
sendPostRequestByIp(ip , "/getjavainfo",{},function(error, response) {
|
||||
if (error) {
|
||||
callback(false,2,0);
|
||||
} else {
|
||||
if(response.info !== null)
|
||||
callback(true,2,response.info);
|
||||
else
|
||||
callback(false,2,0);
|
||||
}
|
||||
})
|
||||
sendPostRequestByIp(ip , "/api/hello",{},function(error, response) {
|
||||
if (error) {
|
||||
msg_control.error("服务器dps管理网关未启动,请前往'个人中心'重新添加服务器 或 询问管理员!")
|
||||
} else {
|
||||
callback(false,3,true);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
//获取自己服务器的全部插件
|
||||
function getServerPlugins(ip){
|
||||
sendPostRequestByIp("http://" + ip + ":65170", "/api/getallplugins",{},function(error, response) {
|
||||
if (!error) {
|
||||
myServerPluginList = response
|
||||
myServerPluginMap = {}
|
||||
// 遍历对象的属性
|
||||
for (var key in myServerPluginList) {
|
||||
var obj = myServerPluginList[key];
|
||||
myServerPluginMap[obj.ProjectName] = obj
|
||||
}
|
||||
} else {
|
||||
myServerPluginList = null;
|
||||
myServerPluginMap = null;
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
//获取自己服务器的全部双端插件
|
||||
function getServerExPlugins(ipk){
|
||||
sendPostRequest("/rindro/getUserProList",{ip:ipk},function(error, response) {
|
||||
if (!error) {
|
||||
myServerExPluginMap = response.data
|
||||
} else {
|
||||
myServerExPluginMap = null;
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
//设置双端插件是否启用
|
||||
function setServerExPluginsEnable(ipk,projectname,cstate){
|
||||
sendPostRequest("/rindro/setproopen",{ip:ipk,projectName:projectname,open:cstate},function(error, response) {
|
||||
if (!error) {
|
||||
|
||||
} else {
|
||||
if(cstate === 1)msg_control.success("开启成功");
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
//请求某个项目的配置
|
||||
function getServerPluginConfig(ip,filename,callback){
|
||||
sendPostRequestByIp("http://" + ip + ":65170", "/api/getpluginconfig",{filename:filename},function(error, response) {
|
||||
if (!error) {
|
||||
callback(response)
|
||||
} else {
|
||||
callback(null)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
//保存某个项目的配置
|
||||
function setServerPluginConfig(ip,filename,config){
|
||||
sendPostRequestByIp("http://" + ip + ":65170", "/api/setpluginconfig",{filename:filename,config:config},function(error, response) {
|
||||
if (!error) {
|
||||
msg_control.success("配置修改成功!")
|
||||
} else {
|
||||
msg_control.error("配置修改失败!")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
//备份某个项目的配置
|
||||
function backupServerPluginConfig(ip,filename,callback){
|
||||
sendPostRequestByIp("http://" + ip + ":65170", "/api/backuppluginconfig",{filename:filename},function(error, response) {
|
||||
if (!error) {
|
||||
callback(response)
|
||||
} else {
|
||||
callback(null)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
module MyGlobals
|
||||
singleton GlobalVars 1.0 GlobalVars.qml
|
||||
|
|
@ -0,0 +1,150 @@
|
|||
import QtQuick 2.15
|
||||
import QtQuick.Window 2.15
|
||||
import DelegateUI 1.0
|
||||
import "../MyGlobals" 1.0
|
||||
import SSHManager 1.0
|
||||
|
||||
Item {
|
||||
id:root_item
|
||||
y : GlobalVars.main_Window.captionBar.height
|
||||
height: GlobalVars.main_Window.height - GlobalVars.main_Window.captionBar.height
|
||||
|
||||
Component.onCompleted: {
|
||||
GlobalVars.page_home = this;
|
||||
|
||||
//获取市场所有插件信息
|
||||
GlobalVars.getShopAllProject();
|
||||
//获取市场所有双端插件信息
|
||||
GlobalVars.getShopAllProjectex();
|
||||
//获取账号下的所有服务器
|
||||
GlobalVars.getAccServerList();
|
||||
//获取账号的个人信息
|
||||
GlobalVars.getAccInfo();
|
||||
|
||||
//临时测试 创建一个下载窗口
|
||||
// 创建新窗口
|
||||
var component = Qt.createComponent("Window_DownloadQuest.qml");
|
||||
var windowbuf = component.createObject(parent);
|
||||
|
||||
// 绑定关闭时
|
||||
windowbuf.closing.connect(function() {
|
||||
windowbuf.close.accepted = false // 阻止默认关闭行为
|
||||
windowbuf.width = 799
|
||||
windowbuf.hide() // 隐藏窗口
|
||||
})
|
||||
|
||||
pageLoader.source = "Tab_home.qml"
|
||||
|
||||
initMenuServerList()
|
||||
}
|
||||
|
||||
function initMenuServerList(){
|
||||
if(GlobalVars.accServerList){
|
||||
var arrbuf = [];
|
||||
for(var i = 0; i < GlobalVars.accServerList.length;i++){
|
||||
var obj = GlobalVars.accServerList[i];
|
||||
var buf = {
|
||||
key:"server",
|
||||
source:"Tab_server.qml",
|
||||
label: qsTr(obj.serverIp),
|
||||
port:obj.port,
|
||||
user:obj.userName,
|
||||
passwd:obj.password,
|
||||
iconSource: DelIcon.HomeOutlined
|
||||
}
|
||||
arrbuf.push(buf);
|
||||
}
|
||||
menu.setProperty(2,"menuChildren" , arrbuf);
|
||||
}
|
||||
}
|
||||
|
||||
//监听全局变量的变化
|
||||
Connections {
|
||||
target: GlobalVars
|
||||
function onAccServerListChanged(){
|
||||
initMenuServerList();
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
visible: true
|
||||
width: 220
|
||||
height: parent.height
|
||||
color: "transparent"
|
||||
DelMenu {
|
||||
id: menu
|
||||
anchors.fill: parent
|
||||
// showEdge: true
|
||||
initModel: [
|
||||
{
|
||||
label: qsTr("首页"),
|
||||
iconSource: DelIcon.HomeOutlined,
|
||||
source:"Tab_home.qml"
|
||||
},
|
||||
{
|
||||
type: "divider"
|
||||
},
|
||||
{
|
||||
label: qsTr("我的服务器"),
|
||||
iconSource: DelIcon.CloudServerOutlined,
|
||||
},
|
||||
{
|
||||
label: qsTr("插件市场(服务端)"),
|
||||
iconSource: DelIcon.ShopOutlined,
|
||||
source:"Tab_shop.qml"
|
||||
},
|
||||
{
|
||||
label: qsTr("插件市场(双端)"),
|
||||
iconSource: DelIcon.ShopOutlined,
|
||||
source:"Tab_shopex.qml"
|
||||
},
|
||||
{
|
||||
label: qsTr("个人中心"),
|
||||
iconSource: DelIcon.ProductOutlined,
|
||||
source:"Tab_personal.qml"
|
||||
},
|
||||
{
|
||||
label: qsTr("更新日志"),
|
||||
iconSource: DelIcon.ClockCircleOutlined,
|
||||
source:"Tab_update.qml"
|
||||
},
|
||||
{
|
||||
label: qsTr("关于(赞助请看这里)"),
|
||||
iconSource: DelIcon.InfoCircleOutlined,
|
||||
source:"Tab_about.qml"
|
||||
}
|
||||
]
|
||||
|
||||
onClickMenu: function(deep,menuKey,menuData){
|
||||
//表层
|
||||
if(deep === 0){
|
||||
pageLoader.source = menuData.source;
|
||||
}
|
||||
//服务器层
|
||||
else if(deep === 1){
|
||||
//连接了服务器
|
||||
if(menuData.key === "server"){
|
||||
pageLoader.source = ""
|
||||
pageLoader.source = menuData.source;
|
||||
pageLoader.item.init(menuData.label, menuData.port, menuData.user, menuData.passwd);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 用于显示页面的区域
|
||||
Rectangle {
|
||||
id: pageArea
|
||||
x: menu.width
|
||||
width: parent.width - menu.width
|
||||
height: parent.height
|
||||
color: "transparent"
|
||||
|
||||
Loader {
|
||||
id: pageLoader
|
||||
anchors.fill: parent
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,264 @@
|
|||
import QtQuick 2.15
|
||||
import QtQuick.Window 2.15
|
||||
import Qt.labs.settings 1.1
|
||||
import DelegateUI 1.0
|
||||
import "../MyGlobals" 1.0
|
||||
|
||||
Item {
|
||||
id:loginPage
|
||||
y : GlobalVars.main_Window.captionBar.height
|
||||
height: GlobalVars.main_Window.height - GlobalVars.main_Window.captionBar.height
|
||||
|
||||
|
||||
property int mode : 0
|
||||
|
||||
onModeChanged: {
|
||||
if(mode === 0)
|
||||
{
|
||||
qqInput.visible = false
|
||||
savecode.visible = false
|
||||
}
|
||||
|
||||
else if(mode === 1){
|
||||
savecode.visible = true
|
||||
qqInput.visible = true
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
property string username: ""
|
||||
property string password: ""
|
||||
property string bindqq: ""
|
||||
|
||||
//自动储存上一次登录的账号
|
||||
Settings{
|
||||
fileName: "./setting.ini"
|
||||
property alias username: loginPage.username
|
||||
property alias password: loginPage.password
|
||||
}
|
||||
|
||||
// ShaderEffect {
|
||||
// id:login_shader
|
||||
// anchors.fill: parent
|
||||
// vertexShader: "qrc:/shaders/effect1.vert"
|
||||
// fragmentShader: "qrc:/shaders/effect1.frag"
|
||||
// opacity: 0.3
|
||||
|
||||
// property vector3d iResolution: Qt.vector3d(width, height, 0)
|
||||
// property real iTime: 0
|
||||
|
||||
// Timer {
|
||||
// id: timer
|
||||
// running: true
|
||||
// repeat: true
|
||||
// interval: 15
|
||||
// onTriggered: parent.iTime += 0.01;
|
||||
// }
|
||||
// }
|
||||
|
||||
Column {
|
||||
anchors.centerIn: parent
|
||||
spacing: 10
|
||||
|
||||
Image {
|
||||
width: 60
|
||||
height: 60
|
||||
source: "qrc:/image/logo.png"
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
|
||||
Text {
|
||||
text: qsTr("登录您的DP-S账号")
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
font.pixelSize: 20
|
||||
font.family: DelTheme.Primary.fontPrimaryFamily
|
||||
color:DelTheme.Primary.colorTextBase
|
||||
}
|
||||
|
||||
DelInput {
|
||||
id: userInput
|
||||
width: 280
|
||||
selectByMouse:true
|
||||
placeholderText: qsTr("账号")
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
text:username
|
||||
onTextChanged: username = text
|
||||
}
|
||||
|
||||
DelInput {
|
||||
id: pwdInput
|
||||
width: 280
|
||||
text :password
|
||||
selectByMouse:true
|
||||
placeholderText: qsTr("密码")
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
onTextChanged: password = text
|
||||
}
|
||||
|
||||
DelInput {
|
||||
id: qqInput
|
||||
visible: false
|
||||
width: 280
|
||||
text :bindqq
|
||||
selectByMouse:true
|
||||
placeholderText: qsTr("安全口令")
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
onTextChanged: bindqq = text
|
||||
DelToolTip {
|
||||
visible: parent.hovered
|
||||
arrowVisible: true
|
||||
text: qsTr("请牢记自己设置的安全口令,并注意不要泄露")
|
||||
position: DelToolTip.Position_Top
|
||||
}
|
||||
}
|
||||
|
||||
Row{
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
spacing: 15
|
||||
DelButton {
|
||||
width:100
|
||||
text: {
|
||||
switch(mode){
|
||||
case 0:
|
||||
return qsTr("登 录");
|
||||
case 1:
|
||||
return qsTr("注 册");
|
||||
case 2:
|
||||
return qsTr("修 改 密 码");
|
||||
}
|
||||
}
|
||||
colorBg:{
|
||||
switch(mode){
|
||||
case 0:
|
||||
return "#2A5CFF"
|
||||
case 1:
|
||||
return "#00C853"
|
||||
case 2:
|
||||
return "#A0B4F6"
|
||||
}
|
||||
}
|
||||
|
||||
height: 35
|
||||
type: DelButton.Type_Primary
|
||||
onClicked: {
|
||||
switch(mode){
|
||||
case 0:
|
||||
loginReq();
|
||||
break;
|
||||
case 1:
|
||||
registerReq();
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
DelButton {
|
||||
id:savecode
|
||||
width:120
|
||||
text: "强制更改密码"
|
||||
colorBg:"#dd2c2c"
|
||||
height: 35
|
||||
visible: false
|
||||
type: DelButton.Type_Primary
|
||||
onClicked: {
|
||||
|
||||
}
|
||||
DelToolTip {
|
||||
visible: parent.hovered
|
||||
arrowVisible: true
|
||||
text: qsTr("可使用注册时的安全口令强制更改密码")
|
||||
position: DelToolTip.Position_Right
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function loginReq(){
|
||||
// 构建 JSON 参数对象
|
||||
var jsonData = {
|
||||
username: username,
|
||||
password: password
|
||||
}
|
||||
GlobalVars.sendPostRequest("/login",jsonData,function(error, response) {
|
||||
if (error) {
|
||||
GlobalVars.error(error.message)
|
||||
} else {
|
||||
//储存Token
|
||||
GlobalVars.token = response.token
|
||||
//跳转页面
|
||||
GlobalVars.main_Window.changePage(1);
|
||||
GlobalVars.msg_control.success('登录成功');
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function registerReq(){
|
||||
// 构建 JSON 参数对象
|
||||
var jsonData = {
|
||||
username: username,
|
||||
password: password,
|
||||
qq:bindqq
|
||||
}
|
||||
GlobalVars.sendPostRequest("/register",jsonData,function(error, response) {
|
||||
if (error) {
|
||||
GlobalVars.msg_control.error('注册失败:' + response);
|
||||
} else {
|
||||
mode = 0;
|
||||
GlobalVars.msg_control.success('注册成功');
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
Row{
|
||||
anchors.bottom: parent.bottom // 关键:贴到父容器底部
|
||||
anchors.bottomMargin: 20
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
|
||||
Text{
|
||||
text: {
|
||||
if(mode === 0)return qsTr("您还没有账号或忘记密码?")
|
||||
else if(mode ===1)return qsTr("已有账号")
|
||||
}
|
||||
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
font.family: DelTheme.Primary.fontPrimaryFamily
|
||||
color:DelTheme.Primary.colorTextBase
|
||||
}
|
||||
|
||||
DelButton {
|
||||
text:{
|
||||
if(mode === 0)return qsTr("点击注册")
|
||||
else if(mode === 1)return qsTr("点击登录")
|
||||
}
|
||||
height: 35
|
||||
type: DelButton.Type_Link
|
||||
onClicked: {
|
||||
if(mode === 0)mode = 1
|
||||
else if(mode === 1)mode = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
DelSelect {
|
||||
id:network_line
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.bottomMargin: 20
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 10
|
||||
width: 180
|
||||
height: 30
|
||||
model: [
|
||||
// { value: 'http://192.168.200.20:8651', label: '线路1' },
|
||||
{ value: 'https://dpsm.senzo.online', label: '线路1' },
|
||||
{ value: 'https://dpsm.senzo.online', label: '线路2' },
|
||||
{ value: 'https://dps.wyyvip.asia', label: '线路3' },
|
||||
]
|
||||
onCurrentIndexChanged: {
|
||||
GlobalVars.server_url = network_line.model[currentIndex].value
|
||||
GlobalVars.getAllVersion();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,133 @@
|
|||
import QtQuick 2.15
|
||||
import QtQuick.Window 2.15
|
||||
import QtQuick.Controls 2.15
|
||||
import QtQuick.Layouts 1.15
|
||||
import DelegateUI 1.0
|
||||
import "../MyGlobals" 1.0
|
||||
|
||||
Item {
|
||||
anchors.fill: parent
|
||||
anchors.leftMargin: 1
|
||||
|
||||
ShaderEffect {
|
||||
id:about_shader
|
||||
anchors.fill: parent
|
||||
vertexShader: "qrc:/shaders/effect2.vert"
|
||||
fragmentShader: "qrc:/shaders/effect2.frag"
|
||||
opacity: 0.3
|
||||
|
||||
property vector3d iResolution: Qt.vector3d(width, height, 0)
|
||||
property real iTime: 0
|
||||
|
||||
Timer {
|
||||
id: timer
|
||||
running: true
|
||||
repeat: true
|
||||
interval: 15
|
||||
onTriggered: parent.iTime += 0.01;
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
id:cl
|
||||
anchors.fill: parent
|
||||
// spacing: 20
|
||||
// padding: 20
|
||||
|
||||
|
||||
Rectangle {
|
||||
visible: true
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
color: "transparent"
|
||||
border.color: DelTheme.isDark ? DelTheme.DelCollapse.colorBorderDark : DelTheme.DelCollapse.colorBorder
|
||||
border.width: 1
|
||||
radius: 8
|
||||
|
||||
Column{
|
||||
spacing: 10
|
||||
topPadding: 10
|
||||
leftPadding: 10
|
||||
|
||||
Text{
|
||||
font.pixelSize: 32
|
||||
font.family: DelTheme.Primary.fontPrimaryFamily
|
||||
color:DelTheme.Primary.colorTextBase
|
||||
text:"DP-S客户端插件管理工具"
|
||||
}
|
||||
|
||||
Text{
|
||||
font.pixelSize: 20
|
||||
font.family: DelTheme.Primary.fontPrimaryFamily
|
||||
color:DelTheme.Primary.colorTextBase
|
||||
text:"作者: 倾泪寒"
|
||||
}
|
||||
|
||||
Text{
|
||||
font.pixelSize: 20
|
||||
font.family: DelTheme.Primary.fontPrimaryFamily
|
||||
color:DelTheme.Primary.colorTextBase
|
||||
text:"联系QQ:947330670"
|
||||
}
|
||||
|
||||
Text{
|
||||
font.pixelSize: 20
|
||||
font.family: DelTheme.Primary.fontPrimaryFamily
|
||||
color:DelTheme.Primary.colorTextBase
|
||||
text:"开发文档:"
|
||||
}
|
||||
|
||||
Text {
|
||||
font.pixelSize: 20
|
||||
font.family: DelTheme.Primary.fontPrimaryFamily
|
||||
color:DelTheme.Primary.colorTextBase
|
||||
text: "<a href='https://dps-doc.senzo.online/#/'>https://dps-doc.senzo.online</a>"
|
||||
textFormat: Text.RichText
|
||||
onLinkActivated: (url) => Qt.openUrlExternally(url)
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
acceptedButtons: Qt.NoButton // 传递点击事件给父控件
|
||||
}
|
||||
}
|
||||
|
||||
Text{
|
||||
font.pixelSize: 20
|
||||
font.family: DelTheme.Primary.fontPrimaryFamily
|
||||
color:DelTheme.Primary.colorTextBase
|
||||
text:"QQ交流群:"
|
||||
}
|
||||
|
||||
Text {
|
||||
font.pixelSize: 20
|
||||
font.family: DelTheme.Primary.fontPrimaryFamily
|
||||
color:DelTheme.Primary.colorTextBase
|
||||
text: "<a href='https://qm.qq.com/q/J0wVu0oAwK'>点击链接加入群聊【DP-S插件】</a>"
|
||||
textFormat: Text.RichText
|
||||
onLinkActivated: (url) => Qt.openUrlExternally(url)
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
acceptedButtons: Qt.NoButton // 传递点击事件给父控件
|
||||
}
|
||||
}
|
||||
|
||||
Row{
|
||||
Text{
|
||||
text:"捐赠:"
|
||||
font.pixelSize: 20
|
||||
font.family: DelTheme.Primary.fontPrimaryFamily
|
||||
color:DelTheme.Primary.colorTextBase
|
||||
}
|
||||
|
||||
Image{
|
||||
width: 781*0.67
|
||||
height: 646*0.67
|
||||
source:"qrc:/image/code.png"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,198 @@
|
|||
import QtQuick 2.15
|
||||
import QtQuick.Window 2.15
|
||||
import QtQuick.Controls 2.15
|
||||
import QtQuick.Layouts 1.15
|
||||
import DelegateUI 1.0
|
||||
import QmlTool 1.0
|
||||
import "../MyGlobals" 1.0
|
||||
import "../Component"
|
||||
|
||||
Item {
|
||||
anchors.fill: parent
|
||||
|
||||
Component.onCompleted: {
|
||||
if(!GlobalVars.serverAdvertisement){
|
||||
GlobalVars.getAllAdvertisement(function(jso){
|
||||
GlobalVars.serverAdvertisement = [];
|
||||
for(var idx in jso.list){
|
||||
var obj = jso.list[idx]
|
||||
var newobj = {
|
||||
img : GlobalVars.server_url + obj.imageurl,
|
||||
link : obj.linkUrl,
|
||||
str : obj.text
|
||||
}
|
||||
GlobalVars.serverAdvertisement.push(newobj)
|
||||
}
|
||||
adver.originalModel = GlobalVars.serverAdvertisement
|
||||
})
|
||||
}
|
||||
else{
|
||||
adver.originalModel = GlobalVars.serverAdvertisement
|
||||
}
|
||||
|
||||
popup.open()
|
||||
}
|
||||
|
||||
DelPopup {
|
||||
id: popup
|
||||
x: (parent.width - width) * 0.5
|
||||
y: (parent.height - height) * 0.5
|
||||
width: 800
|
||||
height: 660
|
||||
parent: Overlay.overlay
|
||||
closePolicy: DelPopup.NoAutoClose
|
||||
movable: true
|
||||
resizable: true
|
||||
minimumX: 0
|
||||
maximumX: parent.width - width
|
||||
minimumY: 0
|
||||
maximumY: parent.height - height
|
||||
minimumWidth: 400
|
||||
minimumHeight: 300
|
||||
contentItem: Item {
|
||||
|
||||
ColumnLayout {
|
||||
id:cl
|
||||
anchors.fill: parent
|
||||
anchors.bottom:parent.top
|
||||
anchors.bottomMargin:30
|
||||
// spacing: 20
|
||||
// padding: 20
|
||||
|
||||
|
||||
Rectangle {
|
||||
visible: true
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
color: "transparent"
|
||||
|
||||
ScrollView {
|
||||
id: scrollView
|
||||
anchors.fill: parent
|
||||
clip: true
|
||||
Column{
|
||||
spacing: 10
|
||||
topPadding: 10
|
||||
leftPadding: 10
|
||||
|
||||
Text{
|
||||
width: 780
|
||||
wrapMode: Text.WordWrap
|
||||
font {
|
||||
pixelSize: 14
|
||||
family: DelTheme.Primary.fontPrimaryFamily
|
||||
}
|
||||
color: DelTheme.Primary.colorTextBase
|
||||
text:"dps管理工具使用申明及免责条款
|
||||
|
||||
重要提示:
|
||||
请您(以下简称“用户”)在使用dps管理工具(以下简称“本软件”)前务必仔细阅读本协议。本协议是您与本软件作者(以下简称“我们”)之间的法律协议,构成双方对本软件使用的完整约定。您点击“同意条款”按钮或开始使用本软件的行为,即视为已充分理解并自愿接受本协议全部条款。
|
||||
|
||||
|
||||
一、协议接受与适用范围
|
||||
1、强制同意条款
|
||||
用户必须完全接受本协议所有内容,方可使用本软件及其未来任何版本更新。若用户不同意任何条款,应立即停止使用并卸载本软件。
|
||||
|
||||
2、用途限制
|
||||
本软件仅供研究、学习及非商业性技术交流使用。我们因开发、维护及后续更新需投入人工成本,保留向用户收取合理技术服务费的权利。
|
||||
|
||||
|
||||
二、禁止行为
|
||||
用户承诺不得实施以下行为,否则我们将立即终止其使用权限,且已支付费用不予退还:
|
||||
1、禁止商业用途
|
||||
利用本软件进行任何营利活动,包括但不限于商业运营、售卖服务或衍生产品。
|
||||
|
||||
2、禁止违法内容
|
||||
加载含色情、赌博、恐怖主义、分裂国家等违反中国法律法规及公序良俗的内容模块。
|
||||
|
||||
3、禁止宣传与引流
|
||||
通过本软件发布广告、诱导用户充值消费或进行其他引流行为。
|
||||
|
||||
4、隐私与安全责任
|
||||
本软件不含任何窃取隐私或破坏系统的代码,但用户加载的第三方模块可能引发风险。若因用户自行加载违法模块造成损失,相关法律责任及后果由用户自行承担,与我们无关。
|
||||
|
||||
|
||||
三、责任免除条款
|
||||
1、功能局限性声明
|
||||
本软件仅提供基础技术框架,用户需自行评估加载模块的安全性。我们不对第三方模块的功能、安全性及合法性承担任何责任。
|
||||
|
||||
2、服务中断与更新风险
|
||||
我们有权随时调整软件功能、暂停服务或终止运营,无需对用户或第三方承担违约责任。软件更新可能引发兼容性问题,用户需自行承担更新风险。
|
||||
|
||||
3、数据保护义务
|
||||
用户应自行备份数据,因软件使用或更新导致的数据丢失,我们不承担恢复或赔偿责任。
|
||||
|
||||
|
||||
四、协议终止与变更
|
||||
1、单方终止权
|
||||
若用户违反本协议,我们有权立即终止其使用权限并删除相关账户数据,无需事先通知。
|
||||
|
||||
2、协议修改权
|
||||
我们保留随时修订本协议的权利。更新后的协议将通过软件内通知或官网公示,用户继续使用即视为接受修订内容。
|
||||
|
||||
|
||||
五、其他条款
|
||||
1、法律适用与争议解决
|
||||
本协议适用中华人民共和国法律,争议应提交至软件运营方所在地法院管辖。
|
||||
|
||||
2、完整协议声明
|
||||
本协议取代双方此前所有口头或书面约定,构成唯一有效法律文件。
|
||||
|
||||
|
||||
用户确认:
|
||||
我已仔细阅读并完全理解本协议内容,自愿承担使用本软件的所有风险,并承诺遵守上述条款。
|
||||
|
||||
生效日期: 本协议自用户首次使用本软件之日起生效。"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
DelButton {
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.right: parent.right
|
||||
height: 30
|
||||
radiusBg: popup.radiusBg * 0.5
|
||||
text: qsTr("我同意")
|
||||
type: DelButton.Type_Primary
|
||||
onClicked: popup.close();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Rectangle{
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.margins: 15
|
||||
height: 400
|
||||
border.color: DelTheme.isDark ? "#23272e" : "#f0f4f7"
|
||||
border.width: 3
|
||||
color:"transparent"
|
||||
radius:8
|
||||
clip: true
|
||||
|
||||
PromotionCard {
|
||||
id:adver
|
||||
anchors.fill: parent
|
||||
switchInterval:3000
|
||||
// originalModel: [
|
||||
// {img:"qrc:/image/a1.png",key:1},
|
||||
// {img:"https://rc.rindro.cn/dps/download3/2_凌众:img.png",key:2},
|
||||
// {img:"qrc:/image/a3.png",key:3},
|
||||
// {img:"qrc:/image/a4.png",key:4},
|
||||
// ]
|
||||
|
||||
onClickedFunction:(Data)=> {
|
||||
Qt.openUrlExternally(Data.link)
|
||||
// Data.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,602 @@
|
|||
import QtQuick 2.15
|
||||
import QtQuick.Window 2.15
|
||||
import QtGraphicalEffects 1.15
|
||||
import QtQuick.Shapes 1.15
|
||||
import QtQuick.Layouts 1.15
|
||||
import DelegateUI 1.0
|
||||
import "../MyGlobals" 1.0
|
||||
import QmlTool 1.0
|
||||
import "../Component" 1.0
|
||||
import SSHManager 1.0
|
||||
|
||||
Item {
|
||||
anchors.fill: parent
|
||||
anchors.leftMargin: 1
|
||||
|
||||
//用户名称
|
||||
property string user_name: "未定名用户XNJASDND"
|
||||
//用户会员等级
|
||||
property string user_level: "普通会员"
|
||||
//用户积分
|
||||
property int user_points: 0
|
||||
|
||||
|
||||
//用户所有服务器列表
|
||||
property var user_server_list : []
|
||||
//当前测试连通性服务器ip
|
||||
property int testip : -1
|
||||
|
||||
|
||||
//新增服务器窗口
|
||||
property var addServerWindow: null
|
||||
|
||||
Component.onCompleted: {
|
||||
GlobalVars.tab_personal = this;
|
||||
|
||||
SSHManager.connectionSuccess.connect(connectionSuccess)
|
||||
SSHManager.connectionFailed.connect(connectionFailed)
|
||||
|
||||
initAccInfo();
|
||||
initAccServerList();
|
||||
}
|
||||
|
||||
// 窗口关闭时自动触发清理
|
||||
Component.onDestruction: {
|
||||
SSHManager.connectionSuccess.disconnect(connectionSuccess);
|
||||
SSHManager.connectionFailed.disconnect(connectionFailed);
|
||||
}
|
||||
|
||||
//构造账号信息
|
||||
function initAccInfo(){
|
||||
if(GlobalVars.accInfo){
|
||||
user_name = GlobalVars.accInfo.username
|
||||
user_level = GlobalVars.accInfo.uservip
|
||||
user_points = GlobalVars.accInfo.userpoints
|
||||
}
|
||||
}
|
||||
|
||||
//构造账号服务器列表
|
||||
function initAccServerList(){
|
||||
if(GlobalVars.accServerList){
|
||||
user_server_list = [];
|
||||
var arrbuf = [];
|
||||
for(var i = 0; i < GlobalVars.accServerList.length;i++){
|
||||
var obj = GlobalVars.accServerList[i];
|
||||
var buf = {
|
||||
action:i,
|
||||
state:0,
|
||||
ip : obj.serverIp,
|
||||
port : obj.port,
|
||||
username : obj.userName,
|
||||
password : obj.password
|
||||
}
|
||||
arrbuf.push(buf);
|
||||
}
|
||||
user_server_list = arrbuf;
|
||||
}
|
||||
}
|
||||
|
||||
//监听全局变量的变化 这里需要监听 因为在本页会产生刷新数据
|
||||
Connections {
|
||||
target: GlobalVars
|
||||
//账号基础信息
|
||||
function onAccInfoChanged(){initAccInfo();}
|
||||
//账号服务器列表
|
||||
function onAccServerListChanged(){initAccServerList();}
|
||||
}
|
||||
|
||||
//尝试连接服务器
|
||||
function testConnect(index){
|
||||
if(testip === -1){
|
||||
testip = index;
|
||||
changeServerValue(index,"state",-1);
|
||||
var data = user_server_list[index];
|
||||
SSHManager.connectAndStartShell(data.ip,data.port,data.username,data.password)
|
||||
}
|
||||
else{
|
||||
GlobalVars.msg_control.warning('当前正在测试' + user_server_list[testip].ip + "的连通性,请不要重复操作!");
|
||||
}
|
||||
}
|
||||
|
||||
//连接成功信号
|
||||
function connectionSuccess(){
|
||||
if (addServerWindow)return;
|
||||
changeServerValue(testip,"state",1);
|
||||
testip = -1
|
||||
}
|
||||
|
||||
//连接失败信号
|
||||
function connectionFailed(error){
|
||||
if (addServerWindow)return;
|
||||
changeServerValue(testip,"state",2);
|
||||
testip = -1
|
||||
}
|
||||
|
||||
//更改服务器列表的数据模型
|
||||
function changeServerValue(index,key,value){
|
||||
var buf = user_server_list;
|
||||
buf[index][key] = value;
|
||||
user_server_list = buf;
|
||||
}
|
||||
|
||||
//打开新增服务器窗口
|
||||
function openAddServerWindow(index = null){
|
||||
if (!addServerWindow) {
|
||||
// 创建新窗口
|
||||
var component = Qt.createComponent("Window_AddServer.qml");
|
||||
addServerWindow = component.createObject(parent);
|
||||
|
||||
// 绑定关闭时自动销毁
|
||||
addServerWindow.closing.connect(function() {
|
||||
addServerWindow.destroy()
|
||||
addServerWindow = null // 关键:释放引用
|
||||
})
|
||||
}
|
||||
//如果有传入数据
|
||||
if(index !== null){
|
||||
var data = user_server_list[index];
|
||||
addServerWindow.serverIP = data.ip
|
||||
addServerWindow.serverPort = data.port
|
||||
addServerWindow.serverUsername = data.username
|
||||
addServerWindow.serverPassword = data.password
|
||||
addServerWindow.mode = 1
|
||||
}
|
||||
addServerWindow.y = GlobalVars.main_Window.y + 130
|
||||
addServerWindow.show()
|
||||
addServerWindow.raise() // 把窗口提到最前面
|
||||
}
|
||||
|
||||
|
||||
Rectangle{
|
||||
id: top_rect
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: 15
|
||||
anchors.leftMargin: 10
|
||||
anchors.rightMargin: 15
|
||||
height: 50
|
||||
radius: 8
|
||||
border.color: DelTheme.isDark ? "#23272e" : "#f0f4f7"
|
||||
border.width: 3
|
||||
color:"transparent"
|
||||
|
||||
// 头像部分
|
||||
DelAvatar {
|
||||
id: avatarImg
|
||||
size:parent.height * 0.65
|
||||
anchors.left: top_rect.left
|
||||
anchors.leftMargin: 20
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
iconSource: DelIcon.UserOutlined
|
||||
}
|
||||
|
||||
//用户名
|
||||
Text {
|
||||
id:username
|
||||
anchors.left: avatarImg.right
|
||||
anchors.leftMargin: 10
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: user_name
|
||||
font {
|
||||
pixelSize: 14
|
||||
family: DelTheme.Primary.fontPrimaryFamily
|
||||
}
|
||||
color: DelTheme.Primary.colorTextBase
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
DelButton {
|
||||
anchors.right: lxkf.left
|
||||
anchors.rightMargin: 2
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
type: DelButton.Type_Link
|
||||
text: qsTr(`关于贡献点`)
|
||||
DelToolTip {
|
||||
visible: parent.hovered
|
||||
arrowVisible: true
|
||||
text: qsTr("扫描赞助码赞助时备注DPS管理工具账号即可(如果长时间未增加贡献点可联系客服)")
|
||||
position: DelToolTip.Position_Left
|
||||
}
|
||||
}
|
||||
DelButton {
|
||||
id:lxkf
|
||||
anchors.right: change_zl.left
|
||||
anchors.rightMargin: 2
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
type: DelButton.Type_Link
|
||||
text: qsTr(`联系客服`)
|
||||
onClicked: {
|
||||
Qt.openUrlExternally("tencent://message/?uin=1713381454")
|
||||
}
|
||||
DelToolTip {
|
||||
visible: parent.hovered
|
||||
arrowVisible: true
|
||||
text: qsTr("如无法跳转QQ链接,可手动添加客服QQ:1713381454")
|
||||
position: DelToolTip.Position_Top
|
||||
}
|
||||
}
|
||||
|
||||
DelButton {
|
||||
id:change_zl
|
||||
anchors.right: top_rect.right
|
||||
anchors.rightMargin: 10
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
type: DelButton.Type_Link
|
||||
text: qsTr(`修改资料`)
|
||||
onClicked: {
|
||||
GlobalVars.msg_control.warning('暂未开放此功能');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle{
|
||||
id:middle_rect
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 10
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 15
|
||||
anchors.bottomMargin: 10
|
||||
anchors.bottom:bottom_rect.top
|
||||
anchors.topMargin: 10
|
||||
anchors.top:top_rect.bottom
|
||||
border.color: DelTheme.isDark ? "#23272e" : "#f0f4f7"
|
||||
border.width: 3
|
||||
color:"transparent"
|
||||
radius: 8
|
||||
|
||||
Row{
|
||||
anchors.fill: parent
|
||||
spacing: 20
|
||||
anchors.leftMargin: 20
|
||||
anchors.rightMargin: 20
|
||||
|
||||
Card {
|
||||
id:points_card
|
||||
width:(parent.width) / 3 - 14
|
||||
height: parent.height*0.8
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
isDarkMode: DelTheme.isDark
|
||||
|
||||
content: Item {
|
||||
anchors.fill:parent
|
||||
DelIconButton {
|
||||
id:points_icon
|
||||
type: DelButton.Type_Primary
|
||||
iconSource: DelIcon.CreditCardOutlined
|
||||
iconSize: 54
|
||||
anchors.top:parent.top
|
||||
anchors.topMargin: 20
|
||||
anchors.horizontalCenter : parent.horizontalCenter
|
||||
}
|
||||
|
||||
Text {
|
||||
id:points_text
|
||||
text: "贡献点: " + user_points
|
||||
font {
|
||||
pixelSize: 14
|
||||
family: DelTheme.Primary.fontPrimaryFamily
|
||||
}
|
||||
color: DelTheme.Primary.colorTextBase
|
||||
elide: Text.ElideRight
|
||||
anchors.top:points_icon.bottom
|
||||
anchors.topMargin: 20
|
||||
anchors.horizontalCenter : parent.horizontalCenter
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Card {
|
||||
id:vip_card
|
||||
width:(parent.width) / 3 - 14
|
||||
height: parent.height*0.8
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
isDarkMode: DelTheme.isDark
|
||||
|
||||
content: Item {
|
||||
anchors.fill:parent
|
||||
DelIconButton {
|
||||
id:vip_icon
|
||||
type: DelButton.Type_Primary
|
||||
iconSource: DelIcon.SketchOutlined
|
||||
iconSize: 54
|
||||
anchors.top:parent.top
|
||||
anchors.topMargin: 20
|
||||
anchors.horizontalCenter : parent.horizontalCenter
|
||||
}
|
||||
|
||||
Text {
|
||||
id:vip_text
|
||||
text: "VIP等级: " + user_level
|
||||
font {
|
||||
pixelSize: 14
|
||||
family: DelTheme.Primary.fontPrimaryFamily
|
||||
}
|
||||
color: DelTheme.Primary.colorTextBase
|
||||
elide: Text.ElideRight
|
||||
anchors.top:vip_icon.bottom
|
||||
anchors.topMargin: 20
|
||||
anchors.horizontalCenter : parent.horizontalCenter
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Card {
|
||||
id:cash_card
|
||||
width:(parent.width) / 3 - 14
|
||||
height: parent.height*0.8
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
isDarkMode: DelTheme.isDark
|
||||
content: Item {
|
||||
anchors.fill:parent
|
||||
DelIconButton {
|
||||
id:cash_icon
|
||||
type: DelButton.Type_Primary
|
||||
iconSource:DelIcon.WalletOutlined
|
||||
iconSize: 54
|
||||
anchors.top:parent.top
|
||||
anchors.topMargin: 20
|
||||
anchors.horizontalCenter : parent.horizontalCenter
|
||||
}
|
||||
|
||||
Text {
|
||||
id:cash_text
|
||||
text: "暂无代金券"
|
||||
font {
|
||||
pixelSize: 14
|
||||
family: DelTheme.Primary.fontPrimaryFamily
|
||||
}
|
||||
color: DelTheme.Primary.colorTextBase
|
||||
elide: Text.ElideRight
|
||||
anchors.top:cash_icon.bottom
|
||||
anchors.topMargin: 20
|
||||
anchors.horizontalCenter : parent.horizontalCenter
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle{
|
||||
id:bottom_rect
|
||||
height: 400
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.leftMargin: 10
|
||||
anchors.rightMargin: 15
|
||||
anchors.bottomMargin: 15
|
||||
anchors.bottom:parent.bottom
|
||||
border.color: DelTheme.isDark ? "#23272e" : "#f0f4f7"
|
||||
border.width: 3
|
||||
color:"transparent"
|
||||
radius: 8
|
||||
|
||||
Column {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 8
|
||||
spacing: 8
|
||||
|
||||
|
||||
Rectangle{
|
||||
id:serverlist_header
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 6
|
||||
width:parent.width
|
||||
height: 20
|
||||
color:"transparent"
|
||||
|
||||
Text {
|
||||
id:label_serverlist_text
|
||||
text: "我的服务器"
|
||||
font {
|
||||
pixelSize: 14
|
||||
family: DelTheme.Primary.fontPrimaryFamily
|
||||
}
|
||||
color: DelTheme.Primary.colorTextBase
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
DelButton {
|
||||
type: DelButton.Type_Link
|
||||
text: qsTr(`新增服务器`)
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 6
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
onClicked: {
|
||||
if (!addServerWindow) openAddServerWindow();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DelTableView {
|
||||
width: parent.width
|
||||
height: parent.height - serverlist_header.height - 1
|
||||
rowHeaderVisible: false
|
||||
columns: [
|
||||
{
|
||||
title: '服务器IP',
|
||||
dataIndex: "ip",
|
||||
key: "ip",
|
||||
delegate: textDelegate,
|
||||
width: 150
|
||||
},
|
||||
{
|
||||
title: 'SSH端口',
|
||||
dataIndex: "port",
|
||||
key: "port",
|
||||
delegate: textDelegate,
|
||||
width: 80
|
||||
},
|
||||
{
|
||||
title: '服务器账号',
|
||||
dataIndex: 'username',
|
||||
key: 'username',
|
||||
delegate: textDelegate,
|
||||
width: 100
|
||||
},
|
||||
{
|
||||
title: '服务器密码',
|
||||
key: 'password',
|
||||
dataIndex: 'password',
|
||||
delegate: textDelegate,
|
||||
width: 160
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
key: 'state',
|
||||
dataIndex: 'state',
|
||||
delegate: tagsDelegate,
|
||||
width: 80
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
dataIndex: 'action',
|
||||
delegate: actionDelegate,
|
||||
width: 250
|
||||
}
|
||||
]
|
||||
initModel: user_server_list
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Component {
|
||||
id: textDelegate
|
||||
|
||||
Text {
|
||||
id: displayText
|
||||
leftPadding: 8
|
||||
rightPadding: 8
|
||||
font {
|
||||
family: DelTheme.Primary.fontPrimaryFamily
|
||||
pixelSize: DelTheme.Primary.fontPrimarySize
|
||||
}
|
||||
text: cellData
|
||||
color: DelTheme.Primary.colorTextBase
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
|
||||
TextMetrics {
|
||||
id: displayWidth
|
||||
font: displayText.font
|
||||
text: displayText.text
|
||||
}
|
||||
|
||||
TextMetrics {
|
||||
id: startWidth
|
||||
font: displayText.font
|
||||
text: {
|
||||
let index = displayText.text.indexOf(filterInput);
|
||||
if (index !== -1)
|
||||
return displayText.text.substring(0, index);
|
||||
else
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
TextMetrics {
|
||||
id: filterWidth
|
||||
font: displayText.font
|
||||
text: filterInput
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
color: "red"
|
||||
opacity: 0.1
|
||||
x: startWidth.advanceWidth + (displayText.width - displayWidth.advanceWidth) * 0.5
|
||||
width: filterWidth.advanceWidth
|
||||
height: parent.height
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: tagsDelegate
|
||||
|
||||
Item {
|
||||
Row {
|
||||
id: row
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 20
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: 6
|
||||
|
||||
DelTag {
|
||||
visible: cellData != -1
|
||||
text: {
|
||||
switch(cellData)
|
||||
{
|
||||
case 0 : return "未知";
|
||||
case 1 : return "成功";
|
||||
case 2 : return "失败";
|
||||
default: return "连接中"
|
||||
}
|
||||
}
|
||||
|
||||
presetColor: {
|
||||
switch(cellData)
|
||||
{
|
||||
case 0 : return "purple";
|
||||
case 1 : return "green";
|
||||
case 2 : return "red";
|
||||
default: return "transparent"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BusyIndicator {
|
||||
visible: cellData === -1
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
color: "#FF5722" // 橙色
|
||||
size: 18
|
||||
duration: 12000
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: actionDelegate
|
||||
|
||||
Item {
|
||||
Row {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
spacing: 4
|
||||
|
||||
DelButton {
|
||||
type: DelButton.Type_Link
|
||||
text: qsTr(`测试`)
|
||||
onClicked: {
|
||||
if (!addServerWindow) testConnect(cellData)
|
||||
}
|
||||
}
|
||||
|
||||
DelButton {
|
||||
type: DelButton.Type_Link
|
||||
text: qsTr(`修改`)
|
||||
onClicked: {
|
||||
if (!addServerWindow) openAddServerWindow(cellData)
|
||||
}
|
||||
}
|
||||
|
||||
DelButton {
|
||||
type: DelButton.Type_Link
|
||||
text: qsTr(`删除`)
|
||||
onClicked: {
|
||||
if (!addServerWindow) {
|
||||
var ip = user_server_list[cellData]["ip"];
|
||||
GlobalVars.removeServer(ip);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,624 @@
|
|||
import QtQuick 2.15
|
||||
import QtQuick.Window 2.15
|
||||
import QtGraphicalEffects 1.15
|
||||
import QtQuick.Shapes 1.15
|
||||
import QtQuick.Layouts 1.15
|
||||
import QtQuick.Dialogs 1.3
|
||||
import DelegateUI 1.0
|
||||
import QmlTool 1.0
|
||||
import SSHManager 1.0
|
||||
import FileTransfer 1.0
|
||||
import "../MyGlobals" 1.0
|
||||
import "../Component" 1.0
|
||||
|
||||
Item {
|
||||
anchors.fill: parent
|
||||
anchors.leftMargin: 1
|
||||
|
||||
property string server_ip: "192.168.1.100" // 示例 IP
|
||||
property string server_port: "22" // 示例 IP
|
||||
property string server_user: "root" // 示例 IP
|
||||
property string server_passwd: "123456" // 示例 IP
|
||||
|
||||
property int page_state: 0 // 页面状态
|
||||
|
||||
property string dps_version_ready: "undefine" // dps版本
|
||||
property string java_p_version_ready: "undefine" // java插件版本
|
||||
property string java_version_ready: "undefine" // java版本
|
||||
|
||||
//安装dps的flag 0需要安装 1需要更新 2不显示
|
||||
property int dpsInstallationFlag : 2;
|
||||
//安装javap的flag 0需要安装 1需要更新 2不显示
|
||||
property int javapInstallationFlag : 2;
|
||||
//安装java的flag 0需要安装 1需要更新 2不显示
|
||||
property int javaInstallationFlag : 2;
|
||||
|
||||
//我的服务器插件窗口
|
||||
property var myPluginsWidow: null
|
||||
|
||||
Component.onCompleted: {
|
||||
GlobalVars.tab_server = this;
|
||||
SSHManager.connectionSuccess.connect(connectionSuccess)
|
||||
SSHManager.connectionFailed.connect(connectionFailed)
|
||||
SSHManager.shellOutput.connect(consoleOutput)
|
||||
|
||||
FileTransfer.downloadCompleted.connect(downloadCompleted)
|
||||
FileTransfer.downloadProgressChanged.connect(downloadProgressChanged)
|
||||
FileTransfer.uploadCompleted.connect(uploadCompleted)
|
||||
FileTransfer.uploadProgressChanged.connect(uploadProgressChanged)
|
||||
}
|
||||
|
||||
Component.onDestruction: {
|
||||
// 断开所有信号连接
|
||||
SSHManager.connectionSuccess.disconnect(connectionSuccess);
|
||||
SSHManager.connectionFailed.disconnect(connectionFailed);
|
||||
SSHManager.shellOutput.disconnect(consoleOutput)
|
||||
|
||||
FileTransfer.downloadCompleted.disconnect(downloadCompleted)
|
||||
FileTransfer.downloadProgressChanged.disconnect(downloadProgressChanged)
|
||||
FileTransfer.uploadCompleted.disconnect(uploadCompleted)
|
||||
FileTransfer.uploadProgressChanged.disconnect(uploadProgressChanged)
|
||||
}
|
||||
|
||||
function init(ip, port, user, passwd){
|
||||
server_ip = ip;
|
||||
server_port = port;
|
||||
server_user = user;
|
||||
server_passwd = passwd;
|
||||
//尝试连接ssh服务器
|
||||
SSHManager.connectAndStartShell(server_ip, server_port, server_user, server_passwd)
|
||||
}
|
||||
|
||||
//ssh链接成功的信号
|
||||
function connectionSuccess(){
|
||||
GlobalVars.sshConnectServer = server_ip
|
||||
getServerPluginsState();
|
||||
}
|
||||
//ssh链接失败的信号
|
||||
function connectionFailed(error){
|
||||
page_state = 2
|
||||
}
|
||||
//ssh控制台输出的信号
|
||||
function consoleOutput(msg){
|
||||
//如果解压命令执行成功重新拉取版本信息
|
||||
if(msg.indexOf("DP-S插件解压成功") !== -1 || msg.indexOf("JAVA插件解压成功") !== -1 || msg.indexOf("JAVA安装成功") !== -1){
|
||||
getServerPluginsState();
|
||||
}
|
||||
//删除插件成功
|
||||
else if(msg.indexOf("删除插件成功!")!== -1){
|
||||
GlobalVars.getServerPlugins(server_ip)
|
||||
GlobalVars.msg_control.success("删除插件成功!");
|
||||
}
|
||||
}
|
||||
//下载完成的信号
|
||||
function downloadCompleted(success, message) {
|
||||
if (success) {
|
||||
//如果下载了dps 就上传dps
|
||||
if(message === "dps"){
|
||||
FileTransfer.postUpload("http://" + server_ip + ":65170/api/uploadfiles","download/dp_s.tar","/rindro/download/","dps")
|
||||
GlobalVars.msg_control.success("DP-S插件本体下载成功!现在准备上传!")
|
||||
}
|
||||
//如果下载javap
|
||||
else if(message === "javap"){
|
||||
FileTransfer.postUpload("http://" + server_ip + ":65170/api/uploadfiles","download/RT.tar.gz","/rindro/download/","javap")
|
||||
GlobalVars.msg_control.success("JAVA插件本体下载成功!现在准备上传!")
|
||||
}
|
||||
|
||||
} else {
|
||||
GlobalVars.msg_control.error("错误:" + message)
|
||||
progressobj.visible = false
|
||||
}
|
||||
}
|
||||
//下载进度的信号
|
||||
function downloadProgressChanged(rate,add){
|
||||
if(rate)progressobj.currentValue = rate * 100.0;
|
||||
}
|
||||
//上传完成的信号
|
||||
function uploadCompleted(success, message) {
|
||||
if (success) {
|
||||
//上传完dp_s解压
|
||||
if(message === "dps"){
|
||||
GlobalVars.sendPostRequestByIp("http://" + server_ip + ":65170","/api/addrun",{},function(error, response) {})
|
||||
SSHManager.sendInput("echo '开始解压DP-S插件'")
|
||||
SSHManager.sendInput("sudo tar -xvf /rindro/download/dp_s.tar -C /")
|
||||
SSHManager.sendInput("chmod 777 /dp_s")
|
||||
SSHManager.sendInput("echo -e \"\\033[32mDP-S插件解压成功\\033[0m\"")
|
||||
GlobalVars.msg_control.success("DP-S插件本体上传成功!现在将执行解压操作!在右侧控制台页面出现'DP-S插件解压成功'字样则为安装成功!")
|
||||
progressobj.visible = false
|
||||
dps_version_ready = "undefine"
|
||||
}
|
||||
else if(message === "javap"){
|
||||
SSHManager.sendInput("echo '开始解压JAVA插件'")
|
||||
SSHManager.sendInput("sudo rm -rf /java_plugin")
|
||||
SSHManager.sendInput("sudo mkdir /java_plugin")
|
||||
SSHManager.sendInput("sudo tar -zxvf /rindro/download/RT.tar.gz -C /java_plugin/")
|
||||
SSHManager.sendInput("grep -q \"cd /java_plugin \n./Restart\" \"/root/run\" || echo \"cd /java_plugin \n./Restart\" >> \"/root/run\" && chmod +x /root/run")
|
||||
SSHManager.sendInput("echo -e \"\\033[32mJAVA插件解压成功\\033[0m\"")
|
||||
GlobalVars.msg_control.success("JAVA插件本体上传成功!现在将执行解压操作!在右侧控制台页面出现'JAVA插件解压成功'字样则为安装成功!")
|
||||
progressobj.visible = false
|
||||
java_p_version_ready = "undefine"
|
||||
}
|
||||
} else {
|
||||
GlobalVars.msg_control.error("错误:" + message)
|
||||
progressobj.visible = false
|
||||
}
|
||||
}
|
||||
//上传进度的信号
|
||||
function uploadProgressChanged(rate,add){
|
||||
if(rate)progressobj.currentValue = rate * 100.0;
|
||||
}
|
||||
|
||||
//获取服务器插件版本
|
||||
function getServerPluginsState(){
|
||||
//向网关发送http请求 获取版本
|
||||
GlobalVars.getServerInfo("http://" + server_ip + ":65170",function(flag,type,info){
|
||||
//dps插件
|
||||
if(type === 0){
|
||||
//如果安装了
|
||||
if(flag){
|
||||
dps_version_ready = info.ProjectVersion
|
||||
//需要更新
|
||||
if(parseFloat(GlobalVars.all_Version.dpsVer) > parseFloat(info.ProjectVersion)){
|
||||
dpsInstallationFlag = 1
|
||||
dps_install.visible = true
|
||||
}
|
||||
else dpsInstallationFlag = 2
|
||||
}
|
||||
//如果没有安装
|
||||
else {
|
||||
dps_version_ready = "noinstall"
|
||||
dpsInstallationFlag = 0
|
||||
}
|
||||
}
|
||||
//java插件
|
||||
else if(type === 1){
|
||||
//如果安装了
|
||||
if(flag){
|
||||
java_p_version_ready = info.ProjectVersion
|
||||
//需要更新
|
||||
if(parseFloat(GlobalVars.all_Version.jarVer) > parseFloat(info.ProjectVersion)){
|
||||
javapInstallationFlag = 1
|
||||
javap_install.visible = true
|
||||
}
|
||||
else javapInstallationFlag = 2
|
||||
}
|
||||
//如果没有安装
|
||||
else {
|
||||
java_p_version_ready = "noinstall"
|
||||
javapInstallationFlag = 0
|
||||
}
|
||||
}
|
||||
//java版本
|
||||
else if(type === 2){
|
||||
//如果安装了
|
||||
if(flag){
|
||||
java_version_ready = info.ProjectVersion
|
||||
javaInstallationFlag = 2
|
||||
}
|
||||
//如果没有安装
|
||||
else {
|
||||
java_version_ready = "noinstall"
|
||||
javaInstallationFlag = 0
|
||||
}
|
||||
}
|
||||
//连通测试函数
|
||||
else if(type === 3){
|
||||
//如果回复了信息才算连接成功
|
||||
page_state = 1
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
FileDialog {
|
||||
id: fileDialog
|
||||
title: "请选择保存路径"
|
||||
selectFolder: true // 选择文件而非文件夹
|
||||
selectMultiple: false // 不允许多选
|
||||
property int dtype: 0
|
||||
|
||||
onAccepted: {
|
||||
var filePath = fileDialog.fileUrl.toString()
|
||||
var cleanPath = filePath.replace(/^(file:\/{2,3})/, "");
|
||||
var filename = "客户端插件包.zip";
|
||||
var filekey = "dps";
|
||||
|
||||
if(dtype === 1){
|
||||
filename = "Yosin.key";
|
||||
filekey = "dps";
|
||||
}
|
||||
|
||||
GlobalVars.downlad_quest_window.addQuest(filename,[
|
||||
function(quest){
|
||||
//下载
|
||||
quest.status = 1;
|
||||
FileTransfer.postDownload(GlobalVars.server_url + "/rindro/download", cleanPath + "/" + filename,{projectName:"通用"},"quest");
|
||||
},
|
||||
function(quest){
|
||||
GlobalVars.msg_control.success(cleanPath + filename + "已下载完成!");
|
||||
quest.instruction = true
|
||||
}]);
|
||||
}
|
||||
|
||||
onRejected: {
|
||||
}
|
||||
}
|
||||
|
||||
property string selectedFilePath: ""
|
||||
|
||||
|
||||
//进度条对象
|
||||
property var progressobj : null
|
||||
//进度
|
||||
property double progressRate: 0.0
|
||||
|
||||
//没加载出来的页面
|
||||
BusyIndicator {
|
||||
id:loadingpage
|
||||
visible: page_state === 0
|
||||
anchors.centerIn: parent
|
||||
color: "#FF5722" // 橙色
|
||||
size: 32
|
||||
duration: 12000
|
||||
}
|
||||
|
||||
//连接错误的页面
|
||||
Text {
|
||||
visible: page_state === 2
|
||||
id:errorpage
|
||||
text: "连接服务器失败,请前往个人中心,修改账号密码以测试您的服务器连接!"
|
||||
anchors.centerIn: parent
|
||||
font {
|
||||
pixelSize: 24
|
||||
family: DelTheme.Primary.fontPrimaryFamily
|
||||
bold: true
|
||||
}
|
||||
color: DelTheme.Primary.colorTextBase
|
||||
}
|
||||
|
||||
// 新增:服务器信息区域
|
||||
Rectangle {
|
||||
visible: page_state === 1
|
||||
id:supage
|
||||
anchors.fill: parent
|
||||
color:"transparent"
|
||||
|
||||
DelRectangle{
|
||||
id: top_rect
|
||||
anchors {
|
||||
top: parent.top
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
bottomMargin: 0
|
||||
topMargin: 4
|
||||
margins:10
|
||||
}
|
||||
height: 50
|
||||
topLeftRadius: 8
|
||||
topRightRadius: 8
|
||||
border.color: DelTheme.isDark ? "#23272e" : "#f0f4f7"
|
||||
border.width: 3
|
||||
color:"transparent"
|
||||
|
||||
|
||||
Text {
|
||||
id:server_ip_label
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 20
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: "服务器IP:"
|
||||
font {
|
||||
pixelSize: 22
|
||||
family: DelTheme.Primary.fontPrimaryFamily
|
||||
bold: true
|
||||
}
|
||||
color: DelTheme.Primary.colorTextBase
|
||||
}
|
||||
|
||||
//用户名
|
||||
Text {
|
||||
id:username
|
||||
anchors.left: server_ip_label.right
|
||||
anchors.leftMargin: 10
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: server_ip
|
||||
font {
|
||||
pixelSize: 14
|
||||
family: DelTheme.Primary.fontPrimaryFamily
|
||||
}
|
||||
color: DelTheme.Primary.colorTextBase
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
DelButton {
|
||||
text: qsTr("查看我的服务器插件")
|
||||
type: DelButton.Type_Text
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 10
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
onClicked: {
|
||||
if (!myPluginsWidow) {
|
||||
// 创建新窗口
|
||||
var component = Qt.createComponent("Window_ServerPlugins.qml");
|
||||
myPluginsWidow = component.createObject(parent);
|
||||
|
||||
// 绑定关闭时自动销毁
|
||||
myPluginsWidow.closing.connect(function() {
|
||||
myPluginsWidow.destroy()
|
||||
myPluginsWidow = null // 关键:释放引用
|
||||
})
|
||||
}
|
||||
myPluginsWidow.y = GlobalVars.main_Window.y + 90
|
||||
myPluginsWidow.show()
|
||||
myPluginsWidow.init(server_ip);
|
||||
myPluginsWidow.raise() // 把窗口提到最前面
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 服务器信息卡片
|
||||
DelRectangle {
|
||||
id:server_info
|
||||
anchors {
|
||||
top: top_rect.bottom
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
topMargin: -2
|
||||
margins:10
|
||||
}
|
||||
height: 94
|
||||
color: "transparent"
|
||||
bottomLeftRadius: 8
|
||||
bottomRightRadius: 8
|
||||
|
||||
border.color: DelTheme.isDark ? "#23272e" : "#f0f4f7"
|
||||
border.width: 3
|
||||
antialiasing: true
|
||||
|
||||
DelButton {
|
||||
id:download_clientplugins
|
||||
text: qsTr("下载双端插件包")
|
||||
type: DelButton.Type_Text
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 10
|
||||
anchors.bottom:parent.bottom
|
||||
anchors.bottomMargin: 10
|
||||
onClicked: {
|
||||
fileDialog.dtype = 0
|
||||
fileDialog.open()
|
||||
}
|
||||
|
||||
DelToolTip {
|
||||
x: 0
|
||||
visible: parent.hovered
|
||||
arrowVisible: true
|
||||
text: qsTr("使用双端插件才需要安装,仅使用dps服务端插件不需要安装!")
|
||||
position: DelToolTip.Position_Top
|
||||
}
|
||||
}
|
||||
// DelButton {
|
||||
// id:build_clientplugins_keys
|
||||
// text: qsTr("生成客户端授权Key")
|
||||
// type: DelButton.Type_Text
|
||||
// anchors.right: parent.right
|
||||
// anchors.rightMargin: 10
|
||||
// anchors.top:download_clientplugins.bottom
|
||||
// anchors.topMargin: 10
|
||||
// onClicked: {
|
||||
// fileDialog.dtype = 1
|
||||
// fileDialog.open()
|
||||
// }
|
||||
// }
|
||||
|
||||
Rectangle{
|
||||
id:plugins_group
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 10
|
||||
width:200
|
||||
height:parent.height
|
||||
color: "transparent"
|
||||
|
||||
Text {
|
||||
id:state_label_dps
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: 10
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 10
|
||||
text: "DP-S插件状态: "
|
||||
font {
|
||||
pixelSize: 16
|
||||
family: DelTheme.Primary.fontPrimaryFamily
|
||||
}
|
||||
color: DelTheme.Primary.colorTextSecondary
|
||||
}
|
||||
Text {
|
||||
id:state_label_javap
|
||||
anchors.top: state_label_dps.top
|
||||
anchors.topMargin: 27
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 10
|
||||
text: "JAVA插件状态: "
|
||||
font {
|
||||
pixelSize: 16
|
||||
family: DelTheme.Primary.fontPrimaryFamily
|
||||
}
|
||||
color: DelTheme.Primary.colorTextSecondary
|
||||
}
|
||||
Text {
|
||||
id:state_label_java
|
||||
anchors.top: state_label_javap.top
|
||||
anchors.topMargin: 27
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 10
|
||||
text: "JAVA版本状态: "
|
||||
font {
|
||||
pixelSize: 16
|
||||
family: DelTheme.Primary.fontPrimaryFamily
|
||||
}
|
||||
color: DelTheme.Primary.colorTextSecondary
|
||||
}
|
||||
|
||||
|
||||
BusyIndicator {
|
||||
anchors.left: state_label_dps.right
|
||||
anchors.leftMargin: 8
|
||||
anchors.top: state_label_dps.top
|
||||
anchors.topMargin: 2
|
||||
visible: dps_version_ready === "undefine" ? true : false
|
||||
color: "#FF5722" // 橙色
|
||||
size: 18
|
||||
duration: 12000
|
||||
}
|
||||
|
||||
BusyIndicator {
|
||||
anchors.left: state_label_javap.right
|
||||
anchors.leftMargin: 8
|
||||
anchors.top: state_label_javap.top
|
||||
anchors.topMargin: 2
|
||||
visible: java_p_version_ready === "undefine" ? true : false
|
||||
color: "#FF5722" // 橙色
|
||||
size: 18
|
||||
duration: 12000
|
||||
}
|
||||
|
||||
BusyIndicator {
|
||||
anchors.left: state_label_java.right
|
||||
anchors.leftMargin: 8
|
||||
anchors.top: state_label_java.top
|
||||
anchors.topMargin: 2
|
||||
visible: java_version_ready === "undefine" ? true : false
|
||||
color: "#FF5722" // 橙色
|
||||
size: 18
|
||||
duration: 12000
|
||||
}
|
||||
|
||||
|
||||
DelTag {
|
||||
id:dps_tag
|
||||
anchors.left: state_label_dps.right
|
||||
anchors.leftMargin: 6
|
||||
anchors.top: state_label_dps.top
|
||||
anchors.topMargin: -2
|
||||
visible: dps_version_ready != "undefine" ? true : false
|
||||
text: dps_version_ready === "noinstall" ? "未安装" : dps_version_ready
|
||||
presetColor: dps_version_ready === "noinstall" ? "red" : "green"
|
||||
}
|
||||
|
||||
DelTag {
|
||||
anchors.left: state_label_javap.right
|
||||
anchors.leftMargin: 6
|
||||
anchors.top: state_label_javap.top
|
||||
anchors.topMargin: -2
|
||||
visible: java_p_version_ready != "undefine" ? true : false
|
||||
text: java_p_version_ready === "noinstall" ? "未安装" : java_p_version_ready
|
||||
presetColor: java_p_version_ready === "noinstall" ? "red" : "green"
|
||||
}
|
||||
|
||||
DelTag {
|
||||
anchors.left: state_label_java.right
|
||||
anchors.leftMargin: 6
|
||||
anchors.top: state_label_java.top
|
||||
anchors.topMargin: -2
|
||||
visible: java_version_ready != "undefine" ? true : false
|
||||
text: java_version_ready === "noinstall" ? "未安装" : java_version_ready
|
||||
presetColor: java_version_ready === "noinstall" ? "red" : "green"
|
||||
}
|
||||
|
||||
|
||||
DelIconButton {
|
||||
id:dps_install
|
||||
anchors.left: plugins_group.left
|
||||
anchors.leftMargin: 200
|
||||
anchors.top: plugins_group.top
|
||||
anchors.topMargin: 5
|
||||
visible: dpsInstallationFlag === 2 ? false : true
|
||||
text: dpsInstallationFlag === 1 ? "更新DP-S插件" : "安装DP-S插件"
|
||||
type: DelButton.Type_Text
|
||||
onClicked: {
|
||||
FileTransfer.postDownload(GlobalVars.server_url + "/rindro/download/all", "download/dp_s.tar",{key : "dps"},"dps")
|
||||
visible = false;
|
||||
dps_progress.visible = true;
|
||||
progressobj = dps_progress;
|
||||
}
|
||||
}
|
||||
|
||||
CustomProgressBar {
|
||||
id:dps_progress
|
||||
anchors.left: dps_install.left
|
||||
anchors.top: dps_install.top
|
||||
anchors.topMargin: 8
|
||||
visible: false
|
||||
}
|
||||
|
||||
|
||||
DelIconButton {
|
||||
id:javap_install
|
||||
anchors.left: plugins_group.left
|
||||
anchors.leftMargin: 200
|
||||
anchors.top: plugins_group.top
|
||||
anchors.topMargin: 32
|
||||
visible: javapInstallationFlag === 2 ? false : true
|
||||
text: javapInstallationFlag === 1 ? "更新JAVA插件" : "安装JAVA插件"
|
||||
type: DelButton.Type_Text
|
||||
onClicked: {
|
||||
FileTransfer.postDownload(GlobalVars.server_url + "/rindro/download/all", "download/RT.tar.gz",{key : "javap"},"javap")
|
||||
visible = false;
|
||||
javap_progress.visible = true;
|
||||
progressobj = javap_progress;
|
||||
}
|
||||
}
|
||||
|
||||
CustomProgressBar {
|
||||
id:javap_progress
|
||||
anchors.left: javap_install.left
|
||||
anchors.top: javap_install.top
|
||||
anchors.topMargin: 8
|
||||
visible: false
|
||||
}
|
||||
|
||||
DelIconButton {
|
||||
id:java_install
|
||||
anchors.left: plugins_group.left
|
||||
anchors.leftMargin: 200
|
||||
anchors.top: plugins_group.top
|
||||
anchors.topMargin: 59
|
||||
visible: javaInstallationFlag === 0 ? true : false
|
||||
text: "安装JAVA"
|
||||
type: DelButton.Type_Text
|
||||
onClicked: {
|
||||
SSHManager.sendInput("yum install -y java")
|
||||
SSHManager.sendInput("echo -e \"\\033[32mJAVA安装成功\\033[0m\"")
|
||||
}
|
||||
}
|
||||
|
||||
CustomProgressBar {
|
||||
id:java_progress
|
||||
anchors.left: java_install.left
|
||||
anchors.top: java_install.top
|
||||
anchors.topMargin: 8
|
||||
visible: false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Rectangle{
|
||||
id:ssh_console
|
||||
// height: parent.height * 0.7
|
||||
// anchors.fill: parent
|
||||
anchors.top: server_info.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.margins: 10
|
||||
radius: 8
|
||||
border.color: DelTheme.isDark ? "#23272e" : "#f0f4f7"
|
||||
border.width: 3
|
||||
color:"transparent"
|
||||
|
||||
ServerConsole{
|
||||
anchors.fill:parent
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,351 @@
|
|||
import QtQuick 2.15
|
||||
import QtQuick.Window 2.15
|
||||
import QtQuick.Controls 2.15
|
||||
import QtGraphicalEffects 1.15
|
||||
import QtQuick.Shapes 1.15
|
||||
import QtQuick.Layouts 1.15
|
||||
import DelegateUI 1.0
|
||||
import "../MyGlobals" 1.0
|
||||
import QmlTool 1.0
|
||||
import "../Component" 1.0
|
||||
Item {
|
||||
anchors.fill: parent
|
||||
anchors.leftMargin: 1
|
||||
|
||||
ListModel {
|
||||
id: pluginModel
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
GlobalVars.tab_shop = this;
|
||||
initServerPluginList();
|
||||
initAccServerList();
|
||||
}
|
||||
|
||||
//监听全局变量的变化 这里需要监听 因为在本页会产生刷新数据
|
||||
Connections {
|
||||
target: GlobalVars
|
||||
//插件市场的全部插件
|
||||
function onServerPluginListChanged(){initServerPluginList();}
|
||||
function onAccServerListChanged(){initAccServerList();}
|
||||
}
|
||||
|
||||
//构造数据
|
||||
function initServerPluginList(){
|
||||
//全局数据插件列表存在
|
||||
if(GlobalVars.serverPluginList){
|
||||
var Data = GlobalVars.serverPluginList;
|
||||
// 遍历对象的属性
|
||||
for (var key in Data) {
|
||||
var obj = Data[key]
|
||||
pluginModel.append(obj);
|
||||
}
|
||||
gv.model = pluginModel;
|
||||
}
|
||||
}
|
||||
|
||||
function initAccServerList(){
|
||||
//全局数据服务器列表存在
|
||||
if(GlobalVars.accServerList){
|
||||
server_select.model = [];
|
||||
var arrbuf = [];
|
||||
for(var i = 0; i < GlobalVars.accServerList.length;i++){
|
||||
var obj = GlobalVars.accServerList[i];
|
||||
var buf = {
|
||||
label : obj.serverIp,
|
||||
value : i
|
||||
}
|
||||
arrbuf.push(buf);
|
||||
}
|
||||
server_select.model = arrbuf;
|
||||
}
|
||||
}
|
||||
|
||||
//插件详情页面的窗口
|
||||
property var pluginInfoWidow: null
|
||||
//搜索插件的模式
|
||||
property int searchMode: 0
|
||||
|
||||
Rectangle{
|
||||
id:search_rect
|
||||
anchors.top : parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.leftMargin: 15
|
||||
anchors.rightMargin: 26
|
||||
height: 80
|
||||
color:"transparent"
|
||||
radius: 8
|
||||
border.color: DelTheme.isDark ? "#23272e" : "#f0f4f7"
|
||||
border.width: 3
|
||||
|
||||
DelIconText {
|
||||
id:server_label
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 10
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: 14
|
||||
text:"选择要安装插件的服务器:"
|
||||
font {
|
||||
pixelSize: 14
|
||||
family: DelTheme.Primary.fontPrimaryFamily
|
||||
}
|
||||
}
|
||||
|
||||
DelSelect {
|
||||
id:server_select
|
||||
anchors.left: server_label.right
|
||||
anchors.leftMargin: 10
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: 10
|
||||
width: 250
|
||||
height: 30
|
||||
tooltipVisible: true
|
||||
onCurrentIndexChanged: {
|
||||
GlobalVars.selectServer = currentIndex
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
anchors.top: server_label.bottom
|
||||
anchors.topMargin: 15
|
||||
anchors.left: server_label.left
|
||||
spacing: 48
|
||||
|
||||
ButtonGroup { id: radioGroup }
|
||||
|
||||
DelRadio {
|
||||
text: qsTr("索引插件名")
|
||||
ButtonGroup.group: radioGroup
|
||||
checked:true
|
||||
onCheckedChanged: {searchMode = 0}
|
||||
DelIconText {
|
||||
anchors.left: parent.right
|
||||
anchors.leftMargin: 2
|
||||
anchors.top: parent.top
|
||||
iconSize: 24
|
||||
iconSource: DelIcon.ReadOutlined
|
||||
}
|
||||
}
|
||||
|
||||
DelRadio {
|
||||
text: qsTr("索引作者名")
|
||||
ButtonGroup.group: radioGroup
|
||||
onCheckedChanged: {searchMode = 1}
|
||||
DelIconText {
|
||||
anchors.left: parent.right
|
||||
anchors.leftMargin: 2
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: -2
|
||||
iconSize: 24
|
||||
iconSource: DelIcon.TeamOutlined
|
||||
}
|
||||
}
|
||||
|
||||
DelRadio {
|
||||
text: qsTr("索引描述内容")
|
||||
ButtonGroup.group: radioGroup
|
||||
onCheckedChanged: {searchMode = 2}
|
||||
DelIconText {
|
||||
anchors.left: parent.right
|
||||
anchors.leftMargin: 2
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: -2
|
||||
iconSize: 24
|
||||
iconSource: DelIcon.ProfileOutlined
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DelAutoComplete {
|
||||
anchors.right: search_rect.right
|
||||
anchors.rightMargin: 10
|
||||
anchors.verticalCenter: search_rect.verticalCenter
|
||||
width: 370
|
||||
height: 40
|
||||
tooltipVisible: true
|
||||
placeholderText: qsTr("输入插件名以查找插件")
|
||||
selectByMouse: true
|
||||
clearIconSource: DelIcon.CloseSquareFilled
|
||||
onSearch: function(input) {
|
||||
if (!input) {
|
||||
options = [];
|
||||
} else {
|
||||
if(GlobalVars.serverPluginList){
|
||||
var Data = GlobalVars.serverPluginList;
|
||||
var SearchBuf = [];
|
||||
// 遍历对象的属性
|
||||
for (var key in Data) {
|
||||
var obj = Data[key];
|
||||
//搜索插件名
|
||||
if(searchMode === 0){
|
||||
if(obj.ProjectName.indexOf(input) !== -1)SearchBuf.push({label: obj.ProjectName + "\t作者: " + obj.ProjectAuthor, index:key});
|
||||
}
|
||||
//搜索作者
|
||||
else if(searchMode === 1){
|
||||
if(obj.ProjectAuthor.indexOf(input) !== -1)SearchBuf.push({label: obj.ProjectName + "\t作者: " + obj.ProjectAuthor, index:key});
|
||||
}
|
||||
//搜索描述
|
||||
else if(searchMode === 2){
|
||||
if(obj.ProjectDescribe.indexOf(input) !== -1)SearchBuf.push({label: obj.ProjectName + "\t作者: " + obj.ProjectAuthor, index:key});
|
||||
}
|
||||
}
|
||||
options = SearchBuf;
|
||||
}
|
||||
else{
|
||||
options = [];
|
||||
}
|
||||
}
|
||||
}
|
||||
onSelect:function(select){
|
||||
if (!pluginInfoWidow) {
|
||||
// 创建新窗口
|
||||
var component = Qt.createComponent("Window_PluginInfo_Goods.qml");
|
||||
pluginInfoWidow = component.createObject(parent);
|
||||
pluginInfoWidow.init(select.index)
|
||||
|
||||
// 绑定关闭时自动销毁
|
||||
pluginInfoWidow.closing.connect(function() {
|
||||
pluginInfoWidow.destroy()
|
||||
pluginInfoWidow = null // 关键:释放引用
|
||||
})
|
||||
}
|
||||
pluginInfoWidow.y = GlobalVars.main_Window.y + 85
|
||||
pluginInfoWidow.show()
|
||||
pluginInfoWidow.raise() // 把窗口提到最前面
|
||||
clearInput()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
GridView {
|
||||
id: gv
|
||||
anchors.top: search_rect.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.margins: 4
|
||||
cellWidth: 420
|
||||
cellHeight: 128
|
||||
clip: true
|
||||
|
||||
delegate:Item { // 每个格子的容器
|
||||
width: gv.cellWidth
|
||||
height: gv.cellHeight
|
||||
|
||||
Card {
|
||||
id:card
|
||||
anchors.centerIn: parent // 在格子容器中居中
|
||||
width: parent.width - 24 // 改用相对尺寸
|
||||
height: parent.height - 24
|
||||
isDarkMode: DelTheme.isDark
|
||||
onClicked:function(){
|
||||
if (!pluginInfoWidow) {
|
||||
// 创建新窗口
|
||||
var component = Qt.createComponent("Window_PluginInfo_Goods.qml");
|
||||
pluginInfoWidow = component.createObject(parent);
|
||||
pluginInfoWidow.init(index)
|
||||
|
||||
// 绑定关闭时自动销毁
|
||||
pluginInfoWidow.closing.connect(function() {
|
||||
pluginInfoWidow.destroy()
|
||||
pluginInfoWidow = null // 关键:释放引用
|
||||
})
|
||||
}
|
||||
pluginInfoWidow.y = GlobalVars.main_Window.y + 85
|
||||
pluginInfoWidow.show()
|
||||
pluginInfoWidow.raise() // 把窗口提到最前面
|
||||
}
|
||||
content: ColumnLayout {
|
||||
width: parent.width
|
||||
height: parent.height
|
||||
spacing: 4
|
||||
Rectangle{
|
||||
id:title_row
|
||||
Layout.fillWidth: true
|
||||
height: 30
|
||||
color:"transparent"
|
||||
Text {
|
||||
id:title
|
||||
text: model.ProjectName
|
||||
font {
|
||||
pixelSize: 24
|
||||
family: DelTheme.Primary.fontPrimaryFamily
|
||||
bold: true
|
||||
}
|
||||
color: DelTheme.Primary.colorTextBase
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
|
||||
Text {
|
||||
id:author
|
||||
anchors.top: title_row.top
|
||||
anchors.topMargin: 4
|
||||
anchors.right: title_row.right
|
||||
anchors.rightMargin: 10
|
||||
text: "作者:" + model.ProjectAuthor
|
||||
font {
|
||||
pixelSize: 16
|
||||
family: DelTheme.Primary.fontPrimaryFamily
|
||||
bold: true
|
||||
}
|
||||
color: DelTheme.Primary.colorTextBase
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
Layout.fillWidth: true
|
||||
text: model.ProjectDescribe
|
||||
wrapMode: Text.WordWrap
|
||||
maximumLineCount: 2
|
||||
font {
|
||||
pixelSize: 14
|
||||
family: DelTheme.Primary.fontPrimaryFamily
|
||||
}
|
||||
color: DelTheme.Primary.colorTextSecondary
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
// Item {
|
||||
// Layout.fillWidth: true
|
||||
// Layout.fillHeight: true
|
||||
// height:300
|
||||
// DelRectangle {
|
||||
// id: imageContainer
|
||||
// anchors.fill: parent
|
||||
// radius: 6
|
||||
// color: "transparent"
|
||||
// clip: true
|
||||
|
||||
// Image {
|
||||
// id: card_image
|
||||
// asynchronous: true // 异步加载不阻塞UI
|
||||
// cache: true // 启用内存缓存
|
||||
// anchors.fill: parent
|
||||
// source: "qrc:/image/moren.png"
|
||||
// // source: model.ImageMini
|
||||
// fillMode: Image.PreserveAspectCrop
|
||||
// }
|
||||
|
||||
// layer.enabled: true
|
||||
// layer.effect: OpacityMask {
|
||||
// maskSource: Rectangle {
|
||||
// width: imageContainer.width
|
||||
// height: imageContainer.height
|
||||
// radius: imageContainer.radius
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,351 @@
|
|||
import QtQuick 2.15
|
||||
import QtQuick.Window 2.15
|
||||
import QtQuick.Controls 2.15
|
||||
import QtGraphicalEffects 1.15
|
||||
import QtQuick.Shapes 1.15
|
||||
import QtQuick.Layouts 1.15
|
||||
import DelegateUI 1.0
|
||||
import "../MyGlobals" 1.0
|
||||
import QmlTool 1.0
|
||||
import "../Component" 1.0
|
||||
Item {
|
||||
anchors.fill: parent
|
||||
anchors.leftMargin: 1
|
||||
|
||||
ListModel {
|
||||
id: pluginModel
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
GlobalVars.tab_shop = this;
|
||||
initServerPluginList();
|
||||
initAccServerList();
|
||||
}
|
||||
|
||||
//监听全局变量的变化 这里需要监听 因为在本页会产生刷新数据
|
||||
Connections {
|
||||
target: GlobalVars
|
||||
//插件市场的全部插件
|
||||
function onServerPluginexMapChanged(){initServerPluginList();}
|
||||
function onAccServerListChanged(){initAccServerList();}
|
||||
}
|
||||
|
||||
//构造数据
|
||||
function initServerPluginList(){
|
||||
//全局数据插件列表存在
|
||||
if(GlobalVars.serverPluginexMap){
|
||||
var Data = GlobalVars.serverPluginexMap;
|
||||
// 遍历对象的属性
|
||||
for (var key in Data) {
|
||||
var obj = Data[key]
|
||||
pluginModel.append(obj);
|
||||
}
|
||||
gv.model = pluginModel;
|
||||
}
|
||||
}
|
||||
|
||||
function initAccServerList(){
|
||||
//全局数据服务器列表存在
|
||||
if(GlobalVars.accServerList){
|
||||
server_select.model = [];
|
||||
var arrbuf = [];
|
||||
for(var i = 0; i < GlobalVars.accServerList.length;i++){
|
||||
var obj = GlobalVars.accServerList[i];
|
||||
var buf = {
|
||||
label : obj.serverIp,
|
||||
value : i
|
||||
}
|
||||
arrbuf.push(buf);
|
||||
}
|
||||
server_select.model = arrbuf;
|
||||
}
|
||||
}
|
||||
|
||||
//插件详情页面的窗口
|
||||
property var pluginInfoWidow: null
|
||||
//搜索插件的模式
|
||||
property int searchMode: 0
|
||||
|
||||
Rectangle{
|
||||
id:search_rect
|
||||
anchors.top : parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.leftMargin: 15
|
||||
anchors.rightMargin: 26
|
||||
height: 80
|
||||
color:"transparent"
|
||||
radius: 8
|
||||
border.color: DelTheme.isDark ? "#23272e" : "#f0f4f7"
|
||||
border.width: 3
|
||||
|
||||
DelIconText {
|
||||
id:server_label
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 10
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: 14
|
||||
text:"选择要安装插件的服务器:"
|
||||
font {
|
||||
pixelSize: 14
|
||||
family: DelTheme.Primary.fontPrimaryFamily
|
||||
}
|
||||
}
|
||||
|
||||
DelSelect {
|
||||
id:server_select
|
||||
anchors.left: server_label.right
|
||||
anchors.leftMargin: 10
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: 10
|
||||
width: 250
|
||||
height: 30
|
||||
tooltipVisible: true
|
||||
onCurrentIndexChanged: {
|
||||
GlobalVars.selectServer = currentIndex
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
anchors.top: server_label.bottom
|
||||
anchors.topMargin: 15
|
||||
anchors.left: server_label.left
|
||||
spacing: 48
|
||||
|
||||
ButtonGroup { id: radioGroup }
|
||||
|
||||
DelRadio {
|
||||
text: qsTr("索引插件名")
|
||||
ButtonGroup.group: radioGroup
|
||||
checked:true
|
||||
onCheckedChanged: {searchMode = 0}
|
||||
DelIconText {
|
||||
anchors.left: parent.right
|
||||
anchors.leftMargin: 2
|
||||
anchors.top: parent.top
|
||||
iconSize: 24
|
||||
iconSource: DelIcon.ReadOutlined
|
||||
}
|
||||
}
|
||||
|
||||
DelRadio {
|
||||
text: qsTr("索引作者名")
|
||||
ButtonGroup.group: radioGroup
|
||||
onCheckedChanged: {searchMode = 1}
|
||||
DelIconText {
|
||||
anchors.left: parent.right
|
||||
anchors.leftMargin: 2
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: -2
|
||||
iconSize: 24
|
||||
iconSource: DelIcon.TeamOutlined
|
||||
}
|
||||
}
|
||||
|
||||
DelRadio {
|
||||
text: qsTr("索引描述内容")
|
||||
ButtonGroup.group: radioGroup
|
||||
onCheckedChanged: {searchMode = 2}
|
||||
DelIconText {
|
||||
anchors.left: parent.right
|
||||
anchors.leftMargin: 2
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: -2
|
||||
iconSize: 24
|
||||
iconSource: DelIcon.ProfileOutlined
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DelAutoComplete {
|
||||
anchors.right: search_rect.right
|
||||
anchors.rightMargin: 10
|
||||
anchors.verticalCenter: search_rect.verticalCenter
|
||||
width: 370
|
||||
height: 40
|
||||
tooltipVisible: true
|
||||
placeholderText: qsTr("输入插件名以查找插件")
|
||||
selectByMouse: true
|
||||
clearIconSource: DelIcon.CloseSquareFilled
|
||||
onSearch: function(input) {
|
||||
if (!input) {
|
||||
options = [];
|
||||
} else {
|
||||
if(GlobalVars.serverPluginexMap){
|
||||
var Data = GlobalVars.serverPluginexMap;
|
||||
var SearchBuf = [];
|
||||
// 遍历对象的属性
|
||||
for (var key in Data) {
|
||||
var obj = Data[key];
|
||||
//搜索插件名
|
||||
if(searchMode === 0){
|
||||
if(obj.ProjectName.indexOf(input) !== -1)SearchBuf.push({label: obj.ProjectName + "\t作者: " + obj.ProjectAuthor, index:key});
|
||||
}
|
||||
//搜索作者
|
||||
else if(searchMode === 1){
|
||||
if(obj.ProjectAuthor.indexOf(input) !== -1)SearchBuf.push({label: obj.ProjectName + "\t作者: " + obj.ProjectAuthor, index:key});
|
||||
}
|
||||
//搜索描述
|
||||
else if(searchMode === 2){
|
||||
if(obj.ProjectDescribe.indexOf(input) !== -1)SearchBuf.push({label: obj.ProjectName + "\t作者: " + obj.ProjectAuthor, index:key});
|
||||
}
|
||||
}
|
||||
options = SearchBuf;
|
||||
}
|
||||
else{
|
||||
options = [];
|
||||
}
|
||||
}
|
||||
}
|
||||
onSelect:function(select){
|
||||
// if (!pluginInfoWidow) {
|
||||
// // 创建新窗口
|
||||
// var component = Qt.createComponent("Window_PluginInfo_Goods.qml");
|
||||
// pluginInfoWidow = component.createObject(parent);
|
||||
// pluginInfoWidow.init(select.index)
|
||||
|
||||
// // 绑定关闭时自动销毁
|
||||
// pluginInfoWidow.closing.connect(function() {
|
||||
// pluginInfoWidow.destroy()
|
||||
// pluginInfoWidow = null // 关键:释放引用
|
||||
// })
|
||||
// }
|
||||
// pluginInfoWidow.y = GlobalVars.main_Window.y + 85
|
||||
// pluginInfoWidow.show()
|
||||
// pluginInfoWidow.raise() // 把窗口提到最前面
|
||||
// clearInput()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
GridView {
|
||||
id: gv
|
||||
anchors.top: search_rect.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.margins: 4
|
||||
cellWidth: 420
|
||||
cellHeight: 300
|
||||
clip: true
|
||||
|
||||
delegate:Item { // 每个格子的容器
|
||||
width: gv.cellWidth
|
||||
height: gv.cellHeight
|
||||
|
||||
Card {
|
||||
id:card
|
||||
anchors.centerIn: parent // 在格子容器中居中
|
||||
width: parent.width - 24 // 改用相对尺寸
|
||||
height: parent.height - 24
|
||||
isDarkMode: DelTheme.isDark
|
||||
onClicked:function(){
|
||||
if (!pluginInfoWidow) {
|
||||
// 创建新窗口
|
||||
var component = Qt.createComponent("Window_ExPluginInfo_Goods.qml");
|
||||
pluginInfoWidow = component.createObject(parent);
|
||||
pluginInfoWidow.init(model.ProjectName)
|
||||
|
||||
// 绑定关闭时自动销毁
|
||||
pluginInfoWidow.closing.connect(function() {
|
||||
pluginInfoWidow.destroy()
|
||||
pluginInfoWidow = null // 关键:释放引用
|
||||
})
|
||||
}
|
||||
pluginInfoWidow.y = GlobalVars.main_Window.y + 85
|
||||
pluginInfoWidow.show()
|
||||
pluginInfoWidow.raise() // 把窗口提到最前面
|
||||
}
|
||||
content: ColumnLayout {
|
||||
width: parent.width
|
||||
height: parent.height
|
||||
spacing: 4
|
||||
Rectangle{
|
||||
id:title_row
|
||||
Layout.fillWidth: true
|
||||
height: 30
|
||||
color:"transparent"
|
||||
Text {
|
||||
id:title
|
||||
text: model.ProjectName
|
||||
font {
|
||||
pixelSize: 24
|
||||
family: DelTheme.Primary.fontPrimaryFamily
|
||||
bold: true
|
||||
}
|
||||
color: DelTheme.Primary.colorTextBase
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
|
||||
Text {
|
||||
id:author
|
||||
anchors.top: title_row.top
|
||||
anchors.topMargin: 4
|
||||
anchors.right: title_row.right
|
||||
anchors.rightMargin: 10
|
||||
text: "作者:" + model.ProjectAuthor
|
||||
font {
|
||||
pixelSize: 16
|
||||
family: DelTheme.Primary.fontPrimaryFamily
|
||||
bold: true
|
||||
}
|
||||
color: DelTheme.Primary.colorTextBase
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
Layout.fillWidth: true
|
||||
text: model.ProjectDescribe
|
||||
wrapMode: Text.WordWrap
|
||||
maximumLineCount: 2
|
||||
font {
|
||||
pixelSize: 14
|
||||
family: DelTheme.Primary.fontPrimaryFamily
|
||||
}
|
||||
color: DelTheme.Primary.colorTextSecondary
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
height:300
|
||||
DelRectangle {
|
||||
id: imageContainer
|
||||
anchors.fill: parent
|
||||
radius: 6
|
||||
color: "transparent"
|
||||
clip: true
|
||||
|
||||
Image {
|
||||
id: card_image
|
||||
asynchronous: true // 异步加载不阻塞UI
|
||||
cache: true // 启用内存缓存
|
||||
anchors.fill: parent
|
||||
//source: "qrc:/image/moren.png"
|
||||
source: GlobalVars.server_url + "/rindro/getimg/" + model.ProjectName + "/0"
|
||||
fillMode: Image.PreserveAspectCrop
|
||||
}
|
||||
|
||||
layer.enabled: true
|
||||
layer.effect: OpacityMask {
|
||||
maskSource: Rectangle {
|
||||
width: imageContainer.width
|
||||
height: imageContainer.height
|
||||
radius: imageContainer.radius
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,88 @@
|
|||
import QtQuick 2.15
|
||||
import QtQuick.Window 2.15
|
||||
import QtQuick.Controls 2.15
|
||||
import QtQuick.Layouts 1.15
|
||||
import DelegateUI 1.0
|
||||
import "../MyGlobals" 1.0
|
||||
|
||||
Item {
|
||||
anchors.fill: parent
|
||||
|
||||
Rectangle {
|
||||
visible: true
|
||||
anchors.fill: parent
|
||||
anchors.margins: 15
|
||||
anchors.bottomMargin: 45
|
||||
color: "transparent"
|
||||
border.color: DelTheme.isDark ? DelTheme.DelCollapse.colorBorderDark : DelTheme.DelCollapse.colorBorder
|
||||
border.width: 1
|
||||
radius: 8
|
||||
|
||||
Rectangle{
|
||||
id:snik
|
||||
anchors.fill: parent
|
||||
anchors.topMargin: 15
|
||||
color: "transparent"
|
||||
|
||||
|
||||
Column {
|
||||
width: parent.width
|
||||
spacing: 10
|
||||
|
||||
DelTimeline {
|
||||
id: reverseTimeline
|
||||
width: parent.width
|
||||
height: snik.height
|
||||
mode: DelTimeline.Mode_Left
|
||||
initModel: [
|
||||
{
|
||||
time: new Date(2025, 2, 21),
|
||||
content: '初版DP-S后台管理工具发布\n由WinUI3编写',
|
||||
},
|
||||
{
|
||||
time: new Date(2025, 3, 7),
|
||||
content: 'DP-S后台管理工具Qt版本发布\n支持win7系统',
|
||||
},
|
||||
{
|
||||
time: new Date(2025, 3, 22),
|
||||
content: 'DP-S后台管理工具整体重构\n大幅度提升流畅性!',
|
||||
},
|
||||
{
|
||||
time: new Date(2025, 4, 9),
|
||||
content: '添加了单个插件的卸载按钮\n优化添加服务器流程\n增加国内线路\n在程序标题栏显示当前程序版本',
|
||||
},
|
||||
{
|
||||
time: new Date(2025, 4, 10),
|
||||
content: '新增更新日志栏目\n采用时间轴方式展示更新内容!\n优化栏目图标设置。',
|
||||
},
|
||||
{
|
||||
time: new Date(2025, 4, 16),
|
||||
content: '开放双端插件栏目!',
|
||||
},
|
||||
{
|
||||
time: new Date(2025, 4, 17),
|
||||
content: '新增服务器双端插件页面显示到期时间与开关按钮\n新增协议说明\n优化添加服务器网关的流程与逻辑',
|
||||
},
|
||||
{
|
||||
time: new Date(2025, 4, 21),
|
||||
content: '新增个人中心联系客服按钮\n开放贡献点系统',
|
||||
},
|
||||
{
|
||||
time: new Date(2025, 4, 22),
|
||||
content: '修复java插件安装的问题',
|
||||
},
|
||||
{
|
||||
time: new Date(2025, 4, 23),
|
||||
content: '修复因为转义字符导致配置无法正常显示的问题\n需要在个人中心重新添加一下服务器',
|
||||
},
|
||||
{
|
||||
time: new Date(2025, 4, 25),
|
||||
content: '双端插件详情显示页面加入视频播放',
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,248 @@
|
|||
import QtQuick 2.15
|
||||
import QtQuick.Window 2.15
|
||||
import DelegateUI 1.0
|
||||
import SSHManager 1.0
|
||||
import "../MyGlobals" 1.0
|
||||
import "../Component"
|
||||
|
||||
DelWindow {
|
||||
id:addsw
|
||||
width: 560
|
||||
height: 580
|
||||
visible: true
|
||||
title: qsTr("新增服务器")
|
||||
captionBar.topButtonVisible: true
|
||||
captionBar.winIconDelegate: Image {
|
||||
width: 20
|
||||
height: 20
|
||||
source: "qrc:/image/logo.png"
|
||||
}
|
||||
|
||||
Shortcut {
|
||||
sequences: ["Esc"]
|
||||
onActivated: close()
|
||||
}
|
||||
|
||||
|
||||
property string serverIP: ""
|
||||
property string serverPort: "22"
|
||||
property string serverUsername: "root"
|
||||
property string serverPassword: ""
|
||||
//0是添加 1是修改
|
||||
property int mode : 0
|
||||
|
||||
property int state : 0
|
||||
|
||||
|
||||
// 初始化后自动连接示例(可选)
|
||||
Component.onCompleted: {
|
||||
SSHManager.connectionSuccess.connect(connectionSuccess)
|
||||
SSHManager.connectionFailed.connect(connectionFailed)
|
||||
SSHManager.shellOutput.connect(consoleOutput)
|
||||
}
|
||||
|
||||
// 窗口关闭时自动触发清理
|
||||
Component.onDestruction: {
|
||||
// 断开所有信号连接
|
||||
SSHManager.connectionSuccess.disconnect(connectionSuccess);
|
||||
SSHManager.connectionFailed.disconnect(connectionFailed);
|
||||
SSHManager.shellOutput.disconnect(consoleOutput)
|
||||
}
|
||||
|
||||
|
||||
//ssh控制台输出的信号
|
||||
function consoleOutput(msg){
|
||||
//如果解压命令执行成功重新拉取版本信息
|
||||
if(msg.indexOf("Lenheart_Service_Success") !== -1){
|
||||
if(msg.indexOf("echo") === -1){
|
||||
var obj = {
|
||||
ip : serverIP,
|
||||
port : serverPort,
|
||||
username : serverUsername,
|
||||
password : serverPassword
|
||||
}
|
||||
GlobalVars.addServer(obj)
|
||||
GlobalVars.msg_control.success("新增服务器成功");
|
||||
state = 0
|
||||
close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function connectionSuccess(){
|
||||
SSHManager.sendInput("curl -O -k " + GlobalVars.server_url + "/rindro/download/sh;chmod +x sh;./sh");
|
||||
GlobalVars.msg_control.info("请耐心等待服务器初始化!");
|
||||
}
|
||||
|
||||
function connectionFailed(error){
|
||||
GlobalVars.msg_control.error("连接服务器失败,请检查您填写的信息!");
|
||||
state = 0
|
||||
}
|
||||
|
||||
function initServer(){
|
||||
SSHManager.connectAndStartShell(serverIP,serverPort,serverUsername,serverPassword)
|
||||
}
|
||||
|
||||
Column {
|
||||
anchors.top: parent.top + 30
|
||||
anchors.topMargin: 40
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
spacing: 20
|
||||
width: parent.width * 0.8
|
||||
|
||||
Item{
|
||||
width: 30
|
||||
height: 30
|
||||
}
|
||||
|
||||
Row{
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
spacing: 15
|
||||
Image {
|
||||
width: 30
|
||||
height: 30
|
||||
source: "qrc:/image/logo.png"
|
||||
}
|
||||
|
||||
Text {
|
||||
text: qsTr("登记您的服务器信息")
|
||||
font.pixelSize: 20
|
||||
font.family: DelTheme.Primary.fontPrimaryFamily
|
||||
color:DelTheme.Primary.colorTextBase
|
||||
}
|
||||
}
|
||||
|
||||
DelInput {
|
||||
id: ipField
|
||||
readOnly: addsw.state === 1 ? true : false
|
||||
placeholderText: qsTr("请输入 IP 地址")
|
||||
width: parent.width * 0.6
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
text: serverIP
|
||||
onTextChanged: serverIP = text
|
||||
iconPosition: DelInput.Position_Left
|
||||
iconSource: DelIcon.HddOutlined
|
||||
DelToolTip {
|
||||
visible: parent.hovered
|
||||
arrowVisible: true
|
||||
text: qsTr("请输入 IP 地址")
|
||||
position: DelToolTip.Position_Right
|
||||
}
|
||||
}
|
||||
|
||||
DelInput {
|
||||
id: portField
|
||||
readOnly: addsw.state === 1 ? true : false
|
||||
placeholderText: qsTr("请输入端口号")
|
||||
width: parent.width * 0.6
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
iconPosition: DelInput.Position_Left
|
||||
iconSource: DelIcon.GoldOutlined
|
||||
validator: IntValidator {
|
||||
bottom: 0
|
||||
top: 65535
|
||||
}
|
||||
text: serverPort.toString()
|
||||
onTextChanged: {
|
||||
if (text.length > 0) {
|
||||
serverPort = parseInt(text)
|
||||
} else {
|
||||
serverPort = 0
|
||||
}
|
||||
}
|
||||
DelToolTip {
|
||||
visible: parent.hovered
|
||||
arrowVisible: true
|
||||
text: qsTr("请输入端口号")
|
||||
position: DelToolTip.Position_Right
|
||||
}
|
||||
}
|
||||
|
||||
DelInput {
|
||||
id: usernameField
|
||||
readOnly: addsw.state === 1 ? true : false
|
||||
placeholderText: qsTr("请输入服务器用户名")
|
||||
width: parent.width * 0.6
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
text: serverUsername
|
||||
onTextChanged: serverUsername = text
|
||||
iconPosition: DelInput.Position_Left
|
||||
iconSource: DelIcon.UserOutlined
|
||||
DelToolTip {
|
||||
visible: parent.hovered
|
||||
arrowVisible: true
|
||||
text: qsTr("请输入服务器用户名")
|
||||
position: DelToolTip.Position_Right
|
||||
}
|
||||
}
|
||||
|
||||
DelInput {
|
||||
id: passwordField
|
||||
readOnly: addsw.state === 1 ? true : false
|
||||
placeholderText: qsTr("请输入服务器密码")
|
||||
width: parent.width * 0.6
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
text: serverPassword
|
||||
onTextChanged: serverPassword = text
|
||||
iconPosition: DelInput.Position_Left
|
||||
iconSource: DelIcon.LockFilled
|
||||
DelToolTip {
|
||||
visible: parent.hovered
|
||||
arrowVisible: true
|
||||
text: qsTr("请输入服务器密码")
|
||||
position: DelToolTip.Position_Right
|
||||
}
|
||||
}
|
||||
|
||||
DelButton {
|
||||
visible: addsw.state === 0 ? true : false
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
type: DelButton.Type_Primary
|
||||
text: {
|
||||
switch(mode){
|
||||
case 0 :return qsTr("添加");
|
||||
case 1 :return qsTr("修改");
|
||||
default :return qsTr("未知");
|
||||
}
|
||||
}
|
||||
|
||||
onClicked: {
|
||||
// 这里可以添加处理添加服务器的逻辑
|
||||
console.log("IP: " + serverIP)
|
||||
console.log("Port: " + serverPort)
|
||||
console.log("Username: " + serverUsername)
|
||||
console.log("Password: " + serverPassword)
|
||||
|
||||
addsw.state = 1;
|
||||
initServer();
|
||||
}
|
||||
}
|
||||
|
||||
BusyIndicator {
|
||||
id:byi
|
||||
visible: addsw.state === 1 ? true : false
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
color: "#FF5722" // 橙色
|
||||
size: 18
|
||||
duration: 12000
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle{
|
||||
id:ssh_console
|
||||
height: 200
|
||||
// anchors.fill: parent
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.margins: 10
|
||||
radius: 8
|
||||
border.color: DelTheme.isDark ? "#23272e" : "#f0f4f7"
|
||||
border.width: 3
|
||||
color:"transparent"
|
||||
|
||||
ServerConsole{
|
||||
anchors.fill:parent
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,183 @@
|
|||
import QtQuick 2.15
|
||||
import QtQuick.Window 2.15
|
||||
import QtQuick.Controls 2.15
|
||||
import QtQuick.Layouts 1.15
|
||||
import QtMultimedia 5.15
|
||||
import DelegateUI 1.0
|
||||
import QmlTool 1.0
|
||||
import Qt.labs.platform 1.1 // 系统托盘支持
|
||||
import FileTransfer 1.0
|
||||
import "../MyGlobals" 1.0
|
||||
import "../Component" 1.0
|
||||
|
||||
DelWindow {
|
||||
width: 800
|
||||
height: 600
|
||||
visible: false
|
||||
title: qsTr("下载任务")
|
||||
captionBar.topButtonVisible: true
|
||||
captionBar.winIconDelegate: Image {
|
||||
source: "qrc:/image/logo.png"
|
||||
width: 20
|
||||
height: 20
|
||||
}
|
||||
|
||||
Shortcut {
|
||||
sequences: ["Esc"]
|
||||
onActivated: close()
|
||||
}
|
||||
|
||||
function showEx(){
|
||||
x = GlobalVars.main_Window.x + GlobalVars.main_Window.width
|
||||
y = GlobalVars.main_Window.y
|
||||
show();
|
||||
width = 800
|
||||
}
|
||||
|
||||
property var downloadModel: []
|
||||
|
||||
function flushQuest(){
|
||||
listView.model = downloadModel
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: timer
|
||||
interval: 10
|
||||
running: true
|
||||
repeat: true
|
||||
onTriggered: {
|
||||
var obj = downloadModel[0];
|
||||
//未开始的任务
|
||||
if(!obj)return;
|
||||
//需要是未完成的任务
|
||||
if(obj.status !== 3){
|
||||
//上一个指令已完成开始检测下一个指令
|
||||
if(obj.instruction){
|
||||
obj.instruction = false;
|
||||
//还有下一个指令
|
||||
if(obj.questInstruction.length > 0){
|
||||
var ins = obj.questInstruction.shift();
|
||||
ins(obj);
|
||||
}
|
||||
//指令已全部做完
|
||||
else{
|
||||
var successobj = downloadModel.shift()
|
||||
successobj.status = 3
|
||||
downloadModel.push(successobj);
|
||||
}
|
||||
}
|
||||
}
|
||||
flushQuest()
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
GlobalVars.downlad_quest_window = this;
|
||||
FileTransfer.downloadCompleted.connect(downloadCompleted)
|
||||
FileTransfer.downloadProgressChanged.connect(progressChanged)
|
||||
FileTransfer.uploadCompleted.connect(uploadCompleted)
|
||||
FileTransfer.uploadProgressChanged.connect(progressChanged)
|
||||
}
|
||||
|
||||
Component.onDestruction: {
|
||||
FileTransfer.downloadCompleted.disconnect(downloadCompleted)
|
||||
FileTransfer.downloadProgressChanged.disconnect(progressChanged)
|
||||
FileTransfer.uploadCompleted.disconnect(uploadCompleted)
|
||||
FileTransfer.uploadProgressChanged.disconnect(progressChanged)
|
||||
}
|
||||
|
||||
//下载完成的信号
|
||||
function downloadCompleted(success, message) {
|
||||
var obj = downloadModel[0];
|
||||
if(!obj)return;
|
||||
if(message === "quest"){
|
||||
if (success) {
|
||||
obj.instruction = true;
|
||||
} else {
|
||||
var successobj = downloadModel.shift()
|
||||
successobj.status = 3
|
||||
successobj.error = 1
|
||||
downloadModel.push(successobj);
|
||||
}
|
||||
}
|
||||
}
|
||||
//进度的信号
|
||||
function progressChanged(rate,add){
|
||||
var obj = downloadModel[0];
|
||||
if(!obj)return;
|
||||
obj.progress = rate * 100.0;
|
||||
}
|
||||
//上传完成的信号
|
||||
function uploadCompleted(success, message) {
|
||||
var obj = downloadModel[0];
|
||||
if(!obj)return;
|
||||
if(message === "quest"){
|
||||
if (success) {
|
||||
obj.instruction = true;
|
||||
} else {
|
||||
var successobj = downloadModel.shift()
|
||||
successobj.status = 3
|
||||
successobj.error = 1
|
||||
downloadModel.push(successobj);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function addQuest(filename,questinstruction){
|
||||
downloadModel.unshift({
|
||||
//文件名
|
||||
fileName: filename,
|
||||
//任务指令
|
||||
questInstruction:questinstruction,
|
||||
//指令完成状态
|
||||
instruction:true,
|
||||
//进度条默认0
|
||||
progress: 0,
|
||||
//任务创建时间
|
||||
remainingTime: Math.floor(Date.now() / 1000),
|
||||
//任务状态
|
||||
status: 0,
|
||||
//失败Flag
|
||||
error:0
|
||||
})
|
||||
showEx();
|
||||
}
|
||||
|
||||
Rectangle{
|
||||
anchors.fill:parent
|
||||
anchors.topMargin: 45
|
||||
anchors.margins: 15
|
||||
color:"transparent"
|
||||
radius:8
|
||||
border.color: DelTheme.isDark ? "#23272e" : "#f0f4f7"
|
||||
border.width: 3
|
||||
|
||||
// 任务列表
|
||||
ScrollView {
|
||||
anchors.fill:parent
|
||||
anchors.margins: 3
|
||||
|
||||
ListView {
|
||||
id: listView
|
||||
model: downloadModel
|
||||
clip: true
|
||||
|
||||
delegate: DownloadItemDelegate {
|
||||
width: listView.width
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// // 底部状态栏
|
||||
// CustomProgressBar {
|
||||
// anchors.bottom: parent.bottom
|
||||
// anchors.left: parent.left
|
||||
// anchors.right: parent.right
|
||||
// anchors.margins: 15
|
||||
// height: 35
|
||||
// antialiasing: true
|
||||
// }
|
||||
}
|
||||
|
|
@ -0,0 +1,557 @@
|
|||
import QtQuick 2.15
|
||||
import QtQuick.Window 2.15
|
||||
import QtQuick.Controls 2.15
|
||||
import QtMultimedia 5.15
|
||||
import DelegateUI 1.0
|
||||
import QmlTool 1.0
|
||||
import FileTransfer 1.0
|
||||
import "../MyGlobals" 1.0
|
||||
import "../Component" 1.0
|
||||
|
||||
DelWindow {
|
||||
id:plugininfo_goods
|
||||
width: 1110
|
||||
height: 630
|
||||
visible: true
|
||||
title: qsTr("插件详情页")
|
||||
captionBar.topButtonVisible: true
|
||||
captionBar.winIconDelegate: Image {
|
||||
id: name
|
||||
width: 20
|
||||
height: 20
|
||||
source: "qrc:/image/logo.png"
|
||||
}
|
||||
|
||||
//原始数据
|
||||
property var p_basedata: null
|
||||
//插件名
|
||||
property string p_name : "未定名插件"
|
||||
//作者名
|
||||
property string p_author : "未定名作者"
|
||||
//版本号
|
||||
property string p_version: "0.0.1"
|
||||
//描述
|
||||
property string p_description: "暂无描述"
|
||||
//售价
|
||||
property int p_price: 0
|
||||
//插件文件数组
|
||||
property var p_filelist : []
|
||||
//是否是凌众插件
|
||||
property bool p_isrindro : false
|
||||
//图像
|
||||
property string p_image:"qrc:/image/logo2.png"
|
||||
|
||||
Component.onCompleted: {
|
||||
|
||||
}
|
||||
|
||||
Shortcut {
|
||||
sequences: ["Esc"]
|
||||
onActivated: close()
|
||||
}
|
||||
|
||||
function init(index){
|
||||
//全局数据插件列表存在
|
||||
if(!GlobalVars.serverPluginexMap)return;
|
||||
var Data = GlobalVars.serverPluginexMap[index]
|
||||
p_basedata = Data;
|
||||
p_name = Data.ProjectName
|
||||
p_author = Data.ProjectAuthor
|
||||
p_version = Data.ProjectVersion
|
||||
p_description = Data.ProjectDescribe
|
||||
//如果有插件文件数组
|
||||
if(Data.ProjectFiles)p_filelist = Data.ProjectFiles
|
||||
//如果有插件的详细描述
|
||||
if(Data.ProjectDetails)details.model = Data.ProjectDetails
|
||||
//如果有售价才设置售价
|
||||
if(Data.ProjectPrice)p_price = Data.ProjectPrice
|
||||
else p_price = 0
|
||||
}
|
||||
|
||||
function isLocalIP(ip) {
|
||||
// 检查是否为空或无效IP
|
||||
if (!ip || typeof ip !== 'string') return false;
|
||||
|
||||
// 常见本地IP模式
|
||||
const localIPPatterns = [
|
||||
/^127\./, // 127.0.0.0-127.255.255.255
|
||||
/^10\./, // 10.0.0.0-10.255.255.255
|
||||
/^172\.(1[6-9]|2[0-9]|3[0-1])\./, // 172.16.0.0-172.31.255.255
|
||||
/^192\.168\./ // 192.168.0.0-192.168.255.255
|
||||
];
|
||||
|
||||
// 检查是否匹配任何本地IP模式
|
||||
return localIPPatterns.some(pattern => pattern.test(ip));
|
||||
}
|
||||
|
||||
function buyPlugin(){
|
||||
var RealBuyIp = GlobalVars.accServerList[GlobalVars.selectServer].serverIp;
|
||||
if(isLocalIP(RealBuyIp)){
|
||||
GlobalVars.msg_control.error("暂不支持单机服务器使用双端插件!");
|
||||
close()
|
||||
return;
|
||||
}
|
||||
|
||||
GlobalVars.buyExPlugin(p_name,RealBuyIp)
|
||||
close()
|
||||
}
|
||||
|
||||
Rectangle{
|
||||
id:title
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 10
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 15
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: 45
|
||||
height:78 + headerdiv.height + description.height
|
||||
radius:8
|
||||
border.color: DelTheme.isDark ? "#23272e" : "#f0f4f7"
|
||||
border.width: 2
|
||||
color:"transparent"
|
||||
|
||||
Image {
|
||||
id: logo
|
||||
source: "qrc:/image/logo.png"
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 10
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: 15
|
||||
width: 48
|
||||
height: 48
|
||||
}
|
||||
|
||||
|
||||
DelDivider {
|
||||
id:headerdiv
|
||||
anchors.top: logo.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 1
|
||||
width: parent.width - 1
|
||||
height: 30
|
||||
}
|
||||
|
||||
Text {
|
||||
id:description
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 10
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 10
|
||||
anchors.top: headerdiv.bottom
|
||||
text: p_description
|
||||
|
||||
wrapMode: Text.WordWrap
|
||||
font {
|
||||
pixelSize: 14
|
||||
family: DelTheme.Primary.fontPrimaryFamily
|
||||
}
|
||||
color: DelTheme.Primary.colorTextBase
|
||||
}
|
||||
|
||||
Text {
|
||||
id:pluginname
|
||||
anchors.left: logo.right
|
||||
anchors.leftMargin: 10
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: 14
|
||||
text: p_name
|
||||
font {
|
||||
pixelSize: 20
|
||||
family: DelTheme.Primary.fontPrimaryFamily
|
||||
}
|
||||
color: DelTheme.Primary.colorTextBase
|
||||
|
||||
}
|
||||
|
||||
Text {
|
||||
anchors.left: logo.right
|
||||
anchors.leftMargin: 10
|
||||
anchors.top: pluginname.bottom
|
||||
anchors.topMargin: 2
|
||||
text: "作者: " + p_author
|
||||
font {
|
||||
pixelSize: 14
|
||||
family: DelTheme.Primary.fontPrimaryFamily
|
||||
}
|
||||
color: DelTheme.Primary.colorTextBase
|
||||
}
|
||||
|
||||
DelTag {
|
||||
id:version_tag
|
||||
anchors.left: pluginname.right
|
||||
anchors.leftMargin: 15
|
||||
anchors.top: pluginname.top
|
||||
anchors.topMargin: 2
|
||||
text:"Ver:" + p_version
|
||||
presetColor:"green"
|
||||
}
|
||||
|
||||
DelTag {
|
||||
id:price_tag
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: 15
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 10
|
||||
text:"所需贡献点:" + p_price
|
||||
presetColor:"green"
|
||||
}
|
||||
|
||||
DelButton {
|
||||
id:buy_button
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 10
|
||||
anchors.top: price_tag.bottom
|
||||
anchors.topMargin: 2
|
||||
height: 26
|
||||
text: "购买"
|
||||
onClicked: {
|
||||
//我这里临时先写免费服务端插件的安装
|
||||
buyPlugin();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle{
|
||||
id:content
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 10
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 15
|
||||
anchors.top: title.bottom
|
||||
anchors.topMargin: 10
|
||||
anchors.bottom:parent.bottom
|
||||
anchors.bottomMargin: 10
|
||||
radius:8
|
||||
border.color: DelTheme.isDark ? "#23272e" : "#f0f4f7"
|
||||
border.width: 2
|
||||
color:"transparent"
|
||||
clip: true
|
||||
|
||||
ScrollView {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 10
|
||||
Column {
|
||||
id: contentColumn
|
||||
anchors.fill: parent
|
||||
spacing: 10
|
||||
|
||||
Repeater {
|
||||
id:details
|
||||
delegate: Column { // 直接使用 Column 作为根元素
|
||||
width: parent.width
|
||||
spacing: 0
|
||||
|
||||
Loader {
|
||||
width: parent.width
|
||||
sourceComponent: {
|
||||
switch(modelData.type) {
|
||||
case "str": return textComponent;
|
||||
case "img": return imageComponent;
|
||||
case "mov": return videoComponent;
|
||||
default: return null;
|
||||
}
|
||||
}
|
||||
|
||||
// 动态绑定组件属性
|
||||
property var itemData: modelData
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 定义组件映射
|
||||
Component {
|
||||
id: textComponent;
|
||||
Text {
|
||||
width: parent.width - 20
|
||||
text: itemData.content
|
||||
wrapMode: Text.Wrap
|
||||
font {
|
||||
pixelSize: 14
|
||||
family: DelTheme.Primary.fontPrimaryFamily
|
||||
}
|
||||
color: DelTheme.Primary.colorTextBase
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: imageComponent;
|
||||
Image {
|
||||
source: GlobalVars.server_url + "/rindro/getimg/" + p_name + "/" + itemData.content
|
||||
fillMode: Image.PreserveAspectFit
|
||||
asynchronous: true
|
||||
cache: true
|
||||
// 动态计算宽度
|
||||
width: Math.min(implicitWidth, parent.width)
|
||||
// 让图像靠左边显示
|
||||
anchors.left: parent.left
|
||||
// 确保父级有明确宽度传递
|
||||
property real maxWidth: parent ? parent.width : 0
|
||||
// 异步加载完成后更新尺寸
|
||||
onStatusChanged: {
|
||||
if (status === Image.Ready) {
|
||||
width = Math.min(implicitWidth, maxWidth)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Component {
|
||||
// id: videoComponent
|
||||
|
||||
// Item {
|
||||
// id: videoContainer
|
||||
// width: parent.width
|
||||
// height: 400
|
||||
|
||||
// // 视频播放器
|
||||
// Video {
|
||||
// id: videoPlayer
|
||||
// anchors.fill: parent
|
||||
// anchors.bottomMargin: 15
|
||||
// source: itemData.content
|
||||
// loops: MediaPlayer.Infinite
|
||||
// autoPlay: false
|
||||
|
||||
// // 错误处理
|
||||
// onErrorChanged: {
|
||||
// if (error !== MediaPlayer.NoError) {
|
||||
// GlobalVars.msg_control.error("播放错误:", errorString);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// // 播放控制按钮(可选)
|
||||
// Row {
|
||||
// anchors.top: videoPlayer.bottom
|
||||
// anchors.topMargin: 4
|
||||
// anchors.horizontalCenter: parent.horizontalCenter
|
||||
// spacing: 10
|
||||
|
||||
// DelButton {
|
||||
// text: videoPlayer.playbackState === MediaPlayer.PlayingState ? "暂停" : "播放"
|
||||
// onClicked: videoPlayer.playbackState === MediaPlayer.PlayingState ? videoPlayer.pause() : videoPlayer.play()
|
||||
// }
|
||||
|
||||
// DelButton {
|
||||
// text: "静音"
|
||||
// onClicked: videoPlayer.muted = !videoPlayer.muted
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
Component {
|
||||
id: videoComponent
|
||||
|
||||
Item {
|
||||
id: videoContainer
|
||||
width: itemData.width
|
||||
height: itemData.height + 40
|
||||
|
||||
// 视频播放器
|
||||
Video {
|
||||
id: videoPlayer
|
||||
anchors {
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
top: parent.top
|
||||
}
|
||||
height: itemData.height
|
||||
anchors.margins: 2
|
||||
// fillMode: MediaPlayer.PreserveAspectFit // 保持视频比例,不拉伸
|
||||
// source: itemData.content
|
||||
source: itemData.content
|
||||
loops: MediaPlayer.Infinite
|
||||
autoPlay: false
|
||||
focus: false // 确保视频播放器不捕获焦点
|
||||
|
||||
// 错误处理
|
||||
onErrorChanged: {
|
||||
if (error !== MediaPlayer.NoError) {
|
||||
GlobalVars.msg_control.error("播放错误:", errorString);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 外边框
|
||||
Rectangle {
|
||||
id: border
|
||||
anchors {
|
||||
left: videoPlayer.left
|
||||
right: videoPlayer.right
|
||||
top: videoPlayer.top
|
||||
bottom: videoPlayer.bottom
|
||||
}
|
||||
border.color: "#cccccc"
|
||||
border.width: 2
|
||||
radius: 4
|
||||
color: "transparent"
|
||||
z: 1 // 确保边框在视频上方
|
||||
}
|
||||
|
||||
// 视频加载状态指示器
|
||||
Rectangle {
|
||||
id: loadingIndicator
|
||||
anchors.centerIn: videoPlayer
|
||||
width: 60
|
||||
height: 60
|
||||
radius: 30
|
||||
color: "#00000048"
|
||||
z: 2 // 确保加载指示器在最上层
|
||||
|
||||
Text {
|
||||
id: loadingText
|
||||
anchors.centerIn: parent
|
||||
text: "加载中..."
|
||||
color: "white"
|
||||
font.pixelSize: 12
|
||||
}
|
||||
|
||||
visible: videoPlayer.status === MediaPlayer.LoadingStatus
|
||||
}
|
||||
|
||||
// 错误提示文本
|
||||
Text {
|
||||
id: errorText
|
||||
anchors.centerIn: videoPlayer
|
||||
text: "视频加载失败,请检查链接或网络"
|
||||
color: "red"
|
||||
font.pixelSize: 14
|
||||
visible: videoPlayer.error !== MediaPlayer.NoError
|
||||
z: 2 // 确保错误文本在最上层
|
||||
}
|
||||
|
||||
// 进度条容器,移到视频下方
|
||||
Rectangle {
|
||||
id: progressBarContainer
|
||||
anchors {
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
bottom: videoPlayer.bottom
|
||||
bottomMargin: 2
|
||||
}
|
||||
height: 10
|
||||
color: "#00000048"
|
||||
radius: 2
|
||||
z: 2 // 确保进度条在最上层
|
||||
|
||||
Rectangle {
|
||||
id: progressBar
|
||||
anchors {
|
||||
left: parent.left
|
||||
top: parent.top
|
||||
bottom: parent.bottom
|
||||
}
|
||||
width: parent.width * (videoPlayer.position / Math.max(1, videoPlayer.duration))
|
||||
color: "#0078d7"
|
||||
radius: 2
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: progressBarMouseArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
acceptedButtons: Qt.LeftButton
|
||||
propagateComposedEvents: true // 允许事件传播
|
||||
|
||||
onPressed: {
|
||||
if (videoPlayer.duration > 0)
|
||||
videoPlayer.seek((mouse.x / width) * videoPlayer.duration)
|
||||
}
|
||||
|
||||
onPositionChanged: {
|
||||
if (mouse.pressedButtons & Qt.LeftButton && videoPlayer.duration > 0) {
|
||||
videoPlayer.seek((mouse.x / width) * videoPlayer.duration)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 播放控制按钮,移到视频下方
|
||||
Row {
|
||||
id: controlButtons
|
||||
anchors {
|
||||
top: videoPlayer.bottom
|
||||
topMargin: 5
|
||||
horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
spacing: 10
|
||||
visible: true
|
||||
z: 2 // 确保控制按钮在最上层
|
||||
|
||||
DelButton {
|
||||
id: playPauseButton
|
||||
text: videoPlayer.playbackState === MediaPlayer.PlayingState ? "暂停" : "播放"
|
||||
onClicked: {
|
||||
if (videoPlayer.playbackState === MediaPlayer.PlayingState)
|
||||
videoPlayer.pause()
|
||||
else
|
||||
videoPlayer.play()
|
||||
}
|
||||
}
|
||||
|
||||
DelButton {
|
||||
id: muteButton
|
||||
text: videoPlayer.muted ? "取消静音" : "静音"
|
||||
onClicked: videoPlayer.muted = !videoPlayer.muted
|
||||
}
|
||||
|
||||
DelButton {
|
||||
id: replayButton
|
||||
text: "重播"
|
||||
onClicked: {
|
||||
videoPlayer.seek(0)
|
||||
videoPlayer.play()
|
||||
}
|
||||
}
|
||||
|
||||
DelButton {
|
||||
text: "外部浏览器打开"
|
||||
onClicked: {
|
||||
Qt.openUrlExternally(GlobalVars.server_url + "/api/videos/" + p_name + "/" + itemData.index)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 鼠标悬停显示控制按钮
|
||||
MouseArea {
|
||||
id: containerMouseArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
propagateComposedEvents: true // 允许事件传播
|
||||
z: 1 // 确保鼠标区域在适当层级
|
||||
|
||||
// onEntered: {
|
||||
// controlButtons.visible = true
|
||||
// progressBarContainer.visible = true
|
||||
// }
|
||||
|
||||
// onExited: {
|
||||
// controlButtons.visible = false
|
||||
// progressBarContainer.visible = false
|
||||
// }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/*
|
||||
|
||||
|
||||
DelButton {
|
||||
text: "Open XLSX File"
|
||||
anchors.centerIn: parent
|
||||
onClicked: {
|
||||
var filePath = "./基础配置.xlsx"; // 请替换为实际的文件路径
|
||||
QmlTool.openFile(filePath);
|
||||
}
|
||||
}
|
||||
|
||||
*/
|
||||
}
|
||||
|
|
@ -0,0 +1,403 @@
|
|||
import QtQuick 2.15
|
||||
import QtQuick.Window 2.15
|
||||
import QtQuick.Controls 2.15
|
||||
import QtQuick.Dialogs 1.3
|
||||
import QtMultimedia 5.15
|
||||
import DelegateUI 1.0
|
||||
import QmlTool 1.0
|
||||
import FileTransfer 1.0
|
||||
import SSHManager 1.0
|
||||
import "../MyGlobals" 1.0
|
||||
import "../Component" 1.0
|
||||
|
||||
DelWindow {
|
||||
id:plugininfo_goods
|
||||
width: 890
|
||||
height: 630
|
||||
visible: true
|
||||
title: qsTr("插件详情页")
|
||||
captionBar.topButtonVisible: true
|
||||
captionBar.winIconDelegate: Image {
|
||||
id: name
|
||||
width: 20
|
||||
height: 20
|
||||
source: "qrc:/image/logo.png"
|
||||
}
|
||||
|
||||
//原始数据
|
||||
property var p_basedata: null
|
||||
//商店原始数据
|
||||
property var p_shopbasedata:null
|
||||
//插件名
|
||||
property string p_name : "未定名插件"
|
||||
//作者名
|
||||
property string p_author : "未定名作者"
|
||||
//版本号
|
||||
property string p_version: "0.0.1"
|
||||
//到期时间
|
||||
property string p_endTime: "null"
|
||||
//描述
|
||||
property string p_description: "暂无描述"
|
||||
//售价
|
||||
property int p_price: 0
|
||||
//插件文件数组
|
||||
property var p_filelist : []
|
||||
//是否是凌众插件
|
||||
property bool p_isrindro : false
|
||||
//图像
|
||||
property string p_image:"qrc:/image/logo2.png"
|
||||
|
||||
Component.onCompleted: {
|
||||
|
||||
}
|
||||
|
||||
Shortcut {
|
||||
sequences: ["Esc"]
|
||||
onActivated: close()
|
||||
}
|
||||
|
||||
function init(index){
|
||||
if(!GlobalVars.myServerExPluginMap)return;
|
||||
var Data = GlobalVars.myServerExPluginMap[index]
|
||||
p_basedata = Data;
|
||||
p_name = Data.ProjectName
|
||||
p_author = Data.ProjectAuthor
|
||||
p_version = Data.ProjectVersion
|
||||
p_description = Data.ProjectDescribe
|
||||
//如果有插件文件数组
|
||||
if(Data.ProjectFiles)p_filelist = Data.ProjectFiles
|
||||
//如果有售价才设置售价
|
||||
if(Data.ProjectPrice)p_price = Data.ProjectPrice
|
||||
else p_price = 0
|
||||
//如果有插件的详细描述
|
||||
if(Data.ProjectDetails)details.model = Data.ProjectDetails
|
||||
//判断是否启用
|
||||
if(Data.open)plugins_check.checked = Data.open === 1 ? true : false
|
||||
//判断到期时间
|
||||
if(Data.endTime)p_endTime = Data.endTime;
|
||||
// //判断是否需要更新
|
||||
// if(p_shopbasedata.ProjectVersion > p_version){
|
||||
// update_button.visible = true
|
||||
// }
|
||||
}
|
||||
|
||||
|
||||
|
||||
function buildQuest(filename){
|
||||
var downloadurl = GlobalVars.server_url + "/dps/download/" + p_name + ":";
|
||||
var updir = (filename.indexOf(".json") === -1 ? ("OfficialProject/" + p_shopbasedata.ProjectName + "/") : "OfficialConfig/");
|
||||
GlobalVars.downlad_quest_window.addQuest(p_shopbasedata.ProjectName + ":" + filename,[
|
||||
function(quest){
|
||||
//下载
|
||||
quest.status = 1;
|
||||
FileTransfer.postDownload(downloadurl + filename, "download/" + filename,{key:p_shopbasedata.ProjectName + ":" + filename},"quest");
|
||||
},
|
||||
function(quest){
|
||||
if(GlobalVars.accServerList){
|
||||
var server = GlobalVars.accServerList[GlobalVars.selectServer].serverIp
|
||||
//上传
|
||||
quest.status = 1;
|
||||
FileTransfer.postUpload("http://" + server + ":65170/api/uploadfiles", "download/" + filename, "/dp_s/" + updir,"quest");
|
||||
}
|
||||
else{
|
||||
quest.instruction = true
|
||||
}
|
||||
},function(quest){
|
||||
GlobalVars.msg_control.success("文件 :" + p_shopbasedata.ProjectName + ":" + filename + " 已下载并上传至服务器!");
|
||||
quest.instruction = true
|
||||
//配置文件是最后来的
|
||||
if(filename.indexOf("Proj.ifo") !== -1){
|
||||
GlobalVars.getServerPlugins(GlobalVars.sshConnectServer)
|
||||
close()
|
||||
}
|
||||
}]);
|
||||
}
|
||||
|
||||
FileDialog {
|
||||
id: fileDialog
|
||||
title: "请选择保存路径"
|
||||
selectFolder: true // 选择文件而非文件夹
|
||||
selectMultiple: false // 不允许多选
|
||||
onAccepted: {
|
||||
var filePath = fileDialog.fileUrl.toString()
|
||||
var cleanPath = filePath.replace(/^(file:\/{2,3})/, "");
|
||||
var filename = "双端插件-" + p_name + "导入包.zip";
|
||||
GlobalVars.downlad_quest_window.addQuest(filename,[
|
||||
function(quest){
|
||||
//下载
|
||||
quest.status = 1;
|
||||
FileTransfer.postDownload(GlobalVars.server_url + "/rindro/download", cleanPath + "/" + filename,{projectName:p_name},"quest");
|
||||
},
|
||||
function(quest){
|
||||
GlobalVars.msg_control.success(cleanPath + filename + "已下载完成!");
|
||||
quest.instruction = true
|
||||
}]);
|
||||
}
|
||||
|
||||
onRejected: {
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle{
|
||||
id:title
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 10
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 15
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: 45
|
||||
height:78 + headerdiv.height + description.height
|
||||
radius:8
|
||||
border.color: DelTheme.isDark ? "#23272e" : "#f0f4f7"
|
||||
border.width: 2
|
||||
color:"transparent"
|
||||
|
||||
Image {
|
||||
id: logo
|
||||
source: "qrc:/image/logo.png"
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 10
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: 15
|
||||
width: 48
|
||||
height: 48
|
||||
}
|
||||
|
||||
|
||||
DelDivider {
|
||||
id:headerdiv
|
||||
anchors.top: logo.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 1
|
||||
width: parent.width - 1
|
||||
height: 30
|
||||
}
|
||||
|
||||
Text {
|
||||
id:description
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 10
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 10
|
||||
anchors.top: headerdiv.bottom
|
||||
text: p_description
|
||||
|
||||
wrapMode: Text.WordWrap
|
||||
font {
|
||||
pixelSize: 14
|
||||
family: DelTheme.Primary.fontPrimaryFamily
|
||||
}
|
||||
color: DelTheme.Primary.colorTextBase
|
||||
}
|
||||
|
||||
Text {
|
||||
id:pluginname
|
||||
anchors.left: logo.right
|
||||
anchors.leftMargin: 10
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: 14
|
||||
text: p_name
|
||||
font {
|
||||
pixelSize: 20
|
||||
family: DelTheme.Primary.fontPrimaryFamily
|
||||
}
|
||||
color: DelTheme.Primary.colorTextBase
|
||||
|
||||
}
|
||||
|
||||
Text {
|
||||
anchors.left: logo.right
|
||||
anchors.leftMargin: 10
|
||||
anchors.top: pluginname.bottom
|
||||
anchors.topMargin: 2
|
||||
text: "作者: " + p_author
|
||||
font {
|
||||
pixelSize: 14
|
||||
family: DelTheme.Primary.fontPrimaryFamily
|
||||
}
|
||||
color: DelTheme.Primary.colorTextBase
|
||||
}
|
||||
|
||||
DelTag {
|
||||
id:version_tag
|
||||
anchors.left: pluginname.right
|
||||
anchors.leftMargin: 15
|
||||
anchors.top: pluginname.top
|
||||
anchors.topMargin: 2
|
||||
text:"Ver:" + p_version
|
||||
presetColor:"green"
|
||||
}
|
||||
|
||||
DelTag {
|
||||
id:endTime_tag
|
||||
anchors.left: version_tag.right
|
||||
anchors.leftMargin: 15
|
||||
anchors.top: pluginname.top
|
||||
anchors.topMargin: 2
|
||||
text:"到期时间:" + p_endTime
|
||||
presetColor:"geekblue"
|
||||
}
|
||||
|
||||
DelSwitch {
|
||||
id:plugins_check
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 10
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: 15
|
||||
checkedText: qsTr("开启插件")
|
||||
uncheckedText: qsTr("关闭插件")
|
||||
onCheckedChanged: {
|
||||
GlobalVars.setServerExPluginsEnable(GlobalVars.sshConnectServer,p_name,checked === true ? 1 : 0);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
DelButton {
|
||||
id:delete_button
|
||||
visible: true
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 10
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: 45
|
||||
height: 26
|
||||
text: "下载插件导入包"
|
||||
// colorText: "#ff0000"
|
||||
onClicked: {
|
||||
fileDialog.open()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Rectangle{
|
||||
id:content
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 10
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 15
|
||||
anchors.top: title.bottom
|
||||
anchors.topMargin: 10
|
||||
anchors.bottom:parent.bottom
|
||||
anchors.bottomMargin: 10
|
||||
radius:8
|
||||
border.color: DelTheme.isDark ? "#23272e" : "#f0f4f7"
|
||||
border.width: 2
|
||||
color:"transparent"
|
||||
clip: true
|
||||
|
||||
ScrollView {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 10
|
||||
Column {
|
||||
id: contentColumn
|
||||
anchors.fill: parent
|
||||
spacing: 10
|
||||
|
||||
Repeater {
|
||||
id:details
|
||||
delegate: Column { // 直接使用 Column 作为根元素
|
||||
width: parent.width
|
||||
spacing: 0
|
||||
|
||||
Loader {
|
||||
width: parent.width
|
||||
sourceComponent: {
|
||||
switch(modelData.type) {
|
||||
case "str": return textComponent;
|
||||
case "img": return imageComponent;
|
||||
case "mov": return videoComponent;
|
||||
default: return null;
|
||||
}
|
||||
}
|
||||
|
||||
// 动态绑定组件属性
|
||||
property var itemData: modelData
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 定义组件映射
|
||||
Component {
|
||||
id: textComponent;
|
||||
Text {
|
||||
width: parent.width - 20
|
||||
text: itemData.content
|
||||
wrapMode: Text.Wrap
|
||||
font {
|
||||
pixelSize: 14
|
||||
family: DelTheme.Primary.fontPrimaryFamily
|
||||
}
|
||||
color: DelTheme.Primary.colorTextBase
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: imageComponent;
|
||||
Image {
|
||||
source: GlobalVars.server_url + "/rindro/getimg/" + p_name + "/" + itemData.content
|
||||
fillMode: Image.PreserveAspectFit
|
||||
asynchronous: true
|
||||
cache: true
|
||||
// 动态计算宽度
|
||||
width: Math.min(implicitWidth, parent.width)
|
||||
// 让图像靠左边显示
|
||||
anchors.left: parent.left
|
||||
// 确保父级有明确宽度传递
|
||||
property real maxWidth: parent ? parent.width : 0
|
||||
// 异步加载完成后更新尺寸
|
||||
onStatusChanged: {
|
||||
if (status === Image.Ready) {
|
||||
width = Math.min(implicitWidth, maxWidth)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: videoComponent
|
||||
|
||||
Item {
|
||||
id: videoContainer
|
||||
width: parent.width
|
||||
height: 400
|
||||
|
||||
// 视频播放器
|
||||
Video {
|
||||
id: videoPlayer
|
||||
anchors.fill: parent
|
||||
anchors.bottomMargin: 15
|
||||
source: itemData.content
|
||||
loops: MediaPlayer.Infinite
|
||||
autoPlay: false
|
||||
|
||||
// 错误处理
|
||||
onErrorChanged: {
|
||||
if (error !== MediaPlayer.NoError) {
|
||||
GlobalVars.msg_control.error("播放错误:", errorString);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 播放控制按钮(可选)
|
||||
Row {
|
||||
anchors.top: videoPlayer.bottom
|
||||
anchors.topMargin: 4
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
spacing: 10
|
||||
|
||||
DelButton {
|
||||
text: videoPlayer.playbackState === MediaPlayer.PlayingState ? "暂停" : "播放"
|
||||
onClicked: videoPlayer.playbackState === MediaPlayer.PlayingState ? videoPlayer.pause() : videoPlayer.play()
|
||||
}
|
||||
|
||||
DelButton {
|
||||
text: "静音"
|
||||
onClicked: videoPlayer.muted = !videoPlayer.muted
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,378 @@
|
|||
import QtQuick 2.15
|
||||
import QtQuick.Window 2.15
|
||||
import QtQuick.Controls 2.15
|
||||
import QtMultimedia 5.15
|
||||
import DelegateUI 1.0
|
||||
import QmlTool 1.0
|
||||
import FileTransfer 1.0
|
||||
import "../MyGlobals" 1.0
|
||||
import "../Component" 1.0
|
||||
|
||||
DelWindow {
|
||||
id:plugininfo_goods
|
||||
width: 890
|
||||
height: 630
|
||||
visible: true
|
||||
title: qsTr("插件详情页")
|
||||
captionBar.topButtonVisible: true
|
||||
captionBar.winIconDelegate: Image {
|
||||
id: name
|
||||
width: 20
|
||||
height: 20
|
||||
source: "qrc:/image/logo.png"
|
||||
}
|
||||
|
||||
//原始数据
|
||||
property var p_basedata: null
|
||||
//插件名
|
||||
property string p_name : "未定名插件"
|
||||
//作者名
|
||||
property string p_author : "未定名作者"
|
||||
//版本号
|
||||
property string p_version: "0.0.1"
|
||||
//描述
|
||||
property string p_description: "暂无描述"
|
||||
//售价
|
||||
property int p_price: 0
|
||||
//插件文件数组
|
||||
property var p_filelist : []
|
||||
//是否是凌众插件
|
||||
property bool p_isrindro : false
|
||||
//图像
|
||||
property string p_image:"qrc:/image/logo2.png"
|
||||
|
||||
Component.onCompleted: {
|
||||
|
||||
}
|
||||
|
||||
Shortcut {
|
||||
sequences: ["Esc"]
|
||||
onActivated: close()
|
||||
}
|
||||
|
||||
function init(index){
|
||||
//全局数据插件列表存在
|
||||
if(!GlobalVars.serverPluginList)return;
|
||||
var Data = GlobalVars.serverPluginList[index]
|
||||
p_basedata = Data;
|
||||
p_name = Data.ProjectName
|
||||
p_author = Data.ProjectAuthor
|
||||
p_version = Data.ProjectVersion
|
||||
p_description = Data.ProjectDescribe
|
||||
//如果有插件文件数组
|
||||
if(Data.ProjectFiles)p_filelist = Data.ProjectFiles
|
||||
//如果有插件的详细描述
|
||||
if(Data.ProjectDetails)details.model = Data.ProjectDetails
|
||||
//如果有售价才设置售价
|
||||
if(Data.ProjectPrice)p_price = Data.ProjectPrice
|
||||
else p_price = 0
|
||||
//如果是凌众的插件
|
||||
if(Data.isrindro)p_isrindro = true
|
||||
else p_isrindro = false
|
||||
}
|
||||
|
||||
function buildQuest(filename){
|
||||
var downloadurl = GlobalVars.server_url + "/dps/download/" + p_name + ":";
|
||||
var updir = (filename.indexOf(".json") === -1 ? ("OfficialProject/" + p_basedata.ProjectName + "/") : "OfficialConfig/");
|
||||
GlobalVars.downlad_quest_window.addQuest(p_basedata.ProjectName + ":" + filename,[
|
||||
function(quest){
|
||||
//下载
|
||||
quest.status = 1;
|
||||
FileTransfer.postDownload(downloadurl + filename, "download/" + filename,{key:p_basedata.ProjectName + ":" + filename},"quest");
|
||||
},
|
||||
function(quest){
|
||||
if(GlobalVars.accServerList){
|
||||
var server = GlobalVars.accServerList[GlobalVars.selectServer].serverIp
|
||||
//上传
|
||||
quest.status = 1;
|
||||
FileTransfer.postUpload("http://" + server + ":65170/api/uploadfiles", "download/" + filename, "/dp_s/" + updir,"quest");
|
||||
}
|
||||
else{
|
||||
quest.instruction = true
|
||||
}
|
||||
},function(quest){
|
||||
GlobalVars.msg_control.success("文件 :" + p_basedata.ProjectName + ":" + filename + " 已下载并上传至服务器!");
|
||||
quest.instruction = true
|
||||
}]);
|
||||
}
|
||||
|
||||
function buyPlugin(){
|
||||
//如果不是凌众插件
|
||||
if(!p_isrindro){
|
||||
|
||||
buildQuest("Proj.ifo");
|
||||
|
||||
//项目配置文件
|
||||
if(p_basedata && p_basedata.ProjectConfig.length > 0){
|
||||
buildQuest(p_basedata.ProjectConfig);
|
||||
}
|
||||
|
||||
//项目逻辑文件
|
||||
for (var i = 0; i < p_filelist.length; i++) {
|
||||
buildQuest(p_filelist[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle{
|
||||
id:title
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 10
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 15
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: 45
|
||||
height:78 + headerdiv.height + description.height
|
||||
radius:8
|
||||
border.color: DelTheme.isDark ? "#23272e" : "#f0f4f7"
|
||||
border.width: 2
|
||||
color:"transparent"
|
||||
|
||||
Image {
|
||||
id: logo
|
||||
source: "qrc:/image/logo.png"
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 10
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: 15
|
||||
width: 48
|
||||
height: 48
|
||||
}
|
||||
|
||||
|
||||
DelDivider {
|
||||
id:headerdiv
|
||||
anchors.top: logo.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 1
|
||||
width: parent.width - 1
|
||||
height: 30
|
||||
}
|
||||
|
||||
Text {
|
||||
id:description
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 10
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 10
|
||||
anchors.top: headerdiv.bottom
|
||||
text: p_description
|
||||
|
||||
wrapMode: Text.WordWrap
|
||||
font {
|
||||
pixelSize: 14
|
||||
family: DelTheme.Primary.fontPrimaryFamily
|
||||
}
|
||||
color: DelTheme.Primary.colorTextBase
|
||||
}
|
||||
|
||||
Text {
|
||||
id:pluginname
|
||||
anchors.left: logo.right
|
||||
anchors.leftMargin: 10
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: 14
|
||||
text: p_name
|
||||
font {
|
||||
pixelSize: 20
|
||||
family: DelTheme.Primary.fontPrimaryFamily
|
||||
}
|
||||
color: DelTheme.Primary.colorTextBase
|
||||
|
||||
}
|
||||
|
||||
Text {
|
||||
anchors.left: logo.right
|
||||
anchors.leftMargin: 10
|
||||
anchors.top: pluginname.bottom
|
||||
anchors.topMargin: 2
|
||||
text: "作者: " + p_author
|
||||
font {
|
||||
pixelSize: 14
|
||||
family: DelTheme.Primary.fontPrimaryFamily
|
||||
}
|
||||
color: DelTheme.Primary.colorTextBase
|
||||
}
|
||||
|
||||
DelTag {
|
||||
id:version_tag
|
||||
anchors.left: pluginname.right
|
||||
anchors.leftMargin: 15
|
||||
anchors.top: pluginname.top
|
||||
anchors.topMargin: 2
|
||||
text:"Ver:" + p_version
|
||||
presetColor:"green"
|
||||
}
|
||||
|
||||
DelTag {
|
||||
id:price_tag
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: 15
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 10
|
||||
text:"所需贡献点:" + p_price
|
||||
presetColor:"green"
|
||||
}
|
||||
|
||||
DelButton {
|
||||
id:buy_button
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 10
|
||||
anchors.top: price_tag.bottom
|
||||
anchors.topMargin: 2
|
||||
height: 26
|
||||
text: "购买"
|
||||
onClicked: {
|
||||
//我这里临时先写免费服务端插件的安装
|
||||
buyPlugin();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Rectangle{
|
||||
id:content
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 10
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 15
|
||||
anchors.top: title.bottom
|
||||
anchors.topMargin: 10
|
||||
anchors.bottom:parent.bottom
|
||||
anchors.bottomMargin: 10
|
||||
radius:8
|
||||
border.color: DelTheme.isDark ? "#23272e" : "#f0f4f7"
|
||||
border.width: 2
|
||||
color:"transparent"
|
||||
clip: true
|
||||
|
||||
ScrollView {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 10
|
||||
Column {
|
||||
id: contentColumn
|
||||
anchors.fill: parent
|
||||
spacing: 10
|
||||
|
||||
Repeater {
|
||||
id:details
|
||||
delegate: Column { // 直接使用 Column 作为根元素
|
||||
width: parent.width
|
||||
spacing: 0
|
||||
|
||||
Loader {
|
||||
width: parent.width
|
||||
sourceComponent: {
|
||||
switch(modelData.type) {
|
||||
case "str": return textComponent;
|
||||
case "img": return imageComponent;
|
||||
case "mov": return videoComponent;
|
||||
default: return null;
|
||||
}
|
||||
}
|
||||
|
||||
// 动态绑定组件属性
|
||||
property var itemData: modelData
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 定义组件映射
|
||||
Component {
|
||||
id: textComponent;
|
||||
Text {
|
||||
width: parent.width - 20
|
||||
text: itemData.content
|
||||
wrapMode: Text.Wrap
|
||||
font {
|
||||
pixelSize: 14
|
||||
family: DelTheme.Primary.fontPrimaryFamily
|
||||
}
|
||||
color: DelTheme.Primary.colorTextBase
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: imageComponent;
|
||||
Image {
|
||||
source: itemData.content
|
||||
fillMode: Image.PreserveAspectFit
|
||||
asynchronous: true
|
||||
cache: true
|
||||
// 动态计算宽度
|
||||
width: Math.min(implicitWidth, parent.width)
|
||||
// 让图像靠左边显示
|
||||
anchors.left: parent.left
|
||||
// 确保父级有明确宽度传递
|
||||
property real maxWidth: parent ? parent.width : 0
|
||||
// 异步加载完成后更新尺寸
|
||||
onStatusChanged: {
|
||||
if (status === Image.Ready) {
|
||||
width = Math.min(implicitWidth, maxWidth)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: videoComponent
|
||||
|
||||
Item {
|
||||
id: videoContainer
|
||||
width: parent.width
|
||||
height: 400
|
||||
|
||||
// 视频播放器
|
||||
Video {
|
||||
id: videoPlayer
|
||||
anchors.fill: parent
|
||||
anchors.bottomMargin: 15
|
||||
source: itemData.content
|
||||
loops: MediaPlayer.Infinite
|
||||
autoPlay: false
|
||||
|
||||
// 错误处理
|
||||
onErrorChanged: {
|
||||
if (error !== MediaPlayer.NoError) {
|
||||
GlobalVars.msg_control.error("播放错误:", errorString);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 播放控制按钮(可选)
|
||||
Row {
|
||||
anchors.top: videoPlayer.bottom
|
||||
anchors.topMargin: 4
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
spacing: 10
|
||||
|
||||
DelButton {
|
||||
text: videoPlayer.playbackState === MediaPlayer.PlayingState ? "暂停" : "播放"
|
||||
onClicked: videoPlayer.playbackState === MediaPlayer.PlayingState ? videoPlayer.pause() : videoPlayer.play()
|
||||
}
|
||||
|
||||
DelButton {
|
||||
text: "静音"
|
||||
onClicked: videoPlayer.muted = !videoPlayer.muted
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
|
||||
DelButton {
|
||||
text: "Open XLSX File"
|
||||
anchors.centerIn: parent
|
||||
onClicked: {
|
||||
var filePath = "./基础配置.xlsx"; // 请替换为实际的文件路径
|
||||
QmlTool.openFile(filePath);
|
||||
}
|
||||
}
|
||||
|
||||
*/
|
||||
}
|
||||
|
|
@ -0,0 +1,350 @@
|
|||
import QtQuick 2.15
|
||||
import QtQuick.Window 2.15
|
||||
import QtQuick.Controls 2.15
|
||||
import QtMultimedia 5.15
|
||||
import DelegateUI 1.0
|
||||
import QmlTool 1.0
|
||||
import FileTransfer 1.0
|
||||
import SSHManager 1.0
|
||||
import "../MyGlobals" 1.0
|
||||
import "../Component" 1.0
|
||||
|
||||
DelWindow {
|
||||
id:plugininfo_goods
|
||||
width: 890
|
||||
height: 630
|
||||
visible: true
|
||||
title: qsTr("插件详情页")
|
||||
captionBar.topButtonVisible: true
|
||||
captionBar.winIconDelegate: Image {
|
||||
id: name
|
||||
width: 20
|
||||
height: 20
|
||||
source: "qrc:/image/logo.png"
|
||||
}
|
||||
|
||||
//原始数据
|
||||
property var p_basedata: null
|
||||
//商店原始数据
|
||||
property var p_shopbasedata:null
|
||||
//插件名
|
||||
property string p_name : "未定名插件"
|
||||
//作者名
|
||||
property string p_author : "未定名作者"
|
||||
//版本号
|
||||
property string p_version: "0.0.1"
|
||||
//描述
|
||||
property string p_description: "暂无描述"
|
||||
//售价
|
||||
property int p_price: 0
|
||||
//插件文件数组
|
||||
property var p_filelist : []
|
||||
//是否是凌众插件
|
||||
property bool p_isrindro : false
|
||||
//图像
|
||||
property string p_image:"qrc:/image/logo2.png"
|
||||
|
||||
Component.onCompleted: {
|
||||
|
||||
}
|
||||
|
||||
Shortcut {
|
||||
sequences: ["Esc"]
|
||||
onActivated: close()
|
||||
}
|
||||
|
||||
|
||||
function escapeString(input) {
|
||||
return input
|
||||
.replace(/\\/g, "\\\\") // 转义反斜杠
|
||||
.replace(/"/g, "\\\"") // 转义双引号
|
||||
.replace(/\n/g, "\\n") // 转义换行符
|
||||
.replace(/\r/g, "\\r") // 转义回车符
|
||||
.replace(/\t/g, "\\t"); // 转义制表符
|
||||
}
|
||||
|
||||
function init(index){
|
||||
if(!GlobalVars.myServerPluginList)return;
|
||||
var Data = GlobalVars.myServerPluginList[index]
|
||||
p_basedata = Data;
|
||||
p_name = Data.ProjectName
|
||||
p_author = Data.ProjectAuthor
|
||||
p_version = Data.ProjectVersion
|
||||
p_description = Data.ProjectDescribe
|
||||
//如果有插件文件数组
|
||||
if(Data.ProjectFiles)p_filelist = Data.ProjectFiles
|
||||
//如果有售价才设置售价
|
||||
if(Data.ProjectPrice)p_price = Data.ProjectPrice
|
||||
else p_price = 0
|
||||
//如果是凌众的插件
|
||||
if(Data.isrindro)p_isrindro = true
|
||||
else p_isrindro = false
|
||||
|
||||
//去商店脚本辨别出自己的项目
|
||||
for(var id in GlobalVars.serverPluginList){
|
||||
var obj = GlobalVars.serverPluginList[id];
|
||||
if(obj.ProjectName === p_name && obj.ProjectAuthor === p_author){
|
||||
p_shopbasedata = obj
|
||||
}
|
||||
}
|
||||
|
||||
//请求自己项目的配置文件
|
||||
if(Data.ProjectConfig)GlobalVars.getServerPluginConfig(GlobalVars.sshConnectServer,Data.ProjectConfig,function(config){
|
||||
var model = [];
|
||||
if(config){
|
||||
configTabView.visible = true
|
||||
configTabView.initModel = [];
|
||||
}
|
||||
//老的解析方法和新的解析方法
|
||||
if(typeof config.newcontent !== "string"){
|
||||
if(config.newcontent)config.newcontent = JSON.stringify(config.newcontent,null,2);
|
||||
if(config.oldcontent)config.oldcontent = JSON.stringify(config.oldcontent,null,2);
|
||||
}
|
||||
else {
|
||||
if(config.newcontent)config.newcontent = Qt.atob(config.newcontent);
|
||||
if(config.oldcontent)config.oldcontent = Qt.atob(config.oldcontent);
|
||||
}
|
||||
|
||||
if(config.newcontent){
|
||||
model.push({
|
||||
key: "1",
|
||||
icon: DelIcon.CreditCardOutlined,
|
||||
title: "配置文件",
|
||||
config:config.newcontent
|
||||
})
|
||||
}
|
||||
if(config.oldcontent){
|
||||
model.push({
|
||||
key: "2",
|
||||
icon: DelIcon.CreditCardOutlined,
|
||||
title: "配置文件(旧)",
|
||||
config:config.oldcontent
|
||||
})
|
||||
}
|
||||
|
||||
if(model.length > 0){
|
||||
configTabView.initModel = model
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
|
||||
//判断是否需要更新
|
||||
if(p_shopbasedata.ProjectVersion > p_version){
|
||||
update_button.visible = true
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
function buildQuest(filename){
|
||||
var downloadurl = GlobalVars.server_url + "/dps/download/" + p_name + ":";
|
||||
var updir = (filename.indexOf(".json") === -1 ? ("OfficialProject/" + p_shopbasedata.ProjectName + "/") : "OfficialConfig/");
|
||||
GlobalVars.downlad_quest_window.addQuest(p_shopbasedata.ProjectName + ":" + filename,[
|
||||
function(quest){
|
||||
//下载
|
||||
quest.status = 1;
|
||||
FileTransfer.postDownload(downloadurl + filename, "download/" + filename,{key:p_shopbasedata.ProjectName + ":" + filename},"quest");
|
||||
},
|
||||
function(quest){
|
||||
if(GlobalVars.accServerList){
|
||||
var server = GlobalVars.accServerList[GlobalVars.selectServer].serverIp
|
||||
//上传
|
||||
quest.status = 1;
|
||||
FileTransfer.postUpload("http://" + server + ":65170/api/uploadfiles", "download/" + filename, "/dp_s/" + updir,"quest");
|
||||
}
|
||||
else{
|
||||
quest.instruction = true
|
||||
}
|
||||
},function(quest){
|
||||
GlobalVars.msg_control.success("文件 :" + p_shopbasedata.ProjectName + ":" + filename + " 已下载并上传至服务器!");
|
||||
quest.instruction = true
|
||||
//配置文件是最后来的
|
||||
if(filename.indexOf("Proj.ifo") !== -1){
|
||||
GlobalVars.getServerPlugins(GlobalVars.sshConnectServer)
|
||||
close()
|
||||
}
|
||||
}]);
|
||||
}
|
||||
|
||||
Rectangle{
|
||||
id:title
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 10
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 15
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: 45
|
||||
height:78 + headerdiv.height + description.height
|
||||
radius:8
|
||||
border.color: DelTheme.isDark ? "#23272e" : "#f0f4f7"
|
||||
border.width: 2
|
||||
color:"transparent"
|
||||
|
||||
Image {
|
||||
id: logo
|
||||
source: "qrc:/image/logo.png"
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 10
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: 15
|
||||
width: 48
|
||||
height: 48
|
||||
}
|
||||
|
||||
|
||||
DelDivider {
|
||||
id:headerdiv
|
||||
anchors.top: logo.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 1
|
||||
width: parent.width - 1
|
||||
height: 30
|
||||
}
|
||||
|
||||
Text {
|
||||
id:description
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 10
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 10
|
||||
anchors.top: headerdiv.bottom
|
||||
text: p_description
|
||||
|
||||
wrapMode: Text.WordWrap
|
||||
font {
|
||||
pixelSize: 14
|
||||
family: DelTheme.Primary.fontPrimaryFamily
|
||||
}
|
||||
color: DelTheme.Primary.colorTextBase
|
||||
}
|
||||
|
||||
Text {
|
||||
id:pluginname
|
||||
anchors.left: logo.right
|
||||
anchors.leftMargin: 10
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: 14
|
||||
text: p_name
|
||||
font {
|
||||
pixelSize: 20
|
||||
family: DelTheme.Primary.fontPrimaryFamily
|
||||
}
|
||||
color: DelTheme.Primary.colorTextBase
|
||||
|
||||
}
|
||||
|
||||
Text {
|
||||
anchors.left: logo.right
|
||||
anchors.leftMargin: 10
|
||||
anchors.top: pluginname.bottom
|
||||
anchors.topMargin: 2
|
||||
text: "作者: " + p_author
|
||||
font {
|
||||
pixelSize: 14
|
||||
family: DelTheme.Primary.fontPrimaryFamily
|
||||
}
|
||||
color: DelTheme.Primary.colorTextBase
|
||||
}
|
||||
|
||||
DelTag {
|
||||
id:version_tag
|
||||
anchors.left: pluginname.right
|
||||
anchors.leftMargin: 15
|
||||
anchors.top: pluginname.top
|
||||
anchors.topMargin: 2
|
||||
text:"Ver:" + p_version
|
||||
presetColor:"green"
|
||||
}
|
||||
|
||||
DelButton {
|
||||
id:update_button
|
||||
visible: false
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 10
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: 12
|
||||
height: 26
|
||||
text: "更新"
|
||||
colorText: "#32cd33"
|
||||
onClicked: {
|
||||
GlobalVars.backupServerPluginConfig(GlobalVars.sshConnectServer,p_basedata.ProjectConfig,function(ret){
|
||||
//备份完成开始更新
|
||||
if(ret){
|
||||
buildQuest("Proj.ifo");
|
||||
|
||||
//项目逻辑文件
|
||||
for (var i = 0; i < p_shopbasedata.ProjectFiles.length; i++) {
|
||||
buildQuest(p_shopbasedata.ProjectFiles[i]);
|
||||
}
|
||||
|
||||
//项目配置文件
|
||||
if(p_shopbasedata && p_shopbasedata.ProjectConfig.length > 0){
|
||||
buildQuest(p_shopbasedata.ProjectConfig);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
DelButton {
|
||||
id:delete_button
|
||||
visible: true
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 10
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: 42
|
||||
height: 26
|
||||
text: "删除"
|
||||
colorText: "#ff0000"
|
||||
onClicked: {
|
||||
SSHManager.sendInput("rm -rf " + "/dp_s/OfficialProject/" + p_shopbasedata.ProjectName)
|
||||
SSHManager.sendInput("echo '删除插件成功!'")
|
||||
close()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Rectangle{
|
||||
id:content
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 10
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 15
|
||||
anchors.top: title.bottom
|
||||
anchors.topMargin: 10
|
||||
anchors.bottom:parent.bottom
|
||||
anchors.bottomMargin: 10
|
||||
radius:8
|
||||
border.color: DelTheme.isDark ? "#23272e" : "#f0f4f7"
|
||||
border.width: 2
|
||||
color:"transparent"
|
||||
clip: true
|
||||
|
||||
DelTabView {
|
||||
id: configTabView
|
||||
visible: false
|
||||
anchors.fill: parent
|
||||
tabSize: DelTabView.Size_Auto
|
||||
tabCentered: true
|
||||
addButtonDelegate:Item{}
|
||||
contentDelegate: Rectangle {
|
||||
anchors.fill: parent
|
||||
color:"transparent"
|
||||
JsonEditor{
|
||||
id:config_editor
|
||||
anchors.fill:parent
|
||||
projectConfigStr:model.config
|
||||
saveFunction:function(config){
|
||||
//在新配置这里点保存才有反应
|
||||
if(model.key === "1"){
|
||||
GlobalVars.setServerPluginConfig(GlobalVars.sshConnectServer,p_basedata.ProjectConfig,config)
|
||||
close()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,292 @@
|
|||
import QtQuick 2.15
|
||||
import QtQuick.Window 2.15
|
||||
import QtQuick.Controls 2.15
|
||||
import DelegateUI 1.0
|
||||
import SSHManager 1.0
|
||||
import QtQuick.Shapes 1.15
|
||||
import QtQuick.Layouts 1.15
|
||||
import QtGraphicalEffects 1.15
|
||||
import "../MyGlobals" 1.0
|
||||
import "../Component"
|
||||
|
||||
DelWindow {
|
||||
id:addsw
|
||||
width: 890
|
||||
height: 630
|
||||
visible: true
|
||||
title: qsTr("我的服务器插件")
|
||||
captionBar.topButtonVisible: true
|
||||
captionBar.winIconDelegate: Image {
|
||||
width: 20
|
||||
height: 20
|
||||
source: "qrc:/image/logo.png"
|
||||
}
|
||||
|
||||
Shortcut {
|
||||
sequences: ["Esc"]
|
||||
onActivated: close()
|
||||
}
|
||||
|
||||
|
||||
// 初始化后自动连接示例(可选)
|
||||
Component.onCompleted: {
|
||||
|
||||
}
|
||||
|
||||
// 窗口关闭时自动触发清理
|
||||
Component.onDestruction: {
|
||||
|
||||
}
|
||||
|
||||
//0 服务端插件 1 双端插件
|
||||
property int showMode : 0
|
||||
|
||||
//服务器的IP
|
||||
property string server_ip: ""
|
||||
|
||||
property var pluginModel: []
|
||||
property var pluginInfoWidow: null
|
||||
|
||||
//监听全局变量的变化 这里需要监听 因为在本页会产生刷新数据
|
||||
Connections {
|
||||
target: GlobalVars
|
||||
//服务器插件信息
|
||||
function onMyServerPluginListChanged(){
|
||||
if(showMode === 0)initModel();
|
||||
}
|
||||
function onMyServerExPluginMapChanged(){
|
||||
if(showMode === 1)initModel();
|
||||
}
|
||||
}
|
||||
|
||||
//窗口初始化
|
||||
function init(serverip){
|
||||
server_ip = serverip
|
||||
getData();
|
||||
}
|
||||
|
||||
//获取插件信息
|
||||
function getData(){
|
||||
if(server_ip.length === 0)return
|
||||
//获取服务器的全部服务器插件
|
||||
if(showMode === 0){
|
||||
GlobalVars.getServerPlugins(server_ip)
|
||||
}
|
||||
//获取服务器的全部双端插件
|
||||
else if(showMode === 1){
|
||||
GlobalVars.getServerExPlugins(server_ip)
|
||||
}
|
||||
}
|
||||
|
||||
//通过信息构造模型
|
||||
function initModel(){
|
||||
var buf = [];
|
||||
var Data = null;
|
||||
if(showMode === 0){
|
||||
//全局数据插件列表存在
|
||||
if(GlobalVars.myServerPluginList){
|
||||
Data = GlobalVars.myServerPluginList;
|
||||
// 遍历对象的属性
|
||||
for (var key in Data) {
|
||||
var obj = Data[key];
|
||||
if(GlobalVars.serverPluginMap[obj.ProjectName]){
|
||||
if(GlobalVars.serverPluginMap[obj.ProjectName].ProjectVersion > obj.ProjectVersion){
|
||||
obj["needupdate"] = true
|
||||
}
|
||||
}
|
||||
buf.push(obj);
|
||||
}
|
||||
pluginModel =buf;
|
||||
}
|
||||
}
|
||||
else if(showMode === 1){
|
||||
//全局数据插件列表存在
|
||||
if(GlobalVars.myServerExPluginMap){
|
||||
Data = GlobalVars.myServerExPluginMap;
|
||||
// 遍历对象的属性
|
||||
for (var key in Data) {
|
||||
var obj = Data[key];
|
||||
obj.ProjectName = key
|
||||
buf.push(obj);
|
||||
}
|
||||
pluginModel =buf;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
DelTabView {
|
||||
id: defaultTabView
|
||||
anchors.fill: parent
|
||||
anchors.topMargin: 30
|
||||
tabSize: DelTabView.Size_Auto
|
||||
tabCentered: true
|
||||
addButtonDelegate:Item{}
|
||||
// 监听标签页变化
|
||||
onCurrentIndexChanged: {
|
||||
showMode = currentIndex
|
||||
pluginModel = [];
|
||||
getData();
|
||||
}
|
||||
|
||||
contentDelegate: Rectangle {
|
||||
anchors.fill: parent
|
||||
color:"transparent"
|
||||
ScrollView {
|
||||
anchors.fill: parent
|
||||
GridView {
|
||||
id: gv
|
||||
anchors.fill: parent
|
||||
anchors.leftMargin: 24
|
||||
anchors.margins: 4
|
||||
cellWidth: 420
|
||||
cellHeight: showMode === 1 ? 300 : 128
|
||||
clip: true
|
||||
model:pluginModel
|
||||
|
||||
delegate:Item { // 每个格子的容器
|
||||
width: gv.cellWidth
|
||||
height: gv.cellHeight
|
||||
|
||||
Card {
|
||||
id:card
|
||||
anchors.centerIn: parent // 在格子容器中居中
|
||||
width: parent.width - 24 // 改用相对尺寸
|
||||
height: parent.height - 24
|
||||
isDarkMode: DelTheme.isDark
|
||||
onClicked:function(){
|
||||
if (!pluginInfoWidow) {
|
||||
var Path = "Window_PluginInfo_Private.qml";
|
||||
if(showMode === 1)Path = "Window_ExPluginInfo_Private.qml"
|
||||
// 创建新窗口
|
||||
var component = Qt.createComponent(Path);
|
||||
pluginInfoWidow = component.createObject(parent);
|
||||
if(showMode === 0)pluginInfoWidow.init(index)
|
||||
if(showMode === 1){
|
||||
pluginInfoWidow.init(modelData.ProjectName)
|
||||
}
|
||||
|
||||
// 绑定关闭时自动销毁
|
||||
pluginInfoWidow.closing.connect(function() {
|
||||
pluginInfoWidow.destroy()
|
||||
pluginInfoWidow = null // 关键:释放引用
|
||||
})
|
||||
}
|
||||
pluginInfoWidow.y = GlobalVars.main_Window.y + 85
|
||||
pluginInfoWidow.show()
|
||||
pluginInfoWidow.raise() // 把窗口提到最前面
|
||||
}
|
||||
content: ColumnLayout {
|
||||
width: parent.width
|
||||
height: parent.height
|
||||
spacing: 4
|
||||
Rectangle{
|
||||
id:title_row
|
||||
Layout.fillWidth: true
|
||||
height: 30
|
||||
color:"transparent"
|
||||
Text {
|
||||
id:title
|
||||
text: modelData.ProjectName
|
||||
font {
|
||||
pixelSize: 24
|
||||
family: DelTheme.Primary.fontPrimaryFamily
|
||||
bold: true
|
||||
}
|
||||
color: DelTheme.Primary.colorTextBase
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
Text {
|
||||
id:author
|
||||
anchors.top: title_row.top
|
||||
anchors.topMargin: 4
|
||||
anchors.right: title_row.right
|
||||
anchors.rightMargin: 10
|
||||
text: {
|
||||
if(modelData.needupdate === true)return "有新的版本!";
|
||||
else return "作者:" + modelData.ProjectAuthor
|
||||
}
|
||||
|
||||
font {
|
||||
pixelSize: 16
|
||||
family: DelTheme.Primary.fontPrimaryFamily
|
||||
bold: true
|
||||
}
|
||||
color: {
|
||||
if(modelData.needupdate === true)return "#32CD32";
|
||||
else return DelTheme.Primary.colorTextBase
|
||||
}
|
||||
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
Layout.fillWidth: true
|
||||
text: modelData.ProjectDescribe
|
||||
wrapMode: Text.WordWrap
|
||||
maximumLineCount: 2
|
||||
font {
|
||||
pixelSize: 14
|
||||
family: DelTheme.Primary.fontPrimaryFamily
|
||||
}
|
||||
color: DelTheme.Primary.colorTextSecondary
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
Item {
|
||||
visible: showMode === 1
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
height:300
|
||||
DelRectangle {
|
||||
id: imageContainer
|
||||
anchors.fill: parent
|
||||
radius: 6
|
||||
color: "transparent"
|
||||
clip: true
|
||||
|
||||
Image {
|
||||
id: card_image
|
||||
asynchronous: true // 异步加载不阻塞UI
|
||||
cache: true // 启用内存缓存
|
||||
anchors.fill: parent
|
||||
source: GlobalVars.server_url + "/rindro/getimg/" + modelData.ProjectName + "/0"
|
||||
fillMode: Image.PreserveAspectCrop
|
||||
}
|
||||
|
||||
layer.enabled: true
|
||||
layer.effect: OpacityMask {
|
||||
maskSource: Rectangle {
|
||||
width: imageContainer.width
|
||||
height: imageContainer.height
|
||||
radius: imageContainer.radius
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
initModel: [
|
||||
{
|
||||
key: "1",
|
||||
icon: DelIcon.CreditCardOutlined,
|
||||
title: "服务端插件"
|
||||
},
|
||||
{
|
||||
key: "2",
|
||||
icon: DelIcon.CreditCardOutlined,
|
||||
title: "双端插件"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
{}
|
||||
|
After Width: | Height: | Size: 547 B |
|
After Width: | Height: | Size: 66 KiB |
|
After Width: | Height: | Size: 1.3 MiB |
|
After Width: | Height: | Size: 1.4 MiB |
|
After Width: | Height: | Size: 1.3 MiB |
|
After Width: | Height: | Size: 1.9 MiB |
|
After Width: | Height: | Size: 540 B |
|
After Width: | Height: | Size: 94 KiB |
|
After Width: | Height: | Size: 529 B |
|
After Width: | Height: | Size: 4.9 KiB |
|
After Width: | Height: | Size: 5.1 KiB |
|
After Width: | Height: | Size: 524 B |
|
After Width: | Height: | Size: 1.1 MiB |
|
After Width: | Height: | Size: 424 B |
|
After Width: | Height: | Size: 682 B |
|
After Width: | Height: | Size: 626 KiB |
|
After Width: | Height: | Size: 94 KiB |
|
|
@ -0,0 +1,134 @@
|
|||
#include "jsonhighlighter.h"
|
||||
#include <QDebug>
|
||||
|
||||
JsonHighlighter::JsonHighlighter(QTextDocument *parent) : QSyntaxHighlighter(parent), m_document(nullptr) {
|
||||
// 关键字的颜色规则
|
||||
addRule(QRegExp("\"[^\"]*\""), Qt::darkGreen); // 字符串
|
||||
addRule(QRegExp("\\b(true|false|null)\\b"), Qt::darkBlue); // 字面量
|
||||
addRule(QRegExp("[{}\\[\\]]"), Qt::darkMagenta); // 括号
|
||||
addRule(QRegExp(":"), Qt::darkRed); // 冒号
|
||||
// 初始化错误格式
|
||||
m_errorFormat.setUnderlineColor(Qt::red);
|
||||
m_errorFormat.setUnderlineStyle(QTextCharFormat::WaveUnderline);
|
||||
m_errorFormat.setForeground(Qt::red); // 可选:文字颜色
|
||||
}
|
||||
void JsonHighlighter::addRule(const QRegExp &pattern, const QColor &color) {
|
||||
HighlightRule rule;
|
||||
rule.pattern = pattern;
|
||||
rule.format.setForeground(color);
|
||||
rule.format.setFontWeight(QFont::Bold);
|
||||
rules.append(rule);
|
||||
}
|
||||
void JsonHighlighter::highlightBlock(const QString &text) {
|
||||
for (const auto &rule : rules) {
|
||||
QRegExp expression(rule.pattern);
|
||||
int index = expression.indexIn(text);
|
||||
while (index >= 0) {
|
||||
int length = expression.matchedLength();
|
||||
setFormat(index, length, rule.format);
|
||||
index = expression.indexIn(text, index + length);
|
||||
}
|
||||
}
|
||||
// 步骤 2: 检测语法错误
|
||||
// detectJsonErrors(text);
|
||||
}
|
||||
// 检测错误并标记
|
||||
void JsonHighlighter::detectJsonErrors(const QString& text) {
|
||||
QStack<QChar> stack;
|
||||
int pos = 0;
|
||||
while (pos < text.length()) {
|
||||
QChar ch = text.at(pos);
|
||||
|
||||
if (ch == '\"') {
|
||||
if (stack.isEmpty() || stack.top() != '\"') {
|
||||
// 开始一个新的字符串
|
||||
stack.push('\"');
|
||||
} else {
|
||||
// 闭合字符串
|
||||
stack.pop();
|
||||
}
|
||||
pos++;
|
||||
} else if (ch == '\\') {
|
||||
// 跳过转义字符后的字符
|
||||
pos += 2;
|
||||
} else if (!stack.isEmpty() && stack.top() == '\"') {
|
||||
// 在字符串内部,忽略其他字符
|
||||
pos++;
|
||||
} else if (ch == '{' || ch == '[') {
|
||||
stack.push(ch);
|
||||
pos++;
|
||||
} else if (ch == '}' || ch == ']') {
|
||||
if (!stack.isEmpty()) {
|
||||
QChar top = stack.top();
|
||||
if ((top == '{' && ch == '}') || (top == '[' && ch == ']')) {
|
||||
stack.pop();
|
||||
} else {
|
||||
// 不匹配的括号,标记错误
|
||||
setFormat(pos, 1, m_errorFormat);
|
||||
}
|
||||
} else {
|
||||
// 没有对应的开始括号,标记错误
|
||||
setFormat(pos, 1, m_errorFormat);
|
||||
}
|
||||
pos++;
|
||||
} else {
|
||||
pos++;
|
||||
}
|
||||
}
|
||||
|
||||
// 检查未闭合的引号、括号和大括号
|
||||
while (!stack.isEmpty()) {
|
||||
QChar top = stack.top();
|
||||
if (top == '\"') {
|
||||
// 未闭合的字符串,标记错误
|
||||
int start = text.lastIndexOf('\"', pos - 1);
|
||||
if (start != -1) {
|
||||
setFormat(start, text.length() - start, m_errorFormat);
|
||||
}
|
||||
} else if (top == '{' || top == '[') {
|
||||
// 未闭合的括号或大括号,标记错误
|
||||
int start = text.lastIndexOf(top, pos - 1);
|
||||
if (start != -1) {
|
||||
setFormat(start, 1, m_errorFormat);
|
||||
}
|
||||
}
|
||||
stack.pop();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
QQuickTextDocument* JsonHighlighter::document() const {
|
||||
return m_document;
|
||||
}
|
||||
void JsonHighlighter::setDocument(QQuickTextDocument* doc) {
|
||||
if (m_document != doc) {
|
||||
m_document = doc;
|
||||
if (doc) {
|
||||
QSyntaxHighlighter::setDocument(doc->textDocument());
|
||||
} else {
|
||||
QSyntaxHighlighter::setDocument(nullptr);
|
||||
}
|
||||
emit documentChanged();
|
||||
}
|
||||
}
|
||||
|
||||
QString JsonProcessor::formatJson(const QString &rawJson) {
|
||||
QJsonDocument doc = QJsonDocument::fromJson(rawJson.toUtf8());
|
||||
if (doc.isNull()) return "";
|
||||
return QString::fromUtf8(doc.toJson(QJsonDocument::Indented));
|
||||
}
|
||||
|
||||
QVariantMap JsonProcessor::validateJson(const QString &json) {
|
||||
QJsonParseError error;
|
||||
QJsonDocument doc = QJsonDocument::fromJson(json.toUtf8(), &error);
|
||||
QVariantMap result;
|
||||
|
||||
if (error.error != QJsonParseError::NoError) {
|
||||
result["hasError"] = true;
|
||||
result["errorOffset"] = error.offset; // 错误位置
|
||||
result["errorMessage"] = error.errorString(); // 错误描述
|
||||
} else {
|
||||
result["hasError"] = false;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
#ifndef JSONHIGHLIGHTER_H
|
||||
#define JSONHIGHLIGHTER_H
|
||||
|
||||
|
||||
// #include <QTextEdit>
|
||||
#include <QSyntaxHighlighter>
|
||||
#include <QTextCharFormat>
|
||||
#include <QQuickTextDocument>
|
||||
#include <QObject>
|
||||
#include <QJsonDocument>
|
||||
#include <QStack>
|
||||
|
||||
class JsonHighlighter : public QSyntaxHighlighter {
|
||||
Q_OBJECT
|
||||
// 关键:定义 document 属性
|
||||
Q_PROPERTY(QQuickTextDocument* document READ document WRITE setDocument NOTIFY documentChanged)
|
||||
public:
|
||||
explicit JsonHighlighter(QTextDocument *parent = nullptr);
|
||||
QQuickTextDocument* document() const;
|
||||
void setDocument(QQuickTextDocument* doc);
|
||||
void detectJsonErrors(const QString& text);
|
||||
signals:
|
||||
void documentChanged();
|
||||
private:
|
||||
QQuickTextDocument* m_document;
|
||||
protected:
|
||||
void highlightBlock(const QString &text) override;
|
||||
private:
|
||||
struct HighlightRule {
|
||||
QRegExp pattern;
|
||||
QTextCharFormat format;
|
||||
};
|
||||
QVector<HighlightRule> rules;
|
||||
void addRule(const QRegExp &pattern, const QColor &color);
|
||||
QTextCharFormat m_errorFormat; // 错误格式(红色波浪线)
|
||||
};
|
||||
|
||||
class JsonProcessor : public QObject {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit JsonProcessor(QObject *parent = nullptr) : QObject(parent) {}
|
||||
Q_INVOKABLE QString formatJson(const QString &rawJson);
|
||||
Q_INVOKABLE QVariantMap validateJson(const QString &json);
|
||||
};
|
||||
|
||||
#endif // JSONHIGHLIGHTER_H
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
root = true
|
||||
|
||||
[*.{c, h}]
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
trim_trailing_whitespace = true
|
||||
max_line_length = 80
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
**To Reproduce**
|
||||
Steps to reproduce the behavior.
|
||||
|
||||
A recipe or example code that reproduces the problem? A stack trace from a crash
|
||||
|
||||
**Expected behavior**
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
**Version (please complete the following information):**
|
||||
- OS and version: [e.g. iOS, macOS, Windows, Linux (distro)]
|
||||
- libssh2 version: [e.g. 1.10.0]
|
||||
- crypto backend and version: [OpenSSL, mbedTLS, Libgcrypt, LibreSSL, WinCNG, OS400, wolfSSL, None]
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
# Security Policy
|
||||
|
||||
See [SECURITY.md](https://github.com/libssh2/libssh2/blob/master/docs/SECURITY.md) for full details.
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
If you have found or just suspect a security problem somewhere in libssh2,
|
||||
email `libssh2-security@haxx.se` about it.
|
||||
|
||||
**Do not submit suspected security issues in the public bug tracker!**
|
||||
|
||||
We treat security issues with confidentiality until controlled and disclosed
|
||||
responsibly.
|
||||