no message

This commit is contained in:
Lenheart 2025-05-29 14:04:05 +08:00
commit 6ece0b6a8f
564 changed files with 109391 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
build/

84
CMakeLists.txt Normal file
View File

@ -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()

1414
CMakeLists.txt.user Normal file

File diff suppressed because it is too large Load Diff

View File

@ -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()
}

99
Component/Card.qml Normal file
View File

@ -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
}
}
}
}

View File

@ -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
}
}

View File

@ -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
}
}
}
}

View File

@ -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)
}
}
}
}
}
}
}
}

180
Component/JsonEditor.qml Normal file
View File

@ -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
// ScrollViewcontentItemFlickable
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)
}
}
}

256
Component/PromotionCard.qml Normal file
View File

@ -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
}
}
}

186
Component/ServerConsole.qml Normal file
View File

@ -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")
}
}
}
// ANSIHTML
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, "&lt;").replace(/>/g, "&gt;").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("窗口关闭,已断开所有信号连接并清理资源。");
}
}

139
Component/UserCard.qml Normal file
View File

@ -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"
}
}
}
}
}
}

View File

@ -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
}
}

View File

@ -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 { }
}
}
}

View File

@ -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;
}
}
}

View File

@ -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();
}

View File

@ -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
}

View File

@ -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;
}
}
}

View File

@ -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
}
}
}

View File

@ -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();
}

View File

@ -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
}
}
}

View File

@ -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
}
}

View File

@ -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());
}
}
}
}
}
}

View File

@ -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
}

View File

@ -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
}
}

View File

@ -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 } }
}
}
}
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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();
}
}
}
}
}
}
}
}
}

View File

@ -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;
}
}
}
}
}

View File

@ -0,0 +1,9 @@
import QtQuick 2.15
import QtQuick.Templates 2.15 as T
import DelegateUI 1.0
Item {
id: control
}

View File

@ -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
}
}
}
}

View File

@ -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
}
}
}

View File

@ -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 } }
}

View File

@ -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
}
}

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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
}
}
}
}
}
}

View File

@ -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;
}
}
}
}
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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
}
}
}
}

View File

@ -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
}
}

View File

@ -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();
}
}
}
}

View File

@ -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
}
}

View File

@ -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
}

View File

@ -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
}
}
}
}
}

View File

@ -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
}
}

View File

@ -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 { }
}

View File

@ -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
}
}
}

View File

@ -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
}
}

BIN
DelegateUI/DelegateUI.dll Normal file

Binary file not shown.

2649
DelegateUI/plugins.qmltypes Normal file

File diff suppressed because it is too large Load Diff

47
DelegateUI/qmldir Normal file
View File

@ -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

204
FileTransfer.h Normal file
View File

@ -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

374
MyGlobals/GlobalVars.qml Normal file
View File

@ -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)
}
})
}
}

2
MyGlobals/qmldir Normal file
View File

@ -0,0 +1,2 @@
module MyGlobals
singleton GlobalVars 1.0 GlobalVars.qml

150
Page/Page_Home.qml Normal file
View File

@ -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
}
}
}

264
Page/Page_Login.qml Normal file
View File

@ -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();
}
}
}

133
Page/Tab_about.qml Normal file
View File

@ -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:"联系QQ947330670"
}
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"
}
}
}
}
}
}

198
Page/Tab_home.qml Normal file
View File

@ -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.
}
}
}
}

602
Page/Tab_personal.qml Normal file
View File

@ -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);
}
}
}
}
}
}
}

624
Page/Tab_server.qml Normal file
View File

@ -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
//dpsflag 0 1 2
property int dpsInstallationFlag : 2;
//javapflag 0 1 2
property int javapInstallationFlag : 2;
//javaflag 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
}
}
}
}

351
Page/Tab_shop.qml Normal file
View File

@ -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
// }
// }
// }
// }
}
}
}
}
}

351
Page/Tab_shopex.qml Normal file
View File

@ -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
}
}
}
}
}
}
}
}
}

88
Page/Tab_update.qml Normal file
View File

@ -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: '双端插件详情显示页面加入视频播放',
}
]
}
}
}
}
}

248
Page/Window_AddServer.qml Normal file
View File

@ -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
}
}
}

View File

@ -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
// }
}

View File

@ -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);
}
}
*/
}

View File

@ -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
}
}
}
}
}

View File

@ -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);
}
}
*/
}

View File

@ -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()
}
}
}
}
}
}
}

View File

@ -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: "双端插件"
}
]
}
}

1
folder-alias.json Normal file
View File

@ -0,0 +1 @@
{}

1
icon.rc Normal file
View File

@ -0,0 +1 @@
IDI_ICON1 ICON DISCARDABLE "image/YosinLogo_sb.ico"

BIN
image/VIP.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 547 B

BIN
image/YosinLogo_sb.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

BIN
image/a1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

BIN
image/a2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

BIN
image/a3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

BIN
image/a4.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 MiB

BIN
image/about.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 540 B

BIN
image/code.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

BIN
image/delete.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 529 B

BIN
image/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

BIN
image/logo2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

BIN
image/main.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 524 B

BIN
image/moren.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

BIN
image/myserver.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 424 B

BIN
image/online.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 682 B

BIN
image/qishimazhan.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 626 KiB

BIN
image/shoukuan.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

134
jsonhighlighter.cpp Normal file
View File

@ -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;
}

46
jsonhighlighter.h Normal file
View File

@ -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

9
libssh2/.editorconfig Normal file
View File

@ -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

View File

@ -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.

13
libssh2/.github/SECURITY.md vendored Normal file
View File

@ -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.

Some files were not shown because too many files have changed in this diff Show More