18034727885
2026-06-23 c87de07e5c055cb774da32d6deb3be310a42d64b
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
package com.xw.yunwei.ui.task.create
 
import android.app.Application
import android.view.View
import androidx.lifecycle.MutableLiveData
import com.blankj.utilcode.util.GsonUtils
import com.xw.common.base.BaseViewModel
import com.xw.common.base.FileSelectBean
import com.xw.common.base.ImageSelectAdapter
import com.xw.common.utils.ToastUtilCommon
import com.xw.yunwei.R
import com.xw.yunwei.model.CacheAccidentModel
import com.xw.yunwei.model.CarModel
import com.xw.yunwei.model.Category
import com.xw.yunwei.model.FinishPage
import com.xw.yunwei.model.Item
import com.xw.yunwei.model.SwitchTabMain
import com.xw.yunwei.model.SwitchTabTask
import com.xw.yunwei.model.VehicleLocationInfo
import com.xw.yunwei.net.CommonRepository
import com.xw.yunwei.net.TaskRepository
import com.xw.yunwei.net.WorkRepository
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.MultipartBody
import okhttp3.RequestBody.Companion.asRequestBody
import org.greenrobot.eventbus.EventBus
import org.koin.core.component.inject
import java.io.File
 
/**
 * 事故上报页面 ViewModel
 *
 * 负责事故工单创建的业务逻辑与数据管理,包括:
 * - 车辆信息、定位地址、事故类型、任务等级、任务描述的 LiveData 维护
 * - 图片选择适配器管理
 * - 事故类型列表的获取与缓存
 * - 本地图片递归上传到服务器
 * - 提交事故工单并通知首页/任务页切换 Tab
 * - 草稿数据组装与恢复
 */
class CreateTaskVM(myApplication: Application) : BaseViewModel(myApplication) {
 
    val workRepository: WorkRepository by inject()
    val taskRepository: TaskRepository by inject()
    val commonRepository: CommonRepository by inject()
 
    /** 事故类型分类列表(大类 + 子类) */
    var categories = mutableListOf<Category>()
 
    /** 当前选中的车辆信息 */
    var selectCar: MutableLiveData<CarModel> = MutableLiveData()
    /** 当前定位信息,null 表示未获取到定位 */
    //    var location: MutableLiveData<AMapLocation?> = MutableLiveData(null)
    /** 根据定位信息转换的地址文案,未定位时显示"定位中..." */
//    val address = location.map {
//        it?.address ?: "定位中..."
//    }
    /** 任务紧急等级:0 = P0(最紧急), 1 = P1 , 2 = P2 , 3 = P3(最不紧急),默认 P3 */
    val taskLevel: MutableLiveData<Int> = MutableLiveData(2)
 
    /** 任务描述文本 */
    val taskDesc = MutableLiveData("")
 
    //车辆实时位置信息
    val carLocationInfo = MutableLiveData<VehicleLocationInfo?>()
 
    /** 页面点击事件统一处理:选择车辆、等级切换、提交 */
    var onClick = View.OnClickListener {
        when (it.id) {
            R.id.imgSelectCar -> {
//                startActivityForResult(
//                    SelectCarActivity::class.java,
//                    110,
//                    bundleOf("car" to selectCar.value)
//                )
                onBackPressed()
            }
 
            R.id.level0 -> taskLevel.value = 0
            R.id.level1 -> taskLevel.value = 1
            R.id.level2 -> taskLevel.value = 2
            R.id.submit -> submit()
        }
    }
 
    /** 图片选择适配器 */
    lateinit var adapter: ImageSelectAdapter
 
    /** 图片最大可上传数量 */
    val maxSize = 6
 
    /**
     * 提交事故上报信息
     *
     * 校验逻辑:
     * 1. 必须选择车辆
     * 2. 必须获取到定位
     * 3. 必须选择事故类型
     * 4. 必须选择任务等级(0~3)
     * 5. 必须填写任务描述
     * 6. 必须上传相关照片
     *
     * 校验通过后先递归上传本地图片,再调用 [TaskRepository.saveVehicleAccident] 提交。
     * 提交成功后通过 EventBus 通知首页切换到任务 Tab 并关闭当前页面。
     */
    fun submit() {
        if (selectCar.value == null) {
            ToastUtilCommon.showWarn("请选择车辆")
            return
        }
        if (carLocationInfo.value == null) {
            ToastUtilCommon.showWarn("未获取到定位")
            return
        }
//        if (getSelectType().isEmpty()) {
//            ToastUtilCommon.showWarn("请选择事故类型")
//            return
//        }
        if (taskLevel.value !in arrayOf(0, 1, 2, 3)) {
            ToastUtilCommon.showWarn("请选择任务等级")
            return
        }
//        if (taskDesc.value.isNullOrEmpty()) {
//            ToastUtilCommon.showWarn("请填写任务描述")
//            return
//        }
//        if (adapter.data.isEmpty()) {
//            ToastUtilCommon.showWarn("请上传相关照片")
//            return
//        }
 
        showDialog("上传中...")
        uploadFile(adapter.data) {
            dismissDialog()
            launchRequest("提交中") {
                val map = mutableMapOf<String, Any?>()
                map["id"] = selectCar.value?.id
                map["taskDescription"] = taskDesc.value
                map["taskImage"] = "${adapter.data.map { it.url }}"
                map["taskTypeId"] = getSelectType()
                map["taskUrgencyLevel"] = taskLevel.value
                taskRepository.saveVehicleAccident(map)
                ToastUtilCommon.showSuc("提交成功")
                EventBus.getDefault().post(SwitchTabMain(1))
                EventBus.getDefault().post(SwitchTabTask(0))
//                delay(1000)
                EventBus.getDefault().post(FinishPage())
                finish()
            }
        }
 
    }
 
    /**
     * 递归上传本地图片文件到服务器
     *
     * 遍历 [files] 列表,找到第一个 [FileSelectBean.url] 为空的本地文件进行上传,
     * 上传成功后将服务器返回的 URL 写入 [FileSelectBean.url],再继续递归处理下一个。
     *
     * @param files 待上传的文件列表
     * @param succ 全部上传完成后的回调
     */
    private fun uploadFile(files: List<FileSelectBean>, succ: () -> Unit) {
        val result = files.find { it.url.isEmpty() }
        if (result == null) {
            succ()
        } else {
            launchRequest(showLoading = false, error = { _, errMsg ->
                ToastUtilCommon.showError(errMsg)
                dismissDialog()
            }) {
                val file = File(result.localPath)
                val fileRequestBody = file.asRequestBody("image/jpg".toMediaType())
                val mBuilder = MultipartBody.Builder()
                mBuilder.addFormDataPart("file", file.name, fileRequestBody)
                mBuilder.setType(MultipartBody.FORM)
                val data = commonRepository.uploadFile(mBuilder.build())
                result.url = GsonUtils.toJson(data)
                uploadFile(files, succ)
            }
        }
    }
 
    /**
     * 获取事故类型列表
     *
     * 若 [categories] 已存在则直接回调,否则通过网络请求拉取并转换数据结构。
     *
     * @param block 数据就绪后的回调(在主线程执行)
     */
    fun getTypeList(block: () -> Unit) {
        if (categories.isNotEmpty()) {
            block()
        } else {
            launchRequest {
                taskRepository.getTypeList()?.forEachIndexed { index, type ->
                    val items = mutableListOf<Item>()
                    type.child?.forEach {
                        items.add(Item(it.id ?: 0L, it.errorName ?: ""))
                    }
                    categories.add(Category(index, type.errorName ?: "", items))
                    runOnMainThread { block() }
                }
            }
        }
    }
 
    /**
     * 获取当前已选中的事故类型 ID 字符串
     *
     * 遍历所有分类的子项,过滤出 [Item.isSelected] 为 true 的项,
     * 将其 ID 以逗号分隔拼接成字符串返回。
     *
     * @return 已选中事故类型 ID 的逗号分隔字符串,未选择时返回空串
     */
    fun getSelectType(): String {
        val items = mutableListOf<Item>()
        categories.forEach { items.addAll(it.items) }
        return items.filter { it.isSelected }.map { it.id }.joinToString()
    }
 
    /**
     * 获取当前已选中的事故类型 name 列表
     *
     */
    fun getSelectTypeName(): List<String> {
        val items = mutableListOf<Item>()
        categories.forEach { items.addAll(it.items) }
        return items.filter { it.isSelected }.map { it.name }
    }
 
    /**
     * 获取当前数据填写情况,用于判断是否需要保存草稿
     *
     * 当事故类型、任务等级、任务描述、相关照片均未填写时返回 null,
     * 否则返回包含当前页面数据的 [CacheAccidentModel]。
     *
     * @return 当前页面缓存数据,或 null 表示未填写任何内容
     */
    /*获取数据填写情况*/
    fun getDataWrite(): CacheAccidentModel? {
        // 事故类型/任务等级/任务描述/相关照片 暂时定义为这几个填了就算填了事故
        return if (getSelectType().isEmpty() && taskLevel.value !in arrayOf(0, 1, 2, 3)
            && taskDesc.value.isNullOrEmpty() && adapter.data.isEmpty()
        ) {
            null
        } else {
            CacheAccidentModel(
                selectCar.value,
                categories,
                taskLevel.value!!,
                taskDesc.value!!,
                adapter.data
            )
        }
    }
 
    //获取车辆实时位置信息
    fun getVehicleLocation() {
        selectCar.value?.also { it ->
            launchRequest {
                val vehicleLocation = workRepository.getVehicleLocation(it.id.toString())
                carLocationInfo.postValue(vehicleLocation)
            }
        }
    }
}