feat(emergency): 新增应急就医群聊功能

- 工作台新增"应急就医"入口,支持多工单选择弹窗
- 新增应急就医相关 API:参与工单列表、群成员增减、专家/员工搜索
- 群聊页面集成侧边导航抽屉(DrawerLayout),展示群成员并支持添加/移除
- 新增添加成员页面(AddMemberActivity),支持搜索专家和员工
- InputActionSetting 扩展:新增结束会话、结束问诊名称、评价等控制字段
- IMManager.startGroupChat 支持传入 InputActionSetting 定制输入区行为
- TUIUtils 新增 WORK_TYPE_EMERGENCY 类型常量
- 新增 ParamedicOperateHelper 统一管理应急群聊操作回调

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
zhanglei
2026-08-04 17:27:23 +08:00
co-authored by Claude Opus 4.7
parent 2c4b640adb
commit f61d8c90c9
89 changed files with 3409 additions and 69 deletions
@@ -56,14 +56,22 @@ class MyApplication : BaseApp() {
DataStoreManager.initialize(this)
DebuggerUtils.checkDebuggableInNotDebugModel(this)
TUIChatConfigs.getConfigs().imInputViewActionListener =
IMInputViewActionListener { workType, workId ->
var activity = CustomActivityManager.getInstance().currentActivity()
if(activity is AppCompatActivity){
activity?.showLoading()
object : IMInputViewActionListener {
override fun finishAction(workType: Int, workId: String) {
var activity = CustomActivityManager.getInstance().currentActivity()
if(activity is AppCompatActivity){
activity?.showLoading()
}
appViewModel.finishIMChat(workType, workId, finallyCall = {
hideLoading()
})
}
override fun appraiseAction(chatId: String, workType: Int, workId: String) {
}
override fun finishActivityToGuidanceHome(workType: Int) {
}
appViewModel.finishIMChat(workType, workId, finallyCall = {
hideLoading()
})
}
registerActivityLifecycleCallbacks(AdjustLifecycleCallbacks())
}
@@ -0,0 +1,12 @@
package com.xjjk.healthexpertclient.bean
/**
* 通用分页数据包装类(MyBatis-Plus 风格)
*/
data class PageBean<T>(
val records: List<T>?,
val total: Int = 0,
val size: Int = 0,
val current: Int = 0,
val pages: Int = 0
)
@@ -0,0 +1,15 @@
package com.xjjk.healthexpertclient.bean.emergency
data class GroupInfo(
val orderId: String,
val sessionId: String,
val salvageUserName: String,
val salvageUserSex: String,
val salvageUserAvatar: String,
val salvageUserAge: Int,
val salvageUserMobile: String,
val orgName: String,
val deptName: String,
val orderStatus: String,
val initTime: String
)
@@ -0,0 +1,10 @@
package com.xjjk.healthexpertclient.bean.emergency
/**
* 应急群组成员信息
*/
data class GroupMemberBean(
val userId: String,
val userName: String = "",
val userAvatar: String = ""
)
@@ -1,6 +1,8 @@
package com.xjjk.healthexpertclient.data.api
import com.btpj.lib_base.data.bean.ApiResponse
import com.tencent.qcloud.tuikit.tuichat.bean.EmployeeBean
import com.tencent.qcloud.tuikit.tuichat.bean.ExpertBean
import com.xjjk.healthexpertclient.bean.AppUpdateBean
import com.xjjk.healthexpertclient.bean.workbench.ArchivesDetailResultBean
import com.xjjk.healthexpertclient.bean.workbench.AudioVideoCallBean
@@ -11,6 +13,9 @@ import com.xjjk.healthexpertclient.bean.workbench.HealthInfoBean
import com.xjjk.healthexpertclient.bean.workbench.MessageBean
import com.xjjk.healthexpertclient.bean.workbench.PhysicalExaminationReportBean
import com.xjjk.healthexpertclient.bean.workbench.RefuseReasonBean
import com.xjjk.healthexpertclient.bean.PageBean
import com.xjjk.healthexpertclient.bean.emergency.GroupInfo
import com.tencent.qcloud.tuikit.tuichat.bean.GroupMemberBean
import com.xjjk.healthexpertclient.bean.workbench.WorkBenchConsultNumBean
import okhttp3.RequestBody
import retrofit2.http.Body
@@ -136,4 +141,40 @@ interface WorkbenchApi {
*/
@GET("/health-system/version/getSysVersionDetailByPackageName")
suspend fun getAppUpdateInfo(@QueryMap params: MutableMap<String, Any?>): ApiResponse<AppUpdateBean>
/**
* 当前登录人参与的应急工单列表
*/
@GET("/health-emergency/api/emergency/order/myParticipatedOrders")
suspend fun getParticipatedOrders(): ApiResponse<PageBean<GroupInfo>>
/**
* 实际群成员列表
*/
@GET("/health-emergency/api/emergency/order/getActualGroupMemberList")
suspend fun getActualGroupMemberList(@QueryMap params: MutableMap<String, Any?>): ApiResponse<List<GroupMemberBean>>
/**
* 邀请进群
*/
@POST("/health-emergency/api/emergency/addGroupUser")
suspend fun addGroupUser(@Body requestBody: RequestBody): ApiResponse<Boolean>
/**
* 移除群成员
*/
@POST("/health-emergency/api/emergency/removeGroupUser")
suspend fun removeGroupUser(@Body requestBody: RequestBody): ApiResponse<Boolean>
/**
* 搜索专家(专业人员)
*/
@GET("/health-emergency/api/emergency/order/searchExpert")
suspend fun searchExpert(@QueryMap params: MutableMap<String, Any?>): ApiResponse<PageBean<ExpertBean>>
/**
* 搜索员工
*/
@GET("/health-emergency/api/emergency/order/searchEmployee")
suspend fun searchEmployee(@QueryMap params: MutableMap<String, Any?>): ApiResponse<PageBean<EmployeeBean>>
}
@@ -7,6 +7,11 @@ import com.btpj.lib_base.http.RetrofitManager
import com.btpj.lib_base.http.RetrofitManager.toRequestBody
import com.xjjk.healthexpertclient.BuildConfig
import com.xjjk.healthexpertclient.bean.AppUpdateBean
import com.xjjk.healthexpertclient.bean.PageBean
import com.xjjk.healthexpertclient.bean.emergency.GroupInfo
import com.tencent.qcloud.tuikit.tuichat.bean.EmployeeBean
import com.tencent.qcloud.tuikit.tuichat.bean.ExpertBean
import com.tencent.qcloud.tuikit.tuichat.bean.GroupMemberBean
import com.xjjk.healthexpertclient.bean.workbench.ArchivesDetailResultBean
import com.xjjk.healthexpertclient.bean.workbench.AudioVideoCallBean
import com.xjjk.healthexpertclient.bean.workbench.CheckRecordUserInfoBean
@@ -190,4 +195,51 @@ object WorkbenchRepository : BaseRepository() {
params["packageName"] = BuildConfig.APPLICATION_ID
return apiCall { service.getAppUpdateInfo(params) }
}
suspend fun getParticipatedOrders(): ApiResponse<PageBean<GroupInfo>> {
return apiCall { service.getParticipatedOrders() }
}
suspend fun getActualGroupMemberList(sessionId: String): ApiResponse<List<GroupMemberBean>> {
val map = mutableMapOf<String, Any?>()
map["sessionId"] = sessionId
return apiCall { service.getActualGroupMemberList(map) }
}
suspend fun addGroupUser(groupId: String, memberList: List<String>, memberType: Int): ApiResponse<Boolean> {
val map = mutableMapOf<String, Any?>()
map["groupId"] = groupId
map["memberList"] = memberList
map["memberType"] = memberType
return apiCall { service.addGroupUser(map.toJson().toRequestBody()) }
}
suspend fun removeGroupUser(groupId: String, memberList: List<String>): ApiResponse<Boolean> {
val map = mutableMapOf<String, Any?>()
map["groupId"] = groupId
map["memberList"] = memberList
return apiCall { service.removeGroupUser(map.toJson().toRequestBody()) }
}
suspend fun searchExpert(realname: String, centerId: String, excludeSessionId: String, pageNo: Int, pageSize: Int): ApiResponse<PageBean<ExpertBean>> {
val map = mutableMapOf<String, Any?>()
map["realname"] = realname
map["centerId"] = centerId
map["excludeSessionId"] = excludeSessionId
map["pageNo"] = pageNo
map["pageSize"] = pageSize
return apiCall { service.searchExpert(map) }
}
suspend fun searchEmployee(realname: String, orgCode: String, workNo: String, phone: String, excludeSessionId: String, pageNo: Int, pageSize: Int): ApiResponse<PageBean<EmployeeBean>> {
val map = mutableMapOf<String, Any?>()
map["realname"] = realname
map["orgCode"] = orgCode
map["workNo"] = workNo
map["phone"] = phone
map["excludeSessionId"] = excludeSessionId
map["pageNo"] = pageNo
map["pageSize"] = pageSize
return apiCall { service.searchEmployee(map) }
}
}
@@ -19,6 +19,7 @@ import com.tencent.qcloud.tuikit.tuicallkit.TUICallKit
import com.tencent.qcloud.tuikit.tuicallkit.base.Constants
import com.tencent.qcloud.tuikit.tuicallkit.config.OfflinePushInfoConfig
import com.tencent.qcloud.tuikit.tuichat.bean.WorkBean
import com.tencent.qcloud.tuikit.tuichat.classicui.setting.InputActionSetting
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
@@ -111,13 +112,13 @@ fun Context.startC2CChat(chatId: String, groupName: String, workBean: WorkBean?
}
}
fun Context.startGroupChat(groupId: String, groupName: String = "", workBean: WorkBean? = null) {
fun Context.startGroupChat(groupId: String, groupName: String = "", workBean: WorkBean? = null,
inputActionSetting: InputActionSetting? = null) {
if (TUILogin.isUserLogined()) {
// TUIUtils.createGroup("VHMQIUMM", "android测试组", V2TIMConversation.V2TIM_GROUP)
TUIUtils.startChat(groupId, groupName, V2TIMConversation.V2TIM_GROUP, workBean)
TUIUtils.startChat(groupId, groupName, V2TIMConversation.V2TIM_GROUP, workBean, inputActionSetting)
} else {
loginIm(successCall = {
TUIUtils.startChat(groupId, groupName, V2TIMConversation.V2TIM_GROUP, workBean)
TUIUtils.startChat(groupId, groupName, V2TIMConversation.V2TIM_GROUP, workBean, inputActionSetting)
})
}
}
@@ -1,5 +1,6 @@
package com.xjjk.healthexpertclient.ui.im.utils
import com.xjjk.healthexpertclient.BuildConfig
import com.tencent.qcloud.tuikit.tuichat.classicui.setting.InputActionSetting
object IMInputActionSettingUtils {
@@ -26,4 +27,22 @@ object IMInputActionSettingUtils {
inputActionSetting.isDisableMedicalExaminationReport = true
inputActionSetting.isDisableSendMessage = false
}
/**
* 应急求助IM功能配置
*/
fun createEmergencySetting(): InputActionSetting {
var inputActionSetting = InputActionSetting()
inputActionSetting.isDisableAudioCall = true
inputActionSetting.isDisableVideoCall = false
inputActionSetting.isDisableArchives = true
inputActionSetting.isDisableMedicalExaminationReport = true
inputActionSetting.isDisableSendMessage = false
inputActionSetting.isDisableFinishName = true
inputActionSetting.isDisableEvaluate = true
inputActionSetting.isDisableFinishSession = true
inputActionSetting.packageName = BuildConfig.APPLICATION_ID
InputActionSetting.setsInstance(inputActionSetting)
return inputActionSetting
}
}
@@ -0,0 +1,117 @@
package com.xjjk.healthexpertclient.ui.workbench.adapter
import android.content.res.ColorStateList
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.core.content.ContextCompat
import androidx.recyclerview.widget.RecyclerView
import com.xjjk.healthexpertclient.R
import com.xjjk.healthexpertclient.bean.emergency.GroupInfo
import com.xjjk.healthexpertclient.databinding.ItemGroupInfoBinding
/**
* 急救工单选择适配器,支持单选
*/
class GroupInfoAdapter(
private val items: List<GroupInfo>,
private val onSelectionChanged: (Int) -> Unit
) : RecyclerView.Adapter<GroupInfoAdapter.ViewHolder>() {
/** 当前选中位置,-1 表示无选中 */
var selectedPosition = -1
private set
/** 获取当前选中的工单 */
val selectedItem: GroupInfo?
get() = if (selectedPosition in items.indices) items[selectedPosition] else null
/** 选中主色 */
private var colorPrimary = 0
/** 未选中灰色 */
private var colorGray = 0
private var colorDark = 0
private var colorBody = 0
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val binding = ItemGroupInfoBinding.inflate(
LayoutInflater.from(parent.context), parent, false
)
if (colorPrimary == 0) {
colorPrimary = ContextCompat.getColor(parent.context, R.color.emergency_primary)
colorGray = ContextCompat.getColor(parent.context, R.color.emergency_text_gray)
colorDark = ContextCompat.getColor(parent.context, R.color.emergency_text_dark)
colorBody = ContextCompat.getColor(parent.context, R.color.emergency_text_body)
}
return ViewHolder(binding)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.bind(items[position], position == selectedPosition)
}
override fun getItemCount(): Int = items.size
inner class ViewHolder(private val binding: ItemGroupInfoBinding) :
RecyclerView.ViewHolder(binding.root) {
private var currentPosition = -1
init {
binding.rootItem.setOnClickListener {
val oldPos = selectedPosition
if (oldPos == currentPosition) {
// 取消选中
selectedPosition = -1
notifyItemChanged(currentPosition)
onSelectionChanged(-1)
} else {
selectedPosition = currentPosition
notifyItemChanged(oldPos)
notifyItemChanged(currentPosition)
onSelectionChanged(currentPosition)
}
}
}
fun bind(groupInfo: GroupInfo, isSelected: Boolean) {
currentPosition = adapterPosition
val ctx = binding.root.context
// 姓名
binding.tvUserName.text = groupInfo.salvageUserName
// 部门标签
binding.tvDeptName.text = groupInfo.deptName
// 地址
binding.tvOrgName.text = groupInfo.orgName
// 时间
binding.tvInitTime.text = "求助时间:${groupInfo.initTime}"
if (isSelected) {
// 选中态
binding.rootItem.setBackgroundResource(R.drawable.bg_emergency_item_selected)
binding.ivRadio.setImageResource(R.drawable.ic_emergency_radio_selected)
binding.tvDeptName.setTextColor(colorPrimary)
binding.tvDeptName.setBackgroundResource(R.drawable.bg_dept_badge_selected)
binding.tvInitTime.setTextColor(colorPrimary)
binding.ivLocation.imageTintList = ColorStateList.valueOf(colorPrimary)
binding.ivClock.imageTintList = ColorStateList.valueOf(colorPrimary)
} else {
// 未选中态
binding.rootItem.setBackgroundResource(R.drawable.bg_emergency_item_unselected)
binding.ivRadio.setImageResource(R.drawable.ic_emergency_radio_unselected)
binding.tvDeptName.setTextColor(colorGray)
binding.tvDeptName.setBackgroundResource(R.drawable.bg_dept_badge_unselected)
binding.tvInitTime.setTextColor(colorGray)
binding.ivLocation.imageTintList = ColorStateList.valueOf(colorGray)
binding.ivClock.imageTintList = ColorStateList.valueOf(colorGray)
}
}
}
}
@@ -3,17 +3,29 @@ package com.xjjk.healthexpertclient.ui.workbench.fragment
import android.os.Bundle
import android.os.SystemClock
import android.view.View
import android.app.Dialog
import android.graphics.Color
import android.graphics.Rect
import android.graphics.drawable.ColorDrawable
import android.view.WindowManager
import androidx.core.util.Consumer
import androidx.fragment.app.activityViewModels
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
import com.btpj.lib_base.data.local.DataStoreManager
import com.btpj.lib_base.ext.initColors
import com.btpj.lib_base.ext.loadCircle
import com.btpj.lib_base.utils.ToastUtil
import com.buddy.kredit.android.base.BaseFragment
import com.xjjk.healthexpertclient.R
import com.xjjk.healthexpertclient.bean.emergency.GroupInfo
import com.xjjk.healthexpertclient.bean.mine.DoctorBean
import com.xjjk.healthexpertclient.data.repository.WorkbenchRepository
import com.xjjk.healthexpertclient.databinding.DialogGroupInfoListBinding
import com.xjjk.healthexpertclient.databinding.FragmentWorkbenchBinding
import com.xjjk.healthexpertclient.event.WorkbenchEvent
import com.xjjk.healthexpertclient.ext.loginIm
@@ -24,14 +36,24 @@ import com.xjjk.healthexpertclient.ext.startSimulateUserActivity
import com.xjjk.healthexpertclient.ui.im.utils.IMInputActionSettingUtils
import com.xjjk.healthexpertclient.ui.main.viewmodel.MainViewModel
import com.xjjk.healthexpertclient.ui.workbench.ConsultRecordActivity
import com.xjjk.healthexpertclient.ui.workbench.adapter.GroupInfoAdapter
import com.xjjk.healthexpertclient.ui.workbench.viewmodel.WorkbenchViewModel
import com.xjjk.healthexpertclient.utils.CommonUtils
import com.xjjk.healthexpertclient.utils.TUIUtils
import com.xjjk.healthexpertclient.utils.orEmptyDefault
import com.xjjk.healthexpertclient.utils.orNullDefault
import com.tencent.qcloud.tuikit.tuichat.bean.EmployeeBean
import com.tencent.qcloud.tuikit.tuichat.bean.ExpertBean
import com.tencent.qcloud.tuikit.tuichat.bean.GroupMemberBean
import com.tencent.qcloud.tuikit.tuichat.bean.WorkBean
import com.tencent.qcloud.tuikit.tuichat.classicui.interfaces.OnParamedicOperateListener
import com.tencent.qcloud.tuikit.tuichat.presenter.ParamedicOperateHelper
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withTimeoutOrNull
import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode
@@ -43,12 +65,93 @@ class WorkbenchFragment :
}
private val mainViewModel by activityViewModels<MainViewModel>()
private val paramedicOperateListener = object : OnParamedicOperateListener {
override fun getActualGroupMemberList(sessionId: String): List<GroupMemberBean>? {
return runBlocking(Dispatchers.IO) {
try {
val deferred = async {
WorkbenchRepository.getActualGroupMemberList(sessionId)
}
val response = withTimeoutOrNull(10000L) { deferred.await() }
?: return@runBlocking emptyList<GroupMemberBean>()
response.takeIf { it.success }?.result ?: emptyList()
} catch (e: Exception) {
emptyList()
}
}
}
override fun addGroupUser(
groupId: String,
memberList: List<String>,
memberType: Int,
callback: Consumer<Boolean>
) {
mViewModel.addGroupUser(groupId, memberList, memberType) { success ->
callback.accept(success)
}
}
override fun removeGroupUser(groupId: String, memberList: List<String>) {
mViewModel.removeGroupUser(groupId, memberList)
}
override fun searchExpert(
realname: String?,
centerId: String?,
excludeSessionId: String?,
pageNo: Int,
pageSize: Int
): List<ExpertBean?>? {
return runBlocking(Dispatchers.IO) {
try {
val deferred = async {
WorkbenchRepository.searchExpert(
realname ?: "", centerId ?: "", excludeSessionId ?: "", pageNo, pageSize
)
}
val response = withTimeoutOrNull(10000L) { deferred.await() }
?: return@runBlocking emptyList<ExpertBean>()
response.takeIf { it.success }?.result?.records ?: emptyList()
} catch (e: Exception) {
emptyList()
}
}
}
override fun searchEmployee(
realname: String?,
orgCode: String?,
workNo: String?,
phone: String?,
excludeSessionId: String?,
pageNo: Int,
pageSize: Int
): List<EmployeeBean?>? {
return runBlocking(Dispatchers.IO) {
try {
val deferred = async {
WorkbenchRepository.searchEmployee(
realname ?: "", orgCode ?: "", workNo ?: "", phone ?: "",
excludeSessionId ?: "", pageNo, pageSize
)
}
val response = withTimeoutOrNull(10000L) { deferred.await() }
?: return@runBlocking emptyList<EmployeeBean>()
response.takeIf { it.success }?.result?.records ?: emptyList()
} catch (e: Exception) {
emptyList()
}
}
}
}
var time: Long = 3000
var mCount = 5
var mLastTime = 0L
var mHits = LongArray(mCount)
override fun initView(view: View, savedInstanceState: Bundle?) {
// setToolBarRightText(getString(R.string.doctor_homepage_message))
ParamedicOperateHelper.getInstance().setListener(paramedicOperateListener)
mBinding.swipeRefresh.initColors()
mBinding.swipeRefresh.setOnRefreshListener(this@WorkbenchFragment)
}
@@ -101,6 +204,7 @@ class WorkbenchFragment :
clImageTextConsultLay,
clAudioVideoConsultLay,
clAssistantConsultLay,
clAssistanceEmergencyLay,
layImageTextConsultStatus.llLeftLay,
layImageTextConsultStatus.llCenterLay,
layImageTextConsultStatus.llRightLay,
@@ -151,6 +255,36 @@ class WorkbenchFragment :
requireContext().startGroupChat(it.groupId, getString(R.string.title_assistant_consult), WorkBean(it.id, TUIUtils.WORK_TYPE_ASSISTANT))
})
}
clAssistanceEmergencyLay -> {
mViewModel.getParticipatedOrders { result ->
if (result.records.isNullOrEmpty()) {
ToastUtil.showShort(requireContext(), "暂无参与的应急就医工单")
} else if (result.records.size == 1) {
mViewModel.getActualGroupMemberList(result.records[0].sessionId) { groupMemberBeans ->
val setting = IMInputActionSettingUtils.createEmergencySetting()
requireContext().startGroupChat(
result.records[0].sessionId,
getString(R.string.title_paramedic_group),
WorkBean(result.records[0].orderId, TUIUtils.WORK_TYPE_EMERGENCY),
setting
)
}
} else {
showChatListDialog(result.records!!) { groupInfo ->
mViewModel.getActualGroupMemberList(groupInfo.sessionId) { groupMemberBeans ->
val setting = IMInputActionSettingUtils.createEmergencySetting()
requireContext().startGroupChat(
groupInfo.sessionId,
getString(R.string.title_paramedic_group),
WorkBean(groupInfo.orderId, TUIUtils.WORK_TYPE_EMERGENCY),
setting
)
}
}
}
}
}
toolbarLay.tvRight -> {
startMessageListActivity(requireContext())
}
@@ -169,6 +303,67 @@ class WorkbenchFragment :
}
}
}
/**
* 展示应急工单选择Dialog
*/
private fun showChatListDialog(
records: List<GroupInfo>,
onChatSelected: (GroupInfo) -> Unit
) {
context?.let { ctx ->
val dialog = Dialog(ctx)
val binding = DialogGroupInfoListBinding.inflate(layoutInflater)
val adapter = GroupInfoAdapter(records) { selectedPos ->
val hasSelection = selectedPos >= 0
binding.tvConfirm.isEnabled = hasSelection
binding.tvConfirm.alpha = if (hasSelection) 1.0f else 0.4f
}
binding.rvGroupList.layoutManager = LinearLayoutManager(ctx)
binding.rvGroupList.adapter = adapter.apply {
binding.tvConfirm.isEnabled = false
binding.tvConfirm.alpha = 0.4f
}
val spacing = ctx.resources.getDimensionPixelSize(R.dimen.dp_12)
binding.rvGroupList.addItemDecoration(object : RecyclerView.ItemDecoration() {
override fun getItemOffsets(
outRect: Rect,
view: View,
parent: RecyclerView,
state: RecyclerView.State
) {
outRect.bottom = spacing
}
})
binding.tvConfirm.setOnClickListener {
adapter.selectedItem?.let { item ->
onChatSelected(item)
dialog.dismiss()
}
}
binding.ivClose.setOnClickListener { dialog.dismiss() }
binding.flRoot.setOnClickListener { dialog.dismiss() }
dialog.setContentView(binding.root)
dialog.setCanceledOnTouchOutside(true)
dialog.setCancelable(true)
dialog.window?.let { window ->
window.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
window.decorView.setBackgroundColor(Color.TRANSPARENT)
window.setLayout(
WindowManager.LayoutParams.MATCH_PARENT,
WindowManager.LayoutParams.MATCH_PARENT
)
}
dialog.show()
}
}
@Subscribe(threadMode = ThreadMode.MAIN)
fun onEvent(event: WorkbenchEvent) {
onRefresh()
@@ -6,8 +6,11 @@ import com.btpj.lib_base.base.BaseViewModel
import com.btpj.lib_base.ext.handleRequest
import com.btpj.lib_base.ext.launch
import com.btpj.lib_base.utils.DateUtil
import com.xjjk.healthexpertclient.bean.PageBean
import com.xjjk.healthexpertclient.bean.emergency.GroupInfo
import com.xjjk.healthexpertclient.bean.workbench.WorkBenchConsultNumBean
import com.xjjk.healthexpertclient.data.repository.WorkbenchRepository
import com.tencent.qcloud.tuikit.tuichat.bean.GroupMemberBean
import com.tencent.imsdk.v2.V2TIMConversation
import com.tencent.imsdk.v2.V2TIMConversationListFilter
import com.tencent.imsdk.v2.V2TIMConversationListener
@@ -121,4 +124,34 @@ class WorkbenchViewModel: BaseViewModel() {
})
})
}
fun getParticipatedOrders(successCall: (PageBean<GroupInfo>) -> Unit){
launch(false, {
handleRequest(WorkbenchRepository.getParticipatedOrders(), successBlock = {
it.result?.let { result -> successCall.invoke(result) }
})
})
}
fun getActualGroupMemberList(sessionId: String, successCall: (List<GroupMemberBean>) -> Unit){
launch(false, {
handleRequest(WorkbenchRepository.getActualGroupMemberList(sessionId), successBlock = {
it.result?.let { result -> successCall.invoke(result) }
})
})
}
fun addGroupUser(groupId: String, memberList: List<String>, memberType: Int, successCall: (Boolean) -> Unit){
launch(false, {
handleRequest(WorkbenchRepository.addGroupUser(groupId, memberList, memberType), successBlock = {
successCall.invoke(it.success)
})
})
}
fun removeGroupUser(groupId: String, memberList: List<String>){
launch(false, {
handleRequest(WorkbenchRepository.removeGroupUser(groupId, memberList), successBlock = {})
})
}
}
@@ -16,6 +16,7 @@ import com.tencent.qcloud.tuicore.TUICore;
import com.tencent.qcloud.tuicore.interfaces.TUILoginConfig;
import com.tencent.qcloud.tuicore.util.TUIBuild;
import com.tencent.qcloud.tuikit.tuichat.bean.WorkBean;
import com.tencent.qcloud.tuikit.tuichat.classicui.setting.InputActionSetting;
import java.util.Locale;
@@ -23,6 +24,7 @@ import java.util.Locale;
public class TUIUtils {
public static final int WORK_TYPE_IMAGE_TEXT_CONSULT = 2;
public static final int WORK_TYPE_ASSISTANT = 3;
public static final int WORK_TYPE_EMERGENCY = 4;
public static final String TAG = TUIUtils.class.getSimpleName();
public static void startActivity(String activityName, Bundle param) {
@@ -30,6 +32,11 @@ public class TUIUtils {
}
public static void startChat(String chatId, String chatName, int chatType, WorkBean workBean) {
startChat(chatId, chatName, chatType, workBean, null);
}
public static void startChat(String chatId, String chatName, int chatType, WorkBean workBean,
InputActionSetting inputActionSetting) {
Bundle bundle = new Bundle();
bundle.putString(TUIConstants.TUIChat.CHAT_ID, chatId);
if(!TextUtils.isEmpty(chatId)){
@@ -38,6 +45,9 @@ public class TUIUtils {
}
bundle.putInt(TUIConstants.TUIChat.CHAT_TYPE, chatType);
bundle.putSerializable(TUIConstants.TUIChat.WORK_BEAN, workBean);
if (inputActionSetting != null) {
bundle.putSerializable("inputActionSetting", inputActionSetting);
}
if (chatType == V2TIMConversation.V2TIM_C2C) {
TUICore.startActivity(TUIConstants.TUIChat.C2C_CHAT_ACTIVITY_NAME, bundle);
} else if (chatType == V2TIMConversation.V2TIM_GROUP) {
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

@@ -323,7 +323,7 @@
app:layout_constraintTop_toBottomOf="@+id/cl_audio_video_consult_lay"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintBottom_toTopOf="@+id/cl_assistance_emergency_lay"
android:background="@drawable/rectangle_round_corner10_white"
android:elevation="@dimen/dp_3"
android:layout_marginHorizontal="@dimen/dp_15"
@@ -394,6 +394,58 @@
app:layout_constraintTop_toBottomOf="@+id/tv_assistant_consult_title"
android:text="@string/doctor_homepage_assistant_consult_explain" />
</androidx.constraintlayout.widget.ConstraintLayout>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/cl_assistance_emergency_lay"
android:layout_width="@dimen/dp_0"
android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="@+id/cl_assistant_consult_lay"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:background="@drawable/rectangle_round_corner10_white"
android:elevation="@dimen/dp_3"
android:layout_marginHorizontal="@dimen/dp_15"
android:layout_marginVertical="@dimen/dp_5"
android:paddingVertical="@dimen/dp_24">
<ImageView
android:id="@+id/iv_assistant_emergency"
android:layout_width="@dimen/dp_0"
android:layout_height="@dimen/dp_0"
app:layout_constraintDimensionRatio="1:1"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintWidth_percent="0.116"
android:layout_marginLeft="@dimen/dp_20"
android:src='@drawable/icon_consult_emergency' />
<TextView
android:id="@+id/tv_assistant_emergency_title"
android:layout_width="@dimen/dp_0"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/dp_12"
android:layout_marginEnd="@dimen/dp_12"
android:includeFontPadding="false"
android:textColor="@color/text_black_33"
android:textSize="@dimen/txt14"
app:layout_constraintTop_toTopOf="@+id/iv_assistant_emergency"
app:layout_constraintLeft_toRightOf="@+id/iv_assistant_emergency"
app:layout_constraintRight_toRightOf="parent"
android:text="员工应急求助" />
<TextView
android:id="@+id/tv_assistant_emergency_explain"
android:layout_width="@dimen/dp_0"
android:layout_height="wrap_content"
android:paddingTop="@dimen/dp_6"
android:includeFontPadding="false"
android:textColor="@color/text_black_66"
android:textSize="@dimen/txt11"
app:layout_constraintLeft_toLeftOf="@id/tv_assistant_emergency_title"
app:layout_constraintRight_toRightOf="@+id/tv_assistant_emergency_title"
app:layout_constraintTop_toBottomOf="@+id/tv_assistant_emergency_title"
android:text="专家视频连线,守护员工健康" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
@@ -0,0 +1,5 @@
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="#1A2EB8B2" />
<corners android:radius="4dp" />
</shape>
@@ -0,0 +1,5 @@
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="#1A77859E" />
<corners android:radius="4dp" />
</shape>
@@ -0,0 +1,4 @@
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<solid android:color="@color/emergency_close_bg" />
</shape>
@@ -0,0 +1,5 @@
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="@color/emergency_primary" />
<corners android:radius="12dp" />
</shape>
@@ -0,0 +1,5 @@
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="@android:color/white" />
<corners android:radius="12dp" />
</shape>
@@ -0,0 +1,8 @@
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="@color/emergency_card_selected_bg" />
<stroke
android:width="1dp"
android:color="@color/emergency_card_selected_border" />
<corners android:radius="8dp" />
</shape>
@@ -0,0 +1,8 @@
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="@android:color/white" />
<stroke
android:width="1dp"
android:color="@color/emergency_card_unselected_border" />
<corners android:radius="8dp" />
</shape>
@@ -0,0 +1,19 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="16dp"
android:height="16dp"
android:viewportWidth="16"
android:viewportHeight="16">
<path
android:fillColor="@android:color/transparent"
android:pathData="M8,14C11.31,14 14,11.31 14,8C14,4.69 11.31,2 8,2C4.69,2 2,4.69 2,8C2,11.31 4.69,14 8,14Z"
android:strokeWidth="1.2"
android:strokeColor="@color/emergency_primary"
android:strokeLineCap="round" />
<path
android:fillColor="@android:color/transparent"
android:pathData="M8,5L8,8.5L10.5,10"
android:strokeWidth="1.2"
android:strokeColor="@color/emergency_primary"
android:strokeLineCap="round"
android:strokeLineJoin="round" />
</vector>
@@ -0,0 +1,12 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="14dp"
android:height="14dp"
android:viewportWidth="14"
android:viewportHeight="14">
<path
android:fillColor="@android:color/transparent"
android:pathData="M1,1L13,13M13,1L1,13"
android:strokeWidth="1.5"
android:strokeColor="@color/emergency_text_gray"
android:strokeLineCap="round" />
</vector>
@@ -0,0 +1,15 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="16dp"
android:height="16dp"
android:viewportWidth="16"
android:viewportHeight="16">
<path
android:fillColor="@android:color/transparent"
android:pathData="M8,14C8,14 14,9.5 14,6C14,2.69 11.31,0 8,0C4.69,0 2,2.69 2,6C2,9.5 8,14 8,14Z"
android:strokeWidth="1"
android:strokeColor="@color/emergency_primary"
android:strokeLineJoin="round" />
<path
android:fillColor="@color/emergency_primary"
android:pathData="M8,8C9.1,8 10,7.1 10,6C10,4.9 9.1,4 8,4C6.9,4 6,4.9 6,6C6,7.1 6.9,8 8,8Z" />
</vector>
@@ -0,0 +1,16 @@
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<shape android:shape="oval">
<solid android:color="@color/emergency_primary" />
</shape>
</item>
<item
android:top="5dp"
android:bottom="5dp"
android:left="5dp"
android:right="5dp">
<shape android:shape="oval">
<solid android:color="@android:color/white" />
</shape>
</item>
</layer-list>
@@ -0,0 +1,7 @@
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<stroke
android:width="1dp"
android:color="@color/emergency_card_unselected_border" />
<solid android:color="@android:color/white" />
</shape>
@@ -0,0 +1,96 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<!-- 全屏半透明背景层,居中显示白色卡片 -->
<FrameLayout
android:id="@+id/fl_root"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clickable="true"
android:focusable="true"
android:background="@android:color/transparent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="20dp"
android:layout_gravity="center"
android:orientation="vertical"
android:background="@drawable/bg_emergency_dialog_card"
android:paddingBottom="16dp">
<!-- 标题栏 -->
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingStart="16dp"
android:paddingEnd="16dp"
android:paddingTop="16dp"
android:paddingBottom="12dp">
<TextView
android:id="@+id/tv_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="请选择正在应急的工单"
android:textColor="@color/emergency_text_dark"
android:textSize="18sp"
android:textStyle="bold" />
<ImageView
android:id="@+id/iv_close"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_gravity="end|center_vertical"
android:src="@drawable/ic_emergency_close"
android:background="@drawable/bg_emergency_close_btn"
android:padding="5dp"
android:contentDescription="关闭" />
</FrameLayout>
<!-- 顶部分割线 -->
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="@color/emergency_divider" />
<!-- 工单列表 -->
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv_group_list"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingHorizontal="16dp"
android:paddingTop="12dp"
android:paddingBottom="4dp"
android:maxHeight="420dp"
android:clipToPadding="false"
android:scrollbars="none"
tools:listitem="@layout/item_group_info" />
<!-- 底部分割线 -->
<View
android:id="@+id/view_bottom_divider"
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="@color/emergency_divider" />
<!-- 确认选择按钮 -->
<TextView
android:id="@+id/tv_confirm"
android:layout_width="match_parent"
android:layout_height="44dp"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:layout_marginTop="16dp"
android:gravity="center"
android:text="确认选择"
android:textColor="@android:color/white"
android:textSize="18sp"
android:textStyle="bold"
android:background="@drawable/bg_emergency_confirm_btn" />
</LinearLayout>
</FrameLayout>
</layout>
+122
View File
@@ -0,0 +1,122 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<LinearLayout
android:id="@+id/root_item"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingStart="16dp"
android:paddingEnd="16dp"
android:paddingTop="16dp"
android:paddingBottom="14dp"
android:background="@drawable/bg_emergency_item_unselected">
<!-- 顶部:姓名 + 选择圆圈 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical">
<TextView
android:id="@+id/tv_user_name"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:textColor="@color/emergency_text_dark"
android:textSize="15sp"
android:textStyle="bold"
android:ellipsize="end"
android:maxLines="1"
tools:text="艾尼瓦尔·吐尔逊" />
<ImageView
android:id="@+id/iv_radio"
android:layout_width="18dp"
android:layout_height="18dp"
android:layout_marginStart="12dp"
android:src="@drawable/ic_emergency_radio_unselected" />
</LinearLayout>
<!-- 部门标签 -->
<TextView
android:id="@+id/tv_dept_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="6dp"
android:paddingHorizontal="8dp"
android:paddingVertical="2dp"
android:textColor="@color/emergency_text_gray"
android:textSize="11sp"
android:textStyle="bold"
android:ellipsize="end"
android:maxLines="1"
android:background="@drawable/bg_dept_badge_unselected"
tools:text="员工健康事务部" />
<!-- 内部分割线 -->
<View
android:id="@+id/view_divider"
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginTop="10dp"
android:background="@color/emergency_divider" />
<!-- 地址行 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:orientation="horizontal"
android:gravity="center_vertical">
<ImageView
android:id="@+id/iv_location"
android:layout_width="16dp"
android:layout_height="16dp"
android:src="@drawable/ic_emergency_location" />
<TextView
android:id="@+id/tv_org_name"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_marginStart="8dp"
android:textColor="@color/emergency_text_body"
android:textSize="13sp"
android:ellipsize="end"
android:maxLines="2"
tools:text="监管中心(石油天然气克拉玛依工程质量监督站)" />
</LinearLayout>
<!-- 时间行 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:orientation="horizontal"
android:gravity="center_vertical">
<ImageView
android:id="@+id/iv_clock"
android:layout_width="16dp"
android:layout_height="16dp"
android:src="@drawable/ic_emergency_clock" />
<TextView
android:id="@+id/tv_init_time"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_marginStart="8dp"
android:textColor="@color/emergency_text_gray"
android:textSize="13sp"
android:ellipsize="end"
android:maxLines="1"
tools:text="求助时间:2026-07-22 16:24:32" />
</LinearLayout>
</LinearLayout>
</layout>
+12
View File
@@ -111,6 +111,7 @@
<color name="light_blue_A400">#FF00B0FF</color>
<color name="black_overlay">#66000000</color>
<color name="theme_color">#21BEBD</color>
<color name="theme_color_alpha_10">#1A21BEBD</color>
<color name="theme_bg">#EFEFEF</color>
<color name="line_grey_color">#E0E0E0</color>
@@ -122,4 +123,15 @@
<color name="text_dark_green">#16C716</color>
<color name="text_green_BD">#21BEBD</color>
<!-- 应急工单弹窗颜色 -->
<color name="emergency_card_selected_bg">#0F2EB8B2</color>
<color name="emergency_card_selected_border">#802EB8B2</color>
<color name="emergency_card_unselected_border">#E6E6EA</color>
<color name="emergency_divider">#EAECFA</color>
<color name="emergency_text_dark">#252535</color>
<color name="emergency_text_body">#394356</color>
<color name="emergency_text_gray">#77859E</color>
<color name="emergency_primary">#2EB8B2</color>
<color name="emergency_close_bg">#F1F5F9</color>
</resources>
+1
View File
@@ -28,6 +28,7 @@
<string name="title_good_at_disease">擅长病种</string>
<string name="title_check_up_detail">体检详情</string>
<string name="title_assistant_consult">全科医生</string>
<string name="title_paramedic_group">应急求助</string>
<string name="title_view_location">位置</string>
<string name="title_message_list">消息</string>
<string name="title_select_location">选择位置</string>