Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
081884b723 | ||
|
|
b7dab20751 | ||
|
|
a7d81d42ea | ||
|
|
85bcdfccaa | ||
|
|
388c0dcfc5 | ||
|
|
57c5362da3 |
@@ -248,7 +248,9 @@ object PrintHelper {
|
|||||||
textY = drawTextBlock(canvas, "", "位置:${item.containerBreadcrumb}", textX, textY, textMaxWidth, maxLines = 4, textSize = 25f, lineSpacingExtra = 4f)
|
textY = drawTextBlock(canvas, "", "位置:${item.containerBreadcrumb}", textX, textY, textMaxWidth, maxLines = 4, textSize = 25f, lineSpacingExtra = 4f)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (textY < maxTextY && !item.linkedCustomers.isNullOrEmpty()) {
|
canvas.drawLine(dividerX.toFloat(), padding.toFloat(), dividerX.toFloat(), maxTextY.toFloat(), sepPaint)
|
||||||
|
|
||||||
|
if (!item.linkedCustomers.isNullOrEmpty()) {
|
||||||
val customerNames = item.linkedCustomers
|
val customerNames = item.linkedCustomers
|
||||||
.mapNotNull { c ->
|
.mapNotNull { c ->
|
||||||
listOfNotNull(c.first_name, c.last_name)
|
listOfNotNull(c.first_name, c.last_name)
|
||||||
@@ -256,11 +258,14 @@ object PrintHelper {
|
|||||||
.ifBlank { null }
|
.ifBlank { null }
|
||||||
}
|
}
|
||||||
.joinToString(", ")
|
.joinToString(", ")
|
||||||
textY = drawTextBlock(canvas, "", "客户:$customerNames", textX, textY, textMaxWidth, maxLines = 2, textSize = 25f, lineSpacingExtra = 4f)
|
val customerText = "客户:$customerNames"
|
||||||
|
val customerY = qrY + qrSize + 4
|
||||||
|
val leftTextMaxWidth = (dividerX - padding * 2).toFloat()
|
||||||
|
if (customerY < maxTextY) {
|
||||||
|
drawTextBlock(canvas, "", customerText, padding.toFloat(), customerY.toFloat(), leftTextMaxWidth, maxLines = 2, textSize = 20f, lineSpacingExtra = 2f)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
canvas.drawLine(dividerX.toFloat(), padding.toFloat(), dividerX.toFloat(), maxTextY.toFloat(), sepPaint)
|
|
||||||
|
|
||||||
if (!item.serial_number.isNullOrBlank()) {
|
if (!item.serial_number.isNullOrBlank()) {
|
||||||
canvas.drawLine(padding.toFloat(), maxTextY.toFloat(), (widthDots - padding).toFloat(), maxTextY.toFloat(), sepPaint)
|
canvas.drawLine(padding.toFloat(), maxTextY.toFloat(), (widthDots - padding).toFloat(), maxTextY.toFloat(), sepPaint)
|
||||||
|
|
||||||
|
|||||||
@@ -80,6 +80,21 @@ fun HomeScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── 快捷入口 ──────────────────────────
|
||||||
|
item {
|
||||||
|
Text("快捷入口", style = MaterialTheme.typography.titleMedium, fontWeight = FontWeight.Bold)
|
||||||
|
}
|
||||||
|
|
||||||
|
item {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(12.dp)
|
||||||
|
) {
|
||||||
|
QuickLinkCard(Icons.Default.Search, "搜索", Color(0xFF4CAF50), Modifier.weight(1f), onSearchClick)
|
||||||
|
QuickLinkCard(Icons.Default.Event, "日程", Color(0xFF9C27B0), Modifier.weight(1f), onScheduleClick)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ── 今日日程 ──────────────────────────
|
// ── 今日日程 ──────────────────────────
|
||||||
item {
|
item {
|
||||||
Text("今日日程", style = MaterialTheme.typography.titleMedium, fontWeight = FontWeight.Bold)
|
Text("今日日程", style = MaterialTheme.typography.titleMedium, fontWeight = FontWeight.Bold)
|
||||||
@@ -172,21 +187,6 @@ fun HomeScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── 快捷入口 ──────────────────────────
|
|
||||||
item {
|
|
||||||
Text("快捷入口", style = MaterialTheme.typography.titleMedium, fontWeight = FontWeight.Bold)
|
|
||||||
}
|
|
||||||
|
|
||||||
item {
|
|
||||||
Row(
|
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
horizontalArrangement = Arrangement.spacedBy(12.dp)
|
|
||||||
) {
|
|
||||||
QuickLinkCard(Icons.Default.Search, "搜索", Color(0xFF4CAF50), Modifier.weight(1f), onSearchClick)
|
|
||||||
QuickLinkCard(Icons.Default.Event, "日程", Color(0xFF9C27B0), Modifier.weight(1f), onScheduleClick)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -111,7 +111,13 @@ fun OPSNavGraph(isLoggedIn: Boolean) {
|
|||||||
onWorkOrderClick = { id -> rootNavController.navigate(Routes.workOrderDetail(id)) },
|
onWorkOrderClick = { id -> rootNavController.navigate(Routes.workOrderDetail(id)) },
|
||||||
onItemClick = { id -> rootNavController.navigate(Routes.itemDetail(id)) },
|
onItemClick = { id -> rootNavController.navigate(Routes.itemDetail(id)) },
|
||||||
onContainerClick = { id -> rootNavController.navigate(Routes.MAIN) },
|
onContainerClick = { id -> rootNavController.navigate(Routes.MAIN) },
|
||||||
onBack = { rootNavController.popBackStack() }
|
onBack = { rootNavController.popBackStack() },
|
||||||
|
onOpsSysNavigate = { module, id ->
|
||||||
|
when (module) {
|
||||||
|
"workorder" -> rootNavController.navigate(Routes.workOrderDetail(id))
|
||||||
|
"item" -> rootNavController.navigate(Routes.itemDetail(id))
|
||||||
|
}
|
||||||
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
composable(Routes.SCHEDULE) {
|
composable(Routes.SCHEDULE) {
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ import androidx.compose.ui.graphics.Color
|
|||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.platform.LocalLifecycleOwner
|
import androidx.compose.ui.platform.LocalLifecycleOwner
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import androidx.compose.ui.viewinterop.AndroidView
|
import androidx.compose.ui.viewinterop.AndroidView
|
||||||
@@ -51,7 +51,8 @@ fun SearchScreen(
|
|||||||
onWorkOrderClick: (Int) -> Unit,
|
onWorkOrderClick: (Int) -> Unit,
|
||||||
onItemClick: (Int) -> Unit,
|
onItemClick: (Int) -> Unit,
|
||||||
onContainerClick: (Int) -> Unit,
|
onContainerClick: (Int) -> Unit,
|
||||||
onBack: () -> Unit
|
onBack: () -> Unit,
|
||||||
|
onOpsSysNavigate: (module: String, id: Int) -> Unit
|
||||||
) {
|
) {
|
||||||
val appContainer = LocalAppContainer.current
|
val appContainer = LocalAppContainer.current
|
||||||
val viewModel: SearchViewModel = viewModel(
|
val viewModel: SearchViewModel = viewModel(
|
||||||
@@ -67,6 +68,13 @@ fun SearchScreen(
|
|||||||
if (granted) showScanner = true
|
if (granted) showScanner = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
LaunchedEffect(uiState.opsSysNavigation) {
|
||||||
|
uiState.opsSysNavigation?.let { navInfo ->
|
||||||
|
onOpsSysNavigate(navInfo.module, navInfo.id)
|
||||||
|
viewModel.clearOpsSysNavigation()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun checkCameraAndScan() {
|
fun checkCameraAndScan() {
|
||||||
if (ContextCompat.checkSelfPermission(context, Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED) {
|
if (ContextCompat.checkSelfPermission(context, Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED) {
|
||||||
hasCameraPermission = true
|
hasCameraPermission = true
|
||||||
@@ -128,14 +136,6 @@ fun SearchScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prefix hints
|
|
||||||
Text(
|
|
||||||
"前缀快捷搜索: po:采购单 wo:工单 item:物品 warehouse:容器",
|
|
||||||
fontSize = 11.sp,
|
|
||||||
color = Color.Gray,
|
|
||||||
modifier = Modifier.padding(horizontal = 16.dp),
|
|
||||||
textAlign = TextAlign.Center
|
|
||||||
)
|
|
||||||
|
|
||||||
// Category tabs
|
// Category tabs
|
||||||
PrimaryScrollableTabRow(
|
PrimaryScrollableTabRow(
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import com.example.ops_android.data.model.Container
|
|||||||
import com.example.ops_android.data.model.Item
|
import com.example.ops_android.data.model.Item
|
||||||
import com.example.ops_android.data.model.PurchaseOrder
|
import com.example.ops_android.data.model.PurchaseOrder
|
||||||
import com.example.ops_android.data.model.WorkOrder
|
import com.example.ops_android.data.model.WorkOrder
|
||||||
import com.example.ops_android.data.repository.CustomerRepository
|
|
||||||
import com.example.ops_android.data.repository.OrderRepository
|
import com.example.ops_android.data.repository.OrderRepository
|
||||||
import com.example.ops_android.data.repository.WarehouseRepository
|
import com.example.ops_android.data.repository.WarehouseRepository
|
||||||
import com.example.ops_android.data.repository.WorkOrderRepository
|
import com.example.ops_android.data.repository.WorkOrderRepository
|
||||||
@@ -15,6 +14,7 @@ import kotlinx.coroutines.flow.MutableStateFlow
|
|||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import kotlinx.coroutines.flow.asStateFlow
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import org.json.JSONObject
|
||||||
|
|
||||||
enum class SearchCategory(val label: String) {
|
enum class SearchCategory(val label: String) {
|
||||||
ALL("全部"),
|
ALL("全部"),
|
||||||
@@ -24,6 +24,11 @@ enum class SearchCategory(val label: String) {
|
|||||||
CONTAINER("容器")
|
CONTAINER("容器")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
data class OpsSysNavInfo(
|
||||||
|
val module: String,
|
||||||
|
val id: Int
|
||||||
|
)
|
||||||
|
|
||||||
data class SearchUiState(
|
data class SearchUiState(
|
||||||
val query: String = "",
|
val query: String = "",
|
||||||
val category: SearchCategory = SearchCategory.ALL,
|
val category: SearchCategory = SearchCategory.ALL,
|
||||||
@@ -32,7 +37,8 @@ data class SearchUiState(
|
|||||||
val items: List<Item> = emptyList(),
|
val items: List<Item> = emptyList(),
|
||||||
val containers: List<Container> = emptyList(),
|
val containers: List<Container> = emptyList(),
|
||||||
val isSearching: Boolean = false,
|
val isSearching: Boolean = false,
|
||||||
val errorMessage: String? = null
|
val errorMessage: String? = null,
|
||||||
|
val opsSysNavigation: OpsSysNavInfo? = null
|
||||||
)
|
)
|
||||||
|
|
||||||
class SearchViewModel(
|
class SearchViewModel(
|
||||||
@@ -61,7 +67,19 @@ class SearchViewModel(
|
|||||||
val query = _uiState.value.query.trim()
|
val query = _uiState.value.query.trim()
|
||||||
if (query.isEmpty()) return
|
if (query.isEmpty()) return
|
||||||
|
|
||||||
// Parse prefix shortcuts
|
val jsonObj = try { JSONObject(query) } catch (e: Exception) { null }
|
||||||
|
if (jsonObj != null) {
|
||||||
|
_uiState.value = _uiState.value.copy(query = "")
|
||||||
|
if (jsonObj.optString("type") == "ops_sys") {
|
||||||
|
val module = jsonObj.optString("module")
|
||||||
|
val id = jsonObj.optInt("id")
|
||||||
|
if (module.isNotBlank() && id > 0) {
|
||||||
|
_uiState.value = _uiState.value.copy(opsSysNavigation = OpsSysNavInfo(module, id))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
val (actualCategory, actualQuery) = parsePrefix(query)
|
val (actualCategory, actualQuery) = parsePrefix(query)
|
||||||
_uiState.value = _uiState.value.copy(category = actualCategory)
|
_uiState.value = _uiState.value.copy(category = actualCategory)
|
||||||
|
|
||||||
@@ -125,6 +143,10 @@ class SearchViewModel(
|
|||||||
_uiState.value = SearchUiState(query = _uiState.value.query, category = _uiState.value.category)
|
_uiState.value = SearchUiState(query = _uiState.value.query, category = _uiState.value.category)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun clearOpsSysNavigation() {
|
||||||
|
_uiState.value = _uiState.value.copy(opsSysNavigation = null)
|
||||||
|
}
|
||||||
|
|
||||||
private fun parsePrefix(query: String): Pair<SearchCategory, String> {
|
private fun parsePrefix(query: String): Pair<SearchCategory, String> {
|
||||||
val prefixes = mapOf(
|
val prefixes = mapOf(
|
||||||
"po:" to SearchCategory.ORDER,
|
"po:" to SearchCategory.ORDER,
|
||||||
|
|||||||
@@ -1,6 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
|
||||||
<background android:drawable="@drawable/ic_launcher_background" />
|
|
||||||
<foreground android:drawable="@drawable/ic_launcher_foreground" />
|
|
||||||
<monochrome android:drawable="@drawable/ic_launcher_foreground" />
|
|
||||||
</adaptive-icon>
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
|
||||||
<background android:drawable="@drawable/ic_launcher_background" />
|
|
||||||
<foreground android:drawable="@drawable/ic_launcher_foreground" />
|
|
||||||
<monochrome android:drawable="@drawable/ic_launcher_foreground" />
|
|
||||||
</adaptive-icon>
|
|
||||||
|
After Width: | Height: | Size: 66 KiB |
|
Before Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 66 KiB |
|
Before Width: | Height: | Size: 2.8 KiB |
|
After Width: | Height: | Size: 66 KiB |
|
Before Width: | Height: | Size: 982 B |
|
After Width: | Height: | Size: 66 KiB |
|
Before Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 66 KiB |
|
Before Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 66 KiB |
|
Before Width: | Height: | Size: 3.8 KiB |
|
After Width: | Height: | Size: 66 KiB |
|
Before Width: | Height: | Size: 2.8 KiB |
|
After Width: | Height: | Size: 66 KiB |
|
Before Width: | Height: | Size: 5.8 KiB |
|
After Width: | Height: | Size: 66 KiB |
|
Before Width: | Height: | Size: 3.8 KiB |
|
After Width: | Height: | Size: 66 KiB |
|
Before Width: | Height: | Size: 7.6 KiB |
@@ -1,3 +1,3 @@
|
|||||||
<resources>
|
<resources>
|
||||||
<string name="app_name">ops_android</string>
|
<string name="app_name">Operations 2</string>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -1,3 +1,3 @@
|
|||||||
#Fri Jun 26 19:51:18 CST 2026
|
#Fri Jun 26 20:46:06 CST 2026
|
||||||
VERSION_CODE=116
|
VERSION_CODE=121
|
||||||
VERSION_NAME=1.2
|
VERSION_NAME=1.2
|
||||||
|
|||||||