diff --git a/applets/CMakeLists.txt b/applets/CMakeLists.txt
index 0486c8430..ead78b856 100644
--- a/applets/CMakeLists.txt
+++ b/applets/CMakeLists.txt
@@ -1,4 +1,4 @@
-# SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd.
+# SPDX-FileCopyrightText: 2024 - 2026 UnionTech Software Technology Co., Ltd.
#
# SPDX-License-Identifier: GPL-3.0-or-later
@@ -7,3 +7,4 @@ add_subdirectory(dde-appearance)
add_subdirectory(dde-apps)
add_subdirectory(dde-key-notify)
add_subdirectory(dde-shutdown)
+add_subdirectory(dde-weather)
diff --git a/applets/dde-weather/CMakeLists.txt b/applets/dde-weather/CMakeLists.txt
new file mode 100644
index 000000000..ee3b62795
--- /dev/null
+++ b/applets/dde-weather/CMakeLists.txt
@@ -0,0 +1,27 @@
+# SPDX-FileCopyrightText: 2024 - 2026 UnionTech Software Technology Co., Ltd.
+#
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+find_package(Qt${QT_VERSION_MAJOR} ${REQUIRED_QT_VERSION} REQUIRED COMPONENTS Network)
+
+add_library(dde-weather SHARED
+ weatherapplet.h
+ weatherapplet.cpp
+ weathermodel.h
+ weathermodel.cpp
+ weatherhelper.h
+ weatherhelper.cpp
+)
+
+target_link_libraries(dde-weather PRIVATE
+ dde-shell-frame
+ Qt${QT_VERSION_MAJOR}::Network
+)
+
+find_package(Dtk${DTK_VERSION_MAJOR}DConfig REQUIRED)
+
+dtk_add_config_meta_files(APPID "org.deepin.dde.shell" FILES configs/org.deepin.ds.weather.json)
+
+ds_install_package(PACKAGE org.deepin.ds.weather TARGET dde-weather)
+
+ds_handle_package_translation(PACKAGE org.deepin.ds.weather)
diff --git a/applets/dde-weather/configs/org.deepin.ds.weather.json b/applets/dde-weather/configs/org.deepin.ds.weather.json
new file mode 100644
index 000000000..b5d0e316c
--- /dev/null
+++ b/applets/dde-weather/configs/org.deepin.ds.weather.json
@@ -0,0 +1,42 @@
+{
+ "magic": "dsg.config.meta",
+ "version": "1.0",
+ "contents": {
+ "city": {
+ "value": "auto",
+ "serial": 0,
+ "flags": [],
+ "name": "city name or 'auto' for auto detection",
+ "name[zh_CN]": "城市名称或'auto'自动检测",
+ "permissions": "readwrite",
+ "visibility": "public"
+ },
+ "refreshInterval": {
+ "value": 30,
+ "serial": 0,
+ "flags": [],
+ "name": "weather refresh interval in minutes",
+ "name[zh_CN]": "天气刷新间隔(分钟)",
+ "permissions": "readwrite",
+ "visibility": "public"
+ },
+ "temperatureUnit": {
+ "value": "celsius",
+ "serial": 0,
+ "flags": [],
+ "name": "temperature unit: celsius or fahrenheit",
+ "name[zh_CN]": "温度单位:摄氏度或华氏度",
+ "permissions": "readwrite",
+ "visibility": "public"
+ },
+ "showInDock": {
+ "value": true,
+ "serial": 0,
+ "flags": [],
+ "name": "show weather widget in dock",
+ "name[zh_CN]": "在任务栏显示天气小组件",
+ "permissions": "readwrite",
+ "visibility": "public"
+ }
+ }
+}
diff --git a/applets/dde-weather/package/WeatherDetail.qml b/applets/dde-weather/package/WeatherDetail.qml
new file mode 100644
index 000000000..344b760cc
--- /dev/null
+++ b/applets/dde-weather/package/WeatherDetail.qml
@@ -0,0 +1,120 @@
+// SPDX-FileCopyrightText: 2024 - 2026 UnionTech Software Technology Co., Ltd.
+//
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+import QtQuick 2.15
+import QtQuick.Controls 2.15
+import QtQuick.Layouts 1.15
+
+import org.deepin.ds 1.0
+
+Rectangle {
+ id: detail
+ color: Qt.rgba(0.15, 0.15, 0.15, 0.95)
+ radius: 12
+
+ ColumnLayout {
+ anchors.fill: parent
+ anchors.margins: 16
+ spacing: 12
+
+ RowLayout {
+ Layout.fillWidth: true
+ spacing: 12
+
+ Text {
+ text: Applet.weatherIcon || "☀"
+ font.pixelSize: 48
+ color: "white"
+ }
+
+ ColumnLayout {
+ Layout.fillWidth: true
+ spacing: 4
+
+ Text {
+ text: Applet.currentCity || qsTr("Unknown")
+ font.pixelSize: 18
+ font.bold: true
+ color: "white"
+ }
+
+ Text {
+ text: Applet.temperature + "°C"
+ font.pixelSize: 28
+ font.bold: true
+ color: "white"
+ }
+
+ Text {
+ text: Applet.weatherDesc || ""
+ font.pixelSize: 14
+ color: Qt.rgba(1, 1, 1, 0.7)
+ }
+ }
+ }
+
+ Rectangle {
+ Layout.fillWidth: true
+ height: 1
+ color: Qt.rgba(1, 1, 1, 0.1)
+ }
+
+ ListView {
+ Layout.fillWidth: true
+ Layout.fillHeight: true
+ model: Applet.model
+ spacing: 8
+ clip: true
+
+ delegate: RowLayout {
+ width: ListView.view.width
+ spacing: 12
+
+ Text {
+ text: model.icon || "☀"
+ font.pixelSize: 20
+ color: "white"
+ }
+
+ Text {
+ text: model.city || ""
+ font.pixelSize: 14
+ color: "white"
+ Layout.fillWidth: true
+ }
+
+ Text {
+ text: model.temperature + "°C"
+ font.pixelSize: 14
+ font.bold: true
+ color: "white"
+ }
+
+ Text {
+ text: model.description || ""
+ font.pixelSize: 12
+ color: Qt.rgba(1, 1, 1, 0.6)
+ }
+ }
+ }
+
+ RowLayout {
+ Layout.fillWidth: true
+ spacing: 8
+
+ Button {
+ text: qsTr("Refresh")
+ onClicked: Applet.refresh()
+ }
+
+ Item { Layout.fillWidth: true }
+
+ Text {
+ text: qsTr("Last update: --:--")
+ font.pixelSize: 11
+ color: Qt.rgba(1, 1, 1, 0.5)
+ }
+ }
+ }
+}
diff --git a/applets/dde-weather/package/main.qml b/applets/dde-weather/package/main.qml
new file mode 100644
index 000000000..4aa1cee21
--- /dev/null
+++ b/applets/dde-weather/package/main.qml
@@ -0,0 +1,84 @@
+// SPDX-FileCopyrightText: 2024 - 2026 UnionTech Software Technology Co., Ltd.
+//
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+import QtQuick 2.15
+import QtQuick.Controls 2.15
+import QtQuick.Layouts 1.15
+
+import org.deepin.ds 1.0
+
+AppletItem {
+ id: root
+ objectName: "weather-applet"
+ implicitWidth: 200
+ implicitHeight: 100
+
+ property bool expanded: false
+
+ Rectangle {
+ anchors.fill: parent
+ radius: 8
+ color: Qt.rgba(1, 1, 1, 0.1)
+
+ ColumnLayout {
+ anchors.centerIn: parent
+ spacing: 4
+
+ Text {
+ Layout.alignment: Qt.AlignHCenter
+ text: Applet.weatherIcon || "☀"
+ font.pixelSize: 32
+ color: "white"
+ }
+
+ Text {
+ Layout.alignment: Qt.AlignHCenter
+ text: Applet.temperature + "°C"
+ font.pixelSize: 16
+ font.bold: true
+ color: "white"
+ }
+
+ Text {
+ Layout.alignment: Qt.AlignHCenter
+ text: Applet.currentCity || qsTr("Loading...")
+ font.pixelSize: 11
+ color: Qt.rgba(1, 1, 1, 0.7)
+ }
+ }
+
+ MouseArea {
+ anchors.fill: parent
+ onClicked: {
+ if (popup.visible) {
+ popup.close()
+ } else {
+ popup.open()
+ }
+ }
+ }
+ }
+
+ PanelPopup {
+ id: popup
+ x: -50
+ y: -320
+ width: 300
+ height: 300
+
+ WeatherDetail {
+ anchors.fill: parent
+ }
+ }
+
+ PanelToolTip {
+ id: toolTip
+ text: Applet.currentCity + " " + Applet.weatherDesc + " " + Applet.temperature + "°C"
+ visible: false
+ }
+
+ Component.onCompleted: {
+ Applet.refresh()
+ }
+}
diff --git a/applets/dde-weather/package/metadata.json b/applets/dde-weather/package/metadata.json
new file mode 100644
index 000000000..cd3887733
--- /dev/null
+++ b/applets/dde-weather/package/metadata.json
@@ -0,0 +1,8 @@
+{
+ "Plugin": {
+ "Version": "1.0",
+ "Id": "org.deepin.ds.weather",
+ "Url": "main.qml",
+ "Category": "DDE"
+ }
+}
diff --git a/applets/dde-weather/translations/org.deepin.ds.weather.ts b/applets/dde-weather/translations/org.deepin.ds.weather.ts
new file mode 100644
index 000000000..85e0d3992
--- /dev/null
+++ b/applets/dde-weather/translations/org.deepin.ds.weather.ts
@@ -0,0 +1,3 @@
+
+
+
diff --git a/applets/dde-weather/translations/org.deepin.ds.weather_ar.ts b/applets/dde-weather/translations/org.deepin.ds.weather_ar.ts
new file mode 100644
index 000000000..85e0d3992
--- /dev/null
+++ b/applets/dde-weather/translations/org.deepin.ds.weather_ar.ts
@@ -0,0 +1,3 @@
+
+
+
diff --git a/applets/dde-weather/translations/org.deepin.ds.weather_az.ts b/applets/dde-weather/translations/org.deepin.ds.weather_az.ts
new file mode 100644
index 000000000..85e0d3992
--- /dev/null
+++ b/applets/dde-weather/translations/org.deepin.ds.weather_az.ts
@@ -0,0 +1,3 @@
+
+
+
diff --git a/applets/dde-weather/translations/org.deepin.ds.weather_bo.ts b/applets/dde-weather/translations/org.deepin.ds.weather_bo.ts
new file mode 100644
index 000000000..85e0d3992
--- /dev/null
+++ b/applets/dde-weather/translations/org.deepin.ds.weather_bo.ts
@@ -0,0 +1,3 @@
+
+
+
diff --git a/applets/dde-weather/translations/org.deepin.ds.weather_ca.ts b/applets/dde-weather/translations/org.deepin.ds.weather_ca.ts
new file mode 100644
index 000000000..85e0d3992
--- /dev/null
+++ b/applets/dde-weather/translations/org.deepin.ds.weather_ca.ts
@@ -0,0 +1,3 @@
+
+
+
diff --git a/applets/dde-weather/translations/org.deepin.ds.weather_de.ts b/applets/dde-weather/translations/org.deepin.ds.weather_de.ts
new file mode 100644
index 000000000..85e0d3992
--- /dev/null
+++ b/applets/dde-weather/translations/org.deepin.ds.weather_de.ts
@@ -0,0 +1,3 @@
+
+
+
diff --git a/applets/dde-weather/translations/org.deepin.ds.weather_es.ts b/applets/dde-weather/translations/org.deepin.ds.weather_es.ts
new file mode 100644
index 000000000..85e0d3992
--- /dev/null
+++ b/applets/dde-weather/translations/org.deepin.ds.weather_es.ts
@@ -0,0 +1,3 @@
+
+
+
diff --git a/applets/dde-weather/translations/org.deepin.ds.weather_fi.ts b/applets/dde-weather/translations/org.deepin.ds.weather_fi.ts
new file mode 100644
index 000000000..85e0d3992
--- /dev/null
+++ b/applets/dde-weather/translations/org.deepin.ds.weather_fi.ts
@@ -0,0 +1,3 @@
+
+
+
diff --git a/applets/dde-weather/translations/org.deepin.ds.weather_fr.ts b/applets/dde-weather/translations/org.deepin.ds.weather_fr.ts
new file mode 100644
index 000000000..85e0d3992
--- /dev/null
+++ b/applets/dde-weather/translations/org.deepin.ds.weather_fr.ts
@@ -0,0 +1,3 @@
+
+
+
diff --git a/applets/dde-weather/translations/org.deepin.ds.weather_hu.ts b/applets/dde-weather/translations/org.deepin.ds.weather_hu.ts
new file mode 100644
index 000000000..85e0d3992
--- /dev/null
+++ b/applets/dde-weather/translations/org.deepin.ds.weather_hu.ts
@@ -0,0 +1,3 @@
+
+
+
diff --git a/applets/dde-weather/translations/org.deepin.ds.weather_it.ts b/applets/dde-weather/translations/org.deepin.ds.weather_it.ts
new file mode 100644
index 000000000..85e0d3992
--- /dev/null
+++ b/applets/dde-weather/translations/org.deepin.ds.weather_it.ts
@@ -0,0 +1,3 @@
+
+
+
diff --git a/applets/dde-weather/translations/org.deepin.ds.weather_ja.ts b/applets/dde-weather/translations/org.deepin.ds.weather_ja.ts
new file mode 100644
index 000000000..85e0d3992
--- /dev/null
+++ b/applets/dde-weather/translations/org.deepin.ds.weather_ja.ts
@@ -0,0 +1,3 @@
+
+
+
diff --git a/applets/dde-weather/translations/org.deepin.ds.weather_ko.ts b/applets/dde-weather/translations/org.deepin.ds.weather_ko.ts
new file mode 100644
index 000000000..85e0d3992
--- /dev/null
+++ b/applets/dde-weather/translations/org.deepin.ds.weather_ko.ts
@@ -0,0 +1,3 @@
+
+
+
diff --git a/applets/dde-weather/translations/org.deepin.ds.weather_lo.ts b/applets/dde-weather/translations/org.deepin.ds.weather_lo.ts
new file mode 100644
index 000000000..85e0d3992
--- /dev/null
+++ b/applets/dde-weather/translations/org.deepin.ds.weather_lo.ts
@@ -0,0 +1,3 @@
+
+
+
diff --git a/applets/dde-weather/translations/org.deepin.ds.weather_nb_NO.ts b/applets/dde-weather/translations/org.deepin.ds.weather_nb_NO.ts
new file mode 100644
index 000000000..85e0d3992
--- /dev/null
+++ b/applets/dde-weather/translations/org.deepin.ds.weather_nb_NO.ts
@@ -0,0 +1,3 @@
+
+
+
diff --git a/applets/dde-weather/translations/org.deepin.ds.weather_pl.ts b/applets/dde-weather/translations/org.deepin.ds.weather_pl.ts
new file mode 100644
index 000000000..85e0d3992
--- /dev/null
+++ b/applets/dde-weather/translations/org.deepin.ds.weather_pl.ts
@@ -0,0 +1,3 @@
+
+
+
diff --git a/applets/dde-weather/translations/org.deepin.ds.weather_pt_BR.ts b/applets/dde-weather/translations/org.deepin.ds.weather_pt_BR.ts
new file mode 100644
index 000000000..85e0d3992
--- /dev/null
+++ b/applets/dde-weather/translations/org.deepin.ds.weather_pt_BR.ts
@@ -0,0 +1,3 @@
+
+
+
diff --git a/applets/dde-weather/translations/org.deepin.ds.weather_ru.ts b/applets/dde-weather/translations/org.deepin.ds.weather_ru.ts
new file mode 100644
index 000000000..85e0d3992
--- /dev/null
+++ b/applets/dde-weather/translations/org.deepin.ds.weather_ru.ts
@@ -0,0 +1,3 @@
+
+
+
diff --git a/applets/dde-weather/translations/org.deepin.ds.weather_sq.ts b/applets/dde-weather/translations/org.deepin.ds.weather_sq.ts
new file mode 100644
index 000000000..85e0d3992
--- /dev/null
+++ b/applets/dde-weather/translations/org.deepin.ds.weather_sq.ts
@@ -0,0 +1,3 @@
+
+
+
diff --git a/applets/dde-weather/translations/org.deepin.ds.weather_uk.ts b/applets/dde-weather/translations/org.deepin.ds.weather_uk.ts
new file mode 100644
index 000000000..85e0d3992
--- /dev/null
+++ b/applets/dde-weather/translations/org.deepin.ds.weather_uk.ts
@@ -0,0 +1,3 @@
+
+
+
diff --git a/applets/dde-weather/translations/org.deepin.ds.weather_zh_CN.ts b/applets/dde-weather/translations/org.deepin.ds.weather_zh_CN.ts
new file mode 100644
index 000000000..85e0d3992
--- /dev/null
+++ b/applets/dde-weather/translations/org.deepin.ds.weather_zh_CN.ts
@@ -0,0 +1,3 @@
+
+
+
diff --git a/applets/dde-weather/translations/org.deepin.ds.weather_zh_HK.ts b/applets/dde-weather/translations/org.deepin.ds.weather_zh_HK.ts
new file mode 100644
index 000000000..85e0d3992
--- /dev/null
+++ b/applets/dde-weather/translations/org.deepin.ds.weather_zh_HK.ts
@@ -0,0 +1,3 @@
+
+
+
diff --git a/applets/dde-weather/translations/org.deepin.ds.weather_zh_TW.ts b/applets/dde-weather/translations/org.deepin.ds.weather_zh_TW.ts
new file mode 100644
index 000000000..85e0d3992
--- /dev/null
+++ b/applets/dde-weather/translations/org.deepin.ds.weather_zh_TW.ts
@@ -0,0 +1,3 @@
+
+
+
diff --git a/applets/dde-weather/weatherapplet.cpp b/applets/dde-weather/weatherapplet.cpp
new file mode 100644
index 000000000..a7cd49c35
--- /dev/null
+++ b/applets/dde-weather/weatherapplet.cpp
@@ -0,0 +1,94 @@
+// SPDX-FileCopyrightText: 2024 - 2026 UnionTech Software Technology Co., Ltd.
+//
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "weatherapplet.h"
+#include "weathermodel.h"
+#include "weatherhelper.h"
+#include "pluginfactory.h"
+
+#include
+
+WeatherApplet::WeatherApplet(QObject *parent)
+ : DApplet(parent)
+ , m_model(new WeatherModel(this))
+ , m_helper(new WeatherHelper(this))
+{
+ connect(m_helper, &WeatherHelper::weatherUpdated, this, [this](const QString &city, int temp, const QString &desc, const QString &icon) {
+ if (m_currentCity != city) {
+ m_currentCity = city;
+ Q_EMIT currentCityChanged();
+ }
+ if (m_temperature != temp) {
+ m_temperature = temp;
+ Q_EMIT temperatureChanged();
+ }
+ if (m_weatherDesc != desc) {
+ m_weatherDesc = desc;
+ Q_EMIT weatherDescChanged();
+ }
+ if (m_weatherIcon != icon) {
+ m_weatherIcon = icon;
+ Q_EMIT weatherIconChanged();
+ }
+ });
+
+ connect(m_helper, &WeatherHelper::forecastUpdated, m_model, &WeatherModel::updateData);
+}
+
+WeatherApplet::~WeatherApplet()
+{
+}
+
+bool WeatherApplet::load()
+{
+ DCORE_USE_NAMESPACE;
+ auto config = DConfig::create("org.deepin.dde.shell", "org.deepin.ds.weather");
+ if (config) {
+ m_helper->setCity(config->value("city", "auto").toString());
+ m_helper->setRefreshInterval(config->value("refreshInterval", 30).toInt());
+ delete config;
+ }
+ return true;
+}
+
+bool WeatherApplet::init()
+{
+ DApplet::init();
+ m_helper->refresh();
+ return true;
+}
+
+WeatherModel *WeatherApplet::model() const
+{
+ return m_model;
+}
+
+QString WeatherApplet::currentCity() const
+{
+ return m_currentCity;
+}
+
+int WeatherApplet::temperature() const
+{
+ return m_temperature;
+}
+
+QString WeatherApplet::weatherDesc() const
+{
+ return m_weatherDesc;
+}
+
+QString WeatherApplet::weatherIcon() const
+{
+ return m_weatherIcon;
+}
+
+void WeatherApplet::refresh()
+{
+ m_helper->refresh();
+}
+
+D_APPLET_CLASS(WeatherApplet)
+
+#include "weatherapplet.moc"
diff --git a/applets/dde-weather/weatherapplet.h b/applets/dde-weather/weatherapplet.h
new file mode 100644
index 000000000..b4eee6786
--- /dev/null
+++ b/applets/dde-weather/weatherapplet.h
@@ -0,0 +1,52 @@
+// SPDX-FileCopyrightText: 2024 - 2026 UnionTech Software Technology Co., Ltd.
+//
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#pragma once
+
+#include "applet.h"
+#include "dsglobal.h"
+#include "weathermodel.h"
+
+DS_USE_NAMESPACE
+
+class WeatherHelper;
+
+class WeatherApplet : public DApplet
+{
+ Q_OBJECT
+ Q_PROPERTY(WeatherModel *model READ model CONSTANT FINAL)
+ Q_PROPERTY(QString currentCity READ currentCity NOTIFY currentCityChanged FINAL)
+ Q_PROPERTY(int temperature READ temperature NOTIFY temperatureChanged FINAL)
+ Q_PROPERTY(QString weatherDesc READ weatherDesc NOTIFY weatherDescChanged FINAL)
+ Q_PROPERTY(QString weatherIcon READ weatherIcon NOTIFY weatherIconChanged FINAL)
+
+public:
+ explicit WeatherApplet(QObject *parent = nullptr);
+ ~WeatherApplet();
+
+ bool load() override;
+ bool init() override;
+
+ WeatherModel *model() const;
+ QString currentCity() const;
+ int temperature() const;
+ QString weatherDesc() const;
+ QString weatherIcon() const;
+
+ Q_INVOKABLE void refresh();
+
+Q_SIGNALS:
+ void currentCityChanged();
+ void temperatureChanged();
+ void weatherDescChanged();
+ void weatherIconChanged();
+
+private:
+ WeatherModel *m_model = nullptr;
+ WeatherHelper *m_helper = nullptr;
+ QString m_currentCity;
+ int m_temperature = 0;
+ QString m_weatherDesc;
+ QString m_weatherIcon;
+};
diff --git a/applets/dde-weather/weatherhelper.cpp b/applets/dde-weather/weatherhelper.cpp
new file mode 100644
index 000000000..c1ec90e3d
--- /dev/null
+++ b/applets/dde-weather/weatherhelper.cpp
@@ -0,0 +1,117 @@
+// SPDX-FileCopyrightText: 2024 - 2026 UnionTech Software Technology Co., Ltd.
+//
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "weatherhelper.h"
+
+#include
+#include
+#include
+#include
+#include
+
+static const char *WEATHER_API_URL = "https://api.openweathermap.org/data/2.5/weather";
+static const char *FORECAST_API_URL = "https://api.openweathermap.org/data/2.5/forecast";
+
+WeatherHelper::WeatherHelper(QObject *parent)
+ : QObject(parent)
+ , m_nam(new QNetworkAccessManager(this))
+ , m_timer(new QTimer(this))
+{
+ connect(m_nam, &QNetworkAccessManager::finished, this, &WeatherHelper::onReplyFinished);
+ connect(m_timer, &QTimer::timeout, this, &WeatherHelper::refresh);
+}
+
+void WeatherHelper::setCity(const QString &city)
+{
+ m_city = city;
+}
+
+void WeatherHelper::setRefreshInterval(int minutes)
+{
+ m_refreshInterval = minutes;
+ m_timer->stop();
+ if (minutes > 0) {
+ m_timer->start(minutes * 60 * 1000);
+ }
+}
+
+void WeatherHelper::refresh()
+{
+ if (m_city == "auto" || m_city.isEmpty()) {
+ updateWithMockData();
+ return;
+ }
+
+ QUrl url(WEATHER_API_URL);
+ QUrlQuery query;
+ query.addQueryItem("q", m_city);
+ query.addQueryItem("appid", "demo");
+ query.addQueryItem("units", "metric");
+ query.addQueryItem("lang", "zh_cn");
+ url.setQuery(query);
+
+ QNetworkRequest request(url);
+ m_nam->get(request);
+}
+
+void WeatherHelper::onReplyFinished(QNetworkReply *reply)
+{
+ if (reply->error() != QNetworkReply::NoError) {
+ updateWithMockData();
+ reply->deleteLater();
+ return;
+ }
+
+ parseWeatherData(reply->readAll());
+ reply->deleteLater();
+}
+
+void WeatherHelper::parseWeatherData(const QByteArray &data)
+{
+ QJsonDocument doc = QJsonDocument::fromJson(data);
+ if (!doc.isObject())
+ return;
+
+ QJsonObject obj = doc.object();
+
+ QString city = obj.value("name").toString();
+ QJsonObject main = obj.value("main").toObject();
+ int temp = static_cast(main.value("temp").toDouble());
+
+ QJsonArray weatherArray = obj.value("weather").toArray();
+ QString desc;
+ QString icon;
+ if (!weatherArray.isEmpty()) {
+ QJsonObject weather = weatherArray.first().toObject();
+ desc = weather.value("description").toString();
+ icon = weatherCodeToIcon(weather.value("icon").toString());
+ }
+
+ Q_EMIT weatherUpdated(city, temp, desc, icon);
+}
+
+void WeatherHelper::updateWithMockData()
+{
+ Q_EMIT weatherUpdated(tr("Demo City"), 26, tr("Partly cloudy"), "⛅");
+
+ QList forecast;
+ forecast.append({ tr("Demo City"), 26, tr("Partly cloudy"), "⛅" });
+ forecast.append({ tr("Demo City"), 24, tr("Light rain"), "🌧" });
+ forecast.append({ tr("Demo City"), 28, tr("Sunny"), "☀" });
+ forecast.append({ tr("Demo City"), 22, tr("Overcast"), "☁" });
+ forecast.append({ tr("Demo City"), 25, tr("Clear"), "🌙" });
+ Q_EMIT forecastUpdated(forecast);
+}
+
+QString WeatherHelper::weatherCodeToIcon(const QString &code) const
+{
+ if (code.startsWith("01")) return "☀";
+ if (code.startsWith("02")) return "⛅";
+ if (code.startsWith("03") || code.startsWith("04")) return "☁";
+ if (code.startsWith("09") || code.startsWith("10")) return "🌧";
+ if (code.startsWith("11")) return "⛈";
+ if (code.startsWith("13")) return "❄";
+ if (code.startsWith("50")) return "🌫";
+ return "☀";
+}
diff --git a/applets/dde-weather/weatherhelper.h b/applets/dde-weather/weatherhelper.h
new file mode 100644
index 000000000..74652a94a
--- /dev/null
+++ b/applets/dde-weather/weatherhelper.h
@@ -0,0 +1,40 @@
+// SPDX-FileCopyrightText: 2024 - 2026 UnionTech Software Technology Co., Ltd.
+//
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#pragma once
+
+#include "weathermodel.h"
+
+#include
+#include
+#include
+
+class WeatherHelper : public QObject
+{
+ Q_OBJECT
+
+public:
+ explicit WeatherHelper(QObject *parent = nullptr);
+
+ void setCity(const QString &city);
+ void setRefreshInterval(int minutes);
+ void refresh();
+
+Q_SIGNALS:
+ void weatherUpdated(const QString &city, int temperature, const QString &description, const QString &icon);
+ void forecastUpdated(const QList &forecast);
+
+private Q_SLOTS:
+ void onReplyFinished(QNetworkReply *reply);
+
+private:
+ void parseWeatherData(const QByteArray &data);
+ void updateWithMockData();
+ QString weatherCodeToIcon(const QString &code) const;
+
+ QNetworkAccessManager *m_nam = nullptr;
+ QTimer *m_timer = nullptr;
+ QString m_city = "auto";
+ int m_refreshInterval = 30;
+};
diff --git a/applets/dde-weather/weathermodel.cpp b/applets/dde-weather/weathermodel.cpp
new file mode 100644
index 000000000..8bedf7ff2
--- /dev/null
+++ b/applets/dde-weather/weathermodel.cpp
@@ -0,0 +1,61 @@
+// SPDX-FileCopyrightText: 2024 - 2026 UnionTech Software Technology Co., Ltd.
+//
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "weathermodel.h"
+
+WeatherModel::WeatherModel(QObject *parent)
+ : QAbstractListModel(parent)
+{
+}
+
+int WeatherModel::rowCount(const QModelIndex &parent) const
+{
+ if (parent.isValid())
+ return 0;
+ return m_data.count();
+}
+
+QVariant WeatherModel::data(const QModelIndex &index, int role) const
+{
+ if (!index.isValid() || index.row() >= m_data.count())
+ return {};
+
+ const auto &item = m_data.at(index.row());
+ switch (role) {
+ case CityRole:
+ return item.city;
+ case TemperatureRole:
+ return item.temperature;
+ case DescriptionRole:
+ return item.description;
+ case IconRole:
+ return item.icon;
+ default:
+ return {};
+ }
+}
+
+QHash WeatherModel::roleNames() const
+{
+ return {
+ { CityRole, "city" },
+ { TemperatureRole, "temperature" },
+ { DescriptionRole, "description" },
+ { IconRole, "icon" },
+ };
+}
+
+void WeatherModel::updateData(const QList &data)
+{
+ beginResetModel();
+ m_data = data;
+ endResetModel();
+}
+
+void WeatherModel::clear()
+{
+ beginResetModel();
+ m_data.clear();
+ endResetModel();
+}
diff --git a/applets/dde-weather/weathermodel.h b/applets/dde-weather/weathermodel.h
new file mode 100644
index 000000000..1d44d4ab2
--- /dev/null
+++ b/applets/dde-weather/weathermodel.h
@@ -0,0 +1,39 @@
+// SPDX-FileCopyrightText: 2024 - 2026 UnionTech Software Technology Co., Ltd.
+//
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#pragma once
+
+#include
+
+struct WeatherData {
+ QString city;
+ int temperature = 0;
+ QString description;
+ QString icon;
+};
+
+class WeatherModel : public QAbstractListModel
+{
+ Q_OBJECT
+
+public:
+ enum Roles {
+ CityRole = Qt::UserRole + 1,
+ TemperatureRole,
+ DescriptionRole,
+ IconRole,
+ };
+
+ explicit WeatherModel(QObject *parent = nullptr);
+
+ int rowCount(const QModelIndex &parent = QModelIndex()) const override;
+ QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
+ QHash roleNames() const override;
+
+ void updateData(const QList &data);
+ void clear();
+
+private:
+ QList m_data;
+};
diff --git a/panels/dock/taskmanager/package/TaskManager.qml b/panels/dock/taskmanager/package/TaskManager.qml
index e143b060e..64c137821 100644
--- a/panels/dock/taskmanager/package/TaskManager.qml
+++ b/panels/dock/taskmanager/package/TaskManager.qml
@@ -1,4 +1,4 @@
-// SPDX-FileCopyrightText: 2023-2026 UnionTech Software Technology Co., Ltd.
+// SPDX-FileCopyrightText: 2023 - 2026 UnionTech Software Technology Co., Ltd.
//
// SPDX-License-Identifier: GPL-3.0-or-later