feat: improve nursing article module with rich content and view tracking

- Add content field (LONGTEXT) with Quill rich text editor in admin UI
- Add view_count field with auto-increment API endpoint
- Replace manual image URL input with image-upload component
- Add /system/nursing/published and /view/{id} public endpoints
- Improve Android error handling with toast and cache fallback
- Optimize ViewHolderFive adapter to reuse instead of recreating

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
renjianbo
2026-05-23 00:52:12 +08:00
parent a53ed96119
commit 12d67c00f4
10 changed files with 125 additions and 37 deletions

View File

@@ -145,7 +145,7 @@ public class HomeFragment extends BaseFragment {
private void initNursingInfo() { private void initNursingInfo() {
OkGo.<String>get(HttpConstants.URi_system_getAppIndexInfo) OkGo.<String>get(HttpConstants.URi_system_getAppIndexInfo)
.converter(new StringConvert()) .converter(new StringConvert())
.cacheMode(CacheMode.NO_CACHE) .cacheMode(CacheMode.REQUEST_FAILED_READ_CACHE)
.adapt(new ObservableResponse<String>()) .adapt(new ObservableResponse<String>())
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
@@ -181,6 +181,7 @@ public class HomeFragment extends BaseFragment {
@Override @Override
public void onError(@NonNull Throwable e) { public void onError(@NonNull Throwable e) {
e.printStackTrace(); e.printStackTrace();
toast("加载护理资讯失败");
} }
@Override @Override

View File

@@ -32,10 +32,12 @@ import de.greenrobot.event.EventBus;
public class ViewHolderFive extends AbstractViewTypeHolder { public class ViewHolderFive extends AbstractViewTypeHolder {
private final RecyclerView style_recyleview; private final RecyclerView style_recyleview;
private BaseQuickAdapter<homeListBean, BaseViewHolder> mAdapter;
public ViewHolderFive(View itemView) { public ViewHolderFive(View itemView) {
super(itemView); super(itemView);
style_recyleview = (RecyclerView) itemView.findViewById(R.id.reply_rcey); style_recyleview = (RecyclerView) itemView.findViewById(R.id.reply_rcey);
style_recyleview.setLayoutManager(new LinearLayoutManager(itemView.getContext()));
} }
@Override @Override
@@ -43,36 +45,40 @@ public class ViewHolderFive extends AbstractViewTypeHolder {
super.bindHolder(dataBean, position, mContext, mviewListener); super.bindHolder(dataBean, position, mContext, mviewListener);
//护理资讯 //护理资讯
LinearLayoutManager manager = new LinearLayoutManager(mContext);
style_recyleview.setLayoutManager(manager);
List<homeListBean> data = dataBean.getNursingInfoList(); List<homeListBean> data = dataBean.getNursingInfoList();
if (data == null) { if (data == null) {
data = new ArrayList<>(); data = new ArrayList<>();
} }
final List<homeListBean> finalData = data; final List<homeListBean> finalData = data;
style_recyleview.setAdapter(new BaseQuickAdapter<homeListBean, BaseViewHolder>(R.layout.home_list_item, data) {
@Override if (mAdapter == null) {
protected void convert(final BaseViewHolder helper, final homeListBean item) { mAdapter = new BaseQuickAdapter<homeListBean, BaseViewHolder>(R.layout.home_list_item, data) {
WidgetTools.setTextfive((TextView) helper.getView(R.id.img_modify), "", helper.getAdapterPosition() + 1 + "." + item.getContent()); @Override
GlideTools.init(mContext).displaypic((ImageView) helper.getView(R.id.home_img_01), item.getPic(), R.mipmap.home_bj01); protected void convert(final BaseViewHolder helper, final homeListBean item) {
RelativeLayout rl_style = (RelativeLayout)helper.getView(R.id.reply_dialog_rl); WidgetTools.setTextfive((TextView) helper.getView(R.id.img_modify), "", helper.getAdapterPosition() + 1 + "." + item.getContent());
rl_style.setOnClickListener(new View.OnClickListener() { GlideTools.init(mContext).displaypic((ImageView) helper.getView(R.id.home_img_01), item.getPic(), R.mipmap.home_bj01);
@Override RelativeLayout rl_style = (RelativeLayout) helper.getView(R.id.reply_dialog_rl);
public void onClick(View view) { rl_style.setOnClickListener(new View.OnClickListener() {
if(!ProfileSpUtils.getInstance().isLogin()){ @Override
Intent intent =new Intent(mContext, NewCodeLoginActivity.class); public void onClick(View view) {
mContext.startActivity(intent); if (!ProfileSpUtils.getInstance().isLogin()) {
return; Intent intent = new Intent(mContext, NewCodeLoginActivity.class);
mContext.startActivity(intent);
return;
}
String url = item.getUrl();
if (url != null && !url.isEmpty()) {
XfiveWebActivity.runActivity(mContext, "了解陪护", url);
} else {
XfiveWebActivity.runActivity(mContext, "了解陪护", "file:///android_asset/privacy.html");
}
} }
String url = item.getUrl(); });
if (url != null && !url.isEmpty()) { }
XfiveWebActivity.runActivity(mContext, "了解陪护", url); };
} else { style_recyleview.setAdapter(mAdapter);
XfiveWebActivity.runActivity(mContext, "了解陪护", "file:///android_asset/privacy.html"); } else {
} mAdapter.setNewData(finalData);
} }
});
}
});
} }
} }

View File

@@ -73,11 +73,12 @@
<el-table-column label="标题" align="center" prop="title" :show-overflow-tooltip="true" /> <el-table-column label="标题" align="center" prop="title" :show-overflow-tooltip="true" />
<el-table-column label="封面图片" align="center" prop="imageUrl" width="100"> <el-table-column label="封面图片" align="center" prop="imageUrl" width="100">
<template slot-scope="scope"> <template slot-scope="scope">
<el-image v-if="scope.row.imageUrl" :src="scope.row.imageUrl" style="width:50px;height:50px;" fit="cover" /> <image-preview v-if="scope.row.imageUrl" :src="scope.row.imageUrl" :width="50" :height="50" />
<span v-else>-</span> <span v-else>-</span>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="文章链接" align="center" prop="articleUrl" :show-overflow-tooltip="true" /> <el-table-column label="文章链接" align="center" prop="articleUrl" :show-overflow-tooltip="true" />
<el-table-column label="阅读量" align="center" prop="viewCount" width="80" />
<el-table-column label="状态" align="center" prop="status" width="80"> <el-table-column label="状态" align="center" prop="status" width="80">
<template slot-scope="scope"> <template slot-scope="scope">
<el-tag :type="scope.row.status == '0' ? 'success' : 'danger'" size="small"> <el-tag :type="scope.row.status == '0' ? 'success' : 'danger'" size="small">
@@ -120,10 +121,13 @@
<el-input v-model="form.title" placeholder="请输入标题" /> <el-input v-model="form.title" placeholder="请输入标题" />
</el-form-item> </el-form-item>
<el-form-item label="封面图片" prop="imageUrl"> <el-form-item label="封面图片" prop="imageUrl">
<el-input v-model="form.imageUrl" placeholder="请输入图片URL地址" /> <image-upload v-model="form.imageUrl" :limit="1" />
</el-form-item> </el-form-item>
<el-form-item label="文章链接" prop="articleUrl"> <el-form-item label="文章链接" prop="articleUrl">
<el-input v-model="form.articleUrl" placeholder="请输入文章链接URL地址" /> <el-input v-model="form.articleUrl" placeholder="请输入文章链接URL地址(可选)" />
</el-form-item>
<el-form-item label="文章正文" prop="content">
<editor v-model="form.content" :min-height="192"/>
</el-form-item> </el-form-item>
<el-form-item label="排序" prop="sortOrder"> <el-form-item label="排序" prop="sortOrder">
<el-input-number v-model="form.sortOrder" :min="0" style="width: 100%" placeholder="请输入排序号" /> <el-input-number v-model="form.sortOrder" :min="0" style="width: 100%" placeholder="请输入排序号" />
@@ -174,12 +178,6 @@ export default {
{ required: true, message: "标题不能为空", trigger: "blur" } { required: true, message: "标题不能为空", trigger: "blur" }
], ],
imageUrl: [ imageUrl: [
{ required: true, message: "封面图片不能为空", trigger: "blur" }
],
articleUrl: [
{ required: true, message: "文章链接不能为空", trigger: "blur" }
],
status: [
{ required: true, message: "请选择状态", trigger: "change" } { required: true, message: "请选择状态", trigger: "change" }
], ],
} }
@@ -207,6 +205,7 @@ export default {
title: null, title: null,
imageUrl: null, imageUrl: null,
articleUrl: null, articleUrl: null,
content: null,
sortOrder: 0, sortOrder: 0,
status: '0', status: '0',
remark: null, remark: null,

View File

@@ -109,7 +109,7 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter
// 过滤请求 // 过滤请求
.authorizeRequests() .authorizeRequests()
// 对于登录login 注册register 验证码captchaImage 允许匿名访问 // 对于登录login 注册register 验证码captchaImage 允许匿名访问
.antMatchers("/login","/appLogin","/system/msm","/smsLogin", "/register", "/captchaImage","/weixinLogin","/getUserToken","/system/weixin/wxPayNotify","/system/weixin/wxRefundNotify","/system/user/getAppIndexInfo").anonymous() .antMatchers("/login","/appLogin","/system/msm","/smsLogin", "/register", "/captchaImage","/weixinLogin","/getUserToken","/system/weixin/wxPayNotify","/system/weixin/wxRefundNotify","/system/user/getAppIndexInfo","/system/nursing/published","/system/nursing/view/**").anonymous()
// 静态资源,可匿名访问 // 静态资源,可匿名访问
.antMatchers(HttpMethod.GET, "/", "/*.html", "/**/*.html", "/**/*.css", "/**/*.js", "/profile/**").permitAll() .antMatchers(HttpMethod.GET, "/", "/*.html", "/**/*.html", "/**/*.css", "/**/*.js", "/profile/**").permitAll()
.antMatchers("/swagger-ui.html", "/swagger-resources/**", "/webjars/**", "/*/api-docs", "/druid/**").permitAll() .antMatchers("/swagger-ui.html", "/swagger-resources/**", "/webjars/**", "/*/api-docs", "/druid/**").permitAll()

View File

@@ -20,6 +20,8 @@ import com.ruoyi.system.domain.RlzNursingArticle;
import com.ruoyi.system.service.IRlzNursingArticleService; import com.ruoyi.system.service.IRlzNursingArticleService;
import com.ruoyi.common.utils.poi.ExcelUtil; import com.ruoyi.common.utils.poi.ExcelUtil;
import com.ruoyi.common.core.page.TableDataInfo; import com.ruoyi.common.core.page.TableDataInfo;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
/** /**
* 护理资讯Controller * 护理资讯Controller
@@ -91,6 +93,39 @@ public class RlzNursingArticleController extends BaseController
return toAjax(rlzNursingArticleService.updateRlzNursingArticle(rlzNursingArticle)); return toAjax(rlzNursingArticleService.updateRlzNursingArticle(rlzNursingArticle));
} }
/**
* 获取已发布的护理资讯列表(无需认证,供App使用)
*/
@GetMapping("/published")
public AjaxResult publishedList()
{
List<RlzNursingArticle> list = rlzNursingArticleService.selectPublishedList();
JSONArray arr = new JSONArray();
if (list != null) {
for (RlzNursingArticle a : list) {
JSONObject item = new JSONObject();
item.put("id", a.getId());
item.put("title", a.getTitle());
item.put("realTimeInfoImage", a.getImageUrl());
item.put("realTimeInfoUrl", a.getArticleUrl());
item.put("content", a.getContent());
item.put("viewCount", a.getViewCount());
arr.add(item);
}
}
return AjaxResult.success(arr);
}
/**
* 阅读量+1(无需认证,App点击文章时调用)
*/
@PutMapping("/view/{id}")
public AjaxResult incrementView(@PathVariable("id") Long id)
{
rlzNursingArticleService.incrementViewCount(id);
return AjaxResult.success();
}
/** /**
* 删除护理资讯 * 删除护理资讯
*/ */

View File

@@ -30,6 +30,9 @@ public class RlzNursingArticle extends BaseEntity
@Excel(name = "文章链接") @Excel(name = "文章链接")
private String articleUrl; private String articleUrl;
/** 文章正文(HTML) */
private String content;
/** 排序 */ /** 排序 */
private Integer sortOrder; private Integer sortOrder;
@@ -37,6 +40,9 @@ public class RlzNursingArticle extends BaseEntity
@Excel(name = "状态", readConverterExp = "0=发布,1=隐藏") @Excel(name = "状态", readConverterExp = "0=发布,1=隐藏")
private String status; private String status;
/** 阅读次数 */
private Long viewCount;
public void setId(Long id) public void setId(Long id)
{ {
this.id = id; this.id = id;
@@ -77,6 +83,16 @@ public class RlzNursingArticle extends BaseEntity
return articleUrl; return articleUrl;
} }
public void setContent(String content)
{
this.content = content;
}
public String getContent()
{
return content;
}
public void setSortOrder(Integer sortOrder) public void setSortOrder(Integer sortOrder)
{ {
this.sortOrder = sortOrder; this.sortOrder = sortOrder;
@@ -97,6 +113,16 @@ public class RlzNursingArticle extends BaseEntity
return status; return status;
} }
public void setViewCount(Long viewCount)
{
this.viewCount = viewCount;
}
public Long getViewCount()
{
return viewCount;
}
@Override @Override
public String toString() { public String toString() {
return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE)
@@ -104,8 +130,10 @@ public class RlzNursingArticle extends BaseEntity
.append("title", getTitle()) .append("title", getTitle())
.append("imageUrl", getImageUrl()) .append("imageUrl", getImageUrl())
.append("articleUrl", getArticleUrl()) .append("articleUrl", getArticleUrl())
.append("content", getContent())
.append("sortOrder", getSortOrder()) .append("sortOrder", getSortOrder())
.append("status", getStatus()) .append("status", getStatus())
.append("viewCount", getViewCount())
.append("createBy", getCreateBy()) .append("createBy", getCreateBy())
.append("createTime", getCreateTime()) .append("createTime", getCreateTime())
.append("updateBy", getUpdateBy()) .append("updateBy", getUpdateBy())

View File

@@ -24,4 +24,6 @@ public interface RlzNursingArticleMapper
public int deleteRlzNursingArticleById(Long id); public int deleteRlzNursingArticleById(Long id);
public int deleteRlzNursingArticleByIds(Long[] ids); public int deleteRlzNursingArticleByIds(Long[] ids);
public int incrementViewCount(Long id);
} }

View File

@@ -24,4 +24,6 @@ public interface IRlzNursingArticleService
public int deleteRlzNursingArticleByIds(Long[] ids); public int deleteRlzNursingArticleByIds(Long[] ids);
public int deleteRlzNursingArticleById(Long id); public int deleteRlzNursingArticleById(Long id);
public int incrementViewCount(Long id);
} }

View File

@@ -63,4 +63,10 @@ public class RlzNursingArticleServiceImpl implements IRlzNursingArticleService
{ {
return rlzNursingArticleMapper.deleteRlzNursingArticleById(id); return rlzNursingArticleMapper.deleteRlzNursingArticleById(id);
} }
@Override
public int incrementViewCount(Long id)
{
return rlzNursingArticleMapper.incrementViewCount(id);
}
} }

View File

@@ -9,8 +9,10 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<result property="title" column="title" /> <result property="title" column="title" />
<result property="imageUrl" column="image_url" /> <result property="imageUrl" column="image_url" />
<result property="articleUrl" column="article_url" /> <result property="articleUrl" column="article_url" />
<result property="content" column="content" />
<result property="sortOrder" column="sort_order" /> <result property="sortOrder" column="sort_order" />
<result property="status" column="status" /> <result property="status" column="status" />
<result property="viewCount" column="view_count" />
<result property="createBy" column="create_by" /> <result property="createBy" column="create_by" />
<result property="createTime" column="create_time" /> <result property="createTime" column="create_time" />
<result property="updateBy" column="update_by" /> <result property="updateBy" column="update_by" />
@@ -19,7 +21,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
</resultMap> </resultMap>
<sql id="selectRlzNursingArticleVo"> <sql id="selectRlzNursingArticleVo">
select id, title, image_url, article_url, sort_order, status, select id, title, image_url, article_url, cast(content as char) as content, sort_order, status, view_count,
create_by, create_time, update_by, update_time, remark create_by, create_time, update_by, update_time, remark
from rlz_nursing_article from rlz_nursing_article
</sql> </sql>
@@ -50,6 +52,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="title != null">title,</if> <if test="title != null">title,</if>
<if test="imageUrl != null">image_url,</if> <if test="imageUrl != null">image_url,</if>
<if test="articleUrl != null">article_url,</if> <if test="articleUrl != null">article_url,</if>
<if test="content != null">content,</if>
<if test="sortOrder != null">sort_order,</if> <if test="sortOrder != null">sort_order,</if>
<if test="status != null">status,</if> <if test="status != null">status,</if>
<if test="createBy != null">create_by,</if> <if test="createBy != null">create_by,</if>
@@ -60,6 +63,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="title != null">#{title},</if> <if test="title != null">#{title},</if>
<if test="imageUrl != null">#{imageUrl},</if> <if test="imageUrl != null">#{imageUrl},</if>
<if test="articleUrl != null">#{articleUrl},</if> <if test="articleUrl != null">#{articleUrl},</if>
<if test="content != null">#{content},</if>
<if test="sortOrder != null">#{sortOrder},</if> <if test="sortOrder != null">#{sortOrder},</if>
<if test="status != null">#{status},</if> <if test="status != null">#{status},</if>
<if test="createBy != null">#{createBy},</if> <if test="createBy != null">#{createBy},</if>
@@ -74,6 +78,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="title != null">title = #{title},</if> <if test="title != null">title = #{title},</if>
<if test="imageUrl != null">image_url = #{imageUrl},</if> <if test="imageUrl != null">image_url = #{imageUrl},</if>
<if test="articleUrl != null">article_url = #{articleUrl},</if> <if test="articleUrl != null">article_url = #{articleUrl},</if>
<if test="content != null">content = #{content},</if>
<if test="sortOrder != null">sort_order = #{sortOrder},</if> <if test="sortOrder != null">sort_order = #{sortOrder},</if>
<if test="status != null">status = #{status},</if> <if test="status != null">status = #{status},</if>
<if test="updateBy != null">update_by = #{updateBy},</if> <if test="updateBy != null">update_by = #{updateBy},</if>
@@ -87,6 +92,10 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
delete from rlz_nursing_article where id = #{id} delete from rlz_nursing_article where id = #{id}
</delete> </delete>
<update id="incrementViewCount" parameterType="Long">
update rlz_nursing_article set view_count = view_count + 1 where id = #{id}
</update>
<delete id="deleteRlzNursingArticleByIds" parameterType="String"> <delete id="deleteRlzNursingArticleByIds" parameterType="String">
delete from rlz_nursing_article where id in delete from rlz_nursing_article where id in
<foreach item="id" collection="array" open="(" separator="," close=")"> <foreach item="id" collection="array" open="(" separator="," close=")">