diff --git a/doc/sql/20261020.sql b/doc/sql/20261020.sql new file mode 100644 index 0000000..427df47 --- /dev/null +++ b/doc/sql/20261020.sql @@ -0,0 +1,17 @@ +CREATE TABLE `user_message` +( + `id` bigint(20) NOT NULL AUTO_INCREMENT, + `user_id` bigint(20) NOT NULL COMMENT '接收用户ID', + `send_user_id` bigint(20) NOT NULL COMMENT '发送用户ID', + `msg_type` tinyint(1) NOT NULL COMMENT '消息类型:1书评被回复 2段评被回复', + `book_id` bigint(20) DEFAULT NULL COMMENT '书籍ID', + `book_index_id` bigint(20) DEFAULT NULL COMMENT '章节ID(段评消息跳转阅读页定位用)', + `target_id` bigint(20) NOT NULL COMMENT '被回复的主评论ID(书评ID或顶层段评ID)', + `content` varchar(200) DEFAULT NULL COMMENT '回复内容摘要', + `read_flag` tinyint(1) NOT NULL DEFAULT 0 COMMENT '是否已读:0未读 1已读', + `create_time` datetime NOT NULL COMMENT '创建时间', + PRIMARY KEY (`id`), + KEY `idx_user_read` (`user_id`, `read_flag`), + KEY `idx_user_time` (`user_id`, `create_time`) +) ENGINE = InnoDB + DEFAULT CHARSET = utf8mb4 COMMENT ='用户消息表'; diff --git a/doc/sql/novel_plus.sql b/doc/sql/novel_plus.sql index bea1d85..5fede06 100644 --- a/doc/sql/novel_plus.sql +++ b/doc/sql/novel_plus.sql @@ -3333,3 +3333,21 @@ CREATE TABLE `book_paragraph_comment` ) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COMMENT ='书籍段落评论表(含回复)'; +CREATE TABLE `user_message` +( + `id` bigint(20) NOT NULL AUTO_INCREMENT, + `user_id` bigint(20) NOT NULL COMMENT '接收用户ID', + `send_user_id` bigint(20) NOT NULL COMMENT '发送用户ID', + `msg_type` tinyint(1) NOT NULL COMMENT '消息类型:1书评被回复 2段评被回复', + `book_id` bigint(20) DEFAULT NULL COMMENT '书籍ID', + `book_index_id` bigint(20) DEFAULT NULL COMMENT '章节ID(段评消息跳转阅读页定位用)', + `target_id` bigint(20) NOT NULL COMMENT '被回复的主评论ID(书评ID或顶层段评ID)', + `content` varchar(200) DEFAULT NULL COMMENT '回复内容摘要', + `read_flag` tinyint(1) NOT NULL DEFAULT 0 COMMENT '是否已读:0未读 1已读', + `create_time` datetime NOT NULL COMMENT '创建时间', + PRIMARY KEY (`id`), + KEY `idx_user_read` (`user_id`, `read_flag`), + KEY `idx_user_time` (`user_id`, `create_time`) +) ENGINE = InnoDB + DEFAULT CHARSET = utf8mb4 COMMENT ='用户消息表'; + diff --git a/novel-common/src/main/java/com/java2nb/novel/entity/BookParagraphComment.java b/novel-common/src/main/java/com/java2nb/novel/entity/BookParagraphComment.java index b40f413..6174f89 100644 --- a/novel-common/src/main/java/com/java2nb/novel/entity/BookParagraphComment.java +++ b/novel-common/src/main/java/com/java2nb/novel/entity/BookParagraphComment.java @@ -59,5 +59,10 @@ public class BookParagraphComment { */ private Long createUserId; + /** + * 被回复人用户ID(仅回复时前端传入,非表字段,用于消息通知) + */ + private Long replyUserId; + private Date createTime; } diff --git a/novel-common/src/main/java/com/java2nb/novel/entity/UserMessage.java b/novel-common/src/main/java/com/java2nb/novel/entity/UserMessage.java new file mode 100644 index 0000000..c34ce74 --- /dev/null +++ b/novel-common/src/main/java/com/java2nb/novel/entity/UserMessage.java @@ -0,0 +1,68 @@ +package com.java2nb.novel.entity; + +import lombok.Data; + +import java.util.Date; + +/** + * 用户消息 + * + * @author 11797 + */ +@Data +public class UserMessage { + + private Long id; + + /** + * 接收用户ID + */ + private Long userId; + + /** + * 发送用户ID + */ + private Long sendUserId; + + /** + * 消息类型:1书评被回复 2段评被回复 + */ + private Byte msgType; + + /** + * 书籍ID + */ + private Long bookId; + + /** + * 章节ID(段评消息跳转阅读页定位用) + */ + private Long bookIndexId; + + /** + * 被回复的主评论ID(书评ID或顶层段评ID) + */ + private Long targetId; + + /** + * 回复内容摘要 + */ + private String content; + + /** + * 是否已读:0未读 1已读 + */ + private Byte readFlag; + + private Date createTime; + + /** + * 书评被回复 + */ + public static final byte TYPE_BOOK_COMMENT_REPLY = 1; + + /** + * 段评被回复 + */ + public static final byte TYPE_PARAGRAPH_REPLY = 2; +} diff --git a/novel-front/src/main/java/com/java2nb/novel/controller/MessageController.java b/novel-front/src/main/java/com/java2nb/novel/controller/MessageController.java new file mode 100644 index 0000000..5c9805d --- /dev/null +++ b/novel-front/src/main/java/com/java2nb/novel/controller/MessageController.java @@ -0,0 +1,80 @@ +package com.java2nb.novel.controller; + +import com.java2nb.novel.core.bean.UserDetails; +import com.java2nb.novel.core.enums.ResponseStatus; +import com.java2nb.novel.service.UserMessageService; +import io.github.xxyopen.model.resp.RestResult; +import jakarta.servlet.http.HttpServletRequest; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +/** + * 用户消息(评论/段评被回复通知) + * + * @author 11797 + */ +@RestController +@RequestMapping("user/message") +@RequiredArgsConstructor +@Slf4j +public class MessageController extends BaseController { + + private final UserMessageService userMessageService; + + /** + * 分页查询我的消息,msgType 可选过滤:1书评回复 2段评回复 + */ + @GetMapping("listByPage") + public RestResult listByPage(@RequestParam(value = "curr", defaultValue = "1") int page, + @RequestParam(value = "limit", defaultValue = "10") int pageSize, + @RequestParam(value = "msgType", required = false) Byte msgType, HttpServletRequest request) { + UserDetails userDetails = getUserDetails(request); + if (userDetails == null) { + return RestResult.fail(ResponseStatus.NO_LOGIN); + } + return RestResult.ok(userMessageService.listMyMessageByPage(userDetails.getId(), msgType, page, pageSize)); + } + + /** + * 未读消息数(个人中心角标用) + */ + @GetMapping("unreadCount") + public RestResult unreadCount(HttpServletRequest request) { + UserDetails userDetails = getUserDetails(request); + if (userDetails == null) { + return RestResult.fail(ResponseStatus.NO_LOGIN); + } + return RestResult.ok(userMessageService.countUnread(userDetails.getId())); + } + + /** + * 单条标记已读 + */ + @PostMapping("read") + public RestResult read(@RequestParam("messageId") Long messageId, HttpServletRequest request) { + UserDetails userDetails = getUserDetails(request); + if (userDetails == null) { + return RestResult.fail(ResponseStatus.NO_LOGIN); + } + userMessageService.markRead(userDetails.getId(), messageId); + return RestResult.ok(); + } + + /** + * 全部标记已读 + */ + @PostMapping("readAll") + public RestResult readAll(HttpServletRequest request) { + UserDetails userDetails = getUserDetails(request); + if (userDetails == null) { + return RestResult.fail(ResponseStatus.NO_LOGIN); + } + userMessageService.markAllRead(userDetails.getId()); + return RestResult.ok(); + } +} diff --git a/novel-front/src/main/java/com/java2nb/novel/controller/page/PageController.java b/novel-front/src/main/java/com/java2nb/novel/controller/page/PageController.java index 9c43747..9ee3044 100644 --- a/novel-front/src/main/java/com/java2nb/novel/controller/page/PageController.java +++ b/novel-front/src/main/java/com/java2nb/novel/controller/page/PageController.java @@ -155,6 +155,14 @@ public class PageController extends BaseController { return ThreadLocalUtil.getTemplateDir() + "user/paragraph_comment"; } + /** + * 我的消息页 + */ + @RequestMapping("user/message.html") + public String userMessage() { + return ThreadLocalUtil.getTemplateDir() + "user/message"; + } + /** * 账号设置页 */ diff --git a/novel-front/src/main/java/com/java2nb/novel/mapper/FrontUserMessageMapper.java b/novel-front/src/main/java/com/java2nb/novel/mapper/FrontUserMessageMapper.java new file mode 100644 index 0000000..94af049 --- /dev/null +++ b/novel-front/src/main/java/com/java2nb/novel/mapper/FrontUserMessageMapper.java @@ -0,0 +1,40 @@ +package com.java2nb.novel.mapper; + +import com.java2nb.novel.entity.UserMessage; +import com.java2nb.novel.vo.UserMessageVO; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 用户消息 Mapper + * + * @author 11797 + */ +public interface FrontUserMessageMapper { + + /** + * 插入一条消息 + */ + int insert(UserMessage userMessage); + + /** + * 分页查询用户消息(按接收人),附带发送人昵称/头像与书名/章节名 + */ + List listByPage(@Param("userId") Long userId, @Param("msgType") Byte msgType); + + /** + * 未读消息数 + */ + long countUnread(@Param("userId") Long userId); + + /** + * 单条标记已读(限本人消息) + */ + int markRead(@Param("userId") Long userId, @Param("id") Long id); + + /** + * 全部标记已读 + */ + int markAllRead(@Param("userId") Long userId); +} diff --git a/novel-front/src/main/java/com/java2nb/novel/service/UserMessageService.java b/novel-front/src/main/java/com/java2nb/novel/service/UserMessageService.java new file mode 100644 index 0000000..1fd37e3 --- /dev/null +++ b/novel-front/src/main/java/com/java2nb/novel/service/UserMessageService.java @@ -0,0 +1,45 @@ +package com.java2nb.novel.service; + +import com.java2nb.novel.vo.UserMessageVO; +import io.github.xxyopen.model.page.PageBean; + +/** + * 用户消息 Service + * + * @author 11797 + */ +public interface UserMessageService { + + /** + * 投递一条被回复消息(自己回复自己不通知;接收人为空时跳过) + * + * @param sendUserId 发送人(回复者) + * @param toUserId 接收人(被回复者) + * @param msgType 消息类型 {@link com.java2nb.novel.entity.UserMessage#TYPE_BOOK_COMMENT_REPLY} 等 + * @param bookId 书籍ID + * @param bookIndexId 章节ID(段评消息传入,书评消息可为空) + * @param targetId 被回复的主评论ID + * @param replyContent 回复内容(截取摘要) + */ + void sendReplyMessage(Long sendUserId, Long toUserId, byte msgType, Long bookId, Long bookIndexId, Long targetId, String replyContent); + + /** + * 分页查询我的消息 + */ + PageBean listMyMessageByPage(Long userId, Byte msgType, int page, int pageSize); + + /** + * 我的未读消息数 + */ + long countUnread(Long userId); + + /** + * 单条标记已读 + */ + void markRead(Long userId, Long id); + + /** + * 全部标记已读 + */ + void markAllRead(Long userId); +} diff --git a/novel-front/src/main/java/com/java2nb/novel/service/impl/BookParagraphCommentServiceImpl.java b/novel-front/src/main/java/com/java2nb/novel/service/impl/BookParagraphCommentServiceImpl.java index 307e427..1bf0296 100644 --- a/novel-front/src/main/java/com/java2nb/novel/service/impl/BookParagraphCommentServiceImpl.java +++ b/novel-front/src/main/java/com/java2nb/novel/service/impl/BookParagraphCommentServiceImpl.java @@ -4,8 +4,10 @@ import com.github.houbb.sensitive.word.core.SensitiveWordHelper; import com.github.pagehelper.PageHelper; import com.java2nb.novel.core.enums.ResponseStatus; import com.java2nb.novel.entity.BookParagraphComment; +import com.java2nb.novel.entity.UserMessage; import com.java2nb.novel.mapper.FrontBookParagraphCommentMapper; import com.java2nb.novel.service.BookParagraphCommentService; +import com.java2nb.novel.service.UserMessageService; import com.java2nb.novel.vo.BookParagraphCommentVO; import io.github.xxyopen.model.page.PageBean; import io.github.xxyopen.model.page.builder.pagehelper.PageBuilder; @@ -43,6 +45,8 @@ public class BookParagraphCommentServiceImpl implements BookParagraphCommentServ private final FrontBookParagraphCommentMapper bookParagraphCommentMapper; + private final UserMessageService userMessageService; + @Override public Map listParagraphCommentCounts(Long bookIndexId) { Map counts = new LinkedHashMap<>(); @@ -130,6 +134,10 @@ public class BookParagraphCommentServiceImpl implements BookParagraphCommentServ comment.setParagraphIdx(root.getParagraphIdx()); bookParagraphCommentMapper.insert(comment); bookParagraphCommentMapper.addReplyCount(rootId); + //段评被回复消息通知:优先通知被回复人(前端传 replyUserId),缺省回退顶层评论作者,自己回复自己除外 + Long toUserId = comment.getReplyUserId() != null ? comment.getReplyUserId() : root.getCreateUserId(); + userMessageService.sendReplyMessage(userId, toUserId, UserMessage.TYPE_PARAGRAPH_REPLY, + root.getBookId(), root.getBookIndexId(), rootId, comment.getCommentContent()); } @Override diff --git a/novel-front/src/main/java/com/java2nb/novel/service/impl/BookServiceImpl.java b/novel-front/src/main/java/com/java2nb/novel/service/impl/BookServiceImpl.java index 53c5ead..802b2a9 100644 --- a/novel-front/src/main/java/com/java2nb/novel/service/impl/BookServiceImpl.java +++ b/novel-front/src/main/java/com/java2nb/novel/service/impl/BookServiceImpl.java @@ -16,6 +16,7 @@ import com.java2nb.novel.service.AuthorService; import com.java2nb.novel.service.BookService; import com.java2nb.novel.service.FileService; import com.java2nb.novel.service.LikeService; +import com.java2nb.novel.service.UserMessageService; import com.java2nb.novel.vo.*; import io.github.xxyopen.model.page.PageBean; import io.github.xxyopen.model.page.builder.pagehelper.PageBuilder; @@ -95,6 +96,8 @@ public class BookServiceImpl implements BookService { private final LikeService likeService; + private final UserMessageService userMessageService; + private final BookPriceProperties bookPriceConfig; private final OpenAiImageModel openAiImageModel; @@ -907,6 +910,13 @@ public class BookServiceImpl implements BookService { bookCommentReplyMapper.insertSelective(commentReply); //增加评论回复数 bookCommentMapper.addReplyCount(commentReply.getCommentId()); + //书评被回复消息通知(回复者自己除外) + BookComment bookComment = getBookComment(commentReply.getCommentId()); + if (bookComment != null) { + userMessageService.sendReplyMessage(userId, bookComment.getCreateUserId(), + UserMessage.TYPE_BOOK_COMMENT_REPLY, bookComment.getBookId(), null, + bookComment.getId(), commentReply.getReplyContent()); + } } @Override diff --git a/novel-front/src/main/java/com/java2nb/novel/service/impl/UserMessageServiceImpl.java b/novel-front/src/main/java/com/java2nb/novel/service/impl/UserMessageServiceImpl.java new file mode 100644 index 0000000..fe07c77 --- /dev/null +++ b/novel-front/src/main/java/com/java2nb/novel/service/impl/UserMessageServiceImpl.java @@ -0,0 +1,80 @@ +package com.java2nb.novel.service.impl; + +import com.github.pagehelper.PageHelper; +import com.java2nb.novel.entity.UserMessage; +import com.java2nb.novel.mapper.FrontUserMessageMapper; +import com.java2nb.novel.service.UserMessageService; +import com.java2nb.novel.vo.UserMessageVO; +import io.github.xxyopen.model.page.PageBean; +import io.github.xxyopen.model.page.builder.pagehelper.PageBuilder; +import lombok.RequiredArgsConstructor; +import org.apache.commons.lang3.StringUtils; +import org.springframework.stereotype.Service; + +/** + * 用户消息服务实现 + * + * @author 11797 + */ +@Service +@RequiredArgsConstructor +public class UserMessageServiceImpl implements UserMessageService { + + /** + * 消息内容摘要最大长度,与表字段 content 保持一致 + */ + private static final int CONTENT_MAX_LENGTH = 200; + + private final FrontUserMessageMapper userMessageMapper; + + @Override + public void sendReplyMessage(Long sendUserId, Long toUserId, byte msgType, Long bookId, + Long bookIndexId, Long targetId, String replyContent) { + // 无接收人、自己回复自己、无被回复主评论,均不通知 + if (toUserId == null || targetId == null || toUserId.equals(sendUserId)) { + return; + } + UserMessage message = new UserMessage(); + message.setUserId(toUserId); + message.setSendUserId(sendUserId); + message.setMsgType(msgType); + message.setBookId(bookId); + message.setBookIndexId(bookIndexId); + message.setTargetId(targetId); + message.setContent(abbreviateContent(replyContent)); + message.setReadFlag((byte) 0); + userMessageMapper.insert(message); + } + + /** + * 截取回复内容摘要,去除换行避免破坏展示 + */ + private String abbreviateContent(String content) { + if (StringUtils.isBlank(content)) { + return null; + } + String clean = content.replaceAll("\\s+", " ").trim(); + return clean.length() > CONTENT_MAX_LENGTH ? clean.substring(0, CONTENT_MAX_LENGTH) : clean; + } + + @Override + public PageBean listMyMessageByPage(Long userId, Byte msgType, int page, int pageSize) { + PageHelper.startPage(page, pageSize); + return PageBuilder.build(userMessageMapper.listByPage(userId, msgType)); + } + + @Override + public long countUnread(Long userId) { + return userMessageMapper.countUnread(userId); + } + + @Override + public void markRead(Long userId, Long id) { + userMessageMapper.markRead(userId, id); + } + + @Override + public void markAllRead(Long userId) { + userMessageMapper.markAllRead(userId); + } +} diff --git a/novel-front/src/main/java/com/java2nb/novel/vo/UserMessageVO.java b/novel-front/src/main/java/com/java2nb/novel/vo/UserMessageVO.java new file mode 100644 index 0000000..c53fa17 --- /dev/null +++ b/novel-front/src/main/java/com/java2nb/novel/vo/UserMessageVO.java @@ -0,0 +1,42 @@ +package com.java2nb.novel.vo; + +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.java2nb.novel.core.serialize.CommentUserNameSerialize; +import com.java2nb.novel.core.serialize.TimeAgoFormatSerialize; +import com.java2nb.novel.entity.UserMessage; +import lombok.Data; + +import java.util.Date; + +/** + * 用户消息展示VO + * + * @author 11797 + */ +@Data +public class UserMessageVO extends UserMessage { + + /** + * 发送人昵称(脱敏展示) + */ + @JsonSerialize(using = CommentUserNameSerialize.class) + private String sendUserName; + + /** + * 发送人头像 + */ + private String sendUserPhoto; + + /** + * 关联书名 + */ + private String bookName; + + /** + * 关联章节名(段评消息) + */ + private String indexName; + + @JsonSerialize(using = TimeAgoFormatSerialize.class) + private Date createTime; +} diff --git a/novel-front/src/main/resources/mybatis/mapping/FrontUserMessageMapper.xml b/novel-front/src/main/resources/mybatis/mapping/FrontUserMessageMapper.xml new file mode 100644 index 0000000..2f9f910 --- /dev/null +++ b/novel-front/src/main/resources/mybatis/mapping/FrontUserMessageMapper.xml @@ -0,0 +1,36 @@ + + + + + + insert into user_message (user_id, send_user_id, msg_type, book_id, book_index_id, target_id, content, read_flag, create_time) + values (#{userId}, #{sendUserId}, #{msgType}, #{bookId}, #{bookIndexId}, #{targetId}, #{content}, 0, now()) + + + + + + + + update user_message set read_flag = 1 where user_id = #{userId} and id = #{id} and read_flag = 0 + + + + update user_message set read_flag = 1 where user_id = #{userId} and read_flag = 0 + + + diff --git a/novel-front/src/main/resources/templates/mobile/book/book_content.html b/novel-front/src/main/resources/templates/mobile/book/book_content.html index 8aecede..f5f033a 100644 --- a/novel-front/src/main/resources/templates/mobile/book/book_content.html +++ b/novel-front/src/main/resources/templates/mobile/book/book_content.html @@ -714,7 +714,7 @@ return dfd.promise(); }, // 发表段落评论/回复(rootId>0表示回复),resolve 完整 RestResult - add: function (idx, content, rootId, replyUserName) { + add: function (idx, content, rootId, replyUserName, replyUserId) { var dfd = $.Deferred(); $.ajax({ type: "POST", @@ -725,6 +725,7 @@ 'paragraphIdx': idx, 'rootId': rootId || 0, 'replyUserName': replyUserName || '', + 'replyUserId': replyUserId || '', 'commentContent': content }, dataType: "json", @@ -826,7 +827,8 @@ $('#pcList').on('click', '.pc-reply-btn', function () { pcReplyTarget = { rootId: $(this).data('root'), - name: String($(this).data('name') || '') + name: String($(this).data('name') || ''), + uid: $(this).data('uid') }; $('#pcReplyHintText').text('回复 @' + pcReplyTarget.name); $('#pcReplyHint').show(); @@ -948,7 +950,7 @@ if (replies.length > 0) { $op.append($('').text(replies.length + '条回复')); } - $op.append($('回复').data('root', item.id).data('name', item.createUserName)); + $op.append($('回复').data('root', item.id).data('name', item.createUserName).data('uid', item.createUserId)); $item.append($op); return $item; } @@ -965,7 +967,7 @@ $ri.append($ru); $ri.append($('
').text(r.commentContent)); $ri.append($('
') - .append($('回复').data('root', rootId).data('name', r.createUserName))); + .append($('回复').data('root', rootId).data('name', r.createUserName).data('uid', r.createUserId))); return $ri; } @@ -995,7 +997,8 @@ $btn.addClass('layui-btn-disabled').attr('disabled', 'disabled').text('提交中...'); var rootId = pcReplyTarget ? pcReplyTarget.rootId : 0; var replyUserName = pcReplyTarget ? pcReplyTarget.name : ''; - ParagraphApi.add(idx, content, rootId, replyUserName).then(function (res) { + var replyUserId = pcReplyTarget ? pcReplyTarget.uid : ''; + ParagraphApi.add(idx, content, rootId, replyUserName, replyUserId).then(function (res) { pcSubmitting = false; $btn.removeClass('layui-btn-disabled').removeAttr('disabled').text('发表'); if (res.code == 200) { diff --git a/novel-front/src/main/resources/templates/mobile/index.html b/novel-front/src/main/resources/templates/mobile/index.html index 1a3dd2b..f913c90 100644 --- a/novel-front/src/main/resources/templates/mobile/index.html +++ b/novel-front/src/main/resources/templates/mobile/index.html @@ -40,8 +40,13 @@ -webkit-transition: all .3s; } - /* 顶部 Logo:等比缩放不溢出 */ + /* 顶部 Logo:等比缩放不溢出;右侧消息入口 */ .site-header { + display: -webkit-flex; + display: flex; + -webkit-align-items: center; + align-items: center; + justify-content: space-between; padding: 10px 12px 0; } @@ -50,6 +55,43 @@ max-width: 140px; } + /* 右上角未读消息:铃铛圆形入口 + 主题绿渐变角标,白描边与图标区分层 */ + .header-msg { + position: relative; + -webkit-flex: 0 0 auto; + flex: 0 0 auto; + display: flex; + align-items: center; + justify-content: center; + width: 36px; + height: 36px; + border-radius: 50%; + background: #fff; + box-shadow: 0 2px 8px rgba(62, 175, 124, .10); + color: #3eaf7c; + } + + .header-msg .layui-icon { + font-size: 20px; + } + + .header-msg .hm-badge { + position: absolute; + top: -5px; + right: -5px; + display: none; + min-width: 16px; + padding: 0 4px; + line-height: 15px; + text-align: center; + font-size: 10px; + color: #fff; + background: linear-gradient(135deg, #3eaf7c, #6cc798); + border: 1.5px solid #fff; + border-radius: 9px; + box-shadow: 0 2px 4px rgba(62, 175, 124, .30); + } + /* 搜索条:胶囊输入框 + 主题色等高按钮 */ .search-bar { display: -webkit-flex; @@ -401,6 +443,10 @@