From f5be5754910e655362c1db0b1ce3fe21d89f8493 Mon Sep 17 00:00:00 2001 From: zhanglei <350328959@qq.com> Date: Mon, 29 Jun 2026 16:47:46 +0800 Subject: [PATCH] =?UTF-8?q?feat(home):=20=E6=96=B0=E5=A2=9E=E5=81=A5?= =?UTF-8?q?=E5=BA=B7=E8=B5=84=E8=AE=AF=E6=A8=A1=E5=9D=97=EF=BC=88=E5=81=A5?= =?UTF-8?q?=E5=BA=B7=E7=9F=A5=E8=AF=86=20+=20=E5=81=A5=E5=BA=B7=E5=92=A8?= =?UTF-8?q?=E8=AF=A2=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 采用 HMAC-SHA256 签名认证获取 Token,通过栏目/文章 API 获取数据, 详情/更多/搜索均跳转 H5 页面。同时新增带下划线指示器的 Tab 样式。 Co-Authored-By: Claude Opus 4.7 --- .../adapter/HealthConsultationAdapter.kt | 27 +++ .../adapter/HealthKnowledgeAdapter.kt | 32 +++ .../bankRequest/HealthInfoRequest.kt | 182 ++++++++++++++++++ .../bean/home/HealthInfoBean.kt | 53 +++++ .../healthyclients/data/api/HealthInfoApi.kt | 63 ++++++ .../healthyclients/fragment/HomeFragment.kt | 160 ++++++++++++++- .../xjjk/healthyclients/retrofit/UrlConfig.kt | 40 +++- .../healthyclients/superfuntion/ViewExt.kt | 1 + .../view/HealthConsultationView.kt | 73 +++++++ .../view/HealthKnowledgeView.kt | 109 +++++++++++ .../healthyclients/view/WatchBindDialog.kt | 2 +- ...olor_child_tab_layout_text_index_theme.xml | 5 + ...xml => bg_child_tab_layout_item_index.xml} | 0 .../bg_child_tab_layout_item_line.xml | 8 + .../drawable/bg_theme_background_shape3.xml | 6 + .../res/layout/custom_search_view_home.xml | 58 ++++++ app/src/main/res/layout/fragment_home.xml | 15 ++ .../res/layout/item_health_consultation.xml | 54 ++++++ .../main/res/layout/item_health_knowledge.xml | 104 ++++++++++ .../item_tablayout_group_index_theme.xml | 25 +++ .../res/layout/layout_health_consultation.xml | 66 +++++++ .../res/layout/layout_health_knowledge.xml | 91 +++++++++ .../res/mipmap-xhdpi/ic_home_know_tag.png | Bin 0 -> 1349 bytes .../mipmap-xhdpi/ic_placeholder_rectangle.png | Bin 0 -> 3131 bytes .../mipmap-xhdpi/ic_placeholder_square.png | Bin 0 -> 4253 bytes 25 files changed, 1169 insertions(+), 5 deletions(-) create mode 100644 app/src/main/java/com/xjjk/healthyclients/adapter/HealthConsultationAdapter.kt create mode 100644 app/src/main/java/com/xjjk/healthyclients/adapter/HealthKnowledgeAdapter.kt create mode 100644 app/src/main/java/com/xjjk/healthyclients/bankRequest/HealthInfoRequest.kt create mode 100644 app/src/main/java/com/xjjk/healthyclients/bean/home/HealthInfoBean.kt create mode 100644 app/src/main/java/com/xjjk/healthyclients/data/api/HealthInfoApi.kt create mode 100644 app/src/main/java/com/xjjk/healthyclients/view/HealthConsultationView.kt create mode 100644 app/src/main/java/com/xjjk/healthyclients/view/HealthKnowledgeView.kt create mode 100644 app/src/main/res/color/color_child_tab_layout_text_index_theme.xml rename app/src/main/res/drawable/{bg_child_tab_layout_item2.xml => bg_child_tab_layout_item_index.xml} (100%) create mode 100644 app/src/main/res/drawable/bg_child_tab_layout_item_line.xml create mode 100644 app/src/main/res/drawable/bg_theme_background_shape3.xml create mode 100644 app/src/main/res/layout/custom_search_view_home.xml create mode 100644 app/src/main/res/layout/item_health_consultation.xml create mode 100644 app/src/main/res/layout/item_health_knowledge.xml create mode 100644 app/src/main/res/layout/item_tablayout_group_index_theme.xml create mode 100644 app/src/main/res/layout/layout_health_consultation.xml create mode 100644 app/src/main/res/layout/layout_health_knowledge.xml create mode 100644 app/src/main/res/mipmap-xhdpi/ic_home_know_tag.png create mode 100644 app/src/main/res/mipmap-xhdpi/ic_placeholder_rectangle.png create mode 100644 app/src/main/res/mipmap-xhdpi/ic_placeholder_square.png diff --git a/app/src/main/java/com/xjjk/healthyclients/adapter/HealthConsultationAdapter.kt b/app/src/main/java/com/xjjk/healthyclients/adapter/HealthConsultationAdapter.kt new file mode 100644 index 0000000..e30d0b0 --- /dev/null +++ b/app/src/main/java/com/xjjk/healthyclients/adapter/HealthConsultationAdapter.kt @@ -0,0 +1,27 @@ +package com.xjjk.healthyclients.adapter + +import android.widget.ImageView +import com.chad.library.adapter.base.BaseQuickAdapter +import com.chad.library.adapter.base.viewholder.BaseViewHolder +import com.xjjk.healthyclients.R +import com.xjjk.healthyclients.bean.home.ArticleBean +import com.xjjk.healthyclients.superfuntion.loadRoundedImage +import com.xjjk.healthyclients.superfuntion.orEmptyDefault + +/** + * 健康咨询列表 Adapter + */ +class HealthConsultationAdapter : + BaseQuickAdapter(R.layout.item_health_consultation) { + + override fun convert(holder: BaseViewHolder, item: ArticleBean) { + // 标题 + holder.setText(R.id.tv_title, item.articleTitle.orEmptyDefault()) + // 收藏和分享数 + holder.setText(R.id.tv_meta, "收藏 ${item.favoriteCount} 分享 ${item.likeCount}") + // 封面图片 80x60 圆角8 + holder.getView(R.id.iv_cover)?.loadRoundedImage( + item.articleHeaderimage, 8f,R.mipmap.ic_placeholder_rectangle + ) + } +} diff --git a/app/src/main/java/com/xjjk/healthyclients/adapter/HealthKnowledgeAdapter.kt b/app/src/main/java/com/xjjk/healthyclients/adapter/HealthKnowledgeAdapter.kt new file mode 100644 index 0000000..52529cf --- /dev/null +++ b/app/src/main/java/com/xjjk/healthyclients/adapter/HealthKnowledgeAdapter.kt @@ -0,0 +1,32 @@ +package com.xjjk.healthyclients.adapter + +import android.widget.ImageView +import com.chad.library.adapter.base.BaseQuickAdapter +import com.chad.library.adapter.base.viewholder.BaseViewHolder +import com.sw.healthyclients.utils.DateUtil +import com.xjjk.healthyclients.R +import com.xjjk.healthyclients.bean.home.ArticleBean +import com.xjjk.healthyclients.superfuntion.loadRoundedImage +import com.xjjk.healthyclients.superfuntion.orEmptyDefault + +/** + * 健康知识列表 Adapter + */ +class HealthKnowledgeAdapter : + BaseQuickAdapter(R.layout.item_health_knowledge) { + + override fun convert(holder: BaseViewHolder, item: ArticleBean) { + // 封面图片 100x100 圆角15 + holder.getView(R.id.iv_cover)?.loadRoundedImage( + item.articleHeaderimage, 15f, R.mipmap.ic_placeholder_square, + ) + // 标题 + holder.setText(R.id.tv_title, item.articleTitle.orEmptyDefault()) + // 摘要 + holder.setText(R.id.tv_detail, item.articleSubtitle.orEmptyDefault("")) + // 标签文本 + holder.setText(R.id.tv_tag, item.articleTag.orEmptyDefault()) + // 日期 + holder.setText(R.id.tv_date, DateUtil.long2ShortDateStr(item.createTime)) + } +} diff --git a/app/src/main/java/com/xjjk/healthyclients/bankRequest/HealthInfoRequest.kt b/app/src/main/java/com/xjjk/healthyclients/bankRequest/HealthInfoRequest.kt new file mode 100644 index 0000000..917f091 --- /dev/null +++ b/app/src/main/java/com/xjjk/healthyclients/bankRequest/HealthInfoRequest.kt @@ -0,0 +1,182 @@ +package com.xjjk.healthyclients.bankRequest + +import com.sw.healthyclients.data.local.DataStoreManager +import com.xjjk.healthyclients.bean.home.ArticleBean +import com.xjjk.healthyclients.bean.home.ArticleListBean +import com.xjjk.healthyclients.bean.home.CategoryBean +import com.xjjk.healthyclients.data.api.CategoryListResponse +import com.xjjk.healthyclients.data.api.HealthInfoApi +import com.xjjk.healthyclients.data.api.TokenResponse +import com.xjjk.healthyclients.retrofit.UrlConfig +import com.xjjk.healthyclients.retrofit.getBankRetrofit +import com.xjjk.healthyclients.superfuntion.toJson +import com.xjjk.healthyclients.utils.SignatureUtils +import okhttp3.RequestBody.Companion.toRequestBody +import retrofit2.Call +import retrofit2.Callback +import retrofit2.Response + +/** + * 健康资讯网络请求封装类 + * 负责 Token 获取(签名认证)、栏目列表、文章列表的 API 调用 + */ +class HealthInfoRequest { + + private val api: HealthInfoApi = getBankRetrofit() + .create(HealthInfoApi::class.java) + + companion object { + /** 缓存的 Token,避免重复请求 */ + private var cachedToken: String? = null + } + + /** + * 获取 Token(带 HMAC-SHA256 签名) + */ + fun fetchToken( + onSuccess: (String) -> Unit, + onFailure: (Throwable) -> Unit + ) { + // 如果已有缓存 Token,直接返回 + cachedToken?.let { + onSuccess(it) + return + } + + try { + val timestamp = System.currentTimeMillis() / 1000 + val userId = DataStoreManager.getUserId() ?: "" + val workNo = DataStoreManager.getUserInfo()?.workNo ?: "" + val bodyString = """{"userId":"$userId","workNo":"$workNo"}""" + val sign = SignatureUtils.generateSignature(timestamp, bodyString, UrlConfig.SECRET_KEY) + + val headers = mapOf( + "Content-Type" to "application/json; charset=utf-8", + "X-Timestamp" to timestamp.toString(), + "X-Sign" to sign + ) + + val body = mapOf("userId" to userId, "workNo" to workNo) + + api.getToken(headers, body.toJson().toRequestBody()) + .enqueue(object : Callback { + override fun onResponse( + call: Call, + response: Response + ) { + if (response.code() == 200) { + val token = response.body()?.data?.token + if (!token.isNullOrEmpty()) { + cachedToken = token + onSuccess(token) + } else { + onFailure(Exception("Token 为空")) + } + } else { + onFailure(Exception("HTTP ${response.code()}: ${response.message()}")) + } + } + + override fun onFailure( + call: Call, + t: Throwable + ) { + onFailure(t) + } + }) + } catch (e: Exception) { + onFailure(e) + } + } + + /** + * 获取栏目列表 + */ + fun fetchCategoryList( + token: String, + onSuccess: (MutableList) -> Unit, + onFailure: (Throwable) -> Unit + ) { + try { + val headers = mapOf("token" to token) + + api.getCategoryList(headers) + .enqueue(object : Callback { + override fun onResponse( + call: Call, + response: Response + ) { + if (response.code() == 200) { + val data = response.body()?.data ?: mutableListOf() + onSuccess(data) + } else { + onFailure(Exception("HTTP ${response.code()}: ${response.message()}")) + } + } + + override fun onFailure( + call: Call, + t: Throwable + ) { + onFailure(t) + } + }) + } catch (e: Exception) { + onFailure(e) + } + } + + /** + * 分页查询文章列表 + * + * @param token 认证 Token + * @param categoryId 栏目 ID + * @param pageNum 页码 + * @param pageSize 每页数量 + * @param articleType 文章类型,"0"-图文,"1"-视频,不传则全部 + */ + fun fetchArticleList( + token: String, + categoryId: Int, + pageNum: Int = 1, + pageSize: Int = 3, + onSuccess: (MutableList?) -> Unit, + onFailure: (Throwable) -> Unit + ) { + try { + val headers = mapOf("token" to token) + val params = mapOf( + "categoryId" to categoryId.toString(), + "pageNum" to pageNum.toString(), + "pageSize" to pageSize.toString(), + "showAppHomepage" to "Y" + ) + + api.getArticleList(headers, params) + .enqueue(object : Callback { + override fun onResponse( + call: Call, + response: Response + ) { + if (response.code() == 200) { + val body = response.body() + onSuccess(body?.rows) + } else { + onFailure(Exception("HTTP ${response.code()}: ${response.message()}")) + } + } + + override fun onFailure(call: Call, t: Throwable) { + onFailure(t) + } + }) + } catch (e: Exception) { + onFailure(e) + } + } + + /** 清除缓存的 Token(登录态变化时调用) */ + fun clearToken() { + cachedToken = null + } +} diff --git a/app/src/main/java/com/xjjk/healthyclients/bean/home/HealthInfoBean.kt b/app/src/main/java/com/xjjk/healthyclients/bean/home/HealthInfoBean.kt new file mode 100644 index 0000000..ae67495 --- /dev/null +++ b/app/src/main/java/com/xjjk/healthyclients/bean/home/HealthInfoBean.kt @@ -0,0 +1,53 @@ +package com.xjjk.healthyclients.bean.home + +import com.google.gson.annotations.SerializedName + +/** + * 获取 token 响应 + */ +data class TokenBean( + @SerializedName("token") val token: String? = null +) + +/** + * 栏目分类 + */ +data class CategoryBean( + @SerializedName("categoryId") val categoryId: Int = 0, + @SerializedName("categoryName") val categoryName: String? = null, + @SerializedName("categoryStatus") val categoryStatus: String? = null, + @SerializedName("children") val children: MutableList? = null, + @SerializedName("parentId") val parentId: Int = 0, + @SerializedName("sortOrder") val sortOrder: Int = 0 +) + +/** + * 文章 + */ +data class ArticleBean( + @SerializedName("articleId") val articleId: Int = 0, + @SerializedName("articleTitle") val articleTitle: String? = null, + @SerializedName("articleSubtitle") val articleSubtitle: String? = null, + @SerializedName("articleTag") val articleTag: String? = null, + @SerializedName("articleHeaderimage") val articleHeaderimage: String? = null, + @SerializedName("articleType") val articleType: String? = null, + @SerializedName("articleExcerpt") val articleExcerpt: String? = null, + @SerializedName("articleContent") val articleContent: String? = null, + @SerializedName("articleVideo") val articleVideo: String? = null, + @SerializedName("favoriteCount") val favoriteCount: Int = 0, + @SerializedName("likeCount") val likeCount: Int = 0, + @SerializedName("viewCount") val viewCount: Int = 0, + @SerializedName("createTime") val createTime: String? = null, + @SerializedName("categoryId") val categoryId: Int = 0, + @SerializedName("articleScore") val articleScore: Double = 0.0, + @SerializedName("articleStatus") val articleStatus: String? = null, + @SerializedName("showAppHomepage") val showAppHomepage: String? = null +) + +/** + * 文章列表分页响应 + */ +data class ArticleListBean( + @SerializedName("rows") val rows: MutableList? = null, + @SerializedName("total") val total: Int = 0 +) diff --git a/app/src/main/java/com/xjjk/healthyclients/data/api/HealthInfoApi.kt b/app/src/main/java/com/xjjk/healthyclients/data/api/HealthInfoApi.kt new file mode 100644 index 0000000..5a7ec6a --- /dev/null +++ b/app/src/main/java/com/xjjk/healthyclients/data/api/HealthInfoApi.kt @@ -0,0 +1,63 @@ +package com.xjjk.healthyclients.data.api + +import com.xjjk.healthyclients.bean.home.ArticleListBean +import com.xjjk.healthyclients.bean.home.CategoryBean +import com.xjjk.healthyclients.bean.home.TokenBean +import com.xjjk.healthyclients.retrofit.UrlConfig +import okhttp3.RequestBody +import retrofit2.Call +import retrofit2.http.Body +import retrofit2.http.GET +import retrofit2.http.HeaderMap +import retrofit2.http.POST +import retrofit2.http.QueryMap + +/** + * 健康资讯 API 接口(使用银行 Retrofit 实例,baseUrl 为 getDefaultBankBaseUrl()) + */ +interface HealthInfoApi { + + /** + * 获取 Token(带签名认证) + */ + @POST("${UrlConfig.middleCheck}pe/frontend/init") + fun getToken( + @HeaderMap headers: Map, + @Body requestBody: RequestBody + ): Call + + /** + * 获取栏目列表 + */ + @GET("${UrlConfig.middleCheck}cms/frontend/category/list") + fun getCategoryList( + @HeaderMap headers: Map + ): Call + + /** + * 分页查询文章列表 + */ + @GET("${UrlConfig.middleCheck}cms/frontend/articleList") + fun getArticleList( + @HeaderMap headers: Map, + @QueryMap params: Map + ): Call +} + +/** + * Token 接口响应外层 + */ +data class TokenResponse( + val msg: String? = null, + val code: Int = 0, + val data: TokenBean? = null +) + +/** + * 栏目列表接口响应外层 + */ +data class CategoryListResponse( + val msg: String? = null, + val code: Int = 0, + val data: MutableList? = null +) diff --git a/app/src/main/java/com/xjjk/healthyclients/fragment/HomeFragment.kt b/app/src/main/java/com/xjjk/healthyclients/fragment/HomeFragment.kt index 7d7affd..a4ded34 100644 --- a/app/src/main/java/com/xjjk/healthyclients/fragment/HomeFragment.kt +++ b/app/src/main/java/com/xjjk/healthyclients/fragment/HomeFragment.kt @@ -15,8 +15,10 @@ import com.tencent.qcloud.tuikit.tuichat.bean.WorkBean import com.xjjk.healthyclients.R import com.xjjk.healthyclients.adapter.common.MultiItemTypeAdapter import com.xjjk.healthyclients.bankRequest.BankPointRequest +import com.xjjk.healthyclients.bankRequest.HealthInfoRequest import com.xjjk.healthyclients.base.BaseVMBFragment import com.xjjk.healthyclients.bean.guidance.GuidanceListBean +import com.xjjk.healthyclients.bean.home.CategoryBean import com.xjjk.healthyclients.bean.home.WatchInfoBean import com.xjjk.healthyclients.data.api.HomeApi import com.xjjk.healthyclients.databinding.FragmentHomeBinding @@ -67,6 +69,13 @@ class HomeFragment : var noticeTime = REQUEST_DURATION var watchInfo: WatchInfoBean? = null + /** 健康资讯请求 */ + private var mHealthInfoRequest: HealthInfoRequest? = null + /** 健康资讯 Token */ + private var mHealthToken: String? = null + /** 健康知识子栏目列表 */ + private var mKnowledgeCategories: MutableList = mutableListOf() + /** 通知轮播协程 Job,重新启动前先取消旧任务,防止多协程并发叠加 */ private var noticeJob: Job? = null @@ -140,6 +149,7 @@ class HomeFragment : }) mBinding.layoutItem3.fragmentGuidanceRvDoctor.adapter = mGuidanceFragmentDoctorAdapter mBinding.watchView.init(this@HomeFragment) + setupHealthViews() } override fun bindEvent() { @@ -169,9 +179,9 @@ class HomeFragment : } mBinding.watchView.mBinding.watchName.setOnClickListener { if (DataStoreManager.isLogin()) { - if (watchInfo?.hasWatch==true){ - showWatchBindDialog(watchInfo?.watchNo) - } +// if (watchInfo?.hasWatch==true){ +// showWatchBindDialog(watchInfo?.watchNo) +// } } else { toActivity(LoginActivity::class.java) } @@ -457,6 +467,7 @@ class HomeFragment : mViewModel.getHomeWatchInfo(DataStoreManager.getUserId()) mViewModel.getSysEnCryptInfo(UrlConfig.getHomeBankH5Url()) getBankPointDetail() + loadHealthInfoData() } } @@ -484,8 +495,151 @@ class HomeFragment : mViewModel.getHomeWatchInfo(DataStoreManager.getUserId()) mViewModel.getSysEnCryptInfo(UrlConfig.getHomeBankH5Url()) getBankPointDetail() + loadHealthInfoData() } } + // ==================== 健康资讯相关 ==================== + + /** 初始化健康资讯两个 View 的事件回调 */ + private fun setupHealthViews() { + mHealthInfoRequest = HealthInfoRequest() + + // 健康知识 - Tab 切换加载对应栏目文章 + mBinding.healthKnowledgeView.setOnTabSelectedListener { category -> + loadKnowledgeArticles(category.categoryId) + } + + // 健康知识 - 列表项点击 → 跳转文章详情 H5 + mBinding.healthKnowledgeView.setOnItemClickListener { article -> + navigateToArticleDetail(article.articleId) + } + + // 健康知识 - 更多按钮 → 跳转健康知识 H5 页面 + mBinding.healthKnowledgeView.setOnMoreClickListener { + navigateToHealthKnowledgeMore() + } + + // 健康知识 - 搜索 + mBinding.healthKnowledgeView.setOnSearchClickListener { + navigateToHealthKnowledgeSearch() + } + + + // 健康咨询 - 列表项点击 → 跳转文章详情 H5 + mBinding.healthConsultationView.setOnItemClickListener { article -> + navigateToArticleDetail(article.articleId) + } + + // 健康咨询 - 更多按钮 → 跳转健康咨询 H5 页面 + mBinding.healthConsultationView.setOnMoreClickListener { + navigateToHealthConsultationMore() + } + + + } + + /** 加载健康资讯数据(Token → 栏目 → 文章) */ + private fun loadHealthInfoData() { + mHealthInfoRequest?.fetchToken( + onSuccess = { token -> + mHealthToken = token + loadCategories(token) + loadConsultationArticles(token) + }, + onFailure = { /* Token 获取失败,静默处理 */ } + ) + } + + /** 加载栏目列表并设置健康知识 Tab */ + private fun loadCategories(token: String) { + mHealthInfoRequest?.fetchCategoryList( + token, + onSuccess = { categories -> + // 查找健康知识栏目(categoryId=1)的子栏目 + val knowledgeCategory = categories.find { it.categoryId == 1 } + mKnowledgeCategories.clear() + knowledgeCategory?.children?.let { mKnowledgeCategories.addAll(it) } + mBinding.healthKnowledgeView.setupTabs(mKnowledgeCategories) + + // 默认加载第一个子栏目文章 + if (mKnowledgeCategories.isNotEmpty()) { + loadKnowledgeArticles(mKnowledgeCategories[0].categoryId) + } + }, + onFailure = { /* 栏目加载失败,静默处理 */ } + ) + } + + /** 加载健康知识指定栏目下的文章列表 */ + private fun loadKnowledgeArticles(categoryId: Int) { + mHealthToken?.let { token -> + mHealthInfoRequest?.fetchArticleList( + token, categoryId, + onSuccess = { articles -> + mBinding.healthKnowledgeView.setArticles(articles) + }, + onFailure = { /* 文章加载失败,静默处理 */ } + ) + } + } + + /** 加载健康咨询文章列表(categoryId=2) */ + private fun loadConsultationArticles(token: String) { + mHealthInfoRequest?.fetchArticleList( + token, 2, + onSuccess = { articles-> + mBinding.healthConsultationView.setArticles(articles) + }, + onFailure = { /* 文章加载失败,静默处理 */ } + ) + } + + /** 跳转文章详情 H5 */ + private fun navigateToArticleDetail(articleId: Int) { + val param = mViewModel.enCryptInfoUrl.value + if (param.isNullOrEmpty()) { + showToast("网络错误") + return + } + val url = "${UrlConfig.getHealthNewsDetailH5Url()}?param=$param&articleId=$articleId" + requireContext().startWebActivity(url, isFull = true, myTitle = "文章详情", hideStatus = true) + } + + /** 跳转健康知识更多 H5 */ + private fun navigateToHealthKnowledgeMore() { + val param = mViewModel.enCryptInfoUrl.value + if (param.isNullOrEmpty()) { + mViewModel.getSysEnCryptInfo(UrlConfig.getHomeBankH5Url()) + showToast("网络错误") + return + } + val url = "${UrlConfig.getHealthKnowledgeH5Url()}?param=$param" + requireContext().startWebActivity(url, isFull = true, myTitle = "健康知识", hideStatus = true) + } + /** 跳转健康知识 搜索 */ + private fun navigateToHealthKnowledgeSearch() { + val param = mViewModel.enCryptInfoUrl.value + if (param.isNullOrEmpty()) { + mViewModel.getSysEnCryptInfo(UrlConfig.getHomeBankH5Url()) + showToast("网络错误") + return + } + val url = "${UrlConfig.getHealthNewsSearchH5Url()}?param=$param" + requireContext().startWebActivity(url, isFull = true, myTitle = "搜索", hideStatus = true) + } + + /** 跳转健康咨询更多 H5 */ + private fun navigateToHealthConsultationMore() { + val param = mViewModel.enCryptInfoUrl.value + if (param.isNullOrEmpty()) { + mViewModel.getSysEnCryptInfo(UrlConfig.getHomeBankH5Url()) + showToast("网络错误") + return + } + val url = "${UrlConfig.getHealthConsultationH5Url()}?param=$param" + requireContext().startWebActivity(url, isFull = true, myTitle = "健康咨询", hideStatus = true) + } + } diff --git a/app/src/main/java/com/xjjk/healthyclients/retrofit/UrlConfig.kt b/app/src/main/java/com/xjjk/healthyclients/retrofit/UrlConfig.kt index cc9914b..297449f 100644 --- a/app/src/main/java/com/xjjk/healthyclients/retrofit/UrlConfig.kt +++ b/app/src/main/java/com/xjjk/healthyclients/retrofit/UrlConfig.kt @@ -47,8 +47,10 @@ object UrlConfig { val baseUrlType: BaseUrlType = BaseUrlType.PRODUCT // const val middle = "xj_health/" //test +// const val middleCheck = "healthcheck/" //test const val middle = "hb/" //product - + const val middleCheck = "hb/" //product +// var isOpenIm: Boolean = true enum class BaseUrlType(val type: Int) { @@ -122,6 +124,42 @@ object UrlConfig { } } } + + /** + * 健康资讯 H5 页面基础地址 + */ + fun getHealthNewsH5BaseUrl(): String { + return getDefaultBankBaseUrl() + "healthnewsweb/" + } + + /** + * 健康知识 H5 页面地址 + */ + fun getHealthKnowledgeH5Url(): String { + return getHealthNewsH5BaseUrl() + "healthKnow.html" + } + + /** + * 健康咨询 H5 页面地址 + */ + fun getHealthConsultationH5Url(): String { + return getHealthNewsH5BaseUrl() + "healthNews.html" + } + + /** + * 健康资讯详情 H5 页面地址 + */ + fun getHealthNewsDetailH5Url(): String { + return getHealthNewsH5BaseUrl() + "healthNewsDetail.html" + } + + /** + * 健康资讯搜索 H5 页面地址 + */ + fun getHealthNewsSearchH5Url(): String { + return getHealthNewsH5BaseUrl() + "healthSecrch.html" + } + /** * 方便后续加一些其他逻辑 */ diff --git a/app/src/main/java/com/xjjk/healthyclients/superfuntion/ViewExt.kt b/app/src/main/java/com/xjjk/healthyclients/superfuntion/ViewExt.kt index 094d3f9..2dc9459 100644 --- a/app/src/main/java/com/xjjk/healthyclients/superfuntion/ViewExt.kt +++ b/app/src/main/java/com/xjjk/healthyclients/superfuntion/ViewExt.kt @@ -590,6 +590,7 @@ fun TabLayout.attachViewPager( fun getTabStyle(i: Int): Int { return when (i) { 1 -> R.layout.item_tablayout_group_title + 2 -> R.layout.item_tablayout_group_index_theme//有下标线的绿色 else -> R.layout.item_tablayout_group_title } } diff --git a/app/src/main/java/com/xjjk/healthyclients/view/HealthConsultationView.kt b/app/src/main/java/com/xjjk/healthyclients/view/HealthConsultationView.kt new file mode 100644 index 0000000..5e87905 --- /dev/null +++ b/app/src/main/java/com/xjjk/healthyclients/view/HealthConsultationView.kt @@ -0,0 +1,73 @@ +package com.xjjk.healthyclients.view + +import android.content.Context +import android.util.AttributeSet +import android.view.LayoutInflater +import android.widget.RelativeLayout +import androidx.databinding.DataBindingUtil +import androidx.recyclerview.widget.LinearLayoutManager +import com.xjjk.healthyclients.R +import com.xjjk.healthyclients.adapter.HealthConsultationAdapter +import com.xjjk.healthyclients.bean.home.ArticleBean +import com.xjjk.healthyclients.databinding.LayoutHealthConsultationBinding + +/** + * 健康咨询 View 组件 + * 头部标题 + 列表(左侧标题+收藏分享信息 / 右侧图片) + */ +class HealthConsultationView(context: Context?, attrs: AttributeSet?) : + RelativeLayout(context, attrs, 0) { + + var mContext: Context? = context + var mBinding: LayoutHealthConsultationBinding = DataBindingUtil.inflate( + LayoutInflater.from(context), + R.layout.layout_health_consultation, this, true + ) + + private val mAdapter = HealthConsultationAdapter() + private var mOnItemClickListener: ((ArticleBean) -> Unit)? = null + private var mOnMoreClickListener: (() -> Unit)? = null + + init { + mBinding.rvList.layoutManager = LinearLayoutManager(mContext) + mBinding.rvList.adapter = mAdapter + + // 列表项点击 + mAdapter.setOnItemClickListener { _, _, position -> + mAdapter.getItem(position)?.let { mOnItemClickListener?.invoke(it) } + } + + // 头部更多按钮点击 + mBinding.ivMore.setOnClickListener { + mOnMoreClickListener?.invoke() + } + + } + + /** 设置文章列表数据(替换),最多取前 2 条 */ + fun setArticles(articles: List?) { + if (articles.isNullOrEmpty()) { + mBinding.rvList.visibility = GONE + mBinding.tvEmpty.visibility = VISIBLE + return + } else { + mBinding.rvList.visibility = VISIBLE + mBinding.tvEmpty.visibility = GONE + } + val topTwo = articles.take(2).toMutableList() + mAdapter.setNewInstance(topTwo) + } + + /** 追加文章列表数据(分页加载) */ + fun addArticles(articles: List) { + mAdapter.addData(articles) + } + + fun setOnItemClickListener(listener: (ArticleBean) -> Unit) { + mOnItemClickListener = listener + } + + fun setOnMoreClickListener(listener: () -> Unit) { + mOnMoreClickListener = listener + } +} diff --git a/app/src/main/java/com/xjjk/healthyclients/view/HealthKnowledgeView.kt b/app/src/main/java/com/xjjk/healthyclients/view/HealthKnowledgeView.kt new file mode 100644 index 0000000..8441779 --- /dev/null +++ b/app/src/main/java/com/xjjk/healthyclients/view/HealthKnowledgeView.kt @@ -0,0 +1,109 @@ +package com.xjjk.healthyclients.view + +import android.content.Context +import android.util.AttributeSet +import android.view.LayoutInflater +import android.widget.RelativeLayout +import androidx.databinding.DataBindingUtil +import androidx.recyclerview.widget.LinearLayoutManager +import com.xjjk.healthyclients.R +import com.xjjk.healthyclients.adapter.HealthKnowledgeAdapter +import com.xjjk.healthyclients.bean.home.ArticleBean +import com.xjjk.healthyclients.bean.home.CategoryBean +import com.xjjk.healthyclients.databinding.LayoutHealthKnowledgeBinding +import com.xjjk.healthyclients.superfuntion.onCreateTab + +/** + * 健康知识 View 组件 + * 头部标题 + 搜索栏 + TabLayout(医生说/体检/保健/药品/疾病) + 列表 + */ +class HealthKnowledgeView(context: Context?, attrs: AttributeSet?) : + RelativeLayout(context, attrs, 0) { + + var mContext: Context? = context + var mBinding: LayoutHealthKnowledgeBinding = DataBindingUtil.inflate( + LayoutInflater.from(context), + R.layout.layout_health_knowledge, this, true + ) + + private val mAdapter = HealthKnowledgeAdapter() + private var mCategoryList: MutableList = mutableListOf() + private var mOnTabSelectedListener: ((CategoryBean) -> Unit)? = null + private var mOnItemClickListener: ((ArticleBean) -> Unit)? = null + private var mOnMoreClickListener: (() -> Unit)? = null + private var mOnSearchClickListener: (() -> Unit)? = null + + init { + mBinding.rvList.layoutManager = LinearLayoutManager(mContext) + mBinding.rvList.adapter = mAdapter + + // 列表项点击 + mAdapter.setOnItemClickListener { _, _, position -> + mAdapter.getItem(position)?.let { mOnItemClickListener?.invoke(it) } + } + + // 头部更多按钮点击 + mBinding.ivMore.setOnClickListener { + mOnMoreClickListener?.invoke() + } + + // 健康知识 - 搜索 + mBinding.searchView.root.setOnClickListener { + mOnSearchClickListener?.invoke() + } + } + + /** + * 根据栏目列表设置 TabLayout + * @param categories 子栏目列表(健康知识 categoryId=1 的 children) + */ + fun setupTabs(categories: MutableList) { + mCategoryList.clear() + mCategoryList.addAll(categories) + + mBinding.tabLayout.removeAllTabs() + val array = Array(categories.size) { + categories[it].categoryName?:"" + } + mBinding.tabLayout.onCreateTab(array,2){index-> + mOnTabSelectedListener?.invoke(categories[index!!.position]) ?: return@onCreateTab + } + } + + /** 设置文章列表数据(替换) */ + fun setArticles(articles: MutableList?) { + if (articles.isNullOrEmpty()) { + mBinding.rvList.visibility = GONE + mBinding.tvEmpty.visibility = VISIBLE + return + } + + mBinding.rvList.visibility = VISIBLE + mBinding.tvEmpty.visibility = GONE + + articles.take(3).let { + mAdapter.setNewInstance(it.toMutableList()) } + } + + /** 追加文章列表数据(分页加载) */ + fun addArticles(articles: List) { + mAdapter.addData(articles) + } + + + fun setOnTabSelectedListener(listener: (CategoryBean) -> Unit) { + mOnTabSelectedListener = listener + } + + fun setOnItemClickListener(listener: (ArticleBean) -> Unit) { + mOnItemClickListener = listener + } + + fun setOnMoreClickListener(listener: () -> Unit) { + mOnMoreClickListener = listener + } + + fun setOnSearchClickListener(listener: () -> Unit) { + mOnSearchClickListener = listener + } +} diff --git a/app/src/main/java/com/xjjk/healthyclients/view/WatchBindDialog.kt b/app/src/main/java/com/xjjk/healthyclients/view/WatchBindDialog.kt index ff6e710..c8d1500 100644 --- a/app/src/main/java/com/xjjk/healthyclients/view/WatchBindDialog.kt +++ b/app/src/main/java/com/xjjk/healthyclients/view/WatchBindDialog.kt @@ -62,7 +62,7 @@ class WatchBindDialog(context: Context, var listener: (String,Int) -> Unit) : Al if (TextUtils.isEmpty(watchId)) { binding.etWatchid.isEnabled = true binding.confirm.setText("绑定") - type = 0 +// type = 0 } else { binding.etWatchid.isEnabled = false binding.confirm.isEnabled = false diff --git a/app/src/main/res/color/color_child_tab_layout_text_index_theme.xml b/app/src/main/res/color/color_child_tab_layout_text_index_theme.xml new file mode 100644 index 0000000..0cd3e25 --- /dev/null +++ b/app/src/main/res/color/color_child_tab_layout_text_index_theme.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/drawable/bg_child_tab_layout_item2.xml b/app/src/main/res/drawable/bg_child_tab_layout_item_index.xml similarity index 100% rename from app/src/main/res/drawable/bg_child_tab_layout_item2.xml rename to app/src/main/res/drawable/bg_child_tab_layout_item_index.xml diff --git a/app/src/main/res/drawable/bg_child_tab_layout_item_line.xml b/app/src/main/res/drawable/bg_child_tab_layout_item_line.xml new file mode 100644 index 0000000..3d7ae4e --- /dev/null +++ b/app/src/main/res/drawable/bg_child_tab_layout_item_line.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_theme_background_shape3.xml b/app/src/main/res/drawable/bg_theme_background_shape3.xml new file mode 100644 index 0000000..52c300e --- /dev/null +++ b/app/src/main/res/drawable/bg_theme_background_shape3.xml @@ -0,0 +1,6 @@ + + + + + diff --git a/app/src/main/res/layout/custom_search_view_home.xml b/app/src/main/res/layout/custom_search_view_home.xml new file mode 100644 index 0000000..c055dd9 --- /dev/null +++ b/app/src/main/res/layout/custom_search_view_home.xml @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/fragment_home.xml b/app/src/main/res/layout/fragment_home.xml index 8166e7b..7465415 100644 --- a/app/src/main/res/layout/fragment_home.xml +++ b/app/src/main/res/layout/fragment_home.xml @@ -169,7 +169,22 @@ android:layout_width="match_parent" android:layout_height="wrap_content" /> + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/item_health_knowledge.xml b/app/src/main/res/layout/item_health_knowledge.xml new file mode 100644 index 0000000..5745c55 --- /dev/null +++ b/app/src/main/res/layout/item_health_knowledge.xml @@ -0,0 +1,104 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/item_tablayout_group_index_theme.xml b/app/src/main/res/layout/item_tablayout_group_index_theme.xml new file mode 100644 index 0000000..da29b45 --- /dev/null +++ b/app/src/main/res/layout/item_tablayout_group_index_theme.xml @@ -0,0 +1,25 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/layout_health_consultation.xml b/app/src/main/res/layout/layout_health_consultation.xml new file mode 100644 index 0000000..be518fc --- /dev/null +++ b/app/src/main/res/layout/layout_health_consultation.xml @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/layout_health_knowledge.xml b/app/src/main/res/layout/layout_health_knowledge.xml new file mode 100644 index 0000000..c296525 --- /dev/null +++ b/app/src/main/res/layout/layout_health_knowledge.xml @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/mipmap-xhdpi/ic_home_know_tag.png b/app/src/main/res/mipmap-xhdpi/ic_home_know_tag.png new file mode 100644 index 0000000000000000000000000000000000000000..3c2bcab1969a64dcd2134950e8ce39b495823d45 GIT binary patch literal 1349 zcmV-L1-kl)P)lcAB2 z7=aiQiO7Q*A}RifOcR#5ViZ0zzEO} z^a7WG9>84gM4@E@#(~4YYruwO&NT#l3v>a)K`#ml7y~{A4gwX+1S?h+_!>9?O!*lH z0el5<*K7VKSg|I7BfxiFMv6zD27F-;@UY>q$Gm9Ip_MW!<-v#z_TmbF$@CMj4@ebv z$ULY47bqVb_FNZ`cv1#<8rZYaL=x?>=Ppn&;6C6`S#ab!1iYHxkpB+GfIe0xAeN(P zpaYnILJywg(Zd1M0iQTO91k`EH-S~ME55&bhi^tlNG+L!LJ$vE@=3D-_SGtqw0H8R#lDLSkzonI_Gm=fKatG_0R-5g0nhkZWi5-%)z!4<5lqYC*Xb#y4Fdp0 z0_rri=}}i7z|cZEDBu-%GH&e-=fDIZ@ z?PrxWO?Jj&Y>ZZ6Sr$Kxk27w2FsiCtni!`)m1N1X7)T|Fhbwb{VMTGJp((>)IFlh> zS?M=*b;*5Lz6{Iag*Ekb)(J0Nota_Meh0&f!j*f|T)8)$vwzx--ms$3tm`C_Zjar{ zq=NvW0dt6TxTK=0%JD>BLEEv$CIBW4gU>rQ@!Vr|0KDJd$F=#noL#FPkphkfi{v>C z81n;6nBg8}Tm)dw{?1pb$i7N-doW}Ao`ReM zOlKd}g{b(Y+7(+vR8@1IT1{hHt%&H|^=k|IR%;r2>mM(;et$y)HJX;Ywnf)@ecgI5 zxU9`oP?K~4@A}0%sEohBHh+ZtG;O|6~t?dZ-D!YH)hUiCZ=YHb>|AzP%hQyaB99!_N00000NkvXX Hu0mjfaj|`*E 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 literal 0 HcmV?d00001 diff --git a/app/src/main/res/mipmap-xhdpi/ic_placeholder_square.png b/app/src/main/res/mipmap-xhdpi/ic_placeholder_square.png new file mode 100644 index 0000000000000000000000000000000000000000..9148c6e56036a4acc6dc720f9a66439172e3f538 GIT binary patch literal 4253 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 YWf{Gcvk9W)`v!o%wux5leTRtu0))F2O8@`> literal 0 HcmV?d00001