From c4938f6f4e839890b032c75c7a57333a6a9157a9 Mon Sep 17 00:00:00 2001
From: peng <peng.com>
Date: 星期四, 06 十一月 2025 17:06:10 +0800
Subject: [PATCH] 添加新闻功能

---
 wx/pages/news/detail.js                                                     |   77 +
 backend/src/main/java/com/rongyichuang/news/entity/News.java                |  102 +
 web/src/api/news.js                                                         |  196 +++
 web/src/layout/index.vue                                                    |    7 
 backend/src/main/java/com/rongyichuang/news/resolver/NewsResolver.java      |   77 +
 backend/src/main/java/com/rongyichuang/common/api/FileUploadController.java |   14 
 wx/pages/news/list.json                                                     |    5 
 web/src/views/NewsDetail.vue                                                |  182 +++
 wx/pages/news/list.js                                                       |  120 ++
 backend/src/main/java/com/rongyichuang/news/dto/NewsResponse.java           |  144 ++
 wx/pages/index/index.wxml                                                   |   31 
 wx/pages/index/index.wxss                                                   |  103 +
 wx/pages/index/index.js                                                     |  116 +
 web/src/views/news-list.vue                                                 |  359 ++++++
 news-module-readme.md                                                       |   97 +
 web/package.json                                                            |    3 
 agents.md                                                                   |  134 ++
 backend/src/main/java/com/rongyichuang/news/service/NewsService.java        |  153 ++
 backend/src/main/java/com/rongyichuang/news/repository/NewsRepository.java  |   30 
 backend/src/main/java/com/rongyichuang/news/dto/NewsInput.java              |   77 +
 wx/pages/news/detail.wxml                                                   |   26 
 wx/pages/news/detail.wxss                                                   |  169 +++
 .claude/settings.local.json                                                 |    9 
 backend/src/main/resources/graphql/news.graphqls                            |   55 +
 web/src/views/NewsForm.vue                                                  |  408 +++++++
 web/src/views/NewsListPage.vue                                              |  277 +++++
 wx/app.json                                                                 |    4 
 wx/pages/news/detail.json                                                   |    3 
 wx/pages/news/list.wxml                                                     |   50 
 wx/pages/news/list.wxss                                                     |  174 +++
 web/src/router/index.ts                                                     |   30 
 31 files changed, 3,173 insertions(+), 59 deletions(-)

diff --git a/.claude/settings.local.json b/.claude/settings.local.json
new file mode 100644
index 0000000..6435e5e
--- /dev/null
+++ b/.claude/settings.local.json
@@ -0,0 +1,9 @@
+{
+  "permissions": {
+    "allow": [
+      "Bash(tree:*)"
+    ],
+    "deny": [],
+    "ask": []
+  }
+}
diff --git a/agents.md b/agents.md
new file mode 100644
index 0000000..b902ae7
--- /dev/null
+++ b/agents.md
@@ -0,0 +1,134 @@
+\# Codex Agent 鍏ㄥ眬瑙勫垯锛堢簿绠�鎵ц鐗堬級
+
+
+
+\## 瑙掕壊瀹氫箟
+
+浣犳槸涓�鍚嶉珮鏍囧噯鐨勮蒋浠跺伐绋嬫櫤鑳藉姪鐞�
+
+鎵�鏈夎緭鍑恒�佷慨鏀逛笌寤鸿蹇呴』閬靛惊浠ヤ笅瑙勫垯
+
+
+
+---
+
+
+
+\## 鏍稿績鍘熷垯锛堜笉鍙繚鍙嶏級
+
+1\. \*\*璐ㄩ噺绗竴\*\*锛氫唬鐮佽川閲忎笌绯荤粺瀹夊叏涓嶅濡ュ崗銆�  
+
+2\. \*\*涓枃缁熶竴\*\*锛氳緭鍑恒�佹敞閲娿�佹枃妗e叏閮ㄤ娇鐢ㄤ腑鏂囥��  
+
+3\. \*\*鎬濊�冨厛琛孿*\*锛氬厛鍒嗘瀽涓庤鍒掞紝鍐嶈繘琛屽疄鐜般��  
+
+4\. \*\*宸ュ叿浼樺厛\*\*锛氫紭鍏堥噰鐢ㄩ獙璇佽繃鐨勬渶浣冲伐鍏烽摼銆�  
+
+5\. \*\*閫忔槑璁板綍\*\*锛氶噸瑕佸喅绛栥�佸彉鏇寸悊鐢卞彲杩芥函銆�  
+
+6\. \*\*鎸佺画鏀硅繘\*\*锛氭墽琛屽悗鍙嶆�濄�佹�荤粨骞朵紭鍖栥��  
+
+7\. \*\*缁撴灉瀵煎悜\*\*锛氫互鐩爣杈炬垚璇勫垽鎵ц鏁堟灉銆�
+
+8\. \*\*缁撴灉瀵煎悜\*\*锛氫笉瑕佷慨鏀瑰垹闄ゆ垜鐨勬暟鎹簱鍜屽瓧娈点��
+
+
+
+---
+
+
+
+\## 璐ㄩ噺鏍囧噯
+
+\- 鍛藉悕娓呮櫚銆佺粨鏋勫悎鐞嗐�佹敞閲婂繀瑕併��  
+
+\- 绂佹鍗婃垚鍝侊紙MVP / TODO / Stub锛夈��  
+
+\- 鎺у埗澶嶆潅搴︼紝浼樺寲鎬ц兘涓庤祫婧愪娇鐢ㄣ��  
+
+\- 寮傚父銆佽竟鐣屻�佸苟鍙戝満鏅繀椤诲鐞嗐��  
+
+\- 璁捐鍙祴璇曠粨鏋勶紝淇濇寔娴嬭瘯鍙墽琛屻��  
+
+\- 閫氳繃闈欐�佹鏌ャ�佹牸寮忓寲涓庝唬鐮佸鏌ャ��
+
+
+
+---
+
+
+
+\## 浠诲姟绫诲瀷绛栫暐
+
+\- \*\*绱ф�ヤ慨澶峔*\*锛氫紭鍏堢ǔ瀹氱郴缁燂紝淇濈暀璐ㄩ噺妫�鏌ャ��  
+
+\- \*\*鏂板姛鑳絓*\*锛氭敞閲嶈璁′笌瀹屾暣娴嬭瘯銆�  
+
+\- \*\*閲嶆瀯浼樺寲\*\*锛氬叧娉ㄦ灦鏋勪笌鎬ц兘鎻愬崌銆�  
+
+\- \*\*瀛︿範鎺㈢储\*\*锛氬彲璇曢獙锛屼絾闇�璁板綍鎬荤粨銆�
+
+
+
+---
+
+
+
+\## 鎸佺画鏀硅繘鏈哄埗
+
+1\. 鎵ц鍓嶏細閫夋嫨绛栫暐銆佽瘎浼伴闄┿��  
+
+2\. 鎵ц涓細璁板綍鍐崇瓥涓庨棶棰樸��  
+
+3\. 鎵ц鍚庯細澶嶇洏骞舵洿鏂版渶浣冲疄璺点��  
+
+4\. 绉疮缁忛獙銆佸伐鍏锋妧宸т笌鍚堢悊渚嬪銆�
+
+
+
+---
+
+
+
+\## 妫�鏌ョ偣
+
+\- 閬靛惊璐ㄩ噺鏍囧噯骞惰褰曞叧閿彉鏇淬��  
+
+\- 楠岃瘉鍔熻兘涓庤川閲忥紝鏇存柊娴嬭瘯涓庢枃妗c��  
+
+\- 鎵ц缁撴潫鍚庢�荤粨鏀硅繘椤广��
+
+
+
+---
+
+
+
+\## 琛屼负鍑嗗垯
+
+> 鏌ヨ鑳滆繃鐚滄祴锛岀‘璁よ儨杩囧亣璁撅紱  
+
+> 澶嶇敤鑳滆繃閲嶅锛屾祴璇曡儨杩囪烦杩囷紱  
+
+> 瑙勮寖鑳滆繃闅忔剰锛岃瘹瀹炶儨杩囧亣瑁咃紱  
+
+> 璋ㄦ厧鑳滆繃鐩茬洰锛屽涔犺儨杩囧仠婊炪��
+
+
+
+---
+
+
+
+\## 鎵ц瑕佹眰
+
+\- 鎵�鏈� AI 杈撳嚭蹇呴』绗﹀悎涓婅堪鏍囧噯銆�  
+
+\- 鍐茬獊鏃朵互鈥滆川閲忕涓�鈥濅负鏈�楂樹紭鍏堢骇銆�  
+
+\- 绂佹缂栭�犳暟鎹�佸拷鐣ヨ竟鐣屻�佺敓鎴愬崰浣嶇銆�  
+
+\- 鎵�鏈夌粨鏋滃繀椤婚�忔槑銆佸彲瑙i噴銆佸彲楠岃瘉銆�
+
+
+
diff --git a/backend/src/main/java/com/rongyichuang/common/api/FileUploadController.java b/backend/src/main/java/com/rongyichuang/common/api/FileUploadController.java
index 54c8c99..f586b93 100644
--- a/backend/src/main/java/com/rongyichuang/common/api/FileUploadController.java
+++ b/backend/src/main/java/com/rongyichuang/common/api/FileUploadController.java
@@ -35,18 +35,22 @@
             // 鏋勫缓鏂囦欢璁块棶URL
             String fileUrl = cosService.getFileUrl(relativePath);
             
+            // 涓哄吋瀹瑰墠绔痺angEditor锛岃繑鍥炲叾鏈熸湜鐨勬牸寮�
             Map<String, Object> response = new HashMap<>();
+            response.put("errno", 0); // 0琛ㄧず鎴愬姛锛�1琛ㄧず澶辫触
             response.put("success", true);
-            response.put("url", fileUrl);
-            response.put("fileName", originalFilename); // 淇濇寔鍘熷鏂囦欢鍚嶏紙甯﹀悗缂�锛�
-            response.put("fileSize", file.getSize());
-            response.put("path", relativePath); // 鍙繑鍥炵浉瀵硅矾寰�
+            Map<String, Object> data = new HashMap<>();
+            data.put("url", fileUrl);
+            data.put("alt", originalFilename);
+            data.put("href", fileUrl);
+            response.put("data", data);
             
             return ResponseEntity.ok(response);
         } catch (Exception e) {
             Map<String, Object> response = new HashMap<>();
             response.put("success", false);
-            response.put("error", e.getMessage());
+            response.put("errno", 1); // 0琛ㄧず鎴愬姛锛�1琛ㄧず澶辫触
+            response.put("message", e.getMessage());
             
             return ResponseEntity.badRequest().body(response);
         }
diff --git a/backend/src/main/java/com/rongyichuang/news/dto/NewsInput.java b/backend/src/main/java/com/rongyichuang/news/dto/NewsInput.java
new file mode 100644
index 0000000..0668c75
--- /dev/null
+++ b/backend/src/main/java/com/rongyichuang/news/dto/NewsInput.java
@@ -0,0 +1,77 @@
+package com.rongyichuang.news.dto;
+
+public class NewsInput {
+
+    private Long id;
+    private String title;
+    private String content;
+    private String summary;
+    private String coverImage;
+    private String author;
+    private Integer state = 1;
+
+    // 鏋勯�犲嚱鏁�
+    public NewsInput() {}
+
+    // Getters and Setters
+
+    public Long getId() {
+        return id;
+    }
+
+    public void setId(Long id) {
+        this.id = id;
+    }
+
+    public String getTitle() {
+        return title;
+    }
+
+    public void setTitle(String title) {
+        this.title = title;
+    }
+
+    public String getContent() {
+        return content;
+    }
+
+    public void setContent(String content) {
+        this.content = content;
+    }
+
+    public String getSummary() {
+        return summary;
+    }
+
+    public void setSummary(String summary) {
+        this.summary = summary;
+    }
+
+    public String getCoverImage() {
+        return coverImage;
+    }
+
+    public void setCoverImage(String coverImage) {
+        this.coverImage = coverImage;
+    }
+
+    public String getAuthor() {
+        return author;
+    }
+
+    public void setAuthor(String author) {
+        this.author = author;
+    }
+
+    public Integer getState() {
+        return state;
+    }
+
+    public void setState(Integer state) {
+        this.state = state;
+    }
+
+    public boolean isNew() {
+        return id == null || id <= 0;
+    }
+}
\ No newline at end of file
diff --git a/backend/src/main/java/com/rongyichuang/news/dto/NewsResponse.java b/backend/src/main/java/com/rongyichuang/news/dto/NewsResponse.java
new file mode 100644
index 0000000..91284f9
--- /dev/null
+++ b/backend/src/main/java/com/rongyichuang/news/dto/NewsResponse.java
@@ -0,0 +1,144 @@
+package com.rongyichuang.news.dto;
+
+import com.rongyichuang.news.entity.News;
+import com.rongyichuang.config.CosConfig;
+
+import java.time.LocalDateTime;
+
+public class NewsResponse {
+
+    private Long id;
+    private String title;
+    private String content;
+    private String summary;
+    private String coverImage;
+    private String author;
+    private Integer viewCount = 0;
+    private Integer state = 1;
+    private String stateName;
+    private LocalDateTime createTime;
+    private LocalDateTime updateTime;
+
+    // 鏋勯�犲嚱鏁�
+    public NewsResponse() {}
+
+    public NewsResponse(News news) {
+        this.id = news.getId();
+        this.title = news.getTitle();
+        this.content = news.getContent();
+        this.summary = news.getSummary();
+        this.coverImage = news.getCoverImage();
+        this.author = news.getAuthor();
+        this.viewCount = news.getViewCount();
+        this.state = news.getState();
+        this.createTime = news.getCreateTime();
+        this.updateTime = news.getUpdateTime();
+        this.stateName = getStateNameByValue(news.getState());
+    }
+
+    // 鐘舵�佸悕绉版槧灏�
+    private String getStateNameByValue(Integer state) {
+        if (state == null) return "鏈煡";
+        switch (state) {
+            case 0: return "鑽夌";
+            case 1: return "鍙戝竷";
+            case 2: return "鍏抽棴";
+            default: return "鏈煡";
+        }
+    }
+
+    // Getters and Setters
+
+    public Long getId() {
+        return id;
+    }
+
+    public void setId(Long id) {
+        this.id = id;
+    }
+
+    public String getTitle() {
+        return title;
+    }
+
+    public void setTitle(String title) {
+        this.title = title;
+    }
+
+    public String getContent() {
+        // 澶勭悊瀵屾枃鏈唴瀹癸紝纭繚鍥剧墖鍏锋湁鍝嶅簲寮忔牱寮�
+        if (content != null) {
+            // 涓烘墍鏈塱mg鏍囩娣诲姞鏍峰紡灞炴�э紝纭繚鍥剧墖涓嶄細瓒呭嚭瀹瑰櫒
+            return content.replaceAll("<img([^>]*?)>", "<img$1 style=\"max-width:100%;height:auto;display:block;margin:10px 0;\" />");
+        }
+        return content;
+    }
+
+    public void setContent(String content) {
+        this.content = content;
+    }
+
+    public String getSummary() {
+        return summary;
+    }
+
+    public void setSummary(String summary) {
+        this.summary = summary;
+    }
+
+    public String getCoverImage() {
+        return coverImage;
+    }
+
+    public void setCoverImage(String coverImage) {
+        this.coverImage = coverImage;
+    }
+
+    public String getAuthor() {
+        return author;
+    }
+
+    public void setAuthor(String author) {
+        this.author = author;
+    }
+
+    public Integer getViewCount() {
+        return viewCount;
+    }
+
+    public void setViewCount(Integer viewCount) {
+        this.viewCount = viewCount;
+    }
+
+    public Integer getState() {
+        return state;
+    }
+
+    public void setState(Integer state) {
+        this.state = state;
+    }
+
+    public String getStateName() {
+        return stateName;
+    }
+
+    public void setStateName(String stateName) {
+        this.stateName = stateName;
+    }
+
+    public LocalDateTime getCreateTime() {
+        return createTime;
+    }
+
+    public void setCreateTime(LocalDateTime createTime) {
+        this.createTime = createTime;
+    }
+
+    public LocalDateTime getUpdateTime() {
+        return updateTime;
+    }
+
+    public void setUpdateTime(LocalDateTime updateTime) {
+        this.updateTime = updateTime;
+    }
+}
\ No newline at end of file
diff --git a/backend/src/main/java/com/rongyichuang/news/entity/News.java b/backend/src/main/java/com/rongyichuang/news/entity/News.java
new file mode 100644
index 0000000..6c033b8
--- /dev/null
+++ b/backend/src/main/java/com/rongyichuang/news/entity/News.java
@@ -0,0 +1,102 @@
+package com.rongyichuang.news.entity;
+
+import com.rongyichuang.common.entity.BaseEntity;
+import jakarta.persistence.*;
+
+@Entity
+@Table(name = "t_news")
+public class News extends BaseEntity {
+
+    @Column(name = "title", nullable = false, length = 255)
+    private String title;
+
+    @Column(name = "content", columnDefinition = "LONGTEXT")
+    private String content;
+
+    @Column(name = "summary", length = 500)
+    private String summary;
+
+    @Column(name = "cover_image", length = 500)
+    private String coverImage;
+
+    @Column(name = "author", length = 100)
+    private String author;
+
+    @Column(name = "view_count", nullable = false)
+    private Integer viewCount = 0;
+
+    /**
+     * 鐘舵�侊細0-鑽夌锛�1-鍙戝竷锛�2-鍏抽棴
+     */
+    @Column(name = "state", nullable = false)
+    private Integer state = 1;
+
+    // 鏋勯�犲嚱鏁�
+    public News() {}
+
+    public News(String title, String content, String summary, String coverImage, String author) {
+        this.title = title;
+        this.content = content;
+        this.summary = summary;
+        this.coverImage = coverImage;
+        this.author = author;
+    }
+
+    // Getters and Setters
+
+    public String getTitle() {
+        return title;
+    }
+
+    public void setTitle(String title) {
+        this.title = title;
+    }
+
+    public String getContent() {
+        return content;
+    }
+
+    public void setContent(String content) {
+        this.content = content;
+    }
+
+    public String getSummary() {
+        return summary;
+    }
+
+    public void setSummary(String summary) {
+        this.summary = summary;
+    }
+
+    public String getCoverImage() {
+        return coverImage;
+    }
+
+    public void setCoverImage(String coverImage) {
+        this.coverImage = coverImage;
+    }
+
+    public String getAuthor() {
+        return author;
+    }
+
+    public void setAuthor(String author) {
+        this.author = author;
+    }
+
+    public Integer getViewCount() {
+        return viewCount;
+    }
+
+    public void setViewCount(Integer viewCount) {
+        this.viewCount = viewCount;
+    }
+
+    public Integer getState() {
+        return state;
+    }
+
+    public void setState(Integer state) {
+        this.state = state;
+    }
+}
\ No newline at end of file
diff --git a/backend/src/main/java/com/rongyichuang/news/repository/NewsRepository.java b/backend/src/main/java/com/rongyichuang/news/repository/NewsRepository.java
new file mode 100644
index 0000000..94b4973
--- /dev/null
+++ b/backend/src/main/java/com/rongyichuang/news/repository/NewsRepository.java
@@ -0,0 +1,30 @@
+package com.rongyichuang.news.repository;
+
+import com.rongyichuang.news.entity.News;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.Query;
+import org.springframework.data.repository.query.Param;
+
+import java.util.List;
+
+public interface NewsRepository extends JpaRepository<News, Long> {
+
+    Page<News> findByStateAndTitleContainingOrderByCreateTimeDesc(Integer state, String title, Pageable pageable);
+
+    Page<News> findByTitleContainingOrderByCreateTimeDesc(String title, Pageable pageable);
+
+    Page<News> findByStateOrderByCreateTimeDesc(Integer state, Pageable pageable);
+
+    List<News> findByStateOrderByCreateTimeDesc(Integer state);
+
+    @Query("SELECT n FROM News n WHERE n.state = 1 ORDER BY n.createTime DESC")
+    List<News> findPublishedNews();
+
+    @Query("SELECT n FROM News n WHERE n.state = 1 ORDER BY n.createTime DESC")
+    Page<News> findPublishedNews(Pageable pageable);
+
+    @Query("SELECT n FROM News n WHERE n.id = :id AND n.state = 1")
+    News findPublishedNewsById(@Param("id") Long id);
+}
\ No newline at end of file
diff --git a/backend/src/main/java/com/rongyichuang/news/resolver/NewsResolver.java b/backend/src/main/java/com/rongyichuang/news/resolver/NewsResolver.java
new file mode 100644
index 0000000..c98b99a
--- /dev/null
+++ b/backend/src/main/java/com/rongyichuang/news/resolver/NewsResolver.java
@@ -0,0 +1,77 @@
+package com.rongyichuang.news.resolver;
+
+import com.rongyichuang.news.dto.NewsInput;
+import com.rongyichuang.news.dto.NewsResponse;
+import com.rongyichuang.news.service.NewsService;
+import com.rongyichuang.common.dto.PageRequest;
+import com.rongyichuang.common.dto.PageResponse;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.graphql.data.method.annotation.Argument;
+import org.springframework.graphql.data.method.annotation.MutationMapping;
+import org.springframework.graphql.data.method.annotation.QueryMapping;
+import org.springframework.stereotype.Controller;
+
+@Controller
+public class NewsResolver {
+
+    @Autowired
+    private NewsService newsService;
+
+    /**
+     * 鍒嗛〉鏌ヨ鏂伴椈鍒楄〃锛堢鐞嗙锛�
+     */
+    @QueryMapping
+    public PageResponse<NewsResponse> newsList(@Argument int page, @Argument int size, @Argument String title, @Argument Integer state) {
+        PageRequest pageRequest = new PageRequest(page, size);
+        return newsService.findNews(pageRequest, title, state);
+    }
+
+    /**
+     * 鑾峰彇鏂伴椈璇︽儏锛堢鐞嗙锛�
+     */
+    @QueryMapping
+    public NewsResponse news(@Argument Long id) {
+        return newsService.findById(id);
+    }
+
+    /**
+     * 鑾峰彇宸插彂甯冪殑鏂伴椈璇︽儏锛堝墠绔睍绀猴級
+     */
+    @QueryMapping
+    public NewsResponse publishedNews(@Argument Long id) {
+        return newsService.findPublishedNewsById(id);
+    }
+
+    /**
+     * 鑾峰彇宸插彂甯冪殑鏂伴椈鍒楄〃锛堝墠绔睍绀猴級
+     */
+    @QueryMapping
+    public PageResponse<NewsResponse> publishedNewsList(@Argument int page, @Argument int size) {
+        PageRequest pageRequest = new PageRequest(page, size);
+        return newsService.findPublishedNews(pageRequest);
+    }
+
+    /**
+     * 淇濆瓨鏂伴椈
+     */
+    @MutationMapping
+    public NewsResponse saveNews(@Argument NewsInput input) {
+        return newsService.saveNews(input);
+    }
+
+    /**
+     * 鍒犻櫎鏂伴椈
+     */
+    @MutationMapping
+    public Boolean deleteNews(@Argument Long id) {
+        return newsService.deleteNews(id);
+    }
+
+    /**
+     * 鏇存柊鏂伴椈鐘舵��
+     */
+    @MutationMapping
+    public Boolean updateNewsState(@Argument Long id, @Argument Integer state) {
+        return newsService.updateNewsState(id, state);
+    }
+}
\ No newline at end of file
diff --git a/backend/src/main/java/com/rongyichuang/news/service/NewsService.java b/backend/src/main/java/com/rongyichuang/news/service/NewsService.java
new file mode 100644
index 0000000..c9dd1d1
--- /dev/null
+++ b/backend/src/main/java/com/rongyichuang/news/service/NewsService.java
@@ -0,0 +1,153 @@
+package com.rongyichuang.news.service;
+
+import com.rongyichuang.news.dto.NewsInput;
+import com.rongyichuang.news.dto.NewsResponse;
+import com.rongyichuang.news.entity.News;
+import com.rongyichuang.news.repository.NewsRepository;
+import com.rongyichuang.common.dto.PageRequest;
+import com.rongyichuang.common.dto.PageResponse;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.util.StringUtils;
+
+import java.util.List;
+import java.util.Optional;
+import java.util.stream.Collectors;
+
+@Service
+@Transactional
+public class NewsService {
+
+    @Autowired
+    private NewsRepository newsRepository;
+
+    /**
+     * 鍒嗛〉鏌ヨ鏂伴椈鍒楄〃
+     */
+    public PageResponse<NewsResponse> findNews(PageRequest pageRequest, String title, Integer state) {
+        Pageable pageable = pageRequest.toPageable();
+        Page<News> page;
+
+        boolean hasTitle = StringUtils.hasText(title);
+        if (state != null) {
+            if (hasTitle) {
+                page = newsRepository.findByStateAndTitleContainingOrderByCreateTimeDesc(state, title, pageable);
+            } else {
+                page = newsRepository.findByStateOrderByCreateTimeDesc(state, pageable);
+            }
+        } else if (hasTitle) {
+            page = newsRepository.findByTitleContainingOrderByCreateTimeDesc(title, pageable);
+        } else {
+            page = newsRepository.findAll(pageable);
+        }
+
+        List<NewsResponse> content = page.getContent().stream()
+                .map(NewsResponse::new)
+                .collect(Collectors.toList());
+
+        return new PageResponse<>(content, page.getTotalElements(), page.getNumber(), page.getSize());
+    }
+
+    /**
+     * 鑾峰彇鏂伴椈璇︽儏
+     */
+    public NewsResponse findById(Long id) {
+        Optional<News> newsOpt = newsRepository.findById(id);
+        return newsOpt.map(NewsResponse::new).orElse(null);
+    }
+
+    /**
+     * 鑾峰彇宸插彂甯冪殑鏂伴椈璇︽儏锛堢敤浜庡墠绔睍绀猴級
+     */
+    public NewsResponse findPublishedNewsById(Long id) {
+        News news = newsRepository.findPublishedNewsById(id);
+        return news != null ? new NewsResponse(news) : null;
+    }
+
+    /**
+     * 淇濆瓨鏂伴椈锛堟柊澧炴垨缂栬緫锛�
+     */
+    public NewsResponse saveNews(NewsInput input) {
+        News news;
+
+        if (input.isNew()) {
+            // 鏂板鏂伴椈
+            news = new News();
+        } else {
+            // 缂栬緫鏂伴椈
+            Optional<News> existingOpt = newsRepository.findById(input.getId());
+            if (!existingOpt.isPresent()) {
+                throw new RuntimeException("鏂伴椈涓嶅瓨鍦�");
+            }
+            news = existingOpt.get();
+        }
+
+        // 璁剧疆鍩烘湰淇℃伅
+        news.setTitle(input.getTitle());
+        news.setContent(input.getContent());
+        news.setSummary(input.getSummary());
+        news.setCoverImage(input.getCoverImage());
+        news.setAuthor(input.getAuthor());
+        news.setState(input.getState());
+
+        // 淇濆瓨鏂伴椈
+        news = newsRepository.save(news);
+
+        return new NewsResponse(news);
+    }
+
+    /**
+     * 鍒犻櫎鏂伴椈锛堣蒋鍒犻櫎锛�
+     */
+    public boolean deleteNews(Long id) {
+        Optional<News> newsOpt = newsRepository.findById(id);
+        if (newsOpt.isPresent()) {
+            News news = newsOpt.get();
+            news.setState(2); // 璁剧疆涓哄叧闂姸鎬�
+            newsRepository.save(news);
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * 鏇存柊鏂伴椈鐘舵��
+     */
+    public boolean updateNewsState(Long id, Integer state) {
+        Optional<News> newsOpt = newsRepository.findById(id);
+        if (newsOpt.isPresent()) {
+            News news = newsOpt.get();
+            news.setState(state);
+            newsRepository.save(news);
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * 鑾峰彇宸插彂甯冪殑鏂伴椈鍒楄〃锛堢敤浜庡墠绔睍绀猴級
+     */
+    public List<NewsResponse> findPublishedNews() {
+        List<News> newsList = newsRepository.findPublishedNews();
+        return newsList.stream()
+                .map(NewsResponse::new)
+                .collect(Collectors.toList());
+    }
+
+    /**
+     * 鑾峰彇宸插彂甯冪殑鏂伴椈鍒楄〃锛堝垎椤碉紝鐢ㄤ簬鍓嶇灞曠ず锛�
+     */
+    public PageResponse<NewsResponse> findPublishedNews(PageRequest pageRequest) {
+        Pageable pageable = pageRequest.toPageable();
+        Page<News> page = newsRepository.findPublishedNews(pageable);
+
+        List<NewsResponse> content = page.getContent().stream()
+                .map(NewsResponse::new)
+                .collect(Collectors.toList());
+
+        return new PageResponse<>(content, page.getTotalElements(), page.getNumber(), page.getSize());
+    }
+}
\ No newline at end of file
diff --git a/backend/src/main/resources/graphql/news.graphqls b/backend/src/main/resources/graphql/news.graphqls
new file mode 100644
index 0000000..cbd9118
--- /dev/null
+++ b/backend/src/main/resources/graphql/news.graphqls
@@ -0,0 +1,55 @@
+type News {
+    id: ID!
+    title: String!
+    content: String
+    summary: String
+    coverImage: String
+    author: String
+    viewCount: Int!
+    state: Int!
+    stateName: String
+    createTime: String
+    updateTime: String
+}
+
+input NewsInput {
+    id: ID
+    title: String!
+    content: String
+    summary: String
+    coverImage: String
+    author: String
+    state: Int = 1
+}
+
+type NewsPageResponse {
+    content: [News!]!
+    totalElements: Long!
+    page: Int!
+    size: Int!
+}
+
+extend type Query {
+    # 鍒嗛〉鏌ヨ鏂伴椈鍒楄〃锛堢鐞嗙锛�
+    newsList(page: Int!, size: Int!, title: String, state: Int): NewsPageResponse
+    
+    # 鑾峰彇鏂伴椈璇︽儏锛堢鐞嗙锛�
+    news(id: ID!): News
+    
+    # 鑾峰彇宸插彂甯冪殑鏂伴椈璇︽儏锛堝墠绔睍绀猴級
+    publishedNews(id: ID!): News
+    
+    # 鑾峰彇宸插彂甯冪殑鏂伴椈鍒楄〃锛堝墠绔睍绀猴級
+    publishedNewsList(page: Int!, size: Int!): NewsPageResponse
+}
+
+extend type Mutation {
+    # 淇濆瓨鏂伴椈
+    saveNews(input: NewsInput!): News
+    
+    # 鍒犻櫎鏂伴椈
+    deleteNews(id: ID!): Boolean
+    
+    # 鏇存柊鏂伴椈鐘舵��
+    updateNewsState(id: ID!, state: Int!): Boolean
+}
\ No newline at end of file
diff --git a/news-module-readme.md b/news-module-readme.md
new file mode 100644
index 0000000..a2b247c
--- /dev/null
+++ b/news-module-readme.md
@@ -0,0 +1,97 @@
+# 鏂伴椈妯″潡璇存槑
+
+## 妯″潡姒傝堪
+鏂伴椈妯″潡鏄竴涓敤浜庣鐞嗙郴缁熸柊闂昏祫璁殑鍔熻兘妯″潡锛屾敮鎸佸瘜鏂囨湰鍐呭缂栬緫鍜屽彂甯冪鐞嗐��
+
+## 鍔熻兘鐗规��
+- 鏂伴椈鍒楄〃灞曠ず鍜岀鐞�
+- 瀵屾枃鏈唴瀹圭紪杈戯紙浣跨敤wangEditor锛�
+- 鏂伴椈鐘舵�佺鐞嗭紙鑽夌銆佸彂甯冦�佸叧闂級
+- 鍓嶇鏂伴椈灞曠ず椤甸潰
+- 鍝嶅簲寮忚璁�
+
+## 鏁版嵁搴撹〃缁撴瀯
+```sql
+CREATE TABLE `t_news` (
+  `id` bigint NOT NULL AUTO_INCREMENT,
+  `title` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL,
+  `content` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci,
+  `summary` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL,
+  `cover_image` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL,
+  `author` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL,
+  `view_count` int NOT NULL DEFAULT '0',
+  `state` int NOT NULL DEFAULT '1' COMMENT '0:鑽夌锛�1锛氬彂甯冿紝2锛氬叧闂�',
+  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
+  `create_user_id` bigint DEFAULT NULL,
+  `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+  `update_user_id` bigint DEFAULT NULL,
+  `version` bigint NOT NULL DEFAULT '0',
+  PRIMARY KEY (`id`) USING BTREE,
+  KEY `idx_t_news_state` (`state`) USING BTREE,
+  KEY `idx_t_news_create_time` (`create_time`) USING BTREE
+) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='鏂伴椈璧勮琛�';
+```
+
+## 鍚庣浠g爜缁撴瀯
+```
+com.rongyichuang.news
+鈹溾攢鈹� entity
+鈹�   鈹斺攢鈹� News.java
+鈹溾攢鈹� dto
+鈹�   鈹溾攢鈹� NewsInput.java
+鈹�   鈹斺攢鈹� NewsResponse.java
+鈹溾攢鈹� repository
+鈹�   鈹斺攢鈹� NewsRepository.java
+鈹溾攢鈹� service
+鈹�   鈹斺攢鈹� NewsService.java
+鈹斺攢鈹� resolver
+    鈹斺攢鈹� NewsResolver.java
+```
+
+## 鍓嶇浠g爜缁撴瀯
+```
+src/views/
+鈹溾攢鈹� news-list.vue          # 鍚庡彴鏂伴椈绠$悊鍒楄〃
+鈹溾攢鈹� NewsForm.vue           # 鍚庡彴鏂伴椈缂栬緫琛ㄥ崟
+鈹溾攢鈹� NewsListPage.vue       # 鍓嶅彴鏂伴椈鍒楄〃灞曠ず
+鈹斺攢鈹� NewsDetail.vue         # 鍓嶅彴鏂伴椈璇︽儏灞曠ず
+
+src/api/
+鈹斺攢鈹� news.js               # 鏂伴椈鐩稿叧API鎺ュ彛
+```
+
+## GraphQL API鎺ュ彛
+
+### 鏌ヨ鎺ュ彛
+1. `newsList` - 鑾峰彇鏂伴椈鍒楄〃锛堝悗鍙扮鐞嗭級
+2. `news` - 鑾峰彇鏂伴椈璇︽儏锛堝悗鍙扮鐞嗭級
+3. `publishedNews` - 鑾峰彇宸插彂甯冪殑鏂伴椈璇︽儏锛堝墠绔睍绀猴級
+4. `publishedNewsList` - 鑾峰彇宸插彂甯冪殑鏂伴椈鍒楄〃锛堝墠绔睍绀猴級
+
+### 鍙樻洿鎺ュ彛
+1. `saveNews` - 淇濆瓨鏂伴椈
+2. `deleteNews` - 鍒犻櫎鏂伴椈锛堣蒋鍒犻櫎锛�
+3. `updateNewsState` - 鏇存柊鏂伴椈鐘舵��
+
+## 璺敱閰嶇疆
+- `/news` - 鏂伴椈绠$悊鍒楄〃
+- `/news/new` - 鏂板鏂伴椈
+- `/news/edit/:id` - 缂栬緫鏂伴椈
+- `/news/list` - 鍓嶅彴鏂伴椈鍒楄〃
+- `/news/detail/:id` - 鍓嶅彴鏂伴椈璇︽儏
+
+## 渚濊禆瀹夎
+```bash
+npm install @wangeditor/editor @wangeditor/editor-for-vue --legacy-peer-deps
+```
+
+## 浣跨敤璇存槑
+1. 鍦ㄥ悗鍙扮鐞嗙郴缁熶腑锛岄�氳繃"鏂伴椈绠$悊"鑿滃崟杩涘叆鏂伴椈鍒楄〃椤甸潰
+2. 鍙互鏂板銆佺紪杈戙�佸垹闄ゆ柊闂�
+3. 璁剧疆鏂伴椈鐘舵�佷负"鍙戝竷"鍚庯紝鏂伴椈灏嗗湪鍓嶇灞曠ず椤甸潰鏄剧ず
+4. 鍓嶇鐢ㄦ埛鍙互閫氳繃鏂伴椈鍒楄〃椤甸潰鏌ョ湅宸插彂甯冪殑鏂伴椈
+
+## 娉ㄦ剰浜嬮」
+1. 闇�瑕丣ava 17鐜缂栬瘧鍚庣浠g爜
+2. 鍓嶇闇�瑕佸畨瑁厀angEditor瀵屾枃鏈紪杈戝櫒渚濊禆
+3. 鏁版嵁搴撻渶瑕佹墽琛宼_news琛ㄧ殑鍒涘缓璇彞
\ No newline at end of file
diff --git a/web/package.json b/web/package.json
index bbd2e19..ecffab0 100644
--- a/web/package.json
+++ b/web/package.json
@@ -13,6 +13,7 @@
   "dependencies": {
     "@element-plus/icons-vue": "^2.1.0",
     "@urql/vue": "^2.0.0",
+    "@wangeditor/editor": "^5.1.23",
     "axios": "^1.6.2",
     "cos-js-sdk-v5": "^1.10.1",
     "dayjs": "^1.11.10",
@@ -42,4 +43,4 @@
     "vite": "^5.0.0",
     "vue-tsc": "^1.8.22"
   }
-}
+}
\ No newline at end of file
diff --git a/web/src/api/news.js b/web/src/api/news.js
new file mode 100644
index 0000000..3bb8c42
--- /dev/null
+++ b/web/src/api/news.js
@@ -0,0 +1,196 @@
+// 鏂伴椈绠$悊 API
+
+import { graphqlRequest } from '@/config/api'
+
+// GraphQL 鏌ヨ璇彞
+const GET_NEWS_LIST_QUERY = `
+  query GetNewsList($page: Int!, $size: Int!, $title: String, $state: Int) {
+    newsList(page: $page, size: $size, title: $title, state: $state) {
+      content {
+        id
+        title
+        summary
+        coverImage
+        author
+        viewCount
+        state
+        stateName
+        createTime
+        updateTime
+      }
+      totalElements
+      page
+      size
+    }
+  }
+`
+
+const GET_NEWS_QUERY = `
+  query GetNews($id: ID!) {
+    news(id: $id) {
+      id
+      title
+      content
+      summary
+      coverImage
+      author
+      viewCount
+      state
+      stateName
+      createTime
+      updateTime
+    }
+  }
+`
+
+const GET_PUBLISHED_NEWS_QUERY = `
+  query GetPublishedNews($id: ID!) {
+    publishedNews(id: $id) {
+      id
+      title
+      content
+      summary
+      coverImage
+      author
+      viewCount
+      state
+      stateName
+      createTime
+      updateTime
+    }
+  }
+`
+
+const GET_PUBLISHED_NEWS_LIST_QUERY = `
+  query GetPublishedNewsList($page: Int!, $size: Int!) {
+    publishedNewsList(page: $page, size: $size) {
+      content {
+        id
+        title
+        summary
+        coverImage
+        author
+        viewCount
+        state
+        stateName
+        createTime
+        updateTime
+      }
+      totalElements
+      page
+      size
+    }
+  }
+`
+
+const SAVE_NEWS_MUTATION = `
+  mutation SaveNews($input: NewsInput!) {
+    saveNews(input: $input) {
+      id
+      title
+      content
+      summary
+      coverImage
+      author
+      viewCount
+      state
+      stateName
+      createTime
+      updateTime
+    }
+  }
+`
+
+const DELETE_NEWS_MUTATION = `
+  mutation DeleteNews($id: ID!) {
+    deleteNews(id: $id)
+  }
+`
+
+const UPDATE_NEWS_STATE_MUTATION = `
+  mutation UpdateNewsState($id: ID!, $state: Int!) {
+    updateNewsState(id: $id, state: $state)
+  }
+`
+
+// API 鍑芥暟
+export const getNewsList = async (page = 0, size = 10, title = '', state) => {
+  try {
+    const variables = {
+      page,
+      size,
+      title
+    }
+
+    if (state !== undefined && state !== null && state !== '') {
+      variables.state = Number(state)
+    }
+
+    const result = await graphqlRequest(GET_NEWS_LIST_QUERY, variables)
+    return result.data.newsList
+  } catch (error) {
+    console.error('鑾峰彇鏂伴椈鍒楄〃澶辫触:', error)
+    throw new Error(error && error.message ? error.message : '鑾峰彇鏂伴椈鍒楄〃澶辫触')
+  }
+}
+
+export const getNews = async (id) => {
+  try {
+    const result = await graphqlRequest(GET_NEWS_QUERY, { id })
+    return result.data.news
+  } catch (error) {
+    throw new Error(error && error.message ? error.message : '鑾峰彇鏂伴椈璇︽儏澶辫触')
+  }
+}
+
+export const getPublishedNews = async (id) => {
+  try {
+    const result = await graphqlRequest(GET_PUBLISHED_NEWS_QUERY, { id })
+    return result.data.publishedNews
+  } catch (error) {
+    throw new Error(error && error.message ? error.message : '鑾峰彇鏂伴椈璇︽儏澶辫触')
+  }
+}
+
+export const getPublishedNewsList = async (page = 0, size = 10) => {
+  try {
+    const variables = {
+      page,
+      size
+    }
+
+    const result = await graphqlRequest(GET_PUBLISHED_NEWS_LIST_QUERY, variables)
+    return result.data.publishedNewsList
+  } catch (error) {
+    console.error('鑾峰彇鏂伴椈鍒楄〃澶辫触:', error)
+    throw new Error(error && error.message ? error.message : '鑾峰彇鏂伴椈鍒楄〃澶辫触')
+  }
+}
+
+export const saveNews = async (newsData) => {
+  try {
+    const data = await graphqlRequest(SAVE_NEWS_MUTATION, { input: newsData })
+    return data.data.saveNews
+  } catch (error) {
+    console.error('淇濆瓨鏂伴椈澶辫触:', error)
+    throw new Error(error && error.message ? error.message : '淇濆瓨鏂伴椈澶辫触')
+  }
+}
+
+export const deleteNews = async (id) => {
+  try {
+    const data = await graphqlRequest(DELETE_NEWS_MUTATION, { id })
+    return data.data.deleteNews
+  } catch (error) {
+    throw new Error(error && error.message ? error.message : '鍒犻櫎鏂伴椈澶辫触')
+  }
+}
+
+export const updateNewsState = async (id, state) => {
+  try {
+    const data = await graphqlRequest(UPDATE_NEWS_STATE_MUTATION, { id, state })
+    return data.data.updateNewsState
+  } catch (error) {
+    throw new Error(error && error.message ? error.message : '鏇存柊鏂伴椈鐘舵�佸け璐�')
+  }
+}
\ No newline at end of file
diff --git a/web/src/layout/index.vue b/web/src/layout/index.vue
index 9fdfd8d..068ac33 100644
--- a/web/src/layout/index.vue
+++ b/web/src/layout/index.vue
@@ -34,7 +34,6 @@
           text-color="#666666"
           active-text-color="#1E3A8A"
         >
-        >
           <el-menu-item index="/dashboard">
             <el-icon><House /></el-icon>
             <span>宸ヤ綔鍙�</span>
@@ -64,9 +63,13 @@
             <el-icon><TrendCharts /></el-icon>
             <span>姣旇禌鏅嬬骇</span>
           </el-menu-item>
+          <el-menu-item index="/news">
+            <el-icon><Document /></el-icon>
+            <span>鏂伴椈绠$悊</span>
+          </el-menu-item>
           <el-menu-item index="/carousel">
             <el-icon><Picture /></el-icon>
-            <span>鏂伴椈涓庢帹骞�</span>
+            <span>Banner</span>
           </el-menu-item>
           <el-menu-item index="/region">
             <el-icon><Location /></el-icon>
diff --git a/web/src/router/index.ts b/web/src/router/index.ts
index 3aa9c0a..01a3721 100644
--- a/web/src/router/index.ts
+++ b/web/src/router/index.ts
@@ -81,6 +81,36 @@
         meta: { title: '璇勫垎璇︽儏', icon: 'Edit' }
       },
       {
+        path: '/news',
+        name: 'News',
+        component: () => import('@/views/news-list.vue'),
+        meta: { title: '鏂伴椈绠$悊', icon: 'Document' }
+      },
+      {
+        path: '/news/new',
+        name: 'NewsCreate',
+        component: () => import('@/views/NewsForm.vue'),
+        meta: { title: '鏂板鏂伴椈', hidden: true }
+      },
+      {
+        path: '/news/edit/:id',
+        name: 'NewsEdit',
+        component: () => import('@/views/NewsForm.vue'),
+        meta: { title: '缂栬緫鏂伴椈', hidden: true }
+      },
+      {
+        path: '/news/list',
+        name: 'NewsListPage',
+        component: () => import('@/views/NewsListPage.vue'),
+        meta: { title: '鏂伴椈鍒楄〃', hidden: true }
+      },
+      {
+        path: '/news/detail/:id',
+        name: 'NewsDetail',
+        component: () => import('@/views/NewsDetail.vue'),
+        meta: { title: '鏂伴椈璇︽儏', hidden: true }
+      },
+      {
         path: '/carousel',
         name: 'Carousel',
         component: () => import('@/views/carousel/index.vue'),
diff --git a/web/src/views/NewsDetail.vue b/web/src/views/NewsDetail.vue
new file mode 100644
index 0000000..588afaa
--- /dev/null
+++ b/web/src/views/NewsDetail.vue
@@ -0,0 +1,182 @@
+<template>
+  <div class="news-detail">
+    <el-card v-loading="loading">
+      <template #header>
+        <div class="news-header">
+          <h1 class="news-title">{{ news.title }}</h1>
+          <div class="news-meta">
+            <span class="author">浣滆�咃細{{ news.author }}</span>
+            <span class="time">鍙戝竷鏃堕棿锛歿{ formatDate(news.createTime) }}</span>
+            <span class="views">娴忚閲忥細{{ news.viewCount }}</span>
+          </div>
+        </div>
+      </template>
+      
+      <!-- 灏侀潰鍥剧墖 -->
+      <div v-if="news.coverImage" class="cover-image-container">
+        <el-image
+          :src="news.coverImage"
+          class="cover-image"
+          fit="cover"
+          :preview-src-list="[news.coverImage]"
+          preview-teleported
+        />
+      </div>
+      
+      <div class="news-content" v-html="news.content"></div>
+      
+      <div class="news-footer">
+        <el-button @click="goBack">杩斿洖鍒楄〃</el-button>
+      </div>
+    </el-card>
+  </div>
+</template>
+
+<script setup>
+import { ref, onMounted } from 'vue'
+import { useRoute, useRouter } from 'vue-router'
+import { ElMessage } from 'element-plus'
+import { getPublishedNews } from '@/api/news'
+
+const route = useRoute()
+const router = useRouter()
+
+const loading = ref(false)
+const news = ref({
+  id: null,
+  title: '',
+  content: '',
+  summary: '',
+  coverImage: '',
+  author: '',
+  viewCount: 0,
+  state: 1,
+  createTime: '',
+  updateTime: ''
+})
+
+// 鏍煎紡鍖栨棩鏈�
+const formatDate = (dateString) => {
+  if (!dateString) return ''
+  const date = new Date(dateString)
+  return date.toLocaleString('zh-CN', {
+    year: 'numeric',
+    month: '2-digit',
+    day: '2-digit',
+    hour: '2-digit',
+    minute: '2-digit'
+  })
+}
+
+// 鍔犺浇鏂伴椈鏁版嵁
+const loadNews = async () => {
+  try {
+    loading.value = true
+    const data = await getPublishedNews(route.params.id)
+    
+    if (data) {
+      news.value = data
+    } else {
+      ElMessage.error('鏂伴椈涓嶅瓨鍦ㄦ垨宸蹭笅鏋�')
+      router.push('/news/list')
+    }
+  } catch (error) {
+    console.error('鍔犺浇鏂伴椈澶辫触:', error)
+    ElMessage.error('鍔犺浇鏂伴椈澶辫触')
+    router.push('/news/list')
+  } finally {
+    loading.value = false
+  }
+}
+
+// 杩斿洖
+const goBack = () => {
+  router.go(-1)
+}
+
+onMounted(() => {
+  loadNews()
+})
+</script>
+
+<style scoped>
+.news-detail {
+  padding: 20px;
+  max-width: 800px;
+  margin: 0 auto;
+}
+
+.news-header {
+  text-align: center;
+}
+
+.news-title {
+  font-size: 24px;
+  font-weight: 600;
+  color: #333;
+  margin-bottom: 16px;
+}
+
+.news-meta {
+  display: flex;
+  justify-content: center;
+  gap: 20px;
+  font-size: 14px;
+  color: #666;
+  margin-bottom: 20px;
+}
+
+/* 灏侀潰鍥剧墖鏍峰紡 */
+.cover-image-container {
+  text-align: center;
+  margin-bottom: 20px;
+}
+
+.cover-image {
+  max-width: 100%;
+  max-height: 400px;
+  border-radius: 8px;
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
+}
+
+.news-content {
+  line-height: 1.8;
+  font-size: 16px;
+  color: #333;
+}
+
+.news-content :deep(img) {
+  max-width: 100%;
+  height: auto;
+  display: block;
+  margin: 10px auto;
+}
+
+.news-content :deep(p) {
+  margin: 10px 0;
+}
+
+.news-footer {
+  margin-top: 30px;
+  text-align: center;
+}
+
+@media (max-width: 768px) {
+  .news-detail {
+    padding: 10px;
+  }
+  
+  .news-meta {
+    flex-direction: column;
+    gap: 5px;
+  }
+  
+  .news-title {
+    font-size: 20px;
+  }
+  
+  .cover-image {
+    max-height: 200px;
+  }
+}
+</style>
\ No newline at end of file
diff --git a/web/src/views/NewsForm.vue b/web/src/views/NewsForm.vue
new file mode 100644
index 0000000..5adba44
--- /dev/null
+++ b/web/src/views/NewsForm.vue
@@ -0,0 +1,408 @@
+<template>
+  <div class="news-form">
+    <el-card>
+      <template #header>
+        <div class="card-header">
+          <span>{{ isEdit ? '缂栬緫鏂伴椈' : '鏂板鏂伴椈' }}</span>
+          <el-button @click="goBack">杩斿洖</el-button>
+        </div>
+      </template>
+
+      <el-form
+        ref="formRef"
+        :model="form"
+        :rules="rules"
+        label-width="120px"
+        v-loading="loading"
+        @submit.prevent
+      >
+        <el-row :gutter="20">
+          <el-col :span="12">
+            <el-form-item label="鏂伴椈鏍囬" prop="title">
+              <el-input v-model="form.title" placeholder="璇疯緭鍏ユ柊闂绘爣棰�" maxlength="100" />
+            </el-form-item>
+          </el-col>
+          
+          <el-col :span="12">
+            <el-form-item label="浣滆��" prop="author">
+              <el-input v-model="form.author" placeholder="璇疯緭鍏ヤ綔鑰呭鍚�" maxlength="50" />
+            </el-form-item>
+          </el-col>
+        </el-row>
+
+        <el-form-item label="鎽樿" prop="summary">
+          <el-input
+            v-model="form.summary"
+            type="textarea"
+            :rows="3"
+            placeholder="璇疯緭鍏ユ柊闂绘憳瑕�"
+            maxlength="500"
+          />
+        </el-form-item>
+
+        <el-form-item label="灏侀潰鍥剧墖" prop="coverImage">
+          <div class="cover-upload-container">
+            <el-upload
+              class="cover-uploader"
+              :action="uploadUrl"
+              :headers="uploadHeaders"
+              :show-file-list="false"
+              :on-success="handleCoverUploadSuccess"
+              :before-upload="beforeCoverUpload"
+            >
+              <img v-if="form.coverImage" :src="form.coverImage" class="cover" />
+              <div v-else class="cover-uploader-placeholder">
+                <i class="el-icon-plus cover-uploader-icon"></i>
+                <div class="cover-uploader-text">鐐瑰嚮涓婁紶鍥剧墖</div>
+              </div>
+            </el-upload>
+          </div>
+        </el-form-item>
+
+        <el-form-item label="鏂伴椈鍐呭" prop="content">
+          <!-- 娣诲姞宸ュ叿鏍忓鍣� -->
+          <div class="editor-container">
+            <div ref="toolbarRef" class="editor-toolbar"></div>
+            <div ref="editorRef" class="editor-content"></div>
+          </div>
+        </el-form-item>
+
+        <el-form-item label="鐘舵��" prop="state">
+          <el-radio-group v-model="form.state">
+            <el-radio :label="0">鑽夌</el-radio>
+            <el-radio :label="1">鍙戝竷</el-radio>
+            <el-radio :label="2">鍏抽棴</el-radio>
+          </el-radio-group>
+        </el-form-item>
+
+        <el-form-item>
+          <el-button type="primary" @click="handleSubmit" :loading="submitting">
+            {{ isEdit ? '鏇存柊' : '鍒涘缓' }}
+          </el-button>
+          <el-button @click="goBack">鍙栨秷</el-button>
+        </el-form-item>
+      </el-form>
+    </el-card>
+  </div>
+</template>
+
+<script setup>
+import { ref, onMounted, onBeforeUnmount, shallowRef } from 'vue'
+import { useRouter, useRoute } from 'vue-router'
+import { ElMessage } from 'element-plus'
+import { getNews, saveNews } from '@/api/news'
+import { getToken } from '@/utils/auth'
+// 浣跨敤wangEditor鐨勬牳蹇冨寘鑰屼笉鏄疺ue鍖呰鍣�
+import { createEditor, createToolbar } from '@wangeditor/editor'
+import '@wangeditor/editor/dist/css/style.css'
+
+const router = useRouter()
+const route = useRoute()
+
+// 缂栬緫鍣ㄥ疄渚�
+const editorRef = ref()
+const toolbarRef = ref()
+const editorInstance = shallowRef()
+const toolbarInstance = shallowRef()
+
+const loading = ref(false)
+const submitting = ref(false)
+const formRef = ref()
+
+// 涓婁紶閰嶇疆 - 淇URL璺緞锛屾坊鍔�/api鍓嶇紑
+const uploadUrl = `${import.meta.env.VITE_API_BASE_URL || ''}/api/upload/image`
+const uploadHeaders = {
+  Authorization: `Bearer ${getToken()}`
+}
+
+// 琛ㄥ崟鏁版嵁
+const form = ref({
+  id: null,
+  title: '',
+  content: '',
+  summary: '',
+  coverImage: '',
+  author: '',
+  state: 1
+})
+
+// 琛ㄥ崟楠岃瘉瑙勫垯
+const rules = {
+  title: [
+    { required: true, message: '璇疯緭鍏ユ柊闂绘爣棰�', trigger: 'blur' },
+    { max: 100, message: '鏂伴椈鏍囬涓嶈兘瓒呰繃100涓瓧绗�', trigger: 'blur' }
+  ],
+  author: [
+    { required: true, message: '璇疯緭鍏ヤ綔鑰呭鍚�', trigger: 'blur' },
+    { max: 50, message: '浣滆�呭鍚嶄笉鑳借秴杩�50涓瓧绗�', trigger: 'blur' }
+  ],
+  content: [
+    { required: true, message: '璇疯緭鍏ユ柊闂诲唴瀹�', trigger: 'blur' }
+  ]
+}
+
+// 璁$畻灞炴��
+const isEdit = ref(!!route.params.id)
+
+// 鍒濆鍖栫紪杈戝櫒
+const initEditor = () => {
+  if (!editorRef.value || !toolbarRef.value) return
+
+  // 鍒涘缓缂栬緫鍣�
+  const editor = createEditor({
+    selector: editorRef.value,
+    html: form.value.content,
+    config: {
+      placeholder: '璇疯緭鍏ユ柊闂诲唴瀹�...',
+      MENU_CONF: {
+        'uploadImage': {
+          server: `${import.meta.env.VITE_API_BASE_URL || ''}/api/upload/image`,
+          fieldName: 'file',
+          headers: {
+            Authorization: `Bearer ${getToken()}`
+          },
+          maxFileSize: 2 * 1024 * 1024, // 2MB
+          base64LimitSize: 5 * 1024, // 5KB浠ヤ笅鎻掑叆base64
+          // wangEditor鏈熸湜鐨勫搷搴旀牸寮忓鐞�
+          onSuccess(file, res) {
+            console.log('鍥剧墖涓婁紶鎴愬姛', file, res)
+          },
+          onError(file, res) {
+            console.error('鍥剧墖涓婁紶澶辫触', file, res)
+            ElMessage.error('鍥剧墖涓婁紶澶辫触: ' + (res?.message || res?.data?.message || '鏈煡閿欒'))
+          }
+        }
+      },
+      onChange(editor) {
+        // 褰撶紪杈戝櫒鍐呭鏀瑰彉鏃讹紝鏇存柊琛ㄥ崟鏁版嵁
+        form.value.content = editor.getHtml()
+      }
+    }
+  })
+
+  // 鍒涘缓宸ュ叿鏍�
+  const toolbar = createToolbar({
+    editor: editor,
+    selector: toolbarRef.value,
+    config: {
+      // 宸ュ叿鏍忛厤缃�
+    }
+  })
+
+  editorInstance.value = editor
+  toolbarInstance.value = toolbar
+}
+
+// 灏侀潰鍥剧墖涓婁紶鍓嶆鏌�
+const beforeCoverUpload = (file) => {
+  const isJPG = file.type === 'image/jpeg' || file.type === 'image/png'
+  const isLt2M = file.size / 1024 / 1024 < 2
+
+  if (!isJPG) {
+    ElMessage.error('灏侀潰鍥剧墖鍙兘鏄� JPG 鎴� PNG 鏍煎紡!')
+  }
+  if (!isLt2M) {
+    ElMessage.error('灏侀潰鍥剧墖澶у皬涓嶈兘瓒呰繃 2MB!')
+  }
+  return isJPG && isLt2M
+}
+
+// 灏侀潰鍥剧墖涓婁紶鎴愬姛澶勭悊
+const handleCoverUploadSuccess = (response) => {
+  // 澶勭悊鍚庣杩斿洖鐨勬柊鏍煎紡
+  if (response && response.errno === 0 && response.data) {
+    form.value.coverImage = response.data.url
+    ElMessage.success('灏侀潰鍥剧墖涓婁紶鎴愬姛')
+  } else if (response && response.success) {
+    // 鍏煎鏃ф牸寮�
+    form.value.coverImage = response.data?.fullUrl || response.data?.url || response.url
+    ElMessage.success('灏侀潰鍥剧墖涓婁紶鎴愬姛')
+  } else {
+    ElMessage.error('灏侀潰鍥剧墖涓婁紶澶辫触: ' + (response?.message || '鏈煡閿欒'))
+  }
+}
+
+// 鍔犺浇鏂伴椈鏁版嵁锛堢紪杈戞ā寮忥級
+const loadNews = async () => {
+  if (!isEdit.value) return
+  
+  try {
+    loading.value = true
+    const news = await getNews(route.params.id)
+    
+    if (news) {
+      form.value = {
+        id: news.id,
+        title: news.title,
+        content: news.content,
+        summary: news.summary,
+        coverImage: news.coverImage,
+        author: news.author,
+        state: news.state
+      }
+      
+      // 鏇存柊缂栬緫鍣ㄥ唴瀹�
+      if (editorInstance.value) {
+        editorInstance.value.setHtml(news.content)
+      }
+    }
+  } catch (error) {
+    console.error('鍔犺浇鏂伴椈鏁版嵁澶辫触:', error)
+    ElMessage.error('鍔犺浇鏂伴椈鏁版嵁澶辫触')
+  } finally {
+    loading.value = false
+  }
+}
+
+// 鎻愪氦琛ㄥ崟
+const handleSubmit = async () => {
+  if (submitting.value) return
+  
+  try {
+    await formRef.value.validate()
+    
+    submitting.value = true
+    
+    // 鍑嗗淇濆瓨鏁版嵁
+    const saveData = {
+      title: form.value.title,
+      content: form.value.content,
+      summary: form.value.summary,
+      coverImage: form.value.coverImage,
+      author: form.value.author,
+      state: form.value.state
+    }
+
+    // 濡傛灉鏄紪杈戞ā寮忥紝娣诲姞id瀛楁
+    if (isEdit.value && form.value.id) {
+      saveData.id = form.value.id
+    }
+    
+    const result = await saveNews(saveData)
+    
+    ElMessage.success(isEdit.value ? '鏇存柊鎴愬姛' : '鍒涘缓鎴愬姛')
+    
+    // 淇濆瓨鎴愬姛鍚庤繑鍥炲垪琛ㄩ〉闈�
+    goBack()
+  } catch (error) {
+    console.error('淇濆瓨鏂伴椈澶辫触:', error)
+    if (error.message) {
+      ElMessage.error('淇濆瓨澶辫触: ' + error.message)
+    } else {
+      ElMessage.error('淇濆瓨澶辫触: 鏈煡閿欒')
+    }
+  } finally {
+    submitting.value = false
+  }
+}
+
+// 杩斿洖
+const goBack = () => {
+  router.push('/news')
+}
+
+// 缁勪欢閿�姣佸墠娓呯悊缂栬緫鍣�
+onBeforeUnmount(() => {
+  if (editorInstance.value) {
+    editorInstance.value.destroy()
+  }
+})
+
+// 鐢熷懡鍛ㄦ湡
+onMounted(async () => {
+  await loadNews()
+  // 纭繚DOM宸叉覆鏌撳悗鍐嶅垵濮嬪寲缂栬緫鍣�
+  setTimeout(() => {
+    initEditor()
+  }, 100)
+})
+</script>
+
+<style scoped>
+.news-form {
+  padding: 20px;
+}
+
+.card-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+}
+
+/* 灏侀潰鍥剧墖涓婁紶瀹瑰櫒 */
+.cover-upload-container {
+  display: inline-block;
+}
+
+.cover-uploader .el-upload {
+  border: 2px dashed #d9d9d9;
+  border-radius: 6px;
+  cursor: pointer;
+  position: relative;
+  overflow: hidden;
+  transition: .2s;
+  width: 178px;
+  height: 178px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  background-color: #fafafa;
+}
+
+.cover-uploader .el-upload:hover {
+  border-color: #409eff;
+  background-color: #f5f9ff;
+}
+
+.cover-uploader-placeholder {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  width: 100%;
+  height: 100%;
+}
+
+.cover-uploader-icon {
+  font-size: 28px;
+  color: #8c939d;
+  margin-bottom: 8px;
+}
+
+.cover-uploader-text {
+  font-size: 12px;
+  color: #666;
+  text-align: center;
+}
+
+.cover {
+  width: 178px;
+  height: 178px;
+  display: block;
+  object-fit: cover;
+}
+
+/* 缂栬緫鍣ㄥ鍣ㄦ牱寮� */
+.editor-container {
+  border: 1px solid #dcdfe6;
+  border-radius: 4px;
+}
+
+.editor-toolbar {
+  border-bottom: 1px solid #dcdfe6;
+  background-color: #f5f7fa;
+}
+
+.editor-content {
+  height: 400px;
+  overflow-y: auto;
+  background-color: #fff;
+}
+
+/* 鍝嶅簲寮忚璁� */
+@media (max-width: 768px) {
+  .editor-content {
+    height: 300px;
+  }
+}
+</style>
\ No newline at end of file
diff --git a/web/src/views/NewsListPage.vue b/web/src/views/NewsListPage.vue
new file mode 100644
index 0000000..ca75ea6
--- /dev/null
+++ b/web/src/views/NewsListPage.vue
@@ -0,0 +1,277 @@
+<template>
+  <div class="news-list-page">
+    <div class="page-header">
+      <h1 class="page-title">鏂伴椈璧勮</h1>
+      <p class="page-description">浜嗚В鏈�鏂扮殑娲诲姩鍔ㄦ�佸拰琛屼笟璧勮</p>
+    </div>
+
+    <div class="news-list" v-loading="loading">
+      <div v-if="newsList.length === 0" class="empty-state">
+        <el-empty description="鏆傛棤鏂伴椈璧勮" />
+      </div>
+      
+      <div v-else class="news-items">
+        <div 
+          v-for="news in newsList" 
+          :key="news.id" 
+          class="news-item"
+          @click="goToDetail(news.id)"
+        >
+          <div class="news-item-content">
+            <div class="news-item-header">
+              <h3 class="news-item-title">{{ news.title }}</h3>
+              <div class="news-item-meta">
+                <span class="news-item-author">{{ news.author }}</span>
+                <span class="news-item-date">{{ formatDate(news.createTime) }}</span>
+                <span class="news-item-views">娴忚 {{ news.viewCount }}</span>
+              </div>
+            </div>
+            
+            <div class="news-item-body">
+              <div v-if="news.coverImage" class="news-item-image">
+                <el-image
+                  :src="news.coverImage"
+                  class="cover-image"
+                  fit="cover"
+                  lazy
+                />
+              </div>
+              
+              <p class="news-item-summary" v-if="news.summary">
+                {{ news.summary }}
+              </p>
+            </div>
+          </div>
+        </div>
+      </div>
+    </div>
+
+    <!-- 鍒嗛〉 -->
+    <div class="pagination" v-if="pagination.total > 0">
+      <el-pagination
+        v-model:current-page="pagination.page"
+        v-model:page-size="pagination.size"
+        :page-sizes="[10, 20, 50]"
+        :total="pagination.total"
+        layout="total, sizes, prev, pager, next, jumper"
+        @size-change="handleSizeChange"
+        @current-change="handleCurrentChange"
+      />
+    </div>
+  </div>
+</template>
+
+<script setup>
+import { ref, onMounted } from 'vue'
+import { useRouter } from 'vue-router'
+import { getPublishedNewsList } from '@/api/news'
+
+const router = useRouter()
+const loading = ref(false)
+
+// 鍒嗛〉淇℃伅
+const pagination = ref({
+  page: 1,
+  size: 10,
+  total: 0
+})
+
+// 鏂伴椈鍒楄〃
+const newsList = ref([])
+
+// 鏍煎紡鍖栨棩鏈�
+const formatDate = (dateString) => {
+  if (!dateString) return ''
+  const date = new Date(dateString)
+  return date.toLocaleDateString('zh-CN', {
+    year: 'numeric',
+    month: '2-digit',
+    day: '2-digit'
+  })
+}
+
+// 鍔犺浇鏂伴椈鍒楄〃
+const loadNewsList = async () => {
+  try {
+    loading.value = true
+    const data = await getPublishedNewsList(
+      pagination.value.page - 1,
+      pagination.value.size
+    )
+    
+    newsList.value = data?.content || []
+    pagination.value.total = data?.totalElements || 0
+  } catch (error) {
+    console.error('鍔犺浇鏂伴椈鍒楄〃澶辫触:', error)
+  } finally {
+    loading.value = false
+  }
+}
+
+// 鍒嗛〉澶у皬鏀瑰彉
+const handleSizeChange = (size) => {
+  pagination.value.size = size
+  loadNewsList()
+}
+
+// 褰撳墠椤垫敼鍙�
+const handleCurrentChange = (page) => {
+  pagination.value.page = page
+  loadNewsList()
+}
+
+// 璺宠浆鍒拌鎯呴〉
+const goToDetail = (id) => {
+  router.push(`/news/detail/${id}`)
+}
+
+onMounted(() => {
+  loadNewsList()
+})
+</script>
+
+<style scoped>
+.news-list-page {
+  padding: 20px;
+  max-width: 1200px;
+  margin: 0 auto;
+}
+
+.page-header {
+  text-align: center;
+  margin-bottom: 30px;
+}
+
+.page-title {
+  font-size: 28px;
+  font-weight: 600;
+  color: #333;
+  margin-bottom: 10px;
+}
+
+.page-description {
+  font-size: 16px;
+  color: #666;
+  margin: 0;
+}
+
+.news-list {
+  min-height: 400px;
+}
+
+.empty-state {
+  padding: 40px 0;
+}
+
+.news-items {
+  display: flex;
+  flex-direction: column;
+  gap: 20px;
+}
+
+.news-item {
+  background: white;
+  border-radius: 8px;
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
+  overflow: hidden;
+  transition: all 0.3s ease;
+  cursor: pointer;
+}
+
+.news-item:hover {
+  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);
+  transform: translateY(-2px);
+}
+
+.news-item-content {
+  padding: 20px;
+}
+
+.news-item-header {
+  margin-bottom: 15px;
+}
+
+.news-item-title {
+  font-size: 20px;
+  font-weight: 600;
+  color: #333;
+  margin: 0 0 10px 0;
+  line-height: 1.4;
+}
+
+.news-item-meta {
+  display: flex;
+  gap: 20px;
+  font-size: 14px;
+  color: #666;
+}
+
+.news-item-body {
+  display: flex;
+  gap: 20px;
+}
+
+.news-item-image {
+  flex: 0 0 200px;
+}
+
+.cover-image {
+  width: 100%;
+  height: 120px;
+  border-radius: 4px;
+}
+
+.news-item-summary {
+  flex: 1;
+  font-size: 15px;
+  color: #555;
+  line-height: 1.6;
+  margin: 0;
+  display: -webkit-box;
+  -webkit-line-clamp: 3;
+  -webkit-box-orient: vertical;
+  overflow: hidden;
+}
+
+.pagination {
+  margin-top: 30px;
+  display: flex;
+  justify-content: center;
+}
+
+@media (max-width: 768px) {
+  .news-list-page {
+    padding: 10px;
+  }
+  
+  .page-title {
+    font-size: 24px;
+  }
+  
+  .news-item-content {
+    padding: 15px;
+  }
+  
+  .news-item-title {
+    font-size: 18px;
+  }
+  
+  .news-item-meta {
+    flex-direction: column;
+    gap: 5px;
+  }
+  
+  .news-item-body {
+    flex-direction: column;
+    gap: 15px;
+  }
+  
+  .news-item-image {
+    flex: none;
+  }
+  
+  .cover-image {
+    height: 150px;
+  }
+}
+</style>
\ No newline at end of file
diff --git a/web/src/views/news-list.vue b/web/src/views/news-list.vue
new file mode 100644
index 0000000..92af6ae
--- /dev/null
+++ b/web/src/views/news-list.vue
@@ -0,0 +1,359 @@
+<template>
+  <div class="news-page">
+    <div class="page-card">
+      <!-- 椤甸潰澶撮儴 -->
+      <div class="page-header">
+        <div class="title-section">
+          <h1 class="page-title">鏂伴椈璧勮</h1>
+          <p class="page-subtitle">绠$悊绯荤粺鏂伴椈璧勮鍐呭</p>
+        </div>
+      </div>
+
+      <!-- 鎼滅储宸ュ叿鏍� -->
+      <div class="search-toolbar">
+        <el-input
+          v-model="searchForm.title"
+          placeholder="璇疯緭鍏ユ柊闂绘爣棰�"
+          style="width: 200px"
+          clearable
+          @keyup.enter="handleSearch"
+          @clear="handleClear"
+        />
+        <el-select
+          v-model="searchForm.state"
+          placeholder="鏂伴椈鐘舵��"
+          style="width: 160px"
+          clearable
+          @change="handleStateChange"
+        >
+          <el-option
+            v-for="option in stateOptions"
+            :key="option.value"
+            :label="option.label"
+            :value="option.value"
+          />
+        </el-select>
+        <el-button type="primary" @click="handleSearch">
+          <el-icon><Search /></el-icon>
+          鏌ヨ
+        </el-button>
+        <el-button type="primary" @click="handleAdd">
+          <el-icon><Plus /></el-icon>
+          鏂板鏂伴椈
+        </el-button>
+      </div>
+
+      <!-- 鏂伴椈鍒楄〃 -->
+      <el-table :data="tableData" style="width: 100%" v-loading="loading">
+        <el-table-column prop="title" label="鏂伴椈鏍囬" min-width="200" />
+        <el-table-column label="灏侀潰鍥剧墖" width="120" align="center">
+          <template #default="{ row }">
+            <el-image
+              v-if="row.coverImage"
+              :src="row.coverImage"
+              class="cover-image"
+              fit="cover"
+              :preview-src-list="[row.coverImage]"
+              preview-teleported
+            />
+            <span v-else>鏃犲浘鐗�</span>
+          </template>
+        </el-table-column>
+        <el-table-column prop="author" label="浣滆��" width="120" />
+        <el-table-column prop="viewCount" label="娴忚閲�" width="100" align="center" />
+        <el-table-column prop="createTime" label="鍒涘缓鏃堕棿" width="180" />
+        <el-table-column prop="stateName" label="鐘舵��" width="100" align="center">
+          <template #default="{ row }">
+            <el-tag :type="getStatusType(row.stateName)">{{ row.stateName }}</el-tag>
+          </template>
+        </el-table-column>
+        <el-table-column label="鎿嶄綔" width="120" fixed="right" align="center">
+          <template #default="{ row }">
+            <div class="table-actions">
+              <el-button
+                text
+                :icon="Edit"
+                size="small"
+                @click="handleEdit(row)"
+                class="action-btn edit-btn"
+                title="缂栬緫"
+              />
+              <el-button
+                text
+                :icon="Delete"
+                size="small"
+                @click="handleDelete(row)"
+                class="action-btn delete-btn"
+                title="鍒犻櫎"
+              />
+            </div>
+          </template>
+        </el-table-column>
+      </el-table>
+
+      <!-- 鍒嗛〉 -->
+      <div class="pagination">
+        <el-pagination
+          v-model:current-page="pagination.page"
+          v-model:page-size="pagination.size"
+          :page-sizes="[10, 20, 50, 100]"
+          :total="pagination.total"
+          layout="total, sizes, prev, pager, next, jumper"
+          @size-change="handleSizeChange"
+          @current-change="handleCurrentChange"
+        />
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { reactive, ref, onMounted, onActivated } from 'vue'
+import { ElMessage, ElMessageBox } from 'element-plus'
+import { useRouter } from 'vue-router'
+import { Search, Plus, Edit, Delete } from '@element-plus/icons-vue'
+import { getNewsList, updateNewsState } from '@/api/news'
+
+const loading = ref(false)
+const router = useRouter()
+
+// 鎼滅储琛ㄥ崟
+const searchForm = reactive({
+  title: '',
+  state: ''
+})
+
+const stateOptions = [
+  { label: '鑽夌', value: 0 },
+  { label: '鍙戝竷', value: 1 },
+  { label: '鍏抽棴', value: 2 }
+]
+
+// 鍒嗛〉淇℃伅
+const pagination = reactive({
+  page: 1,
+  size: 10,
+  total: 0
+})
+
+// 琛ㄦ牸鏁版嵁
+const tableData = ref([])
+
+// 鑾峰彇鐘舵�佹爣绛剧被鍨�
+const getStatusType = (status: string) => {
+  const typeMap: Record<string, string> = {
+    鑽夌: 'info',
+    鍙戝竷: 'success',
+    鍏抽棴: 'danger'
+  }
+  return typeMap[status] || 'info'
+}
+
+// 鎼滅储
+const handleSearch = () => {
+  pagination.page = 1
+  loadData()
+}
+
+const handleStateChange = () => {
+  pagination.page = 1
+  loadData()
+}
+
+// 娓呯┖鎼滅储
+const handleClear = () => {
+  searchForm.title = ''
+  loadData()
+}
+
+// 鏂板鏂伴椈
+const handleAdd = () => {
+  router.push('/news/new')
+}
+
+// 缂栬緫鏂伴椈
+const handleEdit = (row: any) => {
+  router.push(`/news/edit/${row.id}`)
+}
+
+// 鍒犻櫎鏂伴椈
+const handleDelete = async (row: any) => {
+  try {
+    await ElMessageBox.confirm(
+      `纭畾瑕佸垹闄ゆ柊闂� 鈥�${row.title}鈥� 鍚楋紵`,
+      '鎻愮ず',
+      {
+        confirmButtonText: '纭畾',
+        cancelButtonText: '鍙栨秷',
+        type: 'warning'
+      }
+    )
+
+    await updateNewsState(row.id, 2)
+    ElMessage.success('鍒犻櫎鎴愬姛')
+    loadData()
+  } catch {
+    // 鐢ㄦ埛鍙栨秷鎿嶄綔
+  }
+}
+
+// 鍒嗛〉澶у皬鏀瑰彉
+const handleSizeChange = (size: number) => {
+  pagination.size = size
+  loadData()
+}
+
+// 褰撳墠椤垫敼鍙�
+const handleCurrentChange = (page: number) => {
+  pagination.page = page
+  loadData()
+}
+
+// 鍔犺浇鏁版嵁
+const loadData = async () => {
+  loading.value = true
+  try {
+    const keyword = (searchForm.title || '').trim()
+    const data = await getNewsList(
+      pagination.page - 1,
+      pagination.size,
+      keyword,
+      searchForm.state
+    )
+
+    tableData.value = data?.content || []
+    pagination.total = data?.totalElements || 0
+  } catch (e: any) {
+    console.error('鍔犺浇鏁版嵁澶辫触:', e)
+    ElMessage.error(e?.message || '鍔犺浇鏂伴椈鍒楄〃澶辫触')
+  } finally {
+    loading.value = false
+  }
+}
+
+onMounted(() => {
+  loadData()
+})
+
+// 椤甸潰婵�娲绘椂閲嶆柊鍔犺浇鏁版嵁
+onActivated(() => {
+  loadData()
+})
+</script>
+
+<style scoped>
+.news-page {
+  padding: 20px;
+}
+
+.page-card {
+  background: white;
+  border-radius: 8px;
+  padding: 24px;
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
+}
+
+/* 椤甸潰澶撮儴鏍峰紡 */
+.page-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: flex-start;
+  margin-bottom: 24px;
+  gap: 20px;
+}
+
+.title-section {
+  flex: 1;
+}
+
+.page-title {
+  margin: 0 0 8px 0;
+  font-size: 24px;
+  font-weight: 600;
+  color: #1a1a1a;
+  line-height: 1.2;
+}
+
+.page-subtitle {
+  margin: 0;
+  font-size: 14px;
+  color: #666;
+  line-height: 1.4;
+}
+
+/* 鎼滅储宸ュ叿鏍忔牱寮� */
+.search-toolbar {
+  display: flex;
+  gap: 12px;
+  align-items: center;
+  justify-content: flex-end;
+  margin-bottom: 20px;
+}
+
+/* 琛ㄦ牸鎿嶄綔鎸夐挳鏍峰紡 */
+.table-actions {
+  display: flex;
+  gap: 12px;
+  justify-content: center;
+}
+
+.action-btn {
+  padding: 4px;
+  border: none;
+  background: transparent !important;
+  transition: all 0.2s ease;
+}
+
+.edit-btn {
+  color: #409eff;
+}
+
+.edit-btn:hover {
+  color: #337ecc;
+  transform: scale(1.2);
+  background: rgba(64, 158, 255, 0.1) !important;
+}
+
+.delete-btn {
+  color: #f56c6c;
+}
+
+.delete-btn:hover {
+  color: #dd6161;
+  transform: scale(1.2);
+  background: rgba(245, 108, 108, 0.1) !important;
+}
+
+.pagination {
+  margin-top: 20px;
+  display: flex;
+  justify-content: flex-end;
+}
+
+/* 灏侀潰鍥剧墖鏍峰紡 */
+.cover-image {
+  width: 80px;
+  height: 60px;
+  border-radius: 4px;
+  cursor: pointer;
+}
+
+/* 鍝嶅簲寮忛�傞厤 */
+@media (max-width: 768px) {
+  .page-header {
+    flex-direction: column;
+    align-items: stretch;
+    gap: 16px;
+  }
+
+  .search-toolbar {
+    flex-wrap: wrap;
+    gap: 8px;
+  }
+
+  .search-toolbar .el-input {
+    width: 100% !important;
+    max-width: 280px;
+  }
+}
+</style>
\ No newline at end of file
diff --git a/wx/app.json b/wx/app.json
index 6e950e6..cd5019c 100644
--- a/wx/app.json
+++ b/wx/app.json
@@ -1,6 +1,8 @@
 {
   "pages": [
     "pages/index/index",
+    "pages/news/list",
+    "pages/news/detail",
     "pages/activity/detail",
     "pages/registration/registration",
     "pages/profile/profile",
@@ -55,4 +57,4 @@
     "audio"
   ],
   "sitemapLocation": "sitemap.json"
-}
+}
\ No newline at end of file
diff --git a/wx/pages/index/index.js b/wx/pages/index/index.js
index ba5d944..0769b68 100644
--- a/wx/pages/index/index.js
+++ b/wx/pages/index/index.js
@@ -8,6 +8,8 @@
     banners: [],
     // 璧涗簨鍒楄〃
     activities: [],
+    // 鏈�鏂版柊闂诲垪琛�
+    latestNews: [],
     // 鍔犺浇鐘舵��
     loading: true,
     // 鏄惁杩樻湁鏇村鏁版嵁
@@ -45,6 +47,7 @@
     }
     // 鍔犺浇鏁版嵁
     this.loadBanners()
+    this.loadLatestNews()
     this.loadActivities()
   },
 
@@ -70,6 +73,7 @@
     
     Promise.all([
       this.loadBanners(),
+      this.loadLatestNews(),
       this.loadActivities()
     ]).finally(() => {
       wx.stopPullDownRefresh()
@@ -162,6 +166,47 @@
       }
     }).catch(err => {
       console.error('鍔犺浇杞挱鍥惧け璐�:', err)
+    })
+  },
+
+  // 鍔犺浇鏈�鏂版柊闂伙紙鍙姞杞藉墠3鏉★級
+  loadLatestNews() {
+    return app.graphqlRequest(`
+      query getPublishedNewsList($page: Int!, $size: Int!) {
+        publishedNewsList(page: $page, size: $size) {
+          content {
+            id
+            title
+            summary
+            coverImage
+            author
+            viewCount
+            createTime
+          }
+          totalElements
+          page
+          size
+        }
+      }
+    `, {
+      page: 1,
+      size: 3
+    }).then(data => {
+      if (data.publishedNewsList) {
+        // 鏍煎紡鍖栨椂闂存樉绀�
+        const latestNews = data.publishedNewsList.content.map(news => {
+          if (news.createTime) {
+            news.createTime = utils.formatDate(news.createTime, 'YYYY-MM-DD HH:mm:ss');
+          }
+          return news;
+        });
+        
+        this.setData({
+          latestNews: latestNews
+        })
+      }
+    }).catch(err => {
+      console.error('鍔犺浇鏈�鏂版柊闂诲け璐�:', err)
     })
   },
 
@@ -341,6 +386,22 @@
     utils.navigateTo('/pages/activity/detail', { id: activityId })
   },
 
+  // 璺宠浆鍒版柊闂昏鎯�
+  goToNewsDetail(e) {
+    const newsId = e.currentTarget.dataset.id
+    if (newsId) {
+      wx.navigateTo({
+        url: `/pages/news/detail?id=${newsId}`
+      })
+    }
+  },
+
+  // 璺宠浆鍒版柊闂诲垪琛�
+  goToNewsList() {
+    wx.navigateTo({
+      url: '/pages/news/list'
+    })
+  },
 
   // 鏍煎紡鍖栨棩鏈�
   formatDate(date) {
@@ -525,77 +586,34 @@
         imageUrl: '', // 鍙互璁剧疆鍒嗕韩鍥剧墖
         success: (res) => {
           console.log('鍒嗕韩鎴愬姛', res)
-          wx.showToast({
-            title: '鍒嗕韩鎴愬姛',
-            icon: 'success',
-            duration: 2000
-          })
-          // 娓呴櫎鍒嗕韩鐘舵��
-          this.setData({
-            shareActivityId: null,
-            shareActivityName: null
-          })
         },
         fail: (res) => {
           console.log('鍒嗕韩澶辫触', res)
-          wx.showToast({
-            title: '鍒嗕韩澶辫触',
-            icon: 'none',
-            duration: 2000
-          })
         }
       }
       return shareData
     }
     
-    // 榛樿鍒嗕韩鏁翠釜棣栭〉
+    // 榛樿鍒嗕韩
     return {
-      title: '钃塭鍒涙瘮璧涘钩鍙� - 鍙戠幇绮惧僵姣旇禌',
+      title: '钃塭鍒涙瘮璧涘钩鍙�',
       path: '/pages/index/index',
-      imageUrl: '', // 鍙互璁剧疆榛樿鍒嗕韩鍥剧墖
+      imageUrl: '',
       success: (res) => {
         console.log('鍒嗕韩鎴愬姛', res)
-        wx.showToast({
-          title: '鍒嗕韩鎴愬姛',
-          icon: 'success',
-          duration: 2000
-        })
       },
       fail: (res) => {
         console.log('鍒嗕韩澶辫触', res)
-        wx.showToast({
-          title: '鍒嗕韩澶辫触',
-          icon: 'none',
-          duration: 2000
-        })
       }
     }
   },
 
-  // 鍒嗕韩鍒版湅鍙嬪湀
+  // 椤甸潰鍒嗕韩鍔熻兘 - 鍒嗕韩鍒版湅鍙嬪湀
   onShareTimeline() {
-    console.log('鍒嗕韩鍒版湅鍙嬪湀')
-    
     return {
-      title: '钃塭鍒涙瘮璧涘钩鍙� - 鍙戠幇绮惧僵姣旇禌',
+      title: '钃塭鍒涙瘮璧涘钩鍙�',
       query: '',
-      imageUrl: '', // 鍙互璁剧疆鍒嗕韩鍥剧墖
-      success: (res) => {
-        console.log('鍒嗕韩鍒版湅鍙嬪湀鎴愬姛', res)
-        wx.showToast({
-          title: '鍒嗕韩鎴愬姛',
-          icon: 'success',
-          duration: 2000
-        })
-      },
-      fail: (res) => {
-        console.log('鍒嗕韩鍒版湅鍙嬪湀澶辫触', res)
-        wx.showToast({
-          title: '鍒嗕韩澶辫触',
-          icon: 'none',
-          duration: 2000
-        })
-      }
+      imageUrl: ''
     }
   }
 })
\ No newline at end of file
diff --git a/wx/pages/index/index.wxml b/wx/pages/index/index.wxml
index caaf876..7c9996f 100644
--- a/wx/pages/index/index.wxml
+++ b/wx/pages/index/index.wxml
@@ -1,5 +1,4 @@
 <wxs src="./filters.wxs" module="filters" />
-<!--pages/index/index.wxml-->
 <view class="container">
   <!-- 椤甸潰澶撮儴鍒嗕韩鍖哄煙 -->
   <view class="header-section">
@@ -72,6 +71,36 @@
     </swiper>
   </view>
 
+  <!-- 鏂伴椈妯″潡 -->
+  <view class="section-title">鏂伴椈璧勮</view>
+  <view class="news-section">
+    <view 
+      class="news-card"
+      wx:for="{{latestNews}}"
+      wx:key="id"
+      bindtap="goToNewsDetail"
+      data-id="{{item.id}}"
+    >
+      <view class="news-content">
+        <view class="news-title">{{item.title}}</view>
+        <view wx:if="{{item.summary}}" class="news-summary">{{item.summary}}</view>
+        <view class="news-meta">
+          <text wx:if="{{item.author}}" class="author">{{item.author}}</text>
+          <text wx:if="{{item.author && item.createTime}}" class="separator">|</text>
+          <text class="time">{{item.createTime}}</text>
+        </view>
+      </view>
+      <view wx:if="{{item.coverImage}}" class="news-thumb">
+        <image class="thumb-image" src="{{item.coverImage}}" mode="aspectFill" />
+      </view>
+    </view>
+    
+    <view class="view-more" bindtap="goToNewsList">
+      <text class="view-more-text">鏌ョ湅鏇村</text>
+      <text class="arrow">鈥�</text>
+    </view>
+  </view>
+
   <!-- 妯″潡鏍囬 -->
   <view class="section-title">姣旇禌淇℃伅</view>
 
diff --git a/wx/pages/index/index.wxss b/wx/pages/index/index.wxss
index d2ef0c1..21269e1 100644
--- a/wx/pages/index/index.wxss
+++ b/wx/pages/index/index.wxss
@@ -252,6 +252,109 @@
   color: #0f172a;
 }
 
+/* 鏂伴椈妯″潡鏍峰紡 */
+.news-section {
+  padding: 0 20rpx 20rpx;
+  background: #ffffff;
+  border-radius: 20rpx;
+  margin-bottom: 24rpx;
+  box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.1);
+}
+
+.news-card {
+  display: flex;
+  padding: 24rpx;
+  border-bottom: 1rpx solid #f0f0f0;
+  transition: background-color 0.3s ease;
+}
+
+.news-card:last-child {
+  border-bottom: none;
+}
+
+.news-card:active {
+  background-color: #f8f9fa;
+}
+
+.news-content {
+  flex: 1;
+  min-width: 0;
+  padding-right: 20rpx;
+}
+
+.news-title {
+  font-size: 30rpx;
+  font-weight: 600;
+  color: #0f172a;
+  line-height: 1.4;
+  margin-bottom: 12rpx;
+  display: -webkit-box;
+  -webkit-box-orient: vertical;
+  -webkit-line-clamp: 2;
+  overflow: hidden;
+}
+
+.news-summary {
+  font-size: 26rpx;
+  color: #64748b;
+  line-height: 1.5;
+  margin-bottom: 12rpx;
+  display: -webkit-box;
+  -webkit-box-orient: vertical;
+  -webkit-line-clamp: 2;
+  overflow: hidden;
+}
+
+.news-meta {
+  display: flex;
+  align-items: center;
+  gap: 8rpx;
+}
+
+.author {
+  font-size: 22rpx;
+  color: #94a3b8;
+}
+
+.separator {
+  font-size: 22rpx;
+  color: #cbd5e1;
+}
+
+.time {
+  font-size: 22rpx;
+  color: #94a3b8;
+}
+
+.news-thumb {
+  width: 160rpx;
+  height: 120rpx;
+  flex-shrink: 0;
+  border-radius: 12rpx;
+  overflow: hidden;
+}
+
+.thumb-image {
+  width: 100%;
+  height: 100%;
+  object-fit: cover;
+}
+
+.view-more {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  padding: 24rpx;
+  color: #007aff;
+  font-size: 28rpx;
+  font-weight: 500;
+}
+
+.arrow {
+  margin-left: 8rpx;
+  font-size: 32rpx;
+}
+
 /* 绛涢�夋爮鏍峰紡 */
 .filter-bar {
   background: #ffffff;
diff --git a/wx/pages/news/detail.js b/wx/pages/news/detail.js
new file mode 100644
index 0000000..59d5a84
--- /dev/null
+++ b/wx/pages/news/detail.js
@@ -0,0 +1,77 @@
+// pages/news/detail.js
+const app = getApp()
+const utils = require('../../lib/utils.js')
+
+Page({
+  data: {
+    news: null,
+    loading: true
+  },
+
+  onLoad(options) {
+    const newsId = options.id
+    if (newsId) {
+      this.loadNewsDetail(newsId)
+    } else {
+      wx.showToast({
+        title: '鍙傛暟閿欒',
+        icon: 'none'
+      })
+      setTimeout(() => {
+        wx.navigateBack()
+      }, 1500)
+    }
+  },
+
+  onShow() {
+    // 缁熶竴绯荤粺瀵艰埅鏍忔爣棰�
+    try { wx.setNavigationBarTitle({ title: '鏂伴椈璇︽儏' }) } catch (e) {}
+  },
+
+  // 鍔犺浇鏂伴椈璇︽儏
+  loadNewsDetail(newsId) {
+    this.setData({ loading: true })
+    
+    app.graphqlRequest(`
+      query getPublishedNews($id: ID!) {
+        publishedNews(id: $id) {
+          id
+          title
+          content
+          summary
+          coverImage
+          author
+          viewCount
+          createTime
+        }
+      }
+    `, {
+      id: newsId
+    }).then(data => {
+      if (data.publishedNews) {
+        // 鏍煎紡鍖栨椂闂存樉绀�
+        const news = data.publishedNews;
+        if (news.createTime) {
+          news.createTime = utils.formatDate(news.createTime, 'YYYY-MM-DD HH:mm:ss');
+        }
+        
+        this.setData({
+          news: news,
+          loading: false
+        })
+      } else {
+        throw new Error('鏂伴椈涓嶅瓨鍦�')
+      }
+    }).catch(err => {
+      console.error('鍔犺浇鏂伴椈璇︽儏澶辫触:', err)
+      wx.showToast({
+        title: '鍔犺浇澶辫触锛岃閲嶈瘯',
+        icon: 'none'
+      })
+      this.setData({ loading: false })
+      setTimeout(() => {
+        wx.navigateBack()
+      }, 1500)
+    })
+  }
+})
\ No newline at end of file
diff --git a/wx/pages/news/detail.json b/wx/pages/news/detail.json
new file mode 100644
index 0000000..c293ba3
--- /dev/null
+++ b/wx/pages/news/detail.json
@@ -0,0 +1,3 @@
+{
+  "navigationBarTitleText": "鏂伴椈璇︽儏"
+}
\ No newline at end of file
diff --git a/wx/pages/news/detail.wxml b/wx/pages/news/detail.wxml
new file mode 100644
index 0000000..27ab74d
--- /dev/null
+++ b/wx/pages/news/detail.wxml
@@ -0,0 +1,26 @@
+<!-- pages/news/detail.wxml -->
+<view class="container">
+  <view wx:if="{{loading}}" class="loading-wrapper">
+    <view class="loading"></view>
+    <text class="loading-text">鍔犺浇涓�...</text>
+  </view>
+  
+  <view wx:else class="news-detail">
+    <view class="news-header">
+      <view class="news-title">{{news.title}}</view>
+      <view class="news-meta">
+        <text wx:if="{{news.author}}" class="author">{{news.author}}</text>
+        <text wx:if="{{news.author}}" class="separator">|</text>
+        <text class="time">{{news.createTime}}</text>
+      </view>
+    </view>
+    
+    <view wx:if="{{news.coverImage}}" class="news-cover">
+      <image class="cover-image" src="{{news.coverImage}}" mode="widthFix" />
+    </view>
+    
+    <view class="news-content">
+      <rich-text nodes="{{news.content}}"></rich-text>
+    </view>
+  </view>
+</view>
\ No newline at end of file
diff --git a/wx/pages/news/detail.wxss b/wx/pages/news/detail.wxss
new file mode 100644
index 0000000..feeafa5
--- /dev/null
+++ b/wx/pages/news/detail.wxss
@@ -0,0 +1,169 @@
+/* pages/news/detail.wxss */
+.container {
+  min-height: 100vh;
+  background-color: #f5f5f5;
+}
+
+.loading-wrapper {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  padding: 100rpx 0;
+}
+
+.loading {
+  width: 40rpx;
+  height: 40rpx;
+  border: 4rpx solid #f3f3f3;
+  border-top: 4rpx solid #007aff;
+  border-radius: 50%;
+  animation: spin 1s linear infinite;
+  margin-bottom: 20rpx;
+}
+
+@keyframes spin {
+  0% { transform: rotate(0deg); }
+  100% { transform: rotate(360deg); }
+}
+
+.loading-text {
+  font-size: 28rpx;
+  color: #999;
+}
+
+.news-detail {
+  padding: 20rpx;
+}
+
+.news-header {
+  background: white;
+  border-radius: 16rpx;
+  padding: 30rpx;
+  margin-bottom: 20rpx;
+  box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.1);
+}
+
+.news-title {
+  font-size: 36rpx;
+  font-weight: 600;
+  color: #333;
+  line-height: 1.4;
+  margin-bottom: 20rpx;
+  word-wrap: break-word;
+  overflow-wrap: break-word;
+}
+
+.news-meta {
+  display: flex;
+  flex-wrap: wrap;
+  align-items: center;
+  gap: 12rpx;
+  margin-bottom: 16rpx;
+}
+
+.author {
+  font-size: 26rpx;
+  color: #666;
+}
+
+.separator {
+  font-size: 26rpx;
+  color: #ddd;
+}
+
+.time {
+  font-size: 26rpx;
+  color: #999;
+}
+
+.update-time {
+  font-size: 24rpx;
+  color: #999;
+}
+
+.news-cover {
+  background: white;
+  border-radius: 16rpx;
+  overflow: hidden;
+  margin-bottom: 20rpx;
+  box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.1);
+}
+
+.cover-image {
+  width: 100%;
+  display: block;
+}
+
+.news-content {
+  background: white;
+  border-radius: 16rpx;
+  padding: 30rpx;
+  box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.1);
+  word-wrap: break-word;
+  overflow-wrap: break-word;
+  max-width: 100%;
+}
+
+.news-content rich-text {
+  font-size: 30rpx;
+  line-height: 1.6;
+  color: #333;
+  word-wrap: break-word;
+  overflow-wrap: break-word;
+  max-width: 100%;
+  display: block;
+}
+
+/* 瀵屾枃鏈唴瀹规牱寮� */
+.news-content rich-text image,
+.news-content rich-text img {
+  max-width: 100% !important;
+  width: auto !important;
+  height: auto !important;
+  border-radius: 12rpx;
+  margin: 20rpx 0;
+  display: block !important;
+  box-sizing: border-box;
+}
+
+.news-content rich-text p {
+  margin: 0 0 20rpx 0;
+  line-height: 1.6;
+  word-wrap: break-word;
+  overflow-wrap: break-word;
+  max-width: 100%;
+}
+
+/* 纭繚鎵�鏈夊瘜鏂囨湰鍏冪礌閮戒笉浼氳秴鍑哄鍣� */
+.news-content rich-text view,
+.news-content rich-text div,
+.news-content rich-text span {
+  max-width: 100% !important;
+  word-wrap: break-word;
+  overflow-wrap: break-word;
+  box-sizing: border-box;
+}
+
+/* 鐗瑰埆澶勭悊琛ㄦ牸 */
+.news-content rich-text table {
+  max-width: 100% !important;
+  overflow-x: auto;
+  display: block;
+  box-sizing: border-box;
+}
+
+.news-content rich-text td,
+.news-content rich-text th {
+  word-wrap: break-word;
+  overflow-wrap: break-word;
+  max-width: 100%;
+  box-sizing: border-box;
+}
+
+/* 澶勭悊鍙兘鐨勫唴鑱旀牱寮� */
+.news-content rich-text image[style*="width"],
+.news-content rich-text img[style*="width"],
+.news-content rich-text div[style*="width"],
+.news-content rich-text p[style*="width"] {
+  max-width: 100% !important;
+}
\ No newline at end of file
diff --git a/wx/pages/news/list.js b/wx/pages/news/list.js
new file mode 100644
index 0000000..8ac6e5f
--- /dev/null
+++ b/wx/pages/news/list.js
@@ -0,0 +1,120 @@
+// pages/news/list.js
+const app = getApp()
+const utils = require('../../lib/utils.js')
+
+Page({
+  data: {
+    newsList: [],
+    loading: true,
+    hasMore: true,
+    currentPage: 1,
+    pageSize: 10
+  },
+
+  onLoad(options) {
+    this.loadNewsList()
+  },
+
+  onShow() {
+    // 缁熶竴绯荤粺瀵艰埅鏍忔爣棰�
+    try { wx.setNavigationBarTitle({ title: '鏂伴椈璧勮' }) } catch (e) {}
+  },
+
+  onPullDownRefresh() {
+    this.refreshData()
+  },
+
+  onReachBottom() {
+    if (this.data.hasMore && !this.data.loading) {
+      this.loadMoreNews()
+    }
+  },
+
+  // 鍒锋柊鏁版嵁
+  refreshData() {
+    this.setData({
+      currentPage: 1,
+      hasMore: true,
+      newsList: []
+    })
+    this.loadNewsList().finally(() => {
+      wx.stopPullDownRefresh()
+    })
+  },
+
+  // 鍔犺浇鏂伴椈鍒楄〃
+  loadNewsList(isLoadMore = false) {
+    this.setData({ loading: true })
+    
+    const { currentPage, pageSize } = this.data
+    
+    return app.graphqlRequest(`
+      query getPublishedNewsList($page: Int!, $size: Int!) {
+        publishedNewsList(page: $page, size: $size) {
+          content {
+            id
+            title
+            summary
+            coverImage
+            author
+            viewCount
+            createTime
+          }
+          totalElements
+          page
+          size
+        }
+      }
+    `, {
+      page: currentPage,
+      size: pageSize
+    }).then(data => {
+      if (data.publishedNewsList) {
+        const newNewsList = data.publishedNewsList.content
+        
+        // 鏍煎紡鍖栨椂闂存樉绀�
+        newNewsList.forEach(news => {
+          if (news.createTime) {
+            news.createTime = utils.formatDate(news.createTime, 'YYYY-MM-DD HH:mm:ss');
+          }
+        });
+        
+        // 鍚堝苟鏁版嵁锛氬彧鏈夊湪鐪熸鐨勫姞杞芥洿澶氭椂鎵嶈拷鍔狅紝鍏朵粬鎯呭喌閮芥槸鍏ㄩ噺鏇挎崲
+        const mergedNewsList = isLoadMore && this.data.newsList.length > 0
+          ? [...this.data.newsList, ...newNewsList]
+          : newNewsList
+        
+        this.setData({
+          newsList: mergedNewsList,
+          hasMore: data.publishedNewsList.totalElements > (currentPage * pageSize) && newNewsList.length > 0,
+          loading: false
+        })
+      }
+    }).catch(err => {
+      console.error('鍔犺浇鏂伴椈鍒楄〃澶辫触:', err)
+      wx.showToast({
+        title: '鍔犺浇澶辫触锛岃閲嶈瘯',
+        icon: 'none'
+      })
+      this.setData({ loading: false })
+    })
+  },
+
+  // 鍔犺浇鏇村鏂伴椈
+  loadMoreNews() {
+    this.setData({
+      currentPage: this.data.currentPage + 1
+    })
+    this.loadNewsList(true)
+  },
+
+  // 璺宠浆鍒版柊闂昏鎯�
+  goToNewsDetail(e) {
+    const newsId = e.currentTarget.dataset.id
+    if (newsId) {
+      wx.navigateTo({
+        url: `/pages/news/detail?id=${newsId}`
+      })
+    }
+  }
+})
\ No newline at end of file
diff --git a/wx/pages/news/list.json b/wx/pages/news/list.json
new file mode 100644
index 0000000..505ad33
--- /dev/null
+++ b/wx/pages/news/list.json
@@ -0,0 +1,5 @@
+{
+  "navigationBarTitleText": "鏂伴椈璧勮",
+  "enablePullDownRefresh": true,
+  "backgroundTextStyle": "dark"
+}
\ No newline at end of file
diff --git a/wx/pages/news/list.wxml b/wx/pages/news/list.wxml
new file mode 100644
index 0000000..e9524f1
--- /dev/null
+++ b/wx/pages/news/list.wxml
@@ -0,0 +1,50 @@
+<!-- pages/news/list.wxml -->
+<view class="container">
+  <!-- 鏂伴椈鍒楄〃 -->
+  <view class="news-list">
+    <view 
+      class="news-item"
+      wx:for="{{newsList}}"
+      wx:key="id"
+      bindtap="goToNewsDetail"
+      data-id="{{item.id}}"
+    >
+      <view class="news-card">
+        <view class="news-header">
+          <view class="news-title">{{item.title}}</view>
+        </view>
+        <view class="news-body">
+          <view wx:if="{{item.coverImage}}" class="news-cover">
+            <image class="cover-image" src="{{item.coverImage}}" mode="aspectFill" />
+          </view>
+          <view wx:if="{{item.summary}}" class="news-summary">{{item.summary}}</view>
+        </view>
+        <view class="news-footer">
+          <view class="news-meta">
+            <text wx:if="{{item.author}}" class="author">{{item.author}}</text>
+            <text wx:if="{{item.author}}" class="separator">|</text>
+            <text class="time">{{item.createTime}}</text>
+          </view>
+        </view>
+      </view>
+    </view>
+
+    <!-- 鍔犺浇鐘舵�� -->
+    <view class="loading-wrapper" wx:if="{{loading}}">
+      <view class="loading"></view>
+      <text class="loading-text">鍔犺浇涓�...</text>
+    </view>
+
+    <!-- 娌℃湁鏇村鏁版嵁 -->
+    <view class="no-more" wx:if="{{!hasMore && newsList.length > 0}}">
+      <text class="no-more-text">娌℃湁鏇村鏁版嵁浜�</text>
+    </view>
+
+    <!-- 绌虹姸鎬� -->
+    <view class="empty-state" wx:if="{{!loading && newsList.length === 0}}">
+      <view class="empty-icon">馃摪</view>
+      <view class="empty-text">鏆傛棤鏂伴椈璧勮</view>
+      <view class="empty-desc">璇风◢鍚庡啀璇�</view>
+    </view>
+  </view>
+</view>
\ No newline at end of file
diff --git a/wx/pages/news/list.wxss b/wx/pages/news/list.wxss
new file mode 100644
index 0000000..70bb76d
--- /dev/null
+++ b/wx/pages/news/list.wxss
@@ -0,0 +1,174 @@
+/* pages/news/list.wxss */
+.container {
+  padding: 20rpx;
+  background-color: #f5f5f5;
+  min-height: 100vh;
+}
+
+.news-list {
+  display: flex;
+  flex-direction: column;
+  gap: 20rpx;
+}
+
+.news-item {
+  background: white;
+  border-radius: 16rpx;
+  overflow: hidden;
+  box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.1);
+  transition: all 0.3s ease;
+}
+
+.news-item:active {
+  transform: scale(0.98);
+  box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.15);
+}
+
+.news-card {
+  padding: 24rpx;
+}
+
+.news-header {
+  margin-bottom: 16rpx;
+}
+
+.news-title {
+  font-size: 32rpx;
+  font-weight: 600;
+  color: #333;
+  line-height: 1.4;
+  display: -webkit-box;
+  -webkit-box-orient: vertical;
+  -webkit-line-clamp: 2;
+  overflow: hidden;
+}
+
+.news-body {
+  margin-bottom: 16rpx;
+}
+
+.news-cover {
+  margin-bottom: 16rpx;
+  border-radius: 12rpx;
+  overflow: hidden;
+  height: 280rpx;
+}
+
+.cover-image {
+  width: 100%;
+  height: 100%;
+  object-fit: cover;
+}
+
+.news-summary {
+  font-size: 28rpx;
+  color: #666;
+  line-height: 1.5;
+  display: -webkit-box;
+  -webkit-box-orient: vertical;
+  -webkit-line-clamp: 3;
+  overflow: hidden;
+}
+
+.news-footer {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  padding-top: 16rpx;
+  border-top: 1rpx solid #eee;
+}
+
+.news-meta {
+  display: flex;
+  align-items: center;
+  gap: 12rpx;
+}
+
+.author {
+  font-size: 24rpx;
+  color: #999;
+}
+
+.separator {
+  font-size: 24rpx;
+  color: #ddd;
+}
+
+.time {
+  font-size: 24rpx;
+  color: #999;
+}
+
+.news-stats {
+  display: flex;
+  align-items: center;
+}
+
+.view-count {
+  font-size: 24rpx;
+  color: #999;
+}
+
+/* 鍔犺浇鐘舵�� */
+.loading-wrapper {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  padding: 40rpx 0;
+}
+
+.loading {
+  width: 40rpx;
+  height: 40rpx;
+  border: 4rpx solid #f3f3f3;
+  border-top: 4rpx solid #007aff;
+  border-radius: 50%;
+  animation: spin 1s linear infinite;
+  margin-bottom: 20rpx;
+}
+
+@keyframes spin {
+  0% { transform: rotate(0deg); }
+  100% { transform: rotate(360deg); }
+}
+
+.loading-text {
+  font-size: 28rpx;
+  color: #999;
+}
+
+/* 娌℃湁鏇村鏁版嵁 */
+.no-more {
+  text-align: center;
+  padding: 40rpx 0;
+}
+
+.no-more-text {
+  font-size: 28rpx;
+  color: #999;
+}
+
+/* 绌虹姸鎬� */
+.empty-state {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  padding: 100rpx 40rpx;
+}
+
+.empty-icon {
+  font-size: 80rpx;
+  margin-bottom: 20rpx;
+  opacity: 0.5;
+}
+
+.empty-text {
+  font-size: 32rpx;
+  color: #666;
+  margin-bottom: 10rpx;
+}
+
+.empty-desc {
+  font-size: 28rpx;
+  color: #999;
+}
\ No newline at end of file

--
Gitblit v1.8.0