bjb 3 месяцев назад
Родитель
Сommit
da9104e5cf

+ 2 - 2
app/src/main/java/com/iscs/bozzys/api/ApiRequest.kt

@@ -297,8 +297,8 @@ object ApiRequest {
      * @param file  上传的文件
      * @param type  文件类型
      */
-    suspend fun uploadFile(file: File, type: String = "image"): Result<Response<String>> {
-        val part = MultipartBody.Part.createFormData("file", file.name, file.asRequestBody("$type/*".toMediaType()))
+    suspend fun uploadFile(file: File, type: String = "image/*"): Result<Response<String>> {
+        val part = MultipartBody.Part.createFormData("file", file.name, file.asRequestBody(type.toMediaType()))
         return requestApi { api.uploadFile(part) }
     }
 

+ 10 - 1
app/src/main/java/com/iscs/bozzys/api/Node.kt

@@ -41,6 +41,13 @@ data class Node(
     val appTemplateCode: String? = null,        // app消息推送
 ) {
 
+    /**
+     * 校验是否可以通过手机进行操作
+     */
+    fun checkCanOptionByPhone(): Boolean {
+        return !(listOf("isolation", "releaseIsolation", "returnLock").contains(type) && !listOf("0", "2").contains(isolationType))
+    }
+
     /**
      * 校验请求参数
      *
@@ -465,7 +472,9 @@ data class FormField(
     val gridColumns: Int = 0,
     val enabled: Boolean = true,
     val multiSelect: Boolean = false,
-    val children: List<FormField> = listOf()
+    val children: List<FormField> = listOf(),
+    val maxCount: Int? = null,          // 文件上传的最大限制个数
+    val uploadType: String? = "file"    // 上传文件类型
 )
 
 

+ 12 - 0
app/src/main/java/com/iscs/bozzys/api/UploadFile.kt

@@ -0,0 +1,12 @@
+package com.iscs.bozzys.api
+
+import java.io.File
+
+/**
+ * 上传文件
+ *
+ * @param url   已经上传的文件地址
+ * @param state 上传的状态 0-上传中 1-上传成功 2-上传失败
+ * @param file  上传的文件,如果已经上传,这里应该是空的
+ */
+data class UploadFile(val url: String = "", val state: Int = 0, val file: File? = null, val type: String = "")

+ 176 - 44
app/src/main/java/com/iscs/bozzys/ui/pages/compose/FormCompose.kt

@@ -6,6 +6,7 @@ import android.os.Build
 import android.util.Log
 import androidx.activity.compose.rememberLauncherForActivityResult
 import androidx.activity.result.contract.ActivityResultContracts
+import androidx.compose.foundation.background
 import androidx.compose.foundation.border
 import androidx.compose.foundation.clickable
 import androidx.compose.foundation.layout.Arrangement
@@ -14,9 +15,13 @@ import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.ExperimentalLayoutApi
 import androidx.compose.foundation.layout.FlowRow
 import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.aspectRatio
+import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.height
 import androidx.compose.foundation.layout.heightIn
+import androidx.compose.foundation.layout.offset
 import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.layout.size
 import androidx.compose.foundation.layout.width
@@ -35,6 +40,7 @@ import androidx.compose.material3.RadioButton
 import androidx.compose.material3.Text
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.collectAsState
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.key
 import androidx.compose.runtime.mutableStateListOf
@@ -44,10 +50,16 @@ import androidx.compose.runtime.setValue
 import androidx.compose.runtime.snapshots.SnapshotStateList
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.alpha
 import androidx.compose.ui.draw.clip
+import androidx.compose.ui.draw.drawBehind
 import androidx.compose.ui.draw.rotate
+import androidx.compose.ui.geometry.CornerRadius
 import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.PathEffect
 import androidx.compose.ui.graphics.SolidColor
+import androidx.compose.ui.graphics.drawscope.Stroke
+import androidx.compose.ui.layout.ContentScale
 import androidx.compose.ui.layout.onSizeChanged
 import androidx.compose.ui.platform.LocalContext
 import androidx.compose.ui.platform.LocalDensity
@@ -57,14 +69,22 @@ import androidx.compose.ui.text.input.KeyboardType
 import androidx.compose.ui.unit.DpOffset
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.unit.sp
+import androidx.lifecycle.viewmodel.compose.viewModel
+import coil.compose.AsyncImage
+import coil.compose.rememberAsyncImagePainter
 import com.iscs.bozzys.R
 import com.iscs.bozzys.api.FormField
 import com.iscs.bozzys.api.FormOption
 import com.iscs.bozzys.api.Job
+import com.iscs.bozzys.api.UploadFile
+import com.iscs.bozzys.ui.common.PageBase
+import com.iscs.bozzys.ui.pages.vm.VMFormUploadFile
 import com.iscs.bozzys.ui.theme.Main
 import com.iscs.bozzys.ui.theme.Text
 import com.iscs.bozzys.utils.DateUtil.dateToTimestamp
 import com.iscs.bozzys.utils.DateUtil.format
+import com.iscs.bozzys.utils.SystemUtil
+import com.iscs.bozzys.utils.SystemUtil.uriToFile
 import com.loper7.date_time_picker.DateTimeConfig
 import com.loper7.date_time_picker.dialog.CardDatePickerDialog
 import kotlinx.serialization.json.Json
@@ -177,8 +197,8 @@ fun FormBox(forms: List<FormField>, onValueChange: (FormField) -> Unit, modifier
                         required = form.required,
                         enable = form.enabled && enabled
                     )
-                    // 图片上传
-                    "image" -> FormImage(
+                    // 上传文件
+                    "upload" -> FormUpload(
                         form.label,
                         form.value,
                         form.options,
@@ -187,7 +207,9 @@ fun FormBox(forms: List<FormField>, onValueChange: (FormField) -> Unit, modifier
                             onValueChange(form)
                         },
                         required = form.required,
-                        enable = form.enabled && enabled
+                        enable = form.enabled && enabled,
+                        maxCount = form.maxCount ?: 1,
+                        uploadType = form.uploadType ?: "file"
                     )
                 }
             }
@@ -414,7 +436,7 @@ fun FormSelect(
             Text(
                 values.data2String()
                     .ifEmpty { (placeholder.getOrNull(0) ?: "").ifEmpty { "请选择$label" } },
-                color = Text.copy(alpha = if (enable) 1f else 0.6f),
+                color = Text.copy(alpha = if (enable && values.data2String().isNotEmpty()) 1f else 0.6f),
                 fontSize = 14.sp,
                 lineHeight = 18.sp,
                 modifier = Modifier.weight(1f),
@@ -628,38 +650,55 @@ fun FormCheckbox(
 }
 
 /**
- * 照片上传组件
+ * 文件上传组件
  *
  * @param label             标题
  * @param value             当前选中
  * @param options           可选列表
  * @param onSelectChange    当前选中
+ * @param maxCount          上传最大限制
+ * @param uploadType        上传类型
  */
 @OptIn(ExperimentalLayoutApi::class)
 @Composable
-fun FormImage(
+fun FormUpload(
     label: String,
     value: List<String>,
     options: List<FormOption>,
     onSelectChange: (List<String>) -> Unit,
     required: Boolean = false,
     enable: Boolean = true,
+    maxCount: Int = 1,
+    uploadType: String = "file",
+    vm: VMFormUploadFile = viewModel()
 ) {
     val ctx = LocalContext.current
+    val state by vm.state.collectAsState()
+    val type = if (uploadType == "image") "image" else "*"
+    LaunchedEffect(Unit) {
+        if (ctx is PageBase) {
+            ctx.apply {
+                vm.toast.initToast()
+                vm.loading.initLoading()
+            }
+        }
+    }
     // 启动页面
     val activityLauncher = rememberLauncherForActivityResult(contract = ActivityResultContracts.GetContent()) { uri ->
-
+        if (uri != null) {
+            val file = ctx.uriToFile(uri)
+            val fileType = ctx.contentResolver.getType(uri) ?: ""
+            vm.uploadFile(UploadFile(file = file, type = fileType)) { onSelectChange(it) }
+        }
     }
     // 权限请求
     val permissionLauncher = rememberLauncherForActivityResult(contract = ActivityResultContracts.RequestPermission()) {
         if (it) {
-            activityLauncher.launch("image/*")
+            activityLauncher.launch("${type}/*")
         }
     }
-    val values = remember { mutableStateListOf<String>() }
     LaunchedEffect(Unit) {
-        values.clear()
-        values.addAll(value)
+        vm.init(value)
     }
     Column(
         Modifier
@@ -684,58 +723,151 @@ fun FormImage(
                 modifier = Modifier.padding(start = 3.dp)
             )
         }
-        FlowRow(
-            Modifier
+
+        Column(
+            modifier = Modifier
                 .fillMaxWidth()
                 .heightIn(max = 120.dp)
+                .border(1.dp, shape = RoundedCornerShape(6.dp), color = Color(0xFFE5E6EB))
+                .padding(vertical = 10.dp)
+                .verticalScroll(rememberScrollState()),
+            verticalArrangement = Arrangement.Top,
+            horizontalAlignment = Alignment.CenterHorizontally
         ) {
-            if (options.isEmpty()) {
-                // 没有默认数据时,显示点击上传
-                Column(
+            if (state.files.isEmpty()) Column(
+                horizontalAlignment = Alignment.CenterHorizontally, modifier = Modifier
+                    .fillMaxSize()
+                    .padding(horizontal = 10.dp)
+                    .clip(RoundedCornerShape(6.dp))
+                    .clickable {
+                        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
+                            if (ctx.checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
+                                permissionLauncher.launch(Manifest.permission.READ_EXTERNAL_STORAGE)
+                            } else {
+                                activityLauncher.launch("${type}/*")
+                            }
+                        } else {
+                            activityLauncher.launch("${type}/*")
+                        }
+                    }
+                    .padding(vertical = 10.dp)) {
+                Icon(
+                    painter = painterResource(R.drawable.upload),
+                    contentDescription = null,
+                    modifier = Modifier
+                        .size(50.dp),
+                    tint = Text.copy(alpha = if (enable) 0.3f else 0.1f)
+                )
+                Text(if (enable) "点击上传" else "暂无上传内容", fontSize = 14.sp, color = Text.copy(alpha = if (enable) 0.3f else 0.1f))
+            }
+            FlowRow(
+                modifier = Modifier
+                    .padding(horizontal = 10.dp)
+                    .fillMaxWidth()
+            ) {
+                // 显示添加的文件列表
+                state.files.forEach { item ->
+                    Box(
+                        modifier = Modifier
+                            .fillMaxWidth(1 / 4f)
+                            .aspectRatio(1f)
+                            .padding(5.dp),
+                        contentAlignment = Alignment.Center
+                    ) {
+                        val src = SystemUtil.getFileTypeIconBySuffix(item.url.ifEmpty { item.file?.name ?: "" })
+                        AsyncImage(
+                            model = item.url.ifEmpty { item.file },
+                            placeholder = rememberAsyncImagePainter(model = item.file),
+                            contentDescription = null,
+                            modifier = Modifier
+                                .fillMaxSize()
+                                .clip(RoundedCornerShape(10))
+                                .alpha(if (enable) 1f else 0.5f),
+                            contentScale = ContentScale.Crop
+                        )
+                        if (!listOf("jpg", "png").contains(src.second)) {
+                            Box(
+                                Modifier
+                                    .fillMaxSize()
+                                    .background(MaterialTheme.colorScheme.primary.copy(alpha = 0.3f), shape = RoundedCornerShape(10.dp))
+                                    .alpha(if (enable) 1f else 0.5f)
+                            ) {
+                                Spacer(
+                                    modifier = Modifier
+                                        .padding(20.dp)
+                                        .fillMaxSize()
+                                        .background(Color.White)
+                                )
+                                Icon(
+                                    painter = painterResource(src.first),
+                                    contentDescription = null,
+                                    modifier = Modifier
+                                        .padding(15.dp)
+                                        .fillMaxSize(),
+                                    tint = MaterialTheme.colorScheme.primary
+                                )
+                            }
+                        }
+                        // 右上角删除操作
+                        if (enable) Icon(
+                            painterResource(R.drawable.delete_all),
+                            contentDescription = null,
+                            modifier = Modifier
+                                .align(Alignment.TopEnd)
+                                .offset(x = 6.dp, y = (-3).dp)
+                                .size(20.dp)
+                                .background(Color.Black.copy(alpha = 0.6f), shape = RoundedCornerShape(6.dp))
+                                .clickable { vm.delete(item) }
+                                .padding(3.dp),
+                            tint = MaterialTheme.colorScheme.primary
+                        )
+                    }
+                }
+                // 上传按钮
+                if (state.files.isNotEmpty() && enable) Box(
                     modifier = Modifier
-                        .fillMaxWidth()
-                        .height(120.dp)
-                        .border(1.dp, shape = RoundedCornerShape(6.dp), color = Color(0xFFE5E6EB))
-                        .clip(RoundedCornerShape(6.dp))
+                        .fillMaxWidth(1 / 4f)
+                        .aspectRatio(1f)
+                        .padding(5.dp)
+                        .clip(RoundedCornerShape(10.dp))
                         .clickable {
+                            if (!vm.checkCanSelect(maxCount)) return@clickable
                             if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
                                 if (ctx.checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
                                     permissionLauncher.launch(Manifest.permission.READ_EXTERNAL_STORAGE)
                                 } else {
-                                    activityLauncher.launch("image/*")
+                                    activityLauncher.launch("${type}/*")
                                 }
                             } else {
-                                activityLauncher.launch("image/*")
+                                activityLauncher.launch("${type}/*")
                             }
                         },
-                    verticalArrangement = Arrangement.Center,
-                    horizontalAlignment = Alignment.CenterHorizontally
+                    contentAlignment = Alignment.Center
                 ) {
                     Icon(
                         painter = painterResource(R.drawable.upload),
                         contentDescription = null,
                         modifier = Modifier
-                            .padding(bottom = 5.dp)
-                            .size(50.dp),
-                        tint = Text.copy(alpha = 0.3f)
-                    )
-                    Text("点击上传", fontSize = 14.sp, color = Text.copy(alpha = 0.3f))
-                }
-            }
-            options.forEach { item ->
-                Row(
-                    modifier = Modifier
-                        .clip(RoundedCornerShape(12.dp))
-                        .clickable(onClick = {
-                            if (values.contains(item.value))
-                                values.remove(item.value)
-                            else values.add(item.value)
-                            onSelectChange(values)
-                        }, enabled = enable)
-                        .padding(horizontal = 10.dp, vertical = 5.dp),
-                    verticalAlignment = Alignment.CenterVertically
-                ) {
+                            .padding(2.dp)
+                            .fillMaxSize()
+                            .drawBehind {
+                                val strokeWidth = 2.dp.toPx()
+                                val dashWidth = 6.dp.toPx()
+                                val dashGap = 4.dp.toPx()
+                                val radius = 10.dp.toPx()
 
+                                drawRoundRect(
+                                    color = Text.copy(alpha = 0.5f),
+                                    cornerRadius = CornerRadius(radius),
+                                    style = Stroke(
+                                        width = strokeWidth,
+                                        pathEffect = PathEffect.dashPathEffect(floatArrayOf(dashWidth, dashGap))
+                                    )
+                                )
+                            }
+                            .padding(12.dp),
+                        tint = Text.copy(alpha = 0.5f)
+                    )
                 }
             }
         }

+ 6 - 10
app/src/main/java/com/iscs/bozzys/ui/pages/detail/task/PageDetailTask.kt

@@ -87,7 +87,7 @@ class PageDetailTask : PageBase() {
         }
         val state by vm.state.collectAsState()
         Column(Modifier.fillMaxSize()) {
-            Title(pv, task.currentNodeName ?: "")
+            Title(pv, task.name)
             Column(
                 modifier = Modifier
                     .weight(1f)
@@ -96,13 +96,9 @@ class PageDetailTask : PageBase() {
             ) {
                 TaskInfo(task)
                 // 表单数据存在即显示
-                if (state.forms.isNotEmpty() && !listOf("isolation", "releaseIsolation", "returnLock").contains(state.node.type)) TaskForm(vm)
-                // 处理动态内容的条件,其实业务逻辑不应该在这里处理的
-                when (state.node.type) {
-                    "isolation",                    // 隔离
-                    "releaseIsolation",             // 解除隔离
-                    "returnLock" -> TaskDevice(vm)  // 还锁
-                }
+                if (state.forms.isNotEmpty() && state.node.checkCanOptionByPhone()) TaskForm(vm)
+                // 处理不能在手机上操作的
+                if (!state.node.checkCanOptionByPhone()) TaskDevice(vm)
             }
             // 底部操作功能封装
             TaskOptions(pv, vm)
@@ -126,7 +122,7 @@ class PageDetailTask : PageBase() {
                             .size(16.dp),
                         tint = MaterialTheme.colorScheme.primary
                     )
-                    Text(task.name, fontSize = 16.sp, fontWeight = FontWeight.Bold, color = Text)
+                    Text(task.currentNodeName ?: "", fontSize = 16.sp, fontWeight = FontWeight.Bold, color = Text)
                 }
                 FlowRow(
                     verticalArrangement = Arrangement.Center, modifier = Modifier
@@ -308,7 +304,7 @@ class PageDetailTask : PageBase() {
                     .padding(horizontal = 16.dp)
                     .fillMaxWidth()
             ) {
-                if (listOf("complete", "inputInfo", "confirm").contains(state.node.type)) {
+                if (listOf("complete", "inputInfo", "confirm").contains(state.node.type) || state.node.checkCanOptionByPhone()) {
                     Button(
                         { destroy() }, modifier = Modifier
                             .padding(horizontal = 8.dp)

+ 1 - 1
app/src/main/java/com/iscs/bozzys/ui/pages/message/PageMessage.kt

@@ -282,7 +282,7 @@ private fun MessageListItemContent(vm: VMMessage, msg: Message) {
                     .size(40.dp)
                     .clip(RoundedCornerShape(50))
                     .background(MaterialTheme.colorScheme.primary)
-                    .padding(12.dp),
+                    .padding(8.dp),
                 tint = Color.White
             )
             Column(

+ 3 - 1
app/src/main/java/com/iscs/bozzys/ui/pages/vm/VMDetailTask.kt

@@ -56,7 +56,9 @@ class VMDetailTask : VMBase() {
                     formInfo = json.decodeFromString<TaskFormInfo>(taskInfo.formData.ifEmpty { "{}" })
                     // 有些表单不需要显示的逻辑处理
                     // 解除隔离和还锁不需要录入信息
-                    if (listOf("isolation", "returnLock").contains(taskInfo.type)) formInfo = TaskFormInfo()
+                    if (!taskInfo.checkCanOptionByPhone()) {
+                        formInfo = TaskFormInfo()
+                    }
                     _state.value = _state.value.copy(forms = formInfo.fields?.getFormListByJsonList() ?: emptyList(), node = taskInfo)
                     delay(500)
                     loading.emit(StateLoading())

+ 77 - 0
app/src/main/java/com/iscs/bozzys/ui/pages/vm/VMFormUploadFile.kt

@@ -0,0 +1,77 @@
+package com.iscs.bozzys.ui.pages.vm
+
+import androidx.lifecycle.viewModelScope
+import com.iscs.bozzys.api.ApiRequest
+import com.iscs.bozzys.api.UploadFile
+import com.iscs.bozzys.ui.common.VMBase
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.launch
+
+class VMFormUploadFile : VMBase() {
+
+    private val _state = MutableStateFlow(StateFormUploadFile())
+
+    val state = _state.asStateFlow()
+
+    /**
+     * 数据初始化操作
+     */
+    fun init(values: List<String>) {
+        val list = values.map { UploadFile(url = it, state = 1) }.toMutableList()
+        _state.value = _state.value.copy(files = list)
+    }
+
+    /**
+     * 上传文件
+     */
+    fun uploadFile(file: UploadFile, done: (values: List<String>) -> Unit) {
+        viewModelScope.launch(Dispatchers.IO) {
+            val list = _state.value.files.toMutableList()
+            list.add(0, file)
+            _state.value = _state.value.copy(files = list)
+            // 执行附件上传
+            if (file.file != null) {
+                ApiRequest.uploadFile(file.file, type = file.type).onSuccess { rsp ->
+                    // 上传成功后修改状态
+                    val list = _state.value.files.map { if (it.file?.path == file.file.path) it.copy(url = rsp.data ?: "", state = 1) else it }
+                        .toMutableList()
+                    _state.value = _state.value.copy(files = list)
+                    done(list.map { it.url })
+                }.onFailure {
+                    val list = _state.value.files.map { if (it.file?.path == file.file.path) it.copy(state = 2) else it }.toMutableList()
+                    _state.value = _state.value.copy(files = list)
+                }
+            }
+        }
+    }
+
+    /**
+     * 删除指定文件
+     */
+    fun delete(file: UploadFile) {
+        val list = _state.value.files.filter { it.url != file.url && ((it.file?.path ?: "") != (file.file?.path ?: "")) }.toMutableList()
+        _state.value = _state.value.copy(files = list)
+    }
+
+    /**
+     * 校验最多可以选择文件个数
+     */
+    fun checkCanSelect(max: Int = 1): Boolean {
+        return if (_state.value.files.size == max) {
+            viewModelScope.launch {
+                toast.emit("最多可选择${max}个文件")
+            }
+            false
+        } else {
+            true
+        }
+    }
+
+}
+
+/**
+ *
+ */
+data class StateFormUploadFile(val files: MutableList<UploadFile> = mutableListOf())

+ 52 - 2
app/src/main/java/com/iscs/bozzys/utils/SystemUtil.kt

@@ -1,15 +1,18 @@
 package com.iscs.bozzys.utils
 
 import android.app.Activity
+import android.content.ContentResolver
 import android.content.Context
 import android.net.Uri
 import android.os.Build
 import android.os.CancellationSignal
+import android.provider.OpenableColumns
 import android.security.keystore.KeyGenParameterSpec
 import android.security.keystore.KeyProperties
 import androidx.biometric.BiometricManager
 import androidx.biometric.BiometricPrompt
 import androidx.fragment.app.FragmentActivity
+import com.iscs.bozzys.R
 import java.io.File
 import java.security.KeyStore
 import javax.crypto.Cipher
@@ -96,6 +99,29 @@ object SystemUtil {
         }
     }
 
+    /**
+     * 获取Uri文件的原始名称
+     */
+    fun Context.getUriFileName(uri: Uri): String? {
+        if (uri.scheme != ContentResolver.SCHEME_CONTENT) return null
+        contentResolver.query(
+            uri,
+            arrayOf(OpenableColumns.DISPLAY_NAME),
+            null,
+            null,
+            null
+        )?.use { cursor ->
+            if (cursor.moveToFirst()) {
+                val index = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)
+                if (index != -1) {
+                    return cursor.getString(index)
+                }
+            }
+        }
+        return null
+    }
+
+
     /**
      * Uri转文件
      */
@@ -103,14 +129,38 @@ object SystemUtil {
         if (uri == null) return null
         val input = contentResolver.openInputStream(uri)!!
         val suffix = ".${contentResolver.getType(uri)!!.split("/")[1]}"
-        LogUtil.i("SystemUtil", "uriToFile -> 文件类型:$suffix")
-        val file = File(cacheDir, "upload_${System.currentTimeMillis()}$suffix")
+        val fileName = getUriFileName(uri) ?: suffix
+        LogUtil.i("SystemUtil", "uriToFile -> 文件:$fileName")
+        val file = File(cacheDir, "${System.currentTimeMillis()}_$fileName")
         file.outputStream().use { output ->
             input.copyTo(output)
         }
         return file
     }
 
+    /**
+     * 通过文件后缀名获取指定图片资源
+     *
+     * @param url 文件地址
+     */
+    fun getFileTypeIconBySuffix(url: String): Pair<Int, String> {
+        val spl = url.split("/")
+        val splPoint = spl[spl.size - 1].split(".")
+        val type = splPoint[splPoint.size - 1].lowercase()
+        LogUtil.i("SystemUtil", "解析的文件格式:${type}")
+        return when (type) {
+            "png" -> R.drawable.file_png to type
+            "jpeg",
+            "jpg" -> R.drawable.file_jpg to type
+
+            "pdf" -> R.drawable.file_pdf to type
+            "xls",
+            "xlsx" -> R.drawable.file_xls to type
+            // 其余识别为未知文件
+            else -> R.drawable.file_unknown to type
+        }
+    }
+
 }
 
 /**

+ 12 - 0
app/src/main/res/drawable/file_png.xml

@@ -0,0 +1,12 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="32dp"
+    android:height="32dp"
+    android:viewportWidth="1024"
+    android:viewportHeight="1024">
+  <path
+      android:fillColor="#FF000000"
+      android:pathData="M351.8,607.5h-25.3v36.9h25.3c12.3,0 19.5,-9 19.5,-18.4s-7.2,-18.4 -19.5,-18.4z"/>
+  <path
+      android:fillColor="#FF000000"
+      android:pathData="M716.8,51.2L307.2,51.2c-141.4,0 -256,114.6 -256,256v409.6c0,141.4 114.6,256 256,256h409.6c141.4,0 256,-114.6 256,-256L972.8,307.2c0,-141.4 -114.6,-256 -256,-256zM353.8,684.5h-27.4L326.4,749.6L281.6,749.6v-182.3h72.2c40.4,0 62.2,28.7 62.2,58.6s-21.8,58.6 -62.2,58.6zM584.5,749.6h-39.2l-58.4,-91.6v91.6h-44.8v-182.3h39.2l58.4,91.6v-91.6h44.8L584.5,749.6zM756,670c0,29.4 -4.6,46.6 -18.4,60.7 -15.6,16.1 -33,20.5 -53,20.5 -21.2,0 -37.4,-7.2 -50.7,-20.5 -19.2,-19.2 -18.4,-44.8 -18.4,-72.2 0,-27.4 -0.8,-53 18.4,-72.2 13.3,-13.3 28.7,-20.5 50.7,-20.5 46.8,0 66.8,30.5 71.2,60.4h-45.1c-3.6,-13.8 -10.8,-20.5 -26.1,-20.5 -8.2,0 -14.3,3.6 -17.7,7.9 -4.1,5.4 -6.7,11.5 -6.7,44.8s2.6,39.7 6.7,45.1c3.3,4.4 9.5,7.7 17.7,7.7 9.5,0 15.9,-2.8 20,-7.2 5.1,-5.1 6.9,-12.5 6.9,-19.5v-2.6h-26.9v-37.4h71.4v25.3zM885.8,445.4h-102.4c-113.1,0 -204.8,-91.7 -204.8,-204.8L578.6,138.2h102.4c113.1,0 204.8,91.7 204.8,204.8v102.4z"/>
+</vector>

+ 9 - 0
app/src/main/res/drawable/file_unknown.xml

@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="32dp"
+    android:height="32dp"
+    android:viewportWidth="1024"
+    android:viewportHeight="1024">
+  <path
+      android:pathData="M529.7,213.3L896,213.3a42.7,42.7 0,0 1,42.7 42.7v597.3a42.7,42.7 0,0 1,-42.7 42.7L128,896a42.7,42.7 0,0 1,-42.7 -42.7L85.3,170.7a42.7,42.7 0,0 1,42.7 -42.7h316.3l85.3,85.3zM469.3,682.7v85.3h85.3v-85.3h-85.3zM365.5,461.4l83.7,16.7A64,64 0,1 1,512 554.7h-42.7v85.3h42.7a149.3,149.3 0,1 0,-146.5 -178.6z"
+      android:fillColor="#1296db"/>
+</vector>

+ 9 - 0
app/src/main/res/drawable/file_xls.xml

@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="32dp"
+    android:height="32dp"
+    android:viewportWidth="1024"
+    android:viewportHeight="1024">
+  <path
+      android:fillColor="#FF000000"
+      android:pathData="M716.8,51.2L307.2,51.2c-141.4,0 -256,114.6 -256,256v409.6c0,141.4 114.6,256 256,256h409.6c141.4,0 256,-114.6 256,-256L972.8,307.2c0,-141.4 -114.6,-256 -256,-256zM396.2,749.6l-30,-56.1 -30,56.1h-51.2l56.8,-93.4 -53.2,-88.8h50.9l26.6,51.5 26.6,-51.5h50.9l-53.3,88.8 56.8,93.4L396.2,749.6zM587.1,749.6L465.8,749.6v-182.3h44.8L510.6,709.6h76.5v39.9zM665.2,751.1c-27.9,0 -50.7,-5.1 -69.1,-24.1l28.7,-28.7c9.5,9.5 26.1,12.8 40.7,12.8 17.7,0 26.1,-5.9 26.1,-16.4 0,-4.4 -1,-7.9 -3.6,-10.8 -2.3,-2.3 -6.1,-4.1 -12,-4.9l-22,-3.1c-16.1,-2.3 -28.4,-7.7 -36.6,-16.1 -8.4,-8.7 -12.5,-21 -12.5,-36.6 0,-33.3 25.1,-57.6 66.6,-57.6 26.1,0 45.8,6.1 61.5,21.8l-28.2,28.2c-11.5,-11.5 -26.6,-10.8 -34.6,-10.8 -15.6,0 -22,9 -22,16.9 0,2.3 0.8,5.6 3.6,8.4 2.3,2.3 6.1,4.6 12.5,5.4l22,3.1c16.4,2.3 28.2,7.4 35.8,15.1 9.7,9.5 13.6,23 13.6,39.9 0,37.1 -32,57.3 -70.4,57.3zM885.8,445.4h-102.4c-113.1,0 -204.8,-91.7 -204.8,-204.8L578.6,138.2h102.4c113.1,0 204.8,91.7 204.8,204.8v102.4z"/>
+</vector>