feat(task): 添加组配任务功能模块

- 新增组配任务列表页面和详情页面
- 实现组配任务查询、搜索和分页功能
- 添加组配任务详情展示和状态管理
- 集成溯源码获取功能到任务详情页面
- 添加组配任务相关的API接口定义和实现
- 创建组配任务数据模型和适配器
- 在设置页面添加组配任务入口
- 重构食材物料列表项布局和显示逻辑
- 更新应用启动时的设备ID初始化逻辑
- 生成配比秤终端接口文档
This commit is contained in:
2026-06-02 14:21:09 +08:00
parent 9801534aad
commit 3fe66b513f
19 changed files with 1167 additions and 36 deletions
+10
View File
@@ -79,6 +79,16 @@
android:screenOrientation="portrait"
tools:ignore="DiscouragedApi,LockedOrientationActivity" />
<activity android:name=".ui.TaskActivity"
android:theme="@style/Theme.DishMatch.NoSplash"
android:screenOrientation="portrait"
tools:ignore="DiscouragedApi,LockedOrientationActivity" />
<activity android:name=".ui.TaskDetailActivity"
android:theme="@style/Theme.DishMatch.NoSplash"
android:screenOrientation="portrait"
tools:ignore="DiscouragedApi,LockedOrientationActivity" />
<activity
android:name=".ui.CookingModeActivity"
android:theme="@style/Theme.DishMatch.NoSplash"
@@ -0,0 +1,53 @@
package com.shuwei.dish.match.adapter
import android.content.Context
import android.view.LayoutInflater
import android.view.ViewGroup
import com.chad.library.adapter4.BaseQuickAdapter
import com.chad.library.adapter4.viewholder.QuickViewHolder
import com.shuwei.dish.match.R
import com.shuwei.dish.match.databinding.ListItemTaskBinding
import com.shuwei.dish.match.model.NutComboTaskVO
/**
* 组配任务列表适配器
*/
class TaskAdapter(list: MutableList<NutComboTaskVO>) :
BaseQuickAdapter<NutComboTaskVO, TaskAdapter.VH>(list) {
inner class VH(var binding: ListItemTaskBinding) : QuickViewHolder(binding.root)
override fun onBindViewHolder(
holder: VH,
position: Int,
item: NutComboTaskVO?
) {
holder.binding.apply {
tvTaskNo.text = "任务编号:${item?.taskNo}"
tvTargetDish.text = item?.targetDish
tvSpec.text = item?.spec
tvPlanDate.text = "计划日期:${item?.planDate}"
tvPortions.text = "总份数:${item?.portions}"
tvOwnerName.text = "负责人:${item?.ownerName}"
// 状态文本映射
val statusText = when (item?.comboStatus) {
0 -> "待组配"
1 -> "组配中"
2 -> "已完成"
else -> "未知"
}
tvStatus.text = statusText
}
}
override fun onCreateViewHolder(
context: Context,
parent: ViewGroup,
viewType: Int
): VH {
val inflater = LayoutInflater.from(context)
val binding = ListItemTaskBinding.inflate(inflater, parent, false)
return VH(binding)
}
}
@@ -0,0 +1,76 @@
package com.shuwei.dish.match.adapter
import android.content.Context
import android.view.LayoutInflater
import android.view.ViewGroup
import com.chad.library.adapter4.BaseQuickAdapter
import com.chad.library.adapter4.viewholder.QuickViewHolder
import com.shuwei.dish.match.R
import com.shuwei.dish.match.databinding.ListItemTaskDetailBinding
import com.shuwei.dish.match.model.NutComboTaskItemDetail
import com.shuwei.dish.match.utils.ext.gone
import com.shuwei.dish.match.utils.ext.visible
import java.math.BigDecimal
/**
* 组配任务详情食材列表适配器
*/
class TaskDetailAdapter(list: MutableList<NutComboTaskItemDetail>) :
BaseQuickAdapter<NutComboTaskItemDetail, TaskDetailAdapter.VH>(list) {
inner class VH(var binding: ListItemTaskDetailBinding) : QuickViewHolder(binding.root)
/**
* 格式化用量字符串,去掉小数点后末尾的0
*/
private fun formatQty(value: String?): String {
if (value.isNullOrBlank()) return "0"
return try {
BigDecimal(value).stripTrailingZeros().toPlainString()
} catch (e: Exception) {
value
}
}
override fun onBindViewHolder(
holder: VH,
position: Int,
item: NutComboTaskItemDetail?
) {
holder.binding.apply {
tvIngredientName.text = item?.ingredientName
// 食材类别
val classText = when (item?.ingredientClass) {
1 -> "主材"
2 -> "辅材"
3 -> "调料"
else -> "未知"
}
tvIngredientClass.text = classText
val unit = item?.unit ?: "g"
tvPerPortionQty.text = "每份用量:${formatQty(item?.perPortionQty)}${unit}"
tvActualQty.text = "实际用量:${formatQty(item?.actualQty)}${unit}"
tvTotalQty.text = "合计用量:${formatQty(item?.totalQty)}${unit}"
tvTraceCode.visible()
tvTraceCode.text = "溯源码:${(item?.traceCode ?:"").ifBlank { "-" }}"
root.setBackgroundResource(
if (item?.isClicked == true) R.drawable.shape_item_cook_dish
else R.drawable.shape_white_fb_15_corners
)
}
}
override fun onCreateViewHolder(
context: Context,
parent: ViewGroup,
viewType: Int
): VH {
val inflater = LayoutInflater.from(context)
val binding = ListItemTaskDetailBinding.inflate(inflater, parent, false)
return VH(binding)
}
}
@@ -31,6 +31,9 @@ class BaseApp : Application() {
GlobalData.appBaseUrl = savedBaseUrl
}
CrashHandler.init(this)
GlobalData.deviceId = AppUtil.getUDID(this)
// val deviceId = if (BuildConfig.IS_TEST_DEVICE) ScaleDeviceConfig.DEVICE_ID_2 else AppUtil.getUDID(this)
// Log.d(TAG, "onCreate: deviceId=$deviceId, isTestDevice=${BuildConfig.IS_TEST_DEVICE}")
// GlobalData.deviceId = deviceId
@@ -183,6 +183,61 @@ data class NutSupMealPackageIngredient(
val traceCode: String
)
/**
* 组配任务VO - 未完成组配任务列表响应
*/
@Parcelize
data class NutComboTaskVO(
val id: Long?,
val taskNo: String?,
val planDate: String?,
val canteenId: Long?,
val mealType: Int?,
val foodId: Long?,
val targetDish: String?,
val spec: String?,
val portions: Int?,
var comboStatus: Int?,
val ownerName: String?,
val createTime: String?
) : Parcelable
/**
* 组配任务详情VO - 组配任务详情响应
*/
@Parcelize
data class NutComboTaskDetailVO(
val id: Long?,
val taskNo: String?,
val planDate: String?,
val canteenId: Long?,
val mealType: Int?,
val foodId: Long?,
val targetDish: String?,
val spec: String?,
val portions: Int?,
val comboStatus: Int?,
val ownerName: String?,
val items: List<NutComboTaskItemDetail>?
) : Parcelable
/**
* 组配任务食材明细
*/
@Parcelize
data class NutComboTaskItemDetail(
val id: Long?,
val ingredientCode: String?,
val ingredientName: String?,
val ingredientClass: Int?,
var traceCode: String?,
val perPortionQty: String?,
val actualQty: String?,
val totalQty: String?,
val unit: String?,
var isClicked: Boolean? = false,
) : Parcelable
/**
* ID通用DTO - 用于开始/完成组配等需要id参数的接口
*/
@@ -24,6 +24,7 @@ data class SettingItem(
WEIGHT_CONFIG("减重配置"),
CLEAN_PACKAGE("净菜包"),
MEAL_PACKAGE("餐品净菜包"),
GROUP_TASK("组配任务"),
MEAL_PACKAGE_TEST("餐品净菜包-测试"),
DB_INSPECT("数据库查看"),
SCALE_OBSERVE("秤数据监控"),
@@ -10,6 +10,8 @@ import com.shuwei.dish.match.model.NutSupHygieneIngredientOptionVO
import com.shuwei.dish.match.model.NutProdComboVideoVO
import com.shuwei.dish.match.model.NutProdComboTaskDTO
import com.shuwei.dish.match.model.IdDTO
import com.shuwei.dish.match.model.NutComboTaskDetailVO
import com.shuwei.dish.match.model.NutComboTaskVO
import retrofit2.http.Body
import retrofit2.http.GET
import retrofit2.http.POST
@@ -135,4 +137,27 @@ interface ApiServiceV2 {
@Body param: Map<String, @JvmSuppressWildcards Any>
): ApiResponse<Unit>
// ========== 五、组配任务接口 ==========
/**
* 查询未完成组配任务(分页)
*/
@GET
suspend fun getIncompleteComboTasks(
@Url url: String = "${GlobalData.appBaseUrl}/nutrition/neglect/combo-task/incomplete",
@Query("deviceCode") deviceCode: String? = null,
@Query("keyword") keyword: String? = null,
@Query("pageNum") pageNum: Int = 1,
@Query("pageSize") pageSize: Int = 10
): ApiResponse<Page<NutComboTaskVO>>
/**
* 获取组配任务详情
*/
@GET
suspend fun getComboTaskDetail(
@Url url: String = "${GlobalData.appBaseUrl}/nutrition/neglect/combo-task/detail",
@Query("id") id: Long
): ApiResponse<NutComboTaskDetailVO>
}
@@ -3,6 +3,8 @@ package com.shuwei.dish.match.net
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.shuwei.dish.match.model.NutCanteenVO
import com.shuwei.dish.match.model.NutComboTaskDetailVO
import com.shuwei.dish.match.model.NutComboTaskVO
import com.shuwei.dish.match.model.NutDictItemVO
import com.shuwei.dish.match.model.NutFoodOptionVO
import com.shuwei.dish.match.model.NutMaterOptionVO
@@ -203,4 +205,37 @@ class NetViewModelV2(
}
}
// ========== 组配任务接口 ==========
/**
* 查询未完成组配任务(回调版本)
*/
fun getIncompleteComboTasksWithCallback(
deviceCode: String? = null,
keyword: String? = null,
pageNum: Int = 1,
pageSize: Int = 10,
onLoading: () -> Unit = {},
onResult: (UiState<Page<NutComboTaskVO>?>) -> Unit
) {
viewModelScope.launch {
onLoading()
onResult(repository.getIncompleteComboTasks(deviceCode = deviceCode, keyword = keyword, pageNum = pageNum, pageSize = pageSize))
}
}
/**
* 获取组配任务详情(回调版本)
*/
fun getComboTaskDetailWithCallback(
id: Long,
onLoading: () -> Unit = {},
onResult: (UiState<NutComboTaskDetailVO?>) -> Unit
) {
viewModelScope.launch {
onLoading()
onResult(repository.getComboTaskDetail(id))
}
}
}
@@ -2,6 +2,8 @@ package com.shuwei.dish.match.net
import com.shuwei.dish.match.model.IdDTO
import com.shuwei.dish.match.model.NutCanteenVO
import com.shuwei.dish.match.model.NutComboTaskDetailVO
import com.shuwei.dish.match.model.NutComboTaskVO
import com.shuwei.dish.match.model.NutDictItemVO
import com.shuwei.dish.match.model.NutFoodOptionVO
import com.shuwei.dish.match.model.NutMaterOptionVO
@@ -87,4 +89,17 @@ class RemoteRepositoryV2 {
suspend fun addMealPackage(param: Map<String, Any>): UiState<Unit?> =
safeApiCall { apiService.addMealPackage(param = param) }
// ========== 组配任务接口 ==========
suspend fun getIncompleteComboTasks(
deviceCode: String? = null,
keyword: String? = null,
pageNum: Int = 1,
pageSize: Int = 10
): UiState<Page<NutComboTaskVO>?> =
safeApiCall { apiService.getIncompleteComboTasks(deviceCode = deviceCode, keyword = keyword, pageNum = pageNum, pageSize = pageSize) }
suspend fun getComboTaskDetail(id: Long): UiState<NutComboTaskDetailVO?> =
safeApiCall { apiService.getComboTaskDetail(id = id) }
}
@@ -109,6 +109,8 @@ class PackActivity : BaseActivity() {
binding.etInputDish.isFocusableInTouchMode = false
binding.ivDishSearch.gone()
binding.tvGoodsName.text = "食材名称"
addViewClickListener()
addBackKeyListener()
initCamera(binding.flCameraContainer)
@@ -206,7 +208,7 @@ class PackActivity : BaseActivity() {
materialAdapter.notifyItemChanged(clickIndex)
}
}
binding.btnGetCode.clickWithDebounce {
binding.btnGetTraceCode.clickWithDebounce {
val item = list.getOrNull(clickIndex)?:return@clickWithDebounce
//viewModelV2.getCode
}
@@ -367,12 +369,19 @@ class PackActivity : BaseActivity() {
private var clickIndex = -1
private val materialAdapter by lazy {
FoodMaterialAdapter(list).apply {
onItemClick = { positon ->
onItemClick = onItemClick@{ positon ->
val item = getItem(positon) ?: return@onItemClick
this@PackActivity.clickIndex = positon
list.forEachIndexed { index, entity ->
entity.isClicked = index == positon
}
notifyDataSetChanged()
binding.tvGoodsName.text = item.goodsName
binding.tvMaterialType.text = when (item.materialType) {
1 -> "主材"
2 -> "辅材"
else -> "未知"
}
}
addOnItemChildClickListener(R.id.ivClearIcon) { _, _, position ->
val item = list.getOrNull(position) ?: return@addOnItemChildClickListener
@@ -116,6 +116,12 @@ class SettingActivity : BaseActivity() {
startActivity<MealListActivity> { }
}
),
SettingItem(
type = SettingItem.Type.GROUP_TASK,
onClick = {
startActivity<TaskActivity> { }
}
),
SettingItem(
type = SettingItem.Type.MEAL_PACKAGE_TEST,
onClick = {
@@ -0,0 +1,207 @@
package com.shuwei.dish.match.ui
import android.content.Intent
import android.os.Bundle
import androidx.recyclerview.widget.LinearLayoutManager
import com.shuwei.dish.match.adapter.TaskAdapter
import com.shuwei.dish.match.base.BaseActivity
import com.shuwei.dish.match.base.GlobalData
import com.shuwei.dish.match.databinding.ActivityTaskBinding
import com.shuwei.dish.match.model.NutComboTaskVO
import com.shuwei.dish.match.net.NetViewModelV2
import com.shuwei.dish.match.net.UiState
import com.shuwei.dish.match.utils.ext.addOnActionSearchListener
import com.shuwei.dish.match.utils.ext.clickWithDebounce
import com.shuwei.dish.match.utils.ext.gone
import com.shuwei.dish.match.utils.ext.startActivity
import com.shuwei.dish.match.utils.ext.toast
import com.shuwei.dish.match.utils.ext.visible
/**
* 组配任务列表页面
*/
class TaskActivity : BaseActivity() {
companion object {
const val TAG = "TaskActivity"
const val TASK_ITEM = "taskItem"
}
private val binding by lazy { ActivityTaskBinding.inflate(layoutInflater) }
private val viewModelV2 by lazy { NetViewModelV2() }
private val taskList = mutableListOf<NutComboTaskVO>()
private val taskAdapter by lazy {
TaskAdapter(taskList).apply {
setOnItemClickListener { adapter, view, position ->
startActivity<TaskDetailActivity> {
putExtra(TaskDetailActivity.TASK_ITEM, taskList[position])
}
}
}
}
private var currentPage = 1
private var keyword: String? = null
private var isLoading = false
private var hasMore = true
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(binding.root)
setHeaderBackground()
setTitleBar(titleBarAction = {
it.visible()
}, titleAction = {
it.text = "组配任务"
}, rightIconAction = {
it.gone()
}, backAction = {
it.setOnClickListener {
finish()
}
})
initRecyclerView()
initRefreshLayout()
initSearchListener()
loadTaskList()
}
/**
* 初始化 RecyclerView
*/
private fun initRecyclerView() {
binding.rvTaskList.layoutManager = LinearLayoutManager(this)
binding.rvTaskList.adapter = taskAdapter
taskAdapter.setOnItemClickListener { _, _, position ->
val item = taskList.getOrNull(position) ?: return@setOnItemClickListener
val intent = Intent(this, TaskDetailActivity::class.java).apply {
putExtra(TASK_ITEM, item)
}
startActivity(intent)
}
}
/**
* 初始化 SmartRefreshLayout 下拉刷新和上拉加载
*/
private fun initRefreshLayout() {
binding.refreshLayout.apply {
// 下拉刷新
setOnRefreshListener {
currentPage = 1
hasMore = true
loadTaskList()
}
// 上拉加载
setOnLoadMoreListener { refreshLayout ->
if (isLoading || !hasMore) {
refreshLayout.finishLoadMoreWithNoMoreData()
return@setOnLoadMoreListener
}
currentPage++
loadTaskList()
}
}
}
/**
* 搜索栏交互
*/
private fun initSearchListener() {
binding.ivSearch.clickWithDebounce {
performSearch()
}
binding.etInputKeyword.addOnActionSearchListener {
performSearch()
}
}
/**
* 执行搜索
*/
private fun performSearch() {
keyword = binding.etInputKeyword.text.toString().trim()
if (keyword.isNullOrBlank()) {
toast("请输入搜索关键字")
return
}
currentPage = 1
hasMore = true
taskList.clear()
taskAdapter.notifyDataSetChanged()
binding.rvTaskList.visible()
binding.tvEmpty.gone()
loadTaskList()
}
/**
* 加载组配任务列表
*/
private fun loadTaskList() {
isLoading = true
viewModelV2.getIncompleteComboTasksWithCallback(
deviceCode = GlobalData.deviceId,
keyword = keyword,
pageNum = currentPage,
pageSize = 10,
onLoading = {
if (currentPage == 1) {
showLoading("加载中……")
}
},
onResult = onResult@{ state ->
dismissLoading()
isLoading = false
when (state) {
is UiState.Success -> {
val records = state.data?.records
if (currentPage == 1 && records.isNullOrEmpty()) {
binding.rvTaskList.gone()
binding.tvEmpty.visible()
binding.refreshLayout.finishRefresh()
binding.refreshLayout.finishLoadMoreWithNoMoreData()
return@onResult
}
if (!records.isNullOrEmpty()) {
binding.rvTaskList.visible()
binding.tvEmpty.gone()
if (currentPage == 1) {
taskList.clear()
}
taskList.addAll(records)
taskAdapter.notifyDataSetChanged()
// 判断是否还有更多数据
if (records.size < 10) {
hasMore = false
binding.refreshLayout.finishLoadMoreWithNoMoreData()
}
} else {
hasMore = false
binding.refreshLayout.finishLoadMoreWithNoMoreData()
}
binding.refreshLayout.finishRefresh()
binding.refreshLayout.finishLoadMore()
}
is UiState.Error -> {
toast(state.msg)
if (currentPage > 1) {
currentPage--
}
if (currentPage == 1 && taskList.isEmpty()) {
binding.rvTaskList.gone()
binding.tvEmpty.visible()
}
binding.refreshLayout.finishRefresh()
binding.refreshLayout.finishLoadMore(false)
}
else -> {}
}
}
)
}
}
@@ -0,0 +1,192 @@
package com.shuwei.dish.match.ui
import android.os.Bundle
import androidx.core.content.IntentCompat
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager
import com.shuwei.dish.match.adapter.TaskDetailAdapter
import com.shuwei.dish.match.base.BaseActivity
import com.shuwei.dish.match.databinding.ActivityTaskDetailBinding
import com.shuwei.dish.match.model.NutComboTaskDetailVO
import com.shuwei.dish.match.model.NutComboTaskVO
import com.shuwei.dish.match.net.NetViewModelV2
import com.shuwei.dish.match.net.UiState
import com.shuwei.dish.match.utils.ext.clickWithDebounce
import com.shuwei.dish.match.utils.ext.gone
import com.shuwei.dish.match.utils.ext.toast
import com.shuwei.dish.match.utils.ext.visible
import kotlinx.coroutines.launch
/**
* 组配任务详情页面
*/
class TaskDetailActivity : BaseActivity() {
companion object {
const val TAG = "TaskDetailActivity"
const val TASK_ITEM = "taskItem"
}
private val binding by lazy { ActivityTaskDetailBinding.inflate(layoutInflater) }
private val viewModelV2 by lazy { NetViewModelV2() }
private var taskItem: NutComboTaskVO? = null
private val itemList = mutableListOf<com.shuwei.dish.match.model.NutComboTaskItemDetail>()
private val itemAdapter by lazy {
TaskDetailAdapter(itemList).apply {
setOnItemClickListener { adapter, view, position ->
getItem(position) ?: return@setOnItemClickListener
itemList.forEachIndexed { index, detail ->
detail.isClicked = index == position
}
notifyDataSetChanged()
}
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(binding.root)
setHeaderBackground()
taskItem = IntentCompat.getParcelableExtra(intent, TASK_ITEM, NutComboTaskVO::class.java)
setTitleBar(titleBarAction = {
it.visible()
}, titleAction = {
it.text = "任务详情"
}, rightIconAction = {
it.gone()
}, backAction = {
it.setOnClickListener {
finish()
}
})
initRecyclerView()
initView()
loadDetail()
}
/**
* 初始化任务概要信息
*/
private fun initView() {
taskItem?.let {
binding.tvTaskNo.text = "任务编号:${it.taskNo}"
binding.tvTargetDish.text = it.targetDish
binding.tvSpec.text = it.spec
binding.tvPlanDate.text = "计划日期:${it.planDate}"
binding.tvPortions.text = "总份数:${it.portions}"
binding.tvOwnerName.text = "负责人:${it.ownerName}"
binding.tvStatus.text = getStatusText(it.comboStatus)
// 仅待组配状态显示"开始组配"按钮
if (it.comboStatus == 0) {
binding.btnStart.visible()
binding.btnStart.clickWithDebounce {
startComboTask(it.id ?: return@clickWithDebounce)
}
} else {
binding.btnStart.gone()
}
}
binding.btnGetTraceCode.clickWithDebounce {
}
}
/**
* 初始化 RecyclerView
*/
private fun initRecyclerView() {
binding.rvItemList.layoutManager = LinearLayoutManager(this)
binding.rvItemList.adapter = itemAdapter
}
/**
* 加载任务详情(食材清单)
*/
private fun loadDetail() {
val taskId = taskItem?.id ?: return
viewModelV2.getComboTaskDetailWithCallback(
id = taskId,
onLoading = {
showLoading("加载中……")
},
onResult = onResult@{ state ->
dismissLoading()
when (state) {
is UiState.Success -> {
val detail = state.data
if (detail == null) {
toast("详情为空")
return@onResult
}
// 更新状态(可能已被其他设备修改)
binding.tvStatus.text = getStatusText(detail.comboStatus)
if (detail.comboStatus != 0) {
binding.btnStart.gone()
}
// 加载食材列表
val items = detail.items
if (!items.isNullOrEmpty()) {
itemList.clear()
itemList.addAll(items)
itemAdapter.notifyDataSetChanged()
} else {
toast("该任务无食材信息")
}
}
is UiState.Error -> toast(state.msg)
else -> {}
}
}
)
}
/**
* 开始组配
*/
private fun startComboTask(taskId: Long) {
showLoading("正在开始组配……")
lifecycleScope.launch {
viewModelV2.startComboTask(taskId)
}
lifecycleScope.launch {
viewModelV2.startComboTaskState.collect { state ->
when (state) {
is UiState.Success -> {
dismissLoading()
toast("已开始组配")
// 刷新详情
loadDetail()
// 更新本地状态
taskItem?.comboStatus = 1
}
is UiState.Error -> {
dismissLoading()
toast(state.msg)
}
else -> {}
}
}
}
}
/**
* 状态文本映射
*/
private fun getStatusText(status: Int?): String {
return when (status) {
0 -> "待组配"
1 -> "组配中"
2 -> "已完成"
else -> "未知"
}
}
}
+19 -14
View File
@@ -70,31 +70,22 @@
android:layout_marginStart="30dp"
android:textColor="@color/black"
android:textSize="30sp"
android:maxLines="1"
android:ellipsize="end"
android:maxLength="12"
android:textStyle="bold"
tools:text="豆腐" />
<TextView
android:id="@+id/tvMaterialType"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="30dp"
android:layout_marginEnd="30dp"
android:textColor="@color/black999"
android:textSize="26sp"
tools:text="主材" />
<TextView
android:id="@+id/btnGetCode"
android:layout_width="wrap_content"
android:layout_height="50dp"
android:layout_marginEnd="30dp"
android:paddingHorizontal="20dp"
android:text="获取溯源码"
android:textSize="18sp"
android:gravity="center"
android:textColor="@color/dish_green"
android:background="@drawable/ripple_effect_light"/>
</LinearLayout>
<com.google.android.material.divider.MaterialDivider
@@ -196,6 +187,20 @@
android:textSize="28sp" />
-->
<TextView
android:id="@+id/btnGetTraceCode"
android:layout_width="wrap_content"
android:layout_height="50dp"
android:layout_marginEnd="30dp"
android:paddingHorizontal="20dp"
android:layout_gravity="end|center_vertical"
android:text="获取溯源码"
android:textSize="18sp"
android:gravity="center"
android:textColor="@color/dish_green"
android:background="@drawable/ripple_effect_light"/>
<FrameLayout
android:id="@+id/flCameraContainer"
android:layout_width="1dp"
+104
View File
@@ -0,0 +1,104 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:background="@drawable/bg_other_page">
<LinearLayout
android:id="@+id/llSearchBar"
android:layout_width="match_parent"
android:layout_height="100dp"
android:layout_margin="30dp"
android:background="@drawable/shape_white_30_corners"
android:gravity="center_vertical"
android:orientation="horizontal">
<EditText
android:id="@+id/etInputKeyword"
android:layout_width="0dp"
android:layout_height="80dp"
android:layout_marginHorizontal="28dp"
android:layout_weight="1"
android:autofillHints=""
android:background="@color/white"
android:gravity="center"
android:hint="请输入菜品名称"
android:inputType="text"
android:imeOptions="actionSearch"
android:paddingStart="3dp"
android:paddingEnd="3dp"
android:maxLines="1"
android:ellipsize="end"
android:textColor="@color/black333"
android:textColorHint="@color/gray_c8"
android:textSize="40sp"
tools:ignore="TextFields" />
<ImageView
android:id="@+id/ivSearch"
android:layout_width="60dp"
android:layout_height="60dp"
android:layout_marginStart="20dp"
android:layout_marginEnd="20dp"
android:paddingStart="10dp"
android:paddingEnd="10dp"
android:src="@drawable/ic_search_gray"
tools:ignore="ContentDescription" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginHorizontal="30dp"
android:layout_weight="1"
android:background="@drawable/shape_white_30_corners"
android:orientation="vertical">
<com.scwang.smart.refresh.layout.SmartRefreshLayout
android:id="@+id/refreshLayout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.scwang.smart.refresh.header.ClassicsHeader
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rvTaskList"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginBottom="20dp"
android:overScrollMode="never"
android:scrollbars="vertical"
tools:itemCount="3"
tools:listitem="@layout/list_item_task" />
<TextView
android:id="@+id/tvEmpty"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="暂无数据"
android:textColor="@color/black999"
android:textSize="26sp"
android:visibility="gone" />
</FrameLayout>
<com.scwang.smart.refresh.footer.ClassicsFooter
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</com.scwang.smart.refresh.layout.SmartRefreshLayout>
</LinearLayout>
</LinearLayout>
@@ -0,0 +1,163 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:background="@drawable/bg_other_page">
<LinearLayout
android:id="@+id/llTaskInfo"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="30dp"
android:background="@drawable/shape_white_30_corners"
android:orientation="vertical"
android:padding="20dp">
<TextView
android:id="@+id/tvTaskNo"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/black999"
android:textSize="20sp"
tools:text="任务编号:RW20260601001" />
<TextView
android:id="@+id/tvTargetDish"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:textColor="@color/black"
android:textSize="32sp"
android:textStyle="bold"
tools:text="红烧肉" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:orientation="horizontal">
<TextView
android:id="@+id/tvSpec"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/black666"
android:textSize="24sp"
tools:text="中组(8份)·50组" />
<TextView
android:id="@+id/tvStatus"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="end"
android:textColor="@color/dish_green"
android:textSize="24sp"
tools:text="待组配" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:orientation="horizontal">
<TextView
android:id="@+id/tvPlanDate"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/black999"
android:textSize="20sp"
tools:text="计划日期:2026-06-01" />
<TextView
android:id="@+id/tvPortions"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="20dp"
android:textColor="@color/black999"
android:textSize="20sp"
tools:text="总份数:400" />
<TextView
android:id="@+id/tvOwnerName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="20dp"
android:textColor="@color/black999"
android:textSize="20sp"
tools:text="负责人:张三" />
</LinearLayout>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginHorizontal="30dp"
android:layout_weight="1"
android:background="@drawable/shape_white_30_corners"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="100dp"
android:gravity="center_vertical">
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="20dp"
android:layout_weight="1"
android:padding="10dp"
android:text="食材明细"
android:textColor="@color/black666"
android:textSize="28sp" />
<TextView
android:id="@+id/btnGetTraceCode"
android:layout_width="wrap_content"
android:layout_height="50dp"
android:layout_marginEnd="30dp"
android:background="@drawable/ripple_effect_light"
android:gravity="center"
android:paddingHorizontal="20dp"
android:text="获取溯源码"
android:textColor="@color/dish_green"
android:textSize="18sp" />
</LinearLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rvItemList"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginBottom="20dp"
android:overScrollMode="never"
android:scrollbars="vertical"
tools:itemCount="3"
tools:listitem="@layout/list_item_task_detail" />
</LinearLayout>
<TextView
android:id="@+id/btnStart"
android:layout_width="match_parent"
android:layout_height="90dp"
android:layout_margin="30dp"
android:background="@drawable/shape_green_bg"
android:foreground="?android:attr/selectableItemBackground"
android:gravity="center"
android:text="开始组配"
android:textColor="@color/white"
android:textSize="32sp"
android:textStyle="bold"
android:visibility="gone" />
</LinearLayout>
@@ -4,7 +4,7 @@
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/clBlock"
android:layout_width="match_parent"
android:layout_height="120dp"
android:layout_height="140dp"
android:layout_marginStart="30dp"
android:layout_marginEnd="30dp"
android:layout_marginBottom="15dp"
@@ -21,41 +21,41 @@
android:textColor="@color/black"
android:textSize="30sp"
android:textStyle="bold"
app:layout_constraintBottom_toTopOf="@+id/tvGoodsCode"
app:layout_constraintBottom_toTopOf="@+id/tvDishType"
app:layout_constraintEnd_toStartOf="@+id/tvDishWeight"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_chainStyle="packed"
tools:text="雪菜" />
<TextView
android:id="@+id/tvGoodsCode"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:ellipsize="end"
android:maxLines="1"
android:layout_marginVertical="3dp"
android:textColor="@color/black999"
android:textSize="20sp"
app:layout_constraintBottom_toTopOf="@+id/tvDishType"
app:layout_constraintEnd_toEndOf="@id/tvDishName"
app:layout_constraintStart_toStartOf="@id/tvDishName"
app:layout_constraintTop_toBottomOf="@id/tvDishName"
android:text="溯源码:-" />
<TextView
android:id="@+id/tvDishType"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:ellipsize="end"
android:maxLines="1"
android:layout_marginVertical="5dp"
android:textColor="@color/black999"
android:textSize="23sp"
android:textSize="22sp"
app:layout_constraintBottom_toTopOf="@id/tvGoodsCode"
app:layout_constraintEnd_toEndOf="@id/tvDishName"
app:layout_constraintStart_toStartOf="@id/tvDishName"
app:layout_constraintTop_toBottomOf="@id/tvDishName"
tools:text="主辅材:主材" />
<TextView
android:id="@+id/tvGoodsCode"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:ellipsize="end"
android:maxLines="1"
android:textColor="@color/black999"
android:textSize="18sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="@id/tvDishName"
app:layout_constraintStart_toStartOf="@id/tvDishName"
app:layout_constraintTop_toBottomOf="@id/tvGoodsCode"
tools:text="主辅材:主材" />
app:layout_constraintTop_toBottomOf="@id/tvDishType"
android:text="溯源码:-" />
<ImageView
android:id="@+id/ivOperateIcon"
@@ -0,0 +1,92 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="160dp"
android:layout_margin="15dp"
android:background="@drawable/shape_white_12_corners"
android:paddingHorizontal="20dp"
android:paddingVertical="15dp">
<TextView
android:id="@+id/tvTaskNo"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/black999"
android:textSize="18sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="任务编号:RW20260601001" />
<TextView
android:id="@+id/tvTargetDish"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/black"
android:textSize="28sp"
android:textStyle="bold"
app:layout_constraintBottom_toTopOf="@id/tvPlanDate"
app:layout_constraintStart_toStartOf="@id/tvTaskNo"
app:layout_constraintTop_toBottomOf="@id/tvTaskNo"
tools:text="红烧肉" />
<TextView
android:id="@+id/tvSpec"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="15dp"
android:textColor="@color/black666"
android:textSize="22sp"
app:layout_constraintBottom_toBottomOf="@id/tvTargetDish"
app:layout_constraintStart_toEndOf="@id/tvTargetDish"
app:layout_constraintTop_toTopOf="@id/tvTargetDish"
tools:text="中组(8份)·50组" />
<TextView
android:id="@+id/tvPlanDate"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/black999"
android:textSize="20sp"
app:layout_constraintBottom_toTopOf="@id/tvPortions"
app:layout_constraintStart_toStartOf="@id/tvTargetDish"
app:layout_constraintTop_toBottomOf="@id/tvTargetDish"
tools:text="计划日期:2026-06-01" />
<TextView
android:id="@+id/tvPortions"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/black999"
android:textSize="20sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="@id/tvTargetDish"
app:layout_constraintTop_toBottomOf="@id/tvPlanDate"
tools:text="总份数:400" />
<TextView
android:id="@+id/tvOwnerName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="10dp"
android:textColor="@color/black999"
android:textSize="20sp"
app:layout_constraintBottom_toBottomOf="@id/tvPortions"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@id/tvPortions"
tools:text="负责人:张三" />
<TextView
android:id="@+id/tvStatus"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="10dp"
android:textColor="@color/dish_green"
android:textSize="22sp"
app:layout_constraintBottom_toBottomOf="@id/tvTargetDish"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@id/tvTargetDish"
tools:text="待组配" />
</androidx.constraintlayout.widget.ConstraintLayout>
@@ -0,0 +1,80 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="15dp"
android:layout_marginBottom="15dp"
android:background="@drawable/shape_white_fb_15_corners"
android:orientation="vertical"
android:padding="15dp">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<TextView
android:id="@+id/tvIngredientName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/black"
android:textSize="26sp"
android:textStyle="bold"
tools:text="五花肉" />
<TextView
android:id="@+id/tvIngredientClass"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="20dp"
android:textColor="@color/black999"
android:textSize="20sp"
tools:text="主材" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:orientation="horizontal">
<TextView
android:id="@+id/tvPerPortionQty"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/black666"
android:textSize="20sp"
tools:text="每份用量:200g" />
<TextView
android:id="@+id/tvActualQty"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="20dp"
android:textColor="@color/black666"
android:textSize="20sp"
tools:text="实际用量:200g" />
<TextView
android:id="@+id/tvTotalQty"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="20dp"
android:textColor="@color/black666"
android:textSize="20sp"
tools:text="合计用量:80000g" />
</LinearLayout>
<TextView
android:id="@+id/tvTraceCode"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="5dp"
android:textColor="@color/black999"
android:textSize="18sp"
android:visibility="gone"
tools:text="溯源码:TRACE-20260601001"
tools:visibility="visible" />
</LinearLayout>