3 Commits 12cbfa4d21 ... 507e91268e

Autor SHA1 Mensaje Fecha
  xj 507e91268e 归还设备页面添加钥匙实时操作信息 hace 3 meses
  xj fbadbd17fd 新增可选时间范围控件 hace 3 meses
  xj cb9e7dafb9 1. 新增启动程序时重置蓝牙服务;2. 新增时分秒等控件 hace 3 meses

+ 2 - 0
Loto.pro

@@ -14,5 +14,7 @@ CONFIG(debug, debug|release) {
 
 DISTFILES += \
     src/qml/components/MComboBox.qml \
+    src/qml/components/MDateRange.qml \
+    src/qml/components/MDateTimePicker.qml \
     src/qml/components/MSelectDateTime.qml \
     src/qml/components/MTimePicker.qml

+ 24 - 24
Loto.pro.user

@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <!DOCTYPE QtCreatorProject>
-<!-- Written by QtCreator 4.14.2, 2026-02-10T09:36:57. -->
+<!-- Written by QtCreator 4.14.2, 2026-02-11T11:47:33. -->
 <qtcreator>
  <data>
   <variable>EnvironmentId</variable>
@@ -8,7 +8,7 @@
  </data>
  <data>
   <variable>ProjectExplorer.Project.ActiveTarget</variable>
-  <value type="int">1</value>
+  <value type="int">0</value>
  </data>
  <data>
   <variable>ProjectExplorer.Project.EditorSettings</variable>
@@ -86,16 +86,16 @@
   <variable>ProjectExplorer.Project.Target.0</variable>
   <valuemap type="QVariantMap">
    <value type="QString" key="DeviceType">GenericLinuxOsType</value>
-   <value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">DaJiGui</value>
-   <value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">DaJiGui</value>
-   <value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">{09bab3f2-c9c3-421a-aef1-57a08250e2d5}</value>
+   <value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">rk3568</value>
+   <value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">rk3568</value>
+   <value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">{4b7c7020-747d-4c4a-8e7b-c68e8d5127ef}</value>
    <value type="int" key="ProjectExplorer.Target.ActiveBuildConfiguration">0</value>
    <value type="int" key="ProjectExplorer.Target.ActiveDeployConfiguration">0</value>
    <value type="int" key="ProjectExplorer.Target.ActiveRunConfiguration">0</value>
    <valuemap type="QVariantMap" key="ProjectExplorer.Target.BuildConfiguration.0">
     <value type="int" key="EnableQmlDebugging">0</value>
-    <value type="QString" key="ProjectExplorer.BuildConfiguration.BuildDirectory">/home/kim/Desktop/ISCS_LOTO_Linux/build-Loto-DaJiGui-Debug</value>
-    <value type="QString" key="ProjectExplorer.BuildConfiguration.BuildDirectory.shadowDir">/home/kim/Desktop/ISCS_LOTO_Linux/build-Loto-DaJiGui-Debug</value>
+    <value type="QString" key="ProjectExplorer.BuildConfiguration.BuildDirectory">/home/kim/Desktop/ISCS_LOTO_Linux/build-Loto-rk3568-Debug</value>
+    <value type="QString" key="ProjectExplorer.BuildConfiguration.BuildDirectory.shadowDir">/home/kim/Desktop/ISCS_LOTO_Linux/build-Loto-rk3568-Debug</value>
     <valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.0">
      <valuemap type="QVariantMap" key="ProjectExplorer.BuildStepList.Step.0">
       <value type="bool" key="ProjectExplorer.BuildStep.Enabled">true</value>
@@ -134,8 +134,8 @@
     <value type="int" key="RunSystemFunction">0</value>
    </valuemap>
    <valuemap type="QVariantMap" key="ProjectExplorer.Target.BuildConfiguration.1">
-    <value type="QString" key="ProjectExplorer.BuildConfiguration.BuildDirectory">/home/kim/Desktop/ISCS_LOTO_Linux/build-Loto-DaJiGui-Release</value>
-    <value type="QString" key="ProjectExplorer.BuildConfiguration.BuildDirectory.shadowDir">/home/kim/Desktop/ISCS_LOTO_Linux/build-Loto-DaJiGui-Release</value>
+    <value type="QString" key="ProjectExplorer.BuildConfiguration.BuildDirectory">/home/kim/Desktop/ISCS_LOTO_Linux/build-Loto-rk3568-Release</value>
+    <value type="QString" key="ProjectExplorer.BuildConfiguration.BuildDirectory.shadowDir">/home/kim/Desktop/ISCS_LOTO_Linux/build-Loto-rk3568-Release</value>
     <valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.0">
      <valuemap type="QVariantMap" key="ProjectExplorer.BuildStepList.Step.0">
       <value type="bool" key="ProjectExplorer.BuildStep.Enabled">true</value>
@@ -176,8 +176,8 @@
    </valuemap>
    <valuemap type="QVariantMap" key="ProjectExplorer.Target.BuildConfiguration.2">
     <value type="int" key="EnableQmlDebugging">0</value>
-    <value type="QString" key="ProjectExplorer.BuildConfiguration.BuildDirectory">/home/kim/Desktop/ISCS_LOTO_Linux/build-Loto-DaJiGui-Profile</value>
-    <value type="QString" key="ProjectExplorer.BuildConfiguration.BuildDirectory.shadowDir">/home/kim/Desktop/ISCS_LOTO_Linux/build-Loto-DaJiGui-Profile</value>
+    <value type="QString" key="ProjectExplorer.BuildConfiguration.BuildDirectory">/home/kim/Desktop/ISCS_LOTO_Linux/build-Loto-rk3568-Profile</value>
+    <value type="QString" key="ProjectExplorer.BuildConfiguration.BuildDirectory.shadowDir">/home/kim/Desktop/ISCS_LOTO_Linux/build-Loto-rk3568-Profile</value>
     <valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.0">
      <valuemap type="QVariantMap" key="ProjectExplorer.BuildStepList.Step.0">
       <value type="bool" key="ProjectExplorer.BuildStep.Enabled">true</value>
@@ -327,10 +327,10 @@
     <valuelist type="QVariantList" key="PE.EnvironmentAspect.Changes">
      <value type="QString">LD_LIBRARY_PATH=$QTDIR/lib:$LD_LIBRARY_PATH</value>
      <value type="QString">QML2_IMPORT_PATH=$QTDIR/qml:$QML2_IMPORT_PATH</value>
-     <value type="QString">QTDIR=/home/teamhd/qt5</value>
+     <value type="QString">QTDIR=/opt/qt5</value>
      <value type="QString">QT_PLUGIN_PATH=$QTDIR/plugins:$QT_PLUGIN_PATH</value>
     </valuelist>
-    <value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">src (on DaJiGui)</value>
+    <value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">src (on rk3568)</value>
     <value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">RemoteLinuxRunConfiguration:/home/kim/Desktop/ISCS_LOTO_Linux/src/src.pro</value>
     <value type="QString" key="ProjectExplorer.RunConfiguration.BuildKey">/home/kim/Desktop/ISCS_LOTO_Linux/src/src.pro</value>
     <value type="int" key="RemoteLinux.EnvironmentAspect.Version">1</value>
@@ -347,16 +347,16 @@
   <variable>ProjectExplorer.Project.Target.1</variable>
   <valuemap type="QVariantMap">
    <value type="QString" key="DeviceType">GenericLinuxOsType</value>
-   <value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">rk3568</value>
-   <value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">rk3568</value>
-   <value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">{4b7c7020-747d-4c4a-8e7b-c68e8d5127ef}</value>
+   <value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">DaJiGui</value>
+   <value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">DaJiGui</value>
+   <value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">{09bab3f2-c9c3-421a-aef1-57a08250e2d5}</value>
    <value type="int" key="ProjectExplorer.Target.ActiveBuildConfiguration">0</value>
    <value type="int" key="ProjectExplorer.Target.ActiveDeployConfiguration">0</value>
    <value type="int" key="ProjectExplorer.Target.ActiveRunConfiguration">0</value>
    <valuemap type="QVariantMap" key="ProjectExplorer.Target.BuildConfiguration.0">
     <value type="int" key="EnableQmlDebugging">0</value>
-    <value type="QString" key="ProjectExplorer.BuildConfiguration.BuildDirectory">/home/kim/Desktop/ISCS_LOTO_Linux/build-Loto-rk3568-Debug</value>
-    <value type="QString" key="ProjectExplorer.BuildConfiguration.BuildDirectory.shadowDir">/home/kim/Desktop/ISCS_LOTO_Linux/build-Loto-rk3568-Debug</value>
+    <value type="QString" key="ProjectExplorer.BuildConfiguration.BuildDirectory">/home/kim/Desktop/ISCS_LOTO_Linux/build-Loto-DaJiGui-Debug</value>
+    <value type="QString" key="ProjectExplorer.BuildConfiguration.BuildDirectory.shadowDir">/home/kim/Desktop/ISCS_LOTO_Linux/build-Loto-DaJiGui-Debug</value>
     <valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.0">
      <valuemap type="QVariantMap" key="ProjectExplorer.BuildStepList.Step.0">
       <value type="bool" key="ProjectExplorer.BuildStep.Enabled">true</value>
@@ -395,8 +395,8 @@
     <value type="int" key="RunSystemFunction">0</value>
    </valuemap>
    <valuemap type="QVariantMap" key="ProjectExplorer.Target.BuildConfiguration.1">
-    <value type="QString" key="ProjectExplorer.BuildConfiguration.BuildDirectory">/home/kim/Desktop/ISCS_LOTO_Linux/build-Loto-rk3568-Release</value>
-    <value type="QString" key="ProjectExplorer.BuildConfiguration.BuildDirectory.shadowDir">/home/kim/Desktop/ISCS_LOTO_Linux/build-Loto-rk3568-Release</value>
+    <value type="QString" key="ProjectExplorer.BuildConfiguration.BuildDirectory">/home/kim/Desktop/ISCS_LOTO_Linux/build-Loto-DaJiGui-Release</value>
+    <value type="QString" key="ProjectExplorer.BuildConfiguration.BuildDirectory.shadowDir">/home/kim/Desktop/ISCS_LOTO_Linux/build-Loto-DaJiGui-Release</value>
     <valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.0">
      <valuemap type="QVariantMap" key="ProjectExplorer.BuildStepList.Step.0">
       <value type="bool" key="ProjectExplorer.BuildStep.Enabled">true</value>
@@ -437,8 +437,8 @@
    </valuemap>
    <valuemap type="QVariantMap" key="ProjectExplorer.Target.BuildConfiguration.2">
     <value type="int" key="EnableQmlDebugging">0</value>
-    <value type="QString" key="ProjectExplorer.BuildConfiguration.BuildDirectory">/home/kim/Desktop/ISCS_LOTO_Linux/build-Loto-rk3568-Profile</value>
-    <value type="QString" key="ProjectExplorer.BuildConfiguration.BuildDirectory.shadowDir">/home/kim/Desktop/ISCS_LOTO_Linux/build-Loto-rk3568-Profile</value>
+    <value type="QString" key="ProjectExplorer.BuildConfiguration.BuildDirectory">/home/kim/Desktop/ISCS_LOTO_Linux/build-Loto-DaJiGui-Profile</value>
+    <value type="QString" key="ProjectExplorer.BuildConfiguration.BuildDirectory.shadowDir">/home/kim/Desktop/ISCS_LOTO_Linux/build-Loto-DaJiGui-Profile</value>
     <valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.0">
      <valuemap type="QVariantMap" key="ProjectExplorer.BuildStepList.Step.0">
       <value type="bool" key="ProjectExplorer.BuildStep.Enabled">true</value>
@@ -588,10 +588,10 @@
     <valuelist type="QVariantList" key="PE.EnvironmentAspect.Changes">
      <value type="QString">LD_LIBRARY_PATH=$QTDIR/lib:$LD_LIBRARY_PATH</value>
      <value type="QString">QML2_IMPORT_PATH=$QTDIR/qml:$QML2_IMPORT_PATH</value>
-     <value type="QString">QTDIR=/opt/qt5</value>
+     <value type="QString">QTDIR=/home/teamhd/qt5</value>
      <value type="QString">QT_PLUGIN_PATH=$QTDIR/plugins:$QT_PLUGIN_PATH</value>
     </valuelist>
-    <value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">src (on rk3568)</value>
+    <value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">src (on DaJiGui)</value>
     <value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">RemoteLinuxRunConfiguration:/home/kim/Desktop/ISCS_LOTO_Linux/src/src.pro</value>
     <value type="QString" key="ProjectExplorer.RunConfiguration.BuildKey">/home/kim/Desktop/ISCS_LOTO_Linux/src/src.pro</value>
     <value type="int" key="RemoteLinux.EnvironmentAspect.Version">1</value>

+ 5 - 0
src/interactive/InteractiveCAN.cpp

@@ -89,6 +89,7 @@ InteractiveCAN::InteractiveCAN()
     connect(m_bleClient, &BLEClient::tokenReceived, this, [this](const BLEToken& token) {
         if (token.success && m_bleConnectTimer) {
             if (m_startGetWorkTicketResultFlag) {
+                ReturnKeyLockManager::instance()->updateStatusMessage("蓝牙已连接,开始获取作业票完成情况...");
                 m_bleClient->startGetWorkTicketResult();
             }
             m_okOpenKey = true;
@@ -1046,10 +1047,12 @@ void InteractiveCAN::connectBLEDevice(const QString &keyNfc)
         QByteArray jsonData = jsonDoc.toJson(QJsonDocument::Compact);
 
         emit signalGetRequestData(timestampSeconds, url, jsonData, GetInteractiveData()->m_token);
+        ReturnKeyLockManager::instance()->updateStatusMessage("获取钥匙MAC地址中...");
 
         if (m_bleClient) {
             qDebug() << "[connectBLEDevice]: " << m_bleClient->isConnected();
             if (m_bleClient->isConnected()) {
+                ReturnKeyLockManager::instance()->updateStatusMessage("蓝牙已连接,开始获取作业票完成情况...");
                 m_bleClient->startGetWorkTicketResult();
             }
             else {
@@ -1168,11 +1171,13 @@ void InteractiveCAN::slotOkSearchedBLE(bool success)
 {
     if (success) {
         if (m_bleClient->isConnected()) {
+            ReturnKeyLockManager::instance()->updateStatusMessage("蓝牙已连接");
             if (!m_okSendJobTicket) {
                 createColockJobTicket();
             }
         }
         else {
+            ReturnKeyLockManager::instance()->updateStatusMessage("蓝牙连接中...");
             m_bleClient->apiConnectDevice(m_currentKeyMAC);
         }
     }

+ 8 - 0
src/interactive/ReturnKeyLockManager.cpp

@@ -78,6 +78,14 @@ void ReturnKeyLockManager::setIsVisible(bool visible)
     }
 }
 
+void ReturnKeyLockManager::updateStatusMessage(const QString &msg)
+{
+    if (m_isVisible) {
+        m_currentReadingStatus = msg;
+        emit currentReadingStatusChanged();
+    }
+}
+
 void ReturnKeyLockManager::onNfcDetected(const QString& nfcId, const QString& deviceType, int slotIndex)
 {
     QMutexLocker locker(&m_mutex);

+ 1 - 0
src/interactive/ReturnKeyLockManager.h

@@ -91,6 +91,7 @@ public:
     int confirmCountdown() const { return m_confirmCountdown; }
     QString currentReadingStatus() const { return m_currentReadingStatus; }
 
+    void updateStatusMessage(const QString& msg);
 public slots:
     /**
      * @brief 接收CAN发送的NFC卡号(由CANClient调用)

+ 2 - 0
src/qml.qrc

@@ -41,5 +41,7 @@
         <file>qml/components/MComboBox.qml</file>
         <file>qml/components/MSelectDateTime.qml</file>
         <file>qml/components/MTimePicker.qml</file>
+        <file>qml/components/MDateTimePicker.qml</file>
+        <file>qml/components/MDateRange.qml</file>
     </qresource>
 </RCC>

+ 25 - 14
src/qml/components/FormCard.qml

@@ -243,24 +243,21 @@ Rectangle {
                         formControlColumn.item.signalToggled.connect(control.slotCollectSwitchChecked);
                         control.signalSubmit.connect(formControlColumn.item.slotShowRequiredMsg);
                     } else if (type === "daterange") {
-                        formControlColumn.source = "MInput.qml";
-                        formControlColumn.item.controlId = id;
-                        formControlColumn.item.modelIndex = groupIndex;
-                        formControlColumn.item.placeholderText = placeholder;
-                        formControlColumn.item.required = required;
-                        formControlColumn.item.requiredMsg = requiredMessage !== "" ? requiredMessage : label.trim()+"不能为空";
-                        formControlColumn.item.height = 48;
-                        formControlColumn.item.readOnly = control.readOnly;
-                        formControlColumn.item.enabled = !control.readOnly;
+                        formControlRow.source = "MDateRange.qml";
+                        formControlRow.item.controlId = id;
+                        formControlRow.item.modelIndex = groupIndex;
+                        formControlRow.item.placeholderText = placeholder;
+                        formControlRow.item.required = required;
+                        formControlRow.item.requiredMsg = requiredMessage !== "" ? requiredMessage : label.trim()+"不能为空";
+                        formControlRow.item.height = 48;
+                        formControlRow.item.enabled = !control.readOnly;
                         // 回显值
                         if (value !== undefined && value !== "") {
-                            formControlColumn.item.text = value;
+                            formControlRow.item.text = value;
                         }
 
-                        formControlColumn.item.signalTextChanged.connect(control.slotCollectInputInfo);
-                        // 连接虚拟键盘
-                        formControlColumn.item.signalInputClicked.connect(control.showVirtualKeyboard);
-                        control.signalSubmit.connect(formControlColumn.item.slotShowRequiredMsg);
+                        formControlRow.item.signalTextChanged.connect(control.slotCollectInputInfo);
+                        control.signalSubmit.connect(formControlRow.item.slotShowRequiredMsg);
                     } else if (type === "date") {
                         formControlRow.source = "MDatePicker.qml";
                         formControlRow.item.controlId = id;
@@ -278,7 +275,21 @@ Rectangle {
                         formControlRow.item.signalTextChanged.connect(control.slotCollectInputInfo);
                         control.signalSubmit.connect(formControlRow.item.slotShowRequiredMsg);
                     } else if (type === "datetime") {
+                        formControlRow.source = "MDateTimePicker.qml";
+                        formControlRow.item.controlId = id;
+                        formControlRow.item.modelIndex = groupIndex;
+                        formControlRow.item.placeholderText = placeholder;
+                        formControlRow.item.required = required;
+                        formControlRow.item.requiredMsg = requiredMessage !== "" ? requiredMessage : label.trim()+"不能为空";
+                        formControlRow.item.height = 48;
+                        formControlRow.item.enabled = !control.readOnly;
+                        // 回显值
+                        if (value !== undefined && value !== "") {
+                            formControlRow.item.text = value;
+                        }
 
+                        formControlRow.item.signalTextChanged.connect(control.slotCollectInputInfo);
+                        control.signalSubmit.connect(formControlRow.item.slotShowRequiredMsg);
                     } else if (type === "timepicker") {
                         formControlRow.source = "MTimePicker.qml";
                         formControlRow.item.controlId = id;

+ 100 - 47
src/qml/components/MCalendar.qml

@@ -4,7 +4,7 @@ import QtQuick.Layouts 1.12
 Item {
     id: control
 
-    // ==== 外部接口 ====
+    // ==== 外部接口(修复类型错误 + 保留范围限制逻辑)====
     property bool backgroundVisible: true           // 是否显示背景
     property real radius: 20                        // 背景圆角
     property int padding: 15                        // 内边距
@@ -13,6 +13,11 @@ Item {
     property date selectedDate: new Date()          // 当前选中日期
     signal dateClicked(date clickedDate)            // 日期点击信号
 
+    // 【核心修复】用极早/极晚日期替代undefined,表示无限制(兼容QDateTime类型)
+    property date minDate: new Date(1970, 0, 1)     // 最小可选日期(默认1970-01-01,几乎无下限)
+    property date maxDate: new Date(2100, 11, 31)   // 最大可选日期(默认2100-12-31,几乎无上限)
+    property color disabledColor: "#cccccc"         // 范围外日期置灰颜色
+
     property int currentYear: selectedDate.getFullYear()  // 当前显示年份
     property int currentMonth: selectedDate.getMonth()    // 当前显示月份
     property var dayModel: []                               // 日历模型数据
@@ -20,7 +25,37 @@ Item {
     implicitWidth: contentLayout.implicitWidth + padding * 2
     implicitHeight: contentLayout.implicitHeight + padding * 2
 
-    // ==== 函数:生成日历模型 ====
+    // ==== 工具函数:判断日期是否在可选范围(兼容默认无限制)====
+    function isDateInRange(checkDate) {
+        if (!checkDate) return false;
+
+        // 标准化日期(忽略时分秒,仅比较年月日)
+        const normalizeDate = (d) => {
+            let nd = new Date(d);
+            nd.setHours(0, 0, 0, 0);
+            return nd;
+        };
+
+        const target = normalizeDate(checkDate);
+        const min = normalizeDate(control.minDate);
+        const max = normalizeDate(control.maxDate);
+
+        // 范围判断逻辑:默认值(1970/2100)视为无限制
+        const isMinUnlimited = min.getFullYear() === 1970 && min.getMonth() === 0 && min.getDate() === 1;
+        const isMaxUnlimited = max.getFullYear() === 2100 && max.getMonth() === 11 && max.getDate() === 31;
+
+        if (!isMinUnlimited && !isMaxUnlimited) {
+            return target >= min && target <= max;
+        } else if (!isMinUnlimited) {
+            return target >= min;
+        } else if (!isMaxUnlimited) {
+            return target <= max;
+        } else {
+            return true; // 都是默认值 = 无范围限制(全选)
+        }
+    }
+
+    // ==== 函数:生成日历模型(无修改,复用范围判断)====
     function generateCalendarModel() {
         var tempModel = [],
             date = new Date(currentYear, currentMonth, 1),
@@ -30,44 +65,54 @@ Item {
             today = new Date(),
             remaining,
             isToday,
-            i; // 循环变量
+            isInRange,
+            i;
 
         // 1. 上个月尾部日期
         for (i = 0; i < firstDayOfWeek; i++) {
+            const dateValue = new Date(currentYear, currentMonth - 1, daysInPrevMonth - firstDayOfWeek + 1 + i);
+            isInRange = isDateInRange(dateValue);
             tempModel.push({
                 day: daysInPrevMonth - firstDayOfWeek + 1 + i,
                 isCurrentMonth: false,
                 isToday: false,
-                dateValue: new Date(currentYear, currentMonth - 1, daysInPrevMonth - firstDayOfWeek + 1 + i)
+                isInRange: isInRange,
+                dateValue: dateValue
             });
         }
 
         // 2. 当前月日期
         for (i = 1; i <= daysInMonth; i++) {
             isToday = (currentYear === today.getFullYear() && currentMonth === today.getMonth() && i === today.getDate());
+            const dateValue = new Date(currentYear, currentMonth, i);
+            isInRange = isDateInRange(dateValue);
             tempModel.push({
                 day: i,
                 isCurrentMonth: true,
                 isToday: isToday,
-                dateValue: new Date(currentYear, currentMonth, i)
+                isInRange: isInRange,
+                dateValue: dateValue
             });
         }
 
         // 3. 下个月开头日期
         remaining = 42 - tempModel.length;
         for (i = 1; i <= remaining; i++) {
+            const dateValue = new Date(currentYear, currentMonth + 1, i);
+            isInRange = isDateInRange(dateValue);
             tempModel.push({
                 day: i,
                 isCurrentMonth: false,
                 isToday: false,
-                dateValue: new Date(currentYear, currentMonth + 1, i)
+                isInRange: isInRange,
+                dateValue: dateValue
             });
         }
 
         dayModel = tempModel;
     }
 
-    // ==== 函数:切换月份 ====
+    // ==== 函数:切换月份(无修改)====
     function goToPrevMonth() {
         if (currentMonth > 0) currentMonth--;
         else { currentMonth = 11; currentYear--; }
@@ -78,29 +123,21 @@ Item {
         else { currentMonth = 0; currentYear++; }
     }
 
-    // ==== 生命周期/属性监听 ====
+    // ==== 生命周期/属性监听(无修改)====
     Component.onCompleted: generateCalendarModel()
     onCurrentMonthChanged: generateCalendarModel()
     onCurrentYearChanged: generateCalendarModel()
+    onMinDateChanged: generateCalendarModel()
+    onMaxDateChanged: generateCalendarModel()
+    onSelectedDateChanged: generateCalendarModel()
 
     // ==== 视觉效果 (背景与阴影) ====
-//    MultiEffect {
-//        source: background
-//        anchors.fill: background
-//        visible: control.shadowEnabled && control.backgroundVisible
-//        shadowEnabled: true
-//        shadowColor: theme.shadowColor
-//        shadowBlur: theme.shadowBlur
-//        shadowVerticalOffset: theme.shadowYOffset
-//        shadowHorizontalOffset: theme.shadowXOffset
-//    }
-
     Rectangle {
         id: background
         visible: control.backgroundVisible
         anchors.fill: parent
         radius: control.radius
-        color: theme.secondaryColor
+        color: theme.secondaryColor || "#f0f0f0"
     }
 
     // ==== 布局主体 ====
@@ -117,10 +154,14 @@ Item {
             Text {
                 text: "<"
                 font.pixelSize: 20
-                color: theme.focusColor
+                color: theme.focusColor || "#5a8fc4"
                 Layout.preferredWidth: 30
                 horizontalAlignment: Text.AlignHCenter
-                MouseArea { anchors.fill: parent; cursorShape: Qt.PointingHandCursor; onClicked: goToPrevMonth() }
+                MouseArea {
+                    anchors.fill: parent;
+                    cursorShape: Qt.PointingHandCursor;
+                    onClicked: goToPrevMonth()
+                }
             }
 
             // 当前年月显示
@@ -128,7 +169,7 @@ Item {
                 text: currentYear + "年 " + (currentMonth + 1) + "月"
                 font.pixelSize: 18
                 font.bold: true
-                color: theme.textColor
+                color: theme.textColor || "#000000"
                 Layout.fillWidth: true
                 horizontalAlignment: Text.AlignHCenter
             }
@@ -137,10 +178,14 @@ Item {
             Text {
                 text: ">"
                 font.pixelSize: 20
-                color: theme.focusColor
+                color: theme.focusColor || "#5a8fc4"
                 Layout.preferredWidth: 30
                 horizontalAlignment: Text.AlignHCenter
-                MouseArea { anchors.fill: parent; cursorShape: Qt.PointingHandCursor; onClicked: goToNextMonth() }
+                MouseArea {
+                    anchors.fill: parent;
+                    cursorShape: Qt.PointingHandCursor;
+                    onClicked: goToNextMonth()
+                }
             }
         }
 
@@ -154,7 +199,7 @@ Item {
                 model: ["日", "一", "二", "三", "四", "五", "六"]
                 delegate: Text {
                     text: modelData
-                    color: theme.borderColor
+                    color: theme.borderColor || "#999999"
                     font.bold: true
                     horizontalAlignment: Text.AlignHCenter
                     Layout.alignment: Qt.AlignCenter
@@ -163,7 +208,7 @@ Item {
             }
         }
 
-        // ==== 日期格子 ====
+        // ==== 日期格子(范围判断+样式/交互)====
         GridLayout {
             id: dayGrid
             columns: 7
@@ -180,41 +225,49 @@ Item {
                     Layout.alignment: Qt.AlignCenter
                     radius: width / 2
 
-                    // 选中背景
+                    // 选中背景(仅范围內日期生效)
                     color: {
-                        var d1 = modelData.dateValue
-                        var d2 = selectedDate
-                        if (d1.getFullYear() === d2.getFullYear() && d1.getMonth() === d2.getMonth() && d1.getDate() === d2.getDate())
-                            return theme.focusColor
-                        return "transparent"
+                        const isSelected = modelData.dateValue.getFullYear() === selectedDate.getFullYear() &&
+                                           modelData.dateValue.getMonth() === selectedDate.getMonth() &&
+                                           modelData.dateValue.getDate() === selectedDate.getDate();
+                        return (isSelected && modelData.isInRange) ? (theme.focusColor || "#5a8fc4") : "transparent";
                     }
 
-                    // 日期文字
+                    // 日期文字(范围外置灰)
                     Text {
                         anchors.centerIn: parent
                         text: modelData.day
-                        font.bold: modelData.isToday && modelData.isCurrentMonth
+                        font.bold: modelData.isToday && modelData.isCurrentMonth && modelData.isInRange
                         color: {
-                            var d1 = modelData.dateValue
-                            var d2 = selectedDate
-                            if (d1.getFullYear() === d2.getFullYear() && d1.getMonth() === d2.getMonth() && d1.getDate() === d2.getDate())
-                                return theme.isDark ? "#000000" : "#FFFFFF"
-                            return modelData.isCurrentMonth ? theme.textColor : theme.borderColor
+                            // 1. 范围外:直接置灰
+                            if (!modelData.isInRange) return control.disabledColor;
+
+                            // 2. 范围內:正常样式逻辑
+                            const isSelected = modelData.dateValue.getFullYear() === selectedDate.getFullYear() &&
+                                               modelData.dateValue.getMonth() === selectedDate.getMonth() &&
+                                               modelData.dateValue.getDate() === selectedDate.getDate();
+                            if (isSelected) {
+                                return theme.isDark ? "#000000" : "#FFFFFF";
+                            }
+                            return modelData.isCurrentMonth ? (theme.textColor || "#000000") : (theme.borderColor || "#999999");
                         }
                     }
 
-                    // 点击交互
+                    // 点击交互(范围外禁用点击)
                     MouseArea {
                         anchors.fill: parent
-                        cursorShape: Qt.PointingHandCursor
+                        cursorShape: modelData.isInRange ? Qt.PointingHandCursor : Qt.ArrowCursor
+                        enabled: modelData.isInRange
                         onClicked: {
                             if (modelData.isCurrentMonth) {
-                                control.selectedDate = modelData.dateValue
-                                control.dateClicked(modelData.dateValue)
+                                control.selectedDate = modelData.dateValue;
+                                control.dateClicked(modelData.dateValue);
                             } else {
-                                control.currentYear = modelData.dateValue.getFullYear()
-                                control.currentMonth = modelData.dateValue.getMonth()
-                                control.selectedDate = modelData.dateValue
+                                if (modelData.isInRange) {
+                                    control.currentYear = modelData.dateValue.getFullYear();
+                                    control.currentMonth = modelData.dateValue.getMonth();
+                                    control.selectedDate = modelData.dateValue;
+                                }
                             }
                         }
                     }

+ 174 - 0
src/qml/components/MDateRange.qml

@@ -0,0 +1,174 @@
+import QtQuick 2.12
+import QtQuick.Controls 2.12
+import QtQuick.Layouts 1.12
+
+Item {
+    id: control
+    width: 240
+    height: 40
+
+    // === 接口属性 & 信号 ===
+    property string controlId
+    property int modelIndex         // 特殊标记,可以省略循环WorkNodeFormModel的时间
+    property alias text: textField.text
+    property alias placeholderText: textField.placeholderText
+    signal accepted()  // 输入回车触发
+    signal signalTextChanged(int index, string id, string data);
+
+    property bool required: false
+    property string requiredMsg
+    property bool showRequiredMsg: false
+
+    // === 样式属性 ===
+    property int fontSize: 16
+    property real radius: 10
+    property string showDatePopupSymbo: "\uf073"
+    property color textColor: "white"
+
+    // === 状态属性 ===
+    property bool enabled: true
+    property bool backgroundVisible: true  // 背景显示控制
+
+    // === 背景与阴影 ===
+//    MultiEffect {
+//        source: background
+//        anchors.fill: background
+//        shadowEnabled: true
+//        shadowColor: theme.shadowColor
+//        shadowBlur: theme.shadowBlur
+//        shadowHorizontalOffset: theme.shadowXOffset
+//        shadowVerticalOffset: theme.shadowYOffset
+//    }
+
+    Rectangle {
+        id: background
+        anchors.fill: parent
+        radius: control.radius
+        // 无背景时:选中用主题高亮色,未选中用次级色;有背景时沿用主题边框色
+        // border.color: control.backgroundVisible
+        //                ? theme.getBorderColor(textField.activeFocus)
+        //                : (textField.activeFocus ? theme.focusColor : "#40A9FF")
+        border.color: "#40A9FF"
+        border.width: 1
+        // color: control.backgroundVisible ? theme.secondaryColor : "transparent"
+        color: "transparent"
+        opacity: control.enabled ? 1.0 : 0.6
+    }
+
+    // === 内容布局 ===
+    RowLayout {
+        id: layout
+        anchors.fill: parent
+        // anchors.margins: 8
+        spacing: 6
+
+        // === 输入框主体 ===
+        TextField {
+            id: textField
+            Layout.fillWidth: true
+            Layout.fillHeight: true
+            Layout.alignment: Qt.AlignVCenter
+
+            font.pixelSize: control.fontSize
+            color: control.textColor
+            placeholderTextColor: control.textColor
+            enabled: control.enabled
+            verticalAlignment: Text.AlignVCenter
+            echoMode: TextInput.Normal
+            background: null
+            onAccepted: control.accepted()
+
+            onFocusChanged: {
+                if (!focus) {
+                    forceActiveFocus()
+                }
+            }
+
+            onTextChanged: signalTextChanged(control.modelIndex, control.controlId, textField.text);
+        }
+
+        Text {
+            id: popupSymbo
+            Layout.rightMargin: 8
+            text: showDatePopupSymbo
+            color: "#666"
+            font.pixelSize: 16
+            verticalAlignment: Text.AlignVCenter
+            horizontalAlignment: Text.AlignHCenter
+
+            MouseArea {
+                anchors.fill: parent
+                enabled: control.enabled
+                cursorShape: Qt.PointingHandCursor
+                onClicked: {
+                    // TODO: 显示Calendar控件
+                    // console.log("显示Calendar控件");
+                    calendarPopup.open();
+                }
+            }
+        }
+    }
+    Popup {
+        id: calendarPopup
+        x: layout.width - width
+        y: -__calendarStart.height/2 - textField.height - 10
+        width: 200
+        height: 100
+        modal: true  // 设置是否为模态窗口
+        // focus: visible  // 获取焦点
+        closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside  // 设置关闭策略
+
+        Row {
+            anchors.centerIn: parent
+
+            MCalendar {
+                id: __calendarStart
+            }
+
+            MCalendar {
+                id: __calendarStop
+            }
+        }
+
+        Connections {
+            target: __calendarStart
+            function onDateClicked(clickedDate) {
+                textField.text = Qt.formatDate(clickedDate, "yyyy-MM-dd") + " 至 " + Qt.formatDate(__calendarStop.selectedDate, "yyyy-MM-dd");
+                __calendarStop.minDate = clickedDate;
+            }
+        }
+
+        Connections {
+            target: __calendarStop
+            function onDateClicked(clickedDate) {
+                textField.text = Qt.formatDate(__calendarStart.selectedDate, "yyyy-MM-dd") + " 至 " + Qt.formatDate(clickedDate, "yyyy-MM-dd");
+                __calendarStart.maxDate = clickedDate;
+            }
+        }
+    }
+
+    Text {
+        id: textRequiredMsg
+        anchors.top: layout.bottom
+        anchors.topMargin: 15
+        anchors.left: layout.left
+        visible: control.required && control.showRequiredMsg
+        text: qsTr(requiredMsg)
+        color: "red"
+        font.pixelSize: control.fontSize
+        // 普通文字不使用图标字体
+        font.bold: true
+        verticalAlignment: Text.AlignVCenter
+    }
+
+    function slotShowRequiredMsg(submit) {
+        if (submit && textField.text === "") {
+            control.showRequiredMsg = true;
+        }
+    }
+
+    function padZero(num) {
+        const n = Number(num) || 0;
+        return n < 10 ? "0" + n : n.toString();
+    }
+}

+ 174 - 0
src/qml/components/MDateTimePicker.qml

@@ -0,0 +1,174 @@
+import QtQuick 2.12
+import QtQuick.Controls 2.12
+import QtQuick.Layouts 1.12
+
+Item {
+    id: control
+    width: 240
+    height: 40
+
+    // === 接口属性 & 信号 ===
+    property string controlId
+    property int modelIndex         // 特殊标记,可以省略循环WorkNodeFormModel的时间
+    property alias text: textField.text
+    property alias placeholderText: textField.placeholderText
+    signal accepted()  // 输入回车触发
+    signal signalTextChanged(int index, string id, string data);
+
+    property bool required: false
+    property string requiredMsg
+    property bool showRequiredMsg: false
+
+    // === 样式属性 ===
+    property int fontSize: 16
+    property real radius: 10
+    property string showDatePopupSymbo: "\uf073"
+    property color textColor: "white"
+
+    // === 状态属性 ===
+    property bool enabled: true
+    property bool backgroundVisible: true  // 背景显示控制
+
+    // === 背景与阴影 ===
+//    MultiEffect {
+//        source: background
+//        anchors.fill: background
+//        shadowEnabled: true
+//        shadowColor: theme.shadowColor
+//        shadowBlur: theme.shadowBlur
+//        shadowHorizontalOffset: theme.shadowXOffset
+//        shadowVerticalOffset: theme.shadowYOffset
+//    }
+
+    Rectangle {
+        id: background
+        anchors.fill: parent
+        radius: control.radius
+        // 无背景时:选中用主题高亮色,未选中用次级色;有背景时沿用主题边框色
+        // border.color: control.backgroundVisible
+        //                ? theme.getBorderColor(textField.activeFocus)
+        //                : (textField.activeFocus ? theme.focusColor : "#40A9FF")
+        border.color: "#40A9FF"
+        border.width: 1
+        // color: control.backgroundVisible ? theme.secondaryColor : "transparent"
+        color: "transparent"
+        opacity: control.enabled ? 1.0 : 0.6
+    }
+
+    // === 内容布局 ===
+    RowLayout {
+        id: layout
+        anchors.fill: parent
+        // anchors.margins: 8
+        spacing: 6
+
+        // === 输入框主体 ===
+        TextField {
+            id: textField
+            Layout.fillWidth: true
+            Layout.fillHeight: true
+            Layout.alignment: Qt.AlignVCenter
+
+            font.pixelSize: control.fontSize
+            color: control.textColor
+            placeholderTextColor: control.textColor
+            enabled: control.enabled
+            verticalAlignment: Text.AlignVCenter
+            echoMode: TextInput.Normal
+            background: null
+            onAccepted: control.accepted()
+
+            onFocusChanged: {
+                if (!focus) {
+                    forceActiveFocus()
+                }
+            }
+
+            onTextChanged: signalTextChanged(control.modelIndex, control.controlId, textField.text);
+        }
+
+        Text {
+            id: popupSymbo
+            Layout.rightMargin: 8
+            text: showDatePopupSymbo
+            color: "#666"
+            font.pixelSize: 16
+            verticalAlignment: Text.AlignVCenter
+            horizontalAlignment: Text.AlignHCenter
+
+            MouseArea {
+                anchors.fill: parent
+                enabled: control.enabled
+                cursorShape: Qt.PointingHandCursor
+                onClicked: {
+                    // TODO: 显示Calendar控件
+                    // console.log("显示Calendar控件");
+                    calendarPopup.open();
+                }
+            }
+        }
+    }
+    Popup {
+        id: calendarPopup
+        x: layout.width - width
+        y: -__calendar.height/2 - textField.height - 10
+        width: 200
+        height: 100
+        modal: true  // 设置是否为模态窗口
+        // focus: visible  // 获取焦点
+        closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside  // 设置关闭策略
+
+        Row {
+            anchors.centerIn: parent
+
+            MCalendar {
+                id: __calendar
+            }
+            MSelectDateTime {
+                id: __selectTime
+            }
+        }
+
+        Connections {
+            target: __calendar
+            function onDateClicked(clickedDate) {
+                var hour = padZero(__selectTime.selectedHour);
+                var minute = padZero(__selectTime.selectedMinute);
+                var second = padZero(__selectTime.selectedSecond);
+                textField.text = Qt.formatDate(clickedDate, "yyyy-MM-dd") + " " + hour + ":" + minute + ":" + second;
+            }
+        }
+
+        Connections {
+            target: __selectTime
+            function onTimeChanged(hour, minute, second) {
+                textField.text = Qt.formatDate(__calendar.selectedDate, "yyyy-MM-dd") + " " + hour + ":" + minute + ":" + second;
+            }
+        }
+    }
+
+    Text {
+        id: textRequiredMsg
+        anchors.top: layout.bottom
+        anchors.topMargin: 15
+        anchors.left: layout.left
+        visible: control.required && control.showRequiredMsg
+        text: qsTr(requiredMsg)
+        color: "red"
+        font.pixelSize: control.fontSize
+        // 普通文字不使用图标字体
+        font.bold: true
+        verticalAlignment: Text.AlignVCenter
+    }
+
+    function slotShowRequiredMsg(submit) {
+        if (submit && textField.text === "") {
+            control.showRequiredMsg = true;
+        }
+    }
+
+    function padZero(num) {
+        const n = Number(num) || 0;
+        return n < 10 ? "0" + n : n.toString();
+    }
+}

+ 88 - 105
src/qml/components/MSelectDateTime.qml

@@ -4,32 +4,26 @@ import QtQuick.Layouts 1.12
 Item {
     id: control
 
-    // ==== 外部接口====
+    // ==== 外部接口:拆分独立属性(核心修复:触发属性更新)====
     property bool backgroundVisible: true
     property real radius: 20
     property int padding: 15
     property bool shadowEnabled: true
 
-    // 选中的时分秒
-    property var selectedTime: {
-        const now = new Date();
-        return {
-            hour: now.getHours() || 0,
-            minute: now.getMinutes() || 0,
-            second: now.getSeconds() || 0
-        };
-    }
-    signal timeChanged(var newTime)  // 时间变更信号
+    property int selectedHour: new Date().getHours() || 0
+    property int selectedMinute: new Date().getMinutes() || 0
+    property int selectedSecond: new Date().getSeconds() || 0
+    signal timeChanged(string hour, string minute, string second)  // 调整信号参数
 
     // 滑动滚轮配置
     property int wheelItemHeight: 40  // 每个数值项高度
-    property int wheelVisibleCount: 5 // 滚轮可见项数(奇数,确保中间项居中
+    property int wheelVisibleCount: 5 // 滚轮可见项数(奇数)
     property real wheelWidth: 80      // 单个滚轮宽度
 
     property real wheelVisibleHeight: wheelItemHeight * wheelVisibleCount
     property real centerOffset: wheelVisibleHeight / 2
     implicitWidth: wheelWidth * 3 + padding * 2 + 20
-    implicitHeight: wheelVisibleHeight + padding * 2 + 20 // 预留标签高度
+    implicitHeight: wheelVisibleHeight + padding * 2 + 20
 
     function padZero(num) {
         const n = Number(num) || 0;
@@ -38,33 +32,44 @@ Item {
 
     function calculateContentY(selectedIndex, totalCount) {
         let contentY = (selectedIndex * wheelItemHeight) + (wheelItemHeight/2) - control.centerOffset;
-        // 最大值居中的临界值
-        const maxContentY = ((totalCount - 1) * wheelItemHeight) + (wheelItemHeight/2) - control.centerOffset;
-        // 边界校验:确保contentY在合法范围
+        const maxContentY = ((totalCount - 1) * wheelItemHeight) - (control.centerOffset - wheelItemHeight/2);
         contentY = Math.max(0, Math.min(contentY, maxContentY));
         return contentY;
     }
 
-    function snapToItem(flickable, totalCount) {
-        const currentIndex = Math.round((flickable.contentY + control.centerOffset) / wheelItemHeight);
-        let targetIndex = (currentIndex % totalCount + totalCount) % totalCount;
-        // 计算精准的居中位置
-        const targetContentY = calculateContentY(targetIndex, totalCount);
+    // 滑动/拖拽结束后
+    function snapToItem(flickable, totalCount, targetProp) {
+        const rawIndex = (flickable.contentY + control.centerOffset - wheelItemHeight/2) / wheelItemHeight;
+        let targetIndex = Math.round(rawIndex);
+        targetIndex = Math.max(0, Math.min(targetIndex, totalCount - 1));
 
-        flickable.contentY = Qt.binding(function() {
-            return targetContentY;
-        });
-        flickable.flickDeceleration = 2000; // 优化滑动手感
-
-        if (flickable === hourWheel) {
-            control.selectedTime.hour = targetIndex;
-        } else if (flickable === minuteWheel) {
-            control.selectedTime.minute = targetIndex;
-        } else if (flickable === secondWheel) {
-            control.selectedTime.second = targetIndex;
+        const targetContentY = calculateContentY(targetIndex, totalCount);
+        flickable.contentY = targetContentY;
+        flickable.flickDeceleration = 2000;
+
+        if (targetProp === "hour") {
+            control.selectedHour = targetIndex;
+        } else if (targetProp === "minute") {
+            control.selectedMinute = targetIndex;
+        } else if (targetProp === "second") {
+            control.selectedSecond = targetIndex;
         }
         // 触发时间变更信号
-        control.timeChanged(control.selectedTime);
+        control.timeChanged(padZero(control.selectedHour), padZero(control.selectedMinute), padZero(control.selectedSecond));
+    }
+
+    function updateRealTimeValue(flickable, totalCount, targetProp) {
+        const rawIndex = (flickable.contentY + control.centerOffset - wheelItemHeight/2) / wheelItemHeight;
+        let targetIndex = Math.round(rawIndex);
+        targetIndex = Math.max(0, Math.min(targetIndex, totalCount - 1));
+
+        if (targetProp === "hour") {
+            control.selectedHour = targetIndex;
+        } else if (targetProp === "minute") {
+            control.selectedMinute = targetIndex;
+        } else if (targetProp === "second") {
+            control.selectedSecond = targetIndex;
+        }
     }
 
     // ==== 视觉背景====
@@ -77,20 +82,7 @@ Item {
         layer.enabled: control.shadowEnabled && control.backgroundVisible
     }
 
-    // ==== 中间选中线 ====
-    Rectangle {
-        id: centerLine
-        anchors.verticalCenter: parent.verticalCenter
-        x: padding + 10
-        width: wheelWidth * 3 + 20
-        height: 2
-        color: "transparent"
-        border.width: 1
-        border.color: "#5a8fc4"
-        z: 10
-    }
-
-    // ==== 主体布局:时/分/秒滚轮并排 ====
+    // ==== 主体布局:时/分/秒滚轮 ====
     RowLayout {
         id: wheelsContainer
         anchors.centerIn: parent
@@ -98,7 +90,7 @@ Item {
         spacing: 10
         width: wheelWidth * 3 + 20
 
-        // ==== 小时滚轮(含顶部标签)====
+        // ==== 小时滚轮 ====
         ColumnLayout {
             spacing: 2
 
@@ -107,29 +99,25 @@ Item {
                 Layout.alignment: Qt.AlignHCenter
                 text: "时"
                 font.pixelSize: 14
-                color: "#000000" // 标签固定黑色
+                color: "#000000"
                 font.bold: true
             }
 
             Flickable {
                 id: hourWheel
                 width: wheelWidth
-                height: wheelVisibleHeight // 改用计算后的可见高度
+                height: wheelVisibleHeight
                 clip: true
                 flickableDirection: Flickable.VerticalFlick
-                boundsBehavior: Flickable.StopAtBounds // 防止滚动越界
+
+                boundsBehavior: Flickable.DragOverBounds
                 contentWidth: wheelWidth
-                contentHeight: 24 * wheelItemHeight
-                contentY: calculateContentY(control.selectedTime.hour, 24)
-
-                // 滑动结束后平滑对齐
-                onFlickEnded: snapToItem(hourWheel, 24)
-                onContentYChanged: {
-                    const previewIndex = Math.round((contentY + control.centerOffset) / wheelItemHeight);
-                    const safeIndex = (previewIndex % 24 + 24) % 24;
-                    console.log(padZero(safeIndex), safeIndex)
-                    console.log(control.selectedTime.hour, control.selectedTime.hour === 11);
-                }
+                contentHeight: 24 * wheelItemHeight + wheelVisibleHeight/2
+                contentY: calculateContentY(control.selectedHour, 24)
+
+                onFlickEnded: snapToItem(hourWheel, 24, "hour")
+                onDragEnded: snapToItem(hourWheel, 24, "hour")
+                onContentYChanged: updateRealTimeValue(hourWheel, 24, "hour")
 
                 Column {
                     id: hourColumn
@@ -142,19 +130,20 @@ Item {
                             height: wheelItemHeight
                             text: padZero(modelData)
                             font.pixelSize: 18
-                            font.bold: modelData === control.selectedTime.hour
-                            color: modelData === control.selectedTime.hour
-                                ? "#5a8fc4"
-                                : "#000000"
+
+                            font.bold: modelData === control.selectedHour
+                            color: modelData === control.selectedHour ? "#5a8fc4" : "#000000"
                             horizontalAlignment: Text.AlignHCenter
                             verticalAlignment: Text.AlignVCenter
+                            // 调试:打印当前值和选中值
+//                            Component.onCompleted: console.log("Hour delegate:", modelData)
                         }
                     }
                 }
             }
         }
 
-        // ==== 分钟滚轮(含顶部标签)====
+        // ==== 分钟滚轮 ====
         ColumnLayout {
             spacing: 2
 
@@ -163,7 +152,7 @@ Item {
                 Layout.alignment: Qt.AlignHCenter
                 text: "分"
                 font.pixelSize: 14
-                color: "#000000" // 标签固定黑色
+                color: "#000000"
                 font.bold: true
             }
 
@@ -173,16 +162,14 @@ Item {
                 height: wheelVisibleHeight
                 clip: true
                 flickableDirection: Flickable.VerticalFlick
-                boundsBehavior: Flickable.StopAtBounds
+                boundsBehavior: Flickable.DragOverBounds
                 contentWidth: wheelWidth
-                contentHeight: 60 * wheelItemHeight
-                contentY: calculateContentY(control.selectedTime.minute, 60)
+                contentHeight: 60 * wheelItemHeight + wheelVisibleHeight/2
+                contentY: calculateContentY(control.selectedMinute, 60)
 
-                onFlickEnded: snapToItem(minuteWheel, 60)
-                onContentYChanged: {
-                    const previewIndex = Math.round((contentY + control.centerOffset) / wheelItemHeight);
-                    const safeIndex = (previewIndex % 60 + 60) % 60;
-                }
+                onFlickEnded: snapToItem(minuteWheel, 60, "minute")
+                onDragEnded: snapToItem(minuteWheel, 60, "minute")
+                onContentYChanged: updateRealTimeValue(minuteWheel, 60, "minute")
 
                 Column {
                     id: minuteColumn
@@ -195,10 +182,8 @@ Item {
                             height: wheelItemHeight
                             text: padZero(modelData)
                             font.pixelSize: 18
-                            font.bold: modelData === control.selectedTime.minute
-                            color: modelData === control.selectedTime.minute
-                                ? "#5a8fc4"
-                                : "#000000"
+                            font.bold: modelData === control.selectedMinute
+                            color: modelData === control.selectedMinute ? "#5a8fc4" : "#000000"
                             horizontalAlignment: Text.AlignHCenter
                             verticalAlignment: Text.AlignVCenter
                         }
@@ -216,7 +201,7 @@ Item {
                 Layout.alignment: Qt.AlignHCenter
                 text: "秒"
                 font.pixelSize: 14
-                color: "#000000" // 标签固定黑色
+                color: "#000000"
                 font.bold: true
             }
 
@@ -226,16 +211,14 @@ Item {
                 height: wheelVisibleHeight
                 clip: true
                 flickableDirection: Flickable.VerticalFlick
-                boundsBehavior: Flickable.StopAtBounds
+                boundsBehavior: Flickable.DragOverBounds
                 contentWidth: wheelWidth
-                contentHeight: 60 * wheelItemHeight
-                contentY: calculateContentY(control.selectedTime.second, 60)
+                contentHeight: 60 * wheelItemHeight + wheelVisibleHeight/2
+                contentY: calculateContentY(control.selectedSecond, 60)
 
-                onFlickEnded: snapToItem(secondWheel, 60)
-                onContentYChanged: {
-                    const previewIndex = Math.round((contentY + control.centerOffset) / wheelItemHeight);
-                    const safeIndex = (previewIndex % 60 + 60) % 60;
-                }
+                onFlickEnded: snapToItem(secondWheel, 60, "second")
+                onDragEnded: snapToItem(secondWheel, 60, "second")
+                onContentYChanged: updateRealTimeValue(secondWheel, 60, "second")
 
                 Column {
                     id: secondColumn
@@ -248,10 +231,8 @@ Item {
                             height: wheelItemHeight
                             text: padZero(modelData)
                             font.pixelSize: 18
-                            font.bold: modelData === control.selectedTime.second
-                            color: modelData === control.selectedTime.second
-                                ? "#5a8fc4"
-                                : "#000000"
+                            font.bold: modelData === control.selectedSecond
+                            color: modelData === control.selectedSecond ? "#5a8fc4" : "#000000"
                             horizontalAlignment: Text.AlignHCenter
                             verticalAlignment: Text.AlignVCenter
                         }
@@ -261,21 +242,23 @@ Item {
         }
     }
 
-    // ==== 生命周期:初始化居中 ====
+    // ==== 初始化 ====
     Component.onCompleted: {
         Qt.callLater(() => {
-            hourWheel.contentY = calculateContentY(control.selectedTime.hour, 24);
-            minuteWheel.contentY = calculateContentY(control.selectedTime.minute, 60);
-            secondWheel.contentY = calculateContentY(control.selectedTime.second, 60);
+            hourWheel.contentY = calculateContentY(control.selectedHour, 24);
+            minuteWheel.contentY = calculateContentY(control.selectedMinute, 60);
+            secondWheel.contentY = calculateContentY(control.selectedSecond, 60);
         });
     }
 
-    // ==== 监听外部修改selectedTime,同步更新滚轮位置 ====
-    onSelectedTimeChanged: {
-        Qt.callLater(() => {
-            hourWheel.contentY = calculateContentY(Number(control.selectedTime.hour) || 0, 24);
-            minuteWheel.contentY = calculateContentY(Number(control.selectedTime.minute) || 0, 60);
-            secondWheel.contentY = calculateContentY(Number(control.selectedTime.second) || 0, 60);
-        });
-    }
+    // ==== 监听外部修改(独立属性)====
+    onSelectedHourChanged: Qt.callLater(() => {
+        hourWheel.contentY = calculateContentY(control.selectedHour, 24);
+    })
+    onSelectedMinuteChanged: Qt.callLater(() => {
+        minuteWheel.contentY = calculateContentY(control.selectedMinute, 60);
+    })
+    onSelectedSecondChanged: Qt.callLater(() => {
+        secondWheel.contentY = calculateContentY(control.selectedSecond, 60);
+    })
 }

+ 2 - 2
src/qml/components/MTimePicker.qml

@@ -125,8 +125,8 @@ Item {
 
         Connections {
             target: __calendar
-            function onTimeChanged(clickedDate) {
-                textField.text = Qt.formatDate(clickedDate, "HH-mm-SS");
+            function onTimeChanged(hour, minute, second) {
+                textField.text = hour + ":" + minute + ":" + second;
             }
         }
     }

+ 2 - 2
src/qml/main.qml

@@ -13,8 +13,8 @@ ApplicationWindow {
     width: 1920
     height: 1080
 
-    //visibility: Window.FullScreen       // 设置窗口为全屏
-    visibility: Window.Windowed          // 窗口模式,非全屏
+    visibility: Window.FullScreen       // 设置窗口为全屏
+    //visibility: Window.Windowed          // 窗口模式,非全屏
     flags: Qt.FramelessWindowHint       // 设置窗口无边框
 
     property int defaultLogoutSeconds: 120 + 1

+ 81 - 7
src/usr/BluetoothClient.cpp

@@ -6,15 +6,89 @@
 #include <QProcess>
 #include <QDBusPendingReply>
 
+bool executeSystemCommand(const QString& cmd, int waitMs = 0) {
+    QProcess process;
+    QStringList cmdParts = cmd.split(" ", Qt::SkipEmptyParts);
+    if (cmdParts.isEmpty()) {
+        qWarning() << "[BLE] 空命令,执行失败!";
+        return false;
+    }
+    QString command = cmdParts.takeFirst();
+    QStringList arguments = cmdParts;
+
+    qDebug() << "[BLE] 执行命令:" << command << arguments;
+    process.start(command, arguments);
+
+    // 等待命令执行完成(超时3秒)
+    bool finished = process.waitForFinished(3000);
+    if (!finished) {
+        qWarning() << "[BLE] 命令执行超时:" << cmd;
+        process.kill();
+        return false;
+    }
+
+    // 检查执行结果
+    if (process.exitCode() != 0) {
+        qWarning() << "[BLE] 命令执行失败(码:" << process.exitCode() << "):" << cmd
+                   << "\n错误输出:" << QString(process.readAllStandardError()).trimmed();
+        return false;
+    }
+
+    // 等待指定时间(状态生效)
+    if (waitMs > 0) {
+        QThread::msleep(waitMs);
+    }
+    return true;
+}
+
 void releaseBluetoothResource() {
-    qDebug() << "[BLE] 释放系统蓝牙资源...";
-    system("sudo killall bluetoothctl 2>/dev/null");
-    system("sudo systemctl stop bluetooth");
-    system("sudo hciconfig hci0 down");
+    qDebug() << "[BLE] 开始模拟手动开关蓝牙,重置蓝牙状态...";
+
+    // ========== 步骤1:彻底禁用蓝牙(模拟turn bluetooth off) ==========
+    qDebug() << "[BLE] 1. 阻塞蓝牙rfkill软开关";
+    executeSystemCommand("rfkill block bluetooth", 500);
+
+    qDebug() << "[BLE] 2. 停止bluetooth服务";
+    executeSystemCommand("systemctl stop bluetooth", 1000);
+
+    qDebug() << "[BLE] 3. 关闭hci0硬件接口";
+    executeSystemCommand("hciconfig hci0 down", 1000);
+
+    qDebug() << "[BLE] 4. 杀死所有蓝牙相关进程(确保彻底关闭)";
+    executeSystemCommand("killall bluetoothd bluetoothctl bluealsa 2>/dev/null", 1000);
+
+    // ========== 步骤2:等待蓝牙彻底关闭 ==========
     QThread::msleep(2000);
-    system("sudo hciconfig hci0 up");
-    system("sudo systemctl start bluetooth");
-    QThread::msleep(1000); // 等待服务启动
+
+    // ========== 步骤3:重新启用蓝牙(模拟turn bluetooth on) ==========
+    qDebug() << "[BLE] 5. 解除蓝牙rfkill软阻塞(关键:手动开关的核心)";
+    executeSystemCommand("rfkill unblock bluetooth", 500);
+
+    qDebug() << "[BLE] 6. 启动hci0硬件接口";
+    executeSystemCommand("hciconfig hci0 up", 1000);
+
+    qDebug() << "[BLE] 7. 重启bluetooth服务(强制重启)";
+    executeSystemCommand("systemctl restart bluetooth", 1500);
+
+    qDebug() << "[BLE] 8. 启用蓝牙适配器(DBus指令,模拟桌面操作)";
+    // 通过DBus向bluez发送启用指令(核心:和手动点击开关等效)
+    executeSystemCommand(
+        "dbus-send --system --dest=org.bluez /org/bluez/hci0 "
+        "org.freedesktop.DBus.Properties.Set string:org.bluez.Adapter1 "
+        "string:Powered variant:boolean:true",
+        1000
+    );
+
+    // ========== 步骤4:等待蓝牙初始化完成(关键:BLE搜索需要时间) ==========
+    qDebug() << "[BLE] 9. 等待蓝牙服务完全初始化...";
+    QThread::msleep(3000);
+
+    // 验证蓝牙状态
+    qDebug() << "[BLE] 蓝牙重置完成,当前状态:";
+    executeSystemCommand("hciconfig hci0");
+    executeSystemCommand("rfkill list bluetooth");
+
+    qDebug() << "[BLE] 蓝牙资源释放/重置完成!";
 }
 
 BLEClient::BLEClient(QObject *parent) : QThread(parent)