refactor(fragment): DbInspectFragment 改用嵌套 RecyclerView 展示字段,优化已删除记录视觉样式
This commit is contained in:
@@ -244,15 +244,27 @@ class DbViewModel : ViewModel() {
|
||||
const val DB_INSPECT_PAGE_SIZE = 50
|
||||
}
|
||||
|
||||
/**
|
||||
* 单条数据库记录的展示模型
|
||||
* @param isDel 是否已逻辑删除
|
||||
* @param fields 字段名-值对列表,按顺序排列
|
||||
* @param columnsPerRow 每行显示的列数,默认 3;槽位表传 1 保持单列
|
||||
*/
|
||||
data class DbRecord(
|
||||
val isDel: Boolean,
|
||||
val fields: List<Pair<String, String>>,
|
||||
val columnsPerRow: Int = 3
|
||||
)
|
||||
|
||||
/**
|
||||
* 数据库调试页面 UI 状态
|
||||
* @param items 当前已加载的记录列表,Pair.first=是否逻辑删除,Pair.second=格式化文本
|
||||
* @param items 当前已加载的记录列表
|
||||
* @param totalCount 当前过滤条件下的总记录数
|
||||
* @param hasMore 是否还有更多数据可加载
|
||||
* @param isLoading 是否正在加载(首次加载或加载更多)
|
||||
*/
|
||||
data class DbInspectUiState(
|
||||
val items: List<Pair<Boolean, String>> = emptyList(),
|
||||
val items: List<DbRecord> = emptyList(),
|
||||
val totalCount: Int = 0,
|
||||
val hasMore: Boolean = false,
|
||||
val isLoading: Boolean = false
|
||||
@@ -303,22 +315,25 @@ class DbViewModel : ViewModel() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行分页查询,返回格式化后的列表和总记录数
|
||||
* 执行分页查询,返回 DbRecord 列表和总记录数
|
||||
*/
|
||||
private suspend fun queryPage(
|
||||
tableIndex: Int,
|
||||
showDel: Boolean,
|
||||
offset: Int
|
||||
): Pair<List<Pair<Boolean, String>>, Int> {
|
||||
): Pair<List<DbRecord>, Int> {
|
||||
val limit = DB_INSPECT_PAGE_SIZE
|
||||
return when (tableIndex) {
|
||||
0 -> {
|
||||
val total = rep.countCookFoodAll(showDel)
|
||||
val rows = rep.getCookFoodPaged(showDel, limit, offset).map { e ->
|
||||
(e.isDel == 1) to fmt(
|
||||
"id" to e.id, "foodId" to e.foodId, "foodName" to e.foodName,
|
||||
"canteenId" to e.canteenId, "foodWeight" to e.foodWeight, "cookMode" to e.cookMode,
|
||||
"dinnerType" to e.dinnerType, "isDel" to e.isDel, "createTime" to e.createTime
|
||||
DbRecord(
|
||||
isDel = e.isDel == 1,
|
||||
fields = listOf(
|
||||
"id" to "${e.id}", "foodId" to "${e.foodId}", "foodName" to "${e.foodName}",
|
||||
"canteenId" to "${e.canteenId}", "foodWeight" to "${e.foodWeight}", "cookMode" to "${e.cookMode}",
|
||||
"dinnerType" to "${e.dinnerType}", "isDel" to "${e.isDel}", "createTime" to "${e.createTime}"
|
||||
)
|
||||
)
|
||||
}
|
||||
rows to total
|
||||
@@ -326,12 +341,15 @@ class DbViewModel : ViewModel() {
|
||||
1 -> {
|
||||
val total = rep.countCookFoodGoodsAll(showDel)
|
||||
val rows = rep.getCookFoodGoodsPaged(showDel, limit, offset).map { e ->
|
||||
(e.isDel == 1) to fmt(
|
||||
"id" to e.id, "pid" to e.pid, "goodsId" to e.goodsId,
|
||||
"goodsName" to e.goodsName, "foodId" to e.foodId, "materialType" to e.materialType,
|
||||
"relateionType" to e.relateionType, "allEdible" to e.allEdible, "useWeight" to e.useWeight,
|
||||
"sort" to e.sort, "zjmCode" to e.zjmCode, "materId" to e.materId,
|
||||
"isDel" to e.isDel, "createTime" to e.createTime
|
||||
DbRecord(
|
||||
isDel = e.isDel == 1,
|
||||
fields = listOf(
|
||||
"id" to "${e.id}", "pid" to "${e.pid}", "goodsId" to "${e.goodsId}",
|
||||
"goodsName" to "${e.goodsName}", "foodId" to "${e.foodId}", "materialType" to "${e.materialType}",
|
||||
"relateionType" to "${e.relateionType}", "allEdible" to "${e.allEdible}", "useWeight" to "${e.useWeight}",
|
||||
"sort" to "${e.sort}", "zjmCode" to "${e.zjmCode}", "materId" to "${e.materId}",
|
||||
"isDel" to "${e.isDel}", "createTime" to "${e.createTime}"
|
||||
)
|
||||
)
|
||||
}
|
||||
rows to total
|
||||
@@ -339,11 +357,14 @@ class DbViewModel : ViewModel() {
|
||||
2 -> {
|
||||
val total = rep.countSeasoningAll(showDel)
|
||||
val rows = rep.getSeasoningPaged(showDel, limit, offset).map { e ->
|
||||
(e.isDel == 1) to fmt(
|
||||
"id" to e.id, "goodsId" to e.goodsId, "goodsName" to e.goodsName,
|
||||
"sort" to e.sort, "materialType" to e.materialType, "useWeight" to e.useWeight,
|
||||
"zjmCode" to e.zjmCode, "materId" to e.materId, "isDel" to e.isDel,
|
||||
"createTime" to e.createTime
|
||||
DbRecord(
|
||||
isDel = e.isDel == 1,
|
||||
fields = listOf(
|
||||
"id" to "${e.id}", "goodsId" to "${e.goodsId}", "goodsName" to "${e.goodsName}",
|
||||
"sort" to "${e.sort}", "materialType" to "${e.materialType}", "useWeight" to "${e.useWeight}",
|
||||
"zjmCode" to "${e.zjmCode}", "materId" to "${e.materId}", "isDel" to "${e.isDel}",
|
||||
"createTime" to "${e.createTime}"
|
||||
)
|
||||
)
|
||||
}
|
||||
rows to total
|
||||
@@ -351,23 +372,21 @@ class DbViewModel : ViewModel() {
|
||||
3 -> {
|
||||
val total = rep.countSlotAll()
|
||||
val rows = rep.getSlotPaged(limit, offset).map { e ->
|
||||
false to "deviceId: ${e.deviceId}\naddress: ${e.address}\ngoodsId: ${e.goodsId}\ngoodsName: ${e.goodsName}"
|
||||
DbRecord(
|
||||
isDel = false,
|
||||
fields = listOf(
|
||||
"deviceId" to e.deviceId, "address" to "${e.address}",
|
||||
"goodsId" to e.goodsId, "goodsName" to e.goodsName
|
||||
),
|
||||
columnsPerRow = 1 // 槽位表字段少,保持单列显示
|
||||
)
|
||||
}
|
||||
rows to total
|
||||
}
|
||||
else -> emptyList<Pair<Boolean, String>>() to 0
|
||||
else -> emptyList<DbRecord>() to 0
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将字段列表格式化为每行 3 个的紧凑布局
|
||||
* 格式:key: value | key: value | key: value
|
||||
*/
|
||||
private fun fmt(vararg fields: Pair<String, Any?>): String =
|
||||
fields.toList().chunked(3).joinToString("\n") { row ->
|
||||
row.joinToString(" | ") { (k, v) -> "$k: ${v ?: "null"}" }
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------
|
||||
|
||||
// viewModelScope.launch {
|
||||
|
||||
@@ -7,7 +7,9 @@ import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.RadioButton
|
||||
import android.widget.TextView
|
||||
import androidx.cardview.widget.CardView
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.recyclerview.widget.GridLayoutManager
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.shuwei.dish.match.R
|
||||
@@ -22,8 +24,8 @@ import kotlinx.coroutines.launch
|
||||
/**
|
||||
* 数据库调试查看页面
|
||||
* 支持按表切换、isDel 过滤开关、分页加载(每页 50 条)
|
||||
* 菜品/食材/调料表每行 3 列等宽显示字段,槽位表保持单列
|
||||
* 已逻辑删除(isDel=1)的记录以灰色文字 + 半透明卡片区分
|
||||
* 槽位表无 isDel 字段,切换到该 Tab 时自动隐藏 isDel 控件
|
||||
*/
|
||||
class DbInspectFragment : BaseFragment<FragmentDbInspectBinding>() {
|
||||
|
||||
@@ -35,10 +37,7 @@ class DbInspectFragment : BaseFragment<FragmentDbInspectBinding>() {
|
||||
/** 是否显示已删除记录(槽位表忽略此参数) */
|
||||
private var showDel = true
|
||||
|
||||
/**
|
||||
* 防止在代码中设置 Switch 状态时触发监听器,
|
||||
* 例如切换回非槽位 Tab 时恢复 Switch 状态
|
||||
*/
|
||||
/** 防止代码中设置 Switch 状态时误触发监听器 */
|
||||
private var ignoreSwitch = false
|
||||
|
||||
private val adapter = DbRecordAdapter()
|
||||
@@ -83,7 +82,7 @@ class DbInspectFragment : BaseFragment<FragmentDbInspectBinding>() {
|
||||
loadFirstPage()
|
||||
}
|
||||
|
||||
// 刷新按钮:重新加载第一页
|
||||
// 刷新按钮
|
||||
binding.tvRefresh.setOnClickListener { loadFirstPage() }
|
||||
|
||||
// 加载更多按钮
|
||||
@@ -98,30 +97,23 @@ class DbInspectFragment : BaseFragment<FragmentDbInspectBinding>() {
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化 Tab 样式并加载默认表
|
||||
// 初始化样式并加载
|
||||
updateTabStyles()
|
||||
updateDelControlsVisibility()
|
||||
binding.vTabIndicator.post { moveIndicator(currentTableIndex) }
|
||||
loadFirstPage()
|
||||
}
|
||||
|
||||
/** 重新从第一页开始加载当前表数据 */
|
||||
private fun loadFirstPage() {
|
||||
currentActivity.appViewModel.loadDbInspect(currentTableIndex, showDel)
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据 UI 状态更新计数文字、加载更多按钮和列表
|
||||
*/
|
||||
private fun renderState(state: DbViewModel.DbInspectUiState) {
|
||||
// 计数文字
|
||||
binding.tvCount.text = when {
|
||||
state.isLoading && state.items.isEmpty() -> "加载中..."
|
||||
state.hasMore -> "已加载 ${state.items.size} / 共 ${state.totalCount} 条"
|
||||
else -> "共 ${state.totalCount} 条"
|
||||
}
|
||||
|
||||
// 加载更多按钮:有更多数据时显示,加载中时显示"加载中..."并禁用点击
|
||||
when {
|
||||
state.isLoading && state.hasMore -> {
|
||||
binding.tvLoadMore.visible()
|
||||
@@ -135,13 +127,11 @@ class DbInspectFragment : BaseFragment<FragmentDbInspectBinding>() {
|
||||
}
|
||||
else -> binding.tvLoadMore.gone()
|
||||
}
|
||||
|
||||
adapter.setData(state.items)
|
||||
}
|
||||
|
||||
/**
|
||||
* 槽位表(index=3)没有 isDel 字段,切换到该 Tab 时隐藏 isDel 相关控件;
|
||||
* 切换回其他 Tab 时恢复,并同步 Switch 状态
|
||||
* 槽位表无 isDel 字段,切换到时隐藏相关控件;切换回其他 Tab 时恢复
|
||||
*/
|
||||
private fun updateDelControlsVisibility() {
|
||||
val isSlot = currentTableIndex == 3
|
||||
@@ -151,16 +141,12 @@ class DbInspectFragment : BaseFragment<FragmentDbInspectBinding>() {
|
||||
} else {
|
||||
binding.tvDelLabel.visible()
|
||||
binding.swShowDel.visible()
|
||||
// 恢复 Switch 显示状态(防止误触发监听器)
|
||||
ignoreSwitch = true
|
||||
binding.swShowDel.isChecked = showDel
|
||||
ignoreSwitch = false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新所有 Tab 的文字颜色和加粗状态
|
||||
*/
|
||||
private fun updateTabStyles() {
|
||||
tabButtons.forEachIndexed { index, btn ->
|
||||
val selected = index == currentTableIndex
|
||||
@@ -169,20 +155,12 @@ class DbInspectFragment : BaseFragment<FragmentDbInspectBinding>() {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将下方指示条平移到选中 Tab 的正下方(150ms 动画)
|
||||
*/
|
||||
private fun moveIndicator(index: Int) {
|
||||
val btn = tabButtons.getOrNull(index) ?: return
|
||||
val tabWidth = btn.width.toFloat()
|
||||
val indicatorWidth = tabWidth / 2f
|
||||
val indicatorX = btn.x + (tabWidth - indicatorWidth) / 2f
|
||||
|
||||
binding.vTabIndicator.animate()
|
||||
.translationX(indicatorX)
|
||||
.setDuration(150)
|
||||
.start()
|
||||
|
||||
binding.vTabIndicator.animate().translationX(indicatorX).setDuration(150).start()
|
||||
binding.vTabIndicator.post {
|
||||
val lp = binding.vTabIndicator.layoutParams
|
||||
lp.width = indicatorWidth.toInt()
|
||||
@@ -190,14 +168,14 @@ class DbInspectFragment : BaseFragment<FragmentDbInspectBinding>() {
|
||||
}
|
||||
}
|
||||
|
||||
// ---- 内部 Adapter ----
|
||||
// ---- 外层 Adapter:每条数据库记录对应一个 CardView ----
|
||||
|
||||
/** 数据库记录列表适配器 */
|
||||
/** 外层列表适配器,每个 item 内嵌一个 GridLayoutManager 的 RecyclerView 展示字段 */
|
||||
private inner class DbRecordAdapter : RecyclerView.Adapter<DbRecordAdapter.VH>() {
|
||||
|
||||
private val data = mutableListOf<Pair<Boolean, String>>()
|
||||
private val data = mutableListOf<DbViewModel.DbRecord>()
|
||||
|
||||
fun setData(list: List<Pair<Boolean, String>>) {
|
||||
fun setData(list: List<DbViewModel.DbRecord>) {
|
||||
data.clear()
|
||||
data.addAll(list)
|
||||
notifyDataSetChanged()
|
||||
@@ -210,20 +188,57 @@ class DbInspectFragment : BaseFragment<FragmentDbInspectBinding>() {
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: VH, position: Int) {
|
||||
val (isDeleted, content) = data[position]
|
||||
holder.tvContent.text = content
|
||||
// isDel=1 的记录用灰色标识,正常记录用深色
|
||||
holder.tvContent.setTextColor(
|
||||
if (isDeleted) Color.parseColor("#BBBBBB") else Color.parseColor("#333333")
|
||||
)
|
||||
// 已删除记录卡片略微透明
|
||||
holder.itemView.alpha = if (isDeleted) 0.6f else 1.0f
|
||||
val record = data[position]
|
||||
|
||||
if (record.isDel) {
|
||||
// 已删除:浅红背景 + 左侧红色标记条 + 文字颜色正常(深色可读)
|
||||
holder.card.setCardBackgroundColor(Color.parseColor("#FFF3F3"))
|
||||
holder.vDelMark.visibility = View.VISIBLE
|
||||
} else {
|
||||
// 正常记录:白色背景,隐藏标记条
|
||||
holder.card.setCardBackgroundColor(Color.WHITE)
|
||||
holder.vDelMark.visibility = View.GONE
|
||||
}
|
||||
val textColor = if (record.isDel) Color.parseColor("#C62828") else Color.parseColor("#333333")
|
||||
|
||||
// 每次绑定时重新设置 GridLayoutManager 列数(槽位表为 1,其余为 3)
|
||||
holder.rvFields.layoutManager = GridLayoutManager(holder.rvFields.context, record.columnsPerRow)
|
||||
holder.rvFields.adapter = DbFieldAdapter(record.fields, textColor)
|
||||
}
|
||||
|
||||
override fun getItemCount() = data.size
|
||||
|
||||
inner class VH(itemView: View) : RecyclerView.ViewHolder(itemView) {
|
||||
val tvContent: TextView = itemView.findViewById(R.id.tvContent)
|
||||
val card: CardView = itemView.findViewById(R.id.cardRecord)
|
||||
val vDelMark: View = itemView.findViewById(R.id.vDelMark)
|
||||
val rvFields: RecyclerView = itemView.findViewById(R.id.rvFields)
|
||||
}
|
||||
}
|
||||
|
||||
// ---- 内层 Adapter:每个字段对应一个 TextView,垂直显示字段名和值 ----
|
||||
|
||||
/** 内层字段适配器,每个 item 为单个 TextView,显示格式:字段名\n字段值 */
|
||||
private inner class DbFieldAdapter(
|
||||
private val fields: List<Pair<String, String>>,
|
||||
private val textColor: Int
|
||||
) : RecyclerView.Adapter<DbFieldAdapter.VH>() {
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): VH {
|
||||
val view = LayoutInflater.from(parent.context)
|
||||
.inflate(R.layout.list_item_db_field, parent, false)
|
||||
return VH(view)
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: VH, position: Int) {
|
||||
val (key, value) = fields[position]
|
||||
holder.tvField.text = "$key\n$value"
|
||||
holder.tvField.setTextColor(textColor)
|
||||
}
|
||||
|
||||
override fun getItemCount() = fields.size
|
||||
|
||||
inner class VH(itemView: View) : RecyclerView.ViewHolder(itemView) {
|
||||
val tvField: TextView = itemView.findViewById(R.id.tvField)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/tvField"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="10sp"
|
||||
android:fontFamily="monospace"
|
||||
android:gravity="start"
|
||||
android:paddingBottom="4dp" />
|
||||
@@ -1,6 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:id="@+id/cardRecord"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
@@ -9,13 +10,28 @@
|
||||
app:cardElevation="1dp"
|
||||
app:cardBackgroundColor="#FFFFFF">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvContent"
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="10dp"
|
||||
android:textSize="11sp"
|
||||
android:lineSpacingMultiplier="1.3"
|
||||
android:fontFamily="monospace" />
|
||||
android:orientation="horizontal">
|
||||
|
||||
<!-- 已删除标记条:isDel=1 时显示红色左边框 -->
|
||||
<View
|
||||
android:id="@+id/vDelMark"
|
||||
android:layout_width="4dp"
|
||||
android:layout_height="match_parent"
|
||||
android:background="#E53935"
|
||||
android:visibility="gone" />
|
||||
|
||||
<!-- 字段由内层 RecyclerView(GridLayoutManager)填充 -->
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/rvFields"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:nestedScrollingEnabled="false"
|
||||
android:padding="8dp"
|
||||
android:clipToPadding="false" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</androidx.cardview.widget.CardView>
|
||||
|
||||
Reference in New Issue
Block a user