| src/api/production.ts | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/api/wls.ts | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/config/app-routes.ts | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/pages.json | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/pages/mes/pro/mps/index.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/pages/mes/pro/mps/preparation-check.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/pages/wls/stocksummary/index.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/pages/workbench/index.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 |
src/api/production.ts
@@ -30,6 +30,48 @@ createTime?: string; } /** ç产å夿£æ¥ - ç©æé½å¥æç» */ export interface MpsPreparationMaterial { itemId?: number; itemCode?: string; itemName?: string; specification?: string; unitMeasureName?: string; requiredQuantity?: number; stockQuantity?: number; purchaseOrderQuantity?: number; shortageQuantity?: number; sufficient?: boolean; } /** ç产å夿£æ¥ - 设å¤å¯ç¨æ§æç» */ export interface MpsPreparationEquipment { processId?: number; processName?: string; machineryId?: number; machineryCode?: string; machineryName?: string; status?: number; statusName?: string; available?: boolean; reason?: string; } /** ç产å夿£æ¥ç»æ */ export interface MpsPreparationCheck { mpsId?: number; mpsCode?: string; productId?: number; productName?: string; quantity?: number; materialReady?: boolean; equipmentReady?: boolean; ready?: boolean; summary?: string; materials?: MpsPreparationMaterial[]; equipments?: MpsPreparationEquipment[]; } export interface WorkOrder { id?: number; parentId?: number; @@ -341,6 +383,9 @@ export function cancelMps(accessToken: string, id: number) { return request<boolean>(`/mes/pro-mps/cancel${query({ id })}`, { method: "POST", header: header(accessToken) }); } export function getMpsPreparationCheck(accessToken: string, id: number) { return request<MpsPreparationCheck>(`/mes/pro-mps/preparation-check${query({ id })}`, { header: header(accessToken) }); } export function getWorkOrderPage(accessToken: string, params: { pageNo: number; pageSize: number; code?: string; name?: string; orderSourceCode?: string; productId?: number; clientId?: number; status?: number; type?: number }) { return request<PageResult<WorkOrder>>(`/mes/pro/work-order/page${query(params)}`, { header: header(accessToken) }); src/api/wls.ts
@@ -125,6 +125,21 @@ createTime?: string; } /** ä»å¨æ¶ååæ±æ»è¡ */ export interface StockSummary { itemId?: number; itemCode?: string; itemName?: string; specification?: string; unitMeasureName?: string; warehouseId?: number; warehouseName?: string; openingQuantity?: number; inQuantity?: number; outQuantity?: number; closingQuantity?: number; } export interface ArrivalNotice { id?: number; code?: string; @@ -570,6 +585,9 @@ export function getTransactionPage(accessToken: string, params: { pageNo: number; pageSize: number; bizCode?: string }) { return request<PageResult<Transaction>>(`/mes/wm/transaction/page${query(params)}`, { header: header(accessToken) }); } export function getStockSummaryPage(accessToken: string, params: { pageNo: number; pageSize: number; itemId?: number; warehouseId?: number; startTime?: string; endTime?: string }) { return request<PageResult<StockSummary>>(`/mes/wm/transaction/summary${query(params)}`, { header: header(accessToken) }); } export function getTransferPage(accessToken: string, params: { pageNo: number; pageSize: number; code?: string; name?: string; type?: number; status?: number }) { return request<PageResult<Transfer>>(`/mes/wm/transfer/page${query(params)}`, { header: header(accessToken) }); } export function getTransfer(accessToken: string, id: number) { return request<Transfer>(`/mes/wm/transfer/get${query({ id })}`, { header: header(accessToken) }); } export function createTransfer(accessToken: string, data: Transfer) { return request<number>("/mes/wm/transfer/create", { method: "POST", data, header: header(accessToken) }); } src/config/app-routes.ts
@@ -103,6 +103,9 @@ "wls/transaction/index": "/pages/wls/transaction/index", "wls/warehouse/index": "/pages/wls/warehouse/index", "wls/materialstock/index": "/pages/wls/material-stock/index", "wls/stocksummary/index": "/pages/wls/stocksummary/index", "wls/warehouse/location/index": "/pages/wls/warehouse/location", "wls/warehouse/area/index": "/pages/wls/warehouse/area", "wls/arrivalnotice/index": "/pages/wls/arrival-notice/index", "wls/itemreceipt/index": "/pages/wls/item-receipt/index", "wls/returnvendor/index": "/pages/wls/return-vendor/index", src/pages.json
@@ -210,6 +210,12 @@ } }, { "path": "stocksummary/index", "style": { "navigationBarTitleText": "æ¶ååæ±æ»" } }, { "path": "arrival-notice/index", "style": { "navigationBarTitleText": "å°è´§éç¥" @@ -1031,6 +1037,12 @@ } }, { "path": "mps/preparation-check", "style": { "navigationBarTitleText": "ç产å夿£æ¥" } }, { "path": "workorder/index", "style": { "navigationBarTitleText": "ç产订å" src/pages/mes/pro/mps/index.vue
@@ -54,6 +54,10 @@ function goForm(id?: number, mode: "edit" | "detail" = "edit") { uni.navigateTo({ url: `/pages/mes/pro/mps/form${id ? `?id=${id}&mode=${mode}` : ""}` }); } function goPreparationCheck(id?: number) { if (!id) return; uni.navigateTo({ url: `/pages/mes/pro/mps/preparation-check?id=${id}` }); } async function confirmAction(title: string, content: string) { return new Promise<boolean>(resolve => uni.showModal({ title, content, success: res => resolve(res.confirm) })); } @@ -130,6 +134,7 @@ <view class="actions" @click.stop> <button @click="goForm(row.id,'detail')">详æ </button> <button v-if="canUpdate && row.status === 0" @click="goForm(row.id)">ç¼è¾</button> <button @click="goPreparationCheck(row.id)">å夿£æ¥</button> <button v-if="canIssue && row.status === 0" @click="issue(row)">ä¸å</button> <button v-if="canCancel && row.status === 1" class="danger" @click="cancel(row)">åæ¶</button> <button v-if="canDelete && row.status === 0" class="danger" @click="remove(row)">å é¤</button> src/pages/mes/pro/mps/preparation-check.vue
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,123 @@ <script setup lang="ts"> import { computed, ref } from "vue"; import { onLoad, onPullDownRefresh } from "@dcloudio/uni-app"; import { getMpsPreparationCheck, type MpsPreparationCheck } from "../../../../api/production"; import { useAuth } from "../../../../stores/auth"; const { isLoggedIn, state } = useAuth(); const mpsId = ref<number>(); const result = ref<MpsPreparationCheck>(); const loading = ref(false); const loadError = ref(""); const canQuery = computed(() => !!state.accessToken); const shortageRows = computed(() => (result.value?.materials || []).filter(item => !item.sufficient)); const readyMaterials = computed(() => (result.value?.materials || []).filter(item => item.sufficient)); const unavailableEquipments = computed(() => (result.value?.equipments || []).filter(item => !item.available)); /** å端å¯è½æ²¡è¿åè®¾å¤æç»ï¼äº§åæªç»å®å·¥èºè·¯çº¿ï¼ï¼æ¤æ¶ç»ç¨æ·ä¸ä¸ªå¯æ§è¡çæç¤º */ const equipmentHint = computed(() => { if ((result.value?.equipments || []).length) return ""; return "该产åå°æªç»´æ¤å·¥èºè·¯çº¿æå·¥åºè®¾å¤ï¼æ æ³èªå¨å¤æè®¾å¤å¯ç¨æ§ï¼è¯·å°ãå·¥èºè·¯çº¿ãç»´æ¤å忣æ¥"; }); function formatNumber(value?: number) { const number = Number(value || 0); return Number.isInteger(number) ? String(number) : number.toFixed(2); } async function load() { if (!state.accessToken || !mpsId.value || loading.value) return; loading.value = true; loadError.value = ""; try { result.value = await getMpsPreparationCheck(state.accessToken, mpsId.value); } catch (error) { loadError.value = error instanceof Error ? error.message : "ç产å夿£æ¥å¤±è´¥"; } finally { loading.value = false; } } function goBack() { uni.navigateBack(); } onLoad(options => { const id = Number(options?.id); mpsId.value = Number.isFinite(id) && id > 0 ? id : undefined; if (!mpsId.value) { loadError.value = "缺å°ç产计åç¼å·ï¼è¯·ä»ç产计ååè¡¨éæ°è¿å ¥"; return; } load(); }); onPullDownRefresh(async () => { await load(); uni.stopPullDownRefresh(); }); </script> <template> <view class="page"> <template v-if="isLoggedIn && canQuery"> <view v-if="loadError" class="state error" @click="load">{{ loadError }}ï¼ç¹å»éè¯</view> <scroll-view v-else-if="result" class="content" scroll-y> <view :class="['banner', result.ready ? 'ok' : 'warn']"> <text class="banner-title">{{ result.ready ? "åå¤å·¥ä½å·²å°±ç»ª" : "æä¸æ»¡è¶³å¼å·¥æ¡ä»¶" }}</text> <text class="banner-sub">{{ result.summary || "ç³»ç»æªè¿åæ£æ¥ç»è®º" }}</text> <view class="banner-tags"> <text :class="['tag', result.materialReady ? 'tag-success' : 'tag-danger']">{{ result.materialReady ? "ç©æé½å¥" : "ç©æç缺" }}</text> <text :class="['tag', result.equipmentReady ? 'tag-success' : 'tag-danger']">{{ result.equipmentReady ? "设å¤å¯ç¨" : "设å¤ä¸å¯ç¨" }}</text> </view> </view> <view class="card"> <view class="card-title">ç产计å</view> <view class="grid"> <text>计ååå·ï¼{{ result.mpsCode || "--" }}</text> <text>产åï¼{{ result.productName || "--" }}</text> <text>è®¡åæ°éï¼{{ formatNumber(result.quantity) }}</text> </view> </view> <view v-if="shortageRows.length" class="card"> <view class="card-title danger">ç©æç¼ºå£ {{ shortageRows.length }} 项</view> <view v-for="item in shortageRows" :key="item.itemId" class="item"> <view class="item-head"><text class="name">{{ item.itemName || "æªå½åç©æ" }}</text><text class="tag tag-danger">缺 {{ formatNumber(item.shortageQuantity) }}{{ item.unitMeasureName || "" }}</text></view> <view class="grid"> <text>ç©æç¼ç ï¼{{ item.itemCode || "--" }}</text> <text>è§æ ¼åå·ï¼{{ item.specification || "--" }}</text> <text>éæ±æ°éï¼{{ formatNumber(item.requiredQuantity) }}</text> <text>ç°æåºåï¼{{ formatNumber(item.stockQuantity) }}</text> <text>å¨ééè´ï¼{{ formatNumber(item.purchaseOrderQuantity) }}</text> <text>ç¼ºå£æ°éï¼{{ formatNumber(item.shortageQuantity) }}</text> </view> </view> </view> <view v-if="readyMaterials.length" class="card"> <view class="card-title">ç©æé½å¥ {{ readyMaterials.length }} 项</view> <view v-for="item in readyMaterials" :key="item.itemId" class="item"> <view class="item-head"><text class="name">{{ item.itemName || "æªå½åç©æ" }}</text><text class="tag tag-success">å è¶³</text></view> <view class="grid"> <text>ç©æç¼ç ï¼{{ item.itemCode || "--" }}</text> <text>éæ±æ°éï¼{{ formatNumber(item.requiredQuantity) }}</text> <text>ç°æåºåï¼{{ formatNumber(item.stockQuantity) }}</text> <text>å¨ééè´ï¼{{ formatNumber(item.purchaseOrderQuantity) }}</text> </view> </view> </view> <view class="card"> <view class="card-title">设å¤å¯ç¨æ§</view> <view v-if="equipmentHint" class="empty">{{ equipmentHint }}</view> <view v-for="item in result.equipments || []" :key="`${item.processId}-${item.machineryId}`" class="item"> <view class="item-head"><text class="name">{{ item.processName || "æªå½åå·¥åº" }}</text><text :class="['tag', item.available ? 'tag-success' : 'tag-danger']">{{ item.available ? "å¯ç¨" : "ä¸å¯ç¨" }}</text></view> <view class="grid"> <text>设å¤ç¼ç ï¼{{ item.machineryCode || "--" }}</text> <text>设å¤åç§°ï¼{{ item.machineryName || "--" }}</text> <text>设å¤ç¶æï¼{{ item.statusName || "--" }}</text> <text v-if="item.reason">ä¸å¯ç¨åå ï¼{{ item.reason }}</text> </view> </view> </view> </scroll-view> <view v-else class="state">{{ loading ? "æ£æ¥ä¸..." : "ææ æ£æ¥ç»æ" }}</view> </template> <view v-else class="state">ç»å½åæ¥çç产å夿£æ¥</view> <button class="back" @click="goBack">è¿åç产计å</button> </view> </template> <style lang="scss" scoped> .page{height:100vh;display:flex;flex-direction:column;padding:0 24rpx;box-sizing:border-box;overflow:hidden;background:#f3f6fb}.content{height:0;flex:1;padding:20rpx 0 140rpx;box-sizing:border-box}.banner{display:flex;flex-direction:column;gap:10rpx;padding:26rpx 24rpx;margin-bottom:16rpx;background:#fff;border-radius:16rpx;box-shadow:0 10rpx 24rpx rgba(43,69,107,.08);border-left:8rpx solid #17865a}.banner.warn{border-left-color:#c65050}.banner-title{color:#20334c;font-size:32rpx;font-weight:600}.banner-sub{color:#66778c;font-size:24rpx;line-height:1.6}.banner-tags{display:flex;gap:12rpx;margin-top:6rpx}.card{padding:22rpx;margin-bottom:16rpx;background:#fff;border-radius:16rpx;box-shadow:0 10rpx 24rpx rgba(43,69,107,.08)}.card-title{margin-bottom:12rpx;color:#20334c;font-size:28rpx;font-weight:600}.card-title.danger{color:#c65050}.item{padding:16rpx 0;border-top:1rpx solid #edf1f6}.item-head{display:flex;align-items:center;justify-content:space-between;gap:12rpx}.name{overflow:hidden;color:#31445c;font-size:27rpx;font-weight:600;text-overflow:ellipsis;white-space:nowrap}.tag{flex:none;display:inline-flex;align-items:center;height:40rpx;padding:0 14rpx;font-size:22rpx;border-radius:999rpx;white-space:nowrap}.tag-success{color:#17865a;background:#eafaf2;border:1rpx solid #b8efd5}.tag-danger{color:#c65050;background:#fff7f7;border:1rpx solid #e3a0a0}.grid{display:grid;grid-template-columns:1fr 1fr;gap:8rpx;margin-top:10rpx;color:#66778c;font-size:23rpx}.grid text{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.empty{padding:16rpx 0;color:#8795a8;font-size:24rpx;line-height:1.6}.more,.state{display:block;padding:35rpx;color:#8795a8;font-size:26rpx;text-align:center}.error{color:#c3654d}.back{position:fixed;left:24rpx;right:24rpx;bottom:calc(24rpx + env(safe-area-inset-bottom));height:84rpx;line-height:84rpx;color:var(--color-primary, #1769d8);font-size:28rpx;background:#fff;border:1rpx solid var(--color-primary-border, #8ab3ee);border-radius:14rpx}.back::after{border:0} </style> src/pages/wls/stocksummary/index.vue
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,159 @@ <script setup lang="ts"> import { computed, ref } from "vue"; import { onPullDownRefresh, onShow } from "@dcloudio/uni-app"; import { getStockSummaryPage, getWarehouseSimpleList, type StockSummary, type Warehouse } from "../../../api/wls"; import { useAuth } from "../../../stores/auth"; import { hasMenuComponent } from "../components/shared"; const { hasPermission, isLoggedIn, state } = useAuth(); const rows = ref<StockSummary[]>([]); const warehouses = ref<Warehouse[]>([]); const warehouseId = ref<number | undefined>(undefined); const startDate = ref(""); const endDate = ref(""); const pageNo = ref(1); const total = ref(0); const loading = ref(false); const loadError = ref(""); const canQuery = computed(() => hasPermission("mes:wm-transaction:query") || hasMenuComponent(state.menus, "wls/stocksummary/index")); const hasMore = computed(() => rows.value.length < total.value); const warehouseOptions = computed(() => ["å ¨é¨ä»åº", ...warehouses.value.map(item => `${item.code || "--"} - ${item.name || "--"}`)]); const warehouseLabel = computed(() => { const index = warehouses.value.findIndex(item => item.id === warehouseId.value); return index >= 0 ? warehouseOptions.value[index + 1] : "å ¨é¨ä»åº"; }); /** å½åå·²å è½½è¡çåè®¡ï¼æ¹ä¾¿ä»ç®¡å¨ææºä¸å¿«éçæ¶ååæ»é */ const totals = computed(() => rows.value.reduce( (sum, row) => ({ opening: sum.opening + Number(row.openingQuantity || 0), inbound: sum.inbound + Number(row.inQuantity || 0), outbound: sum.outbound + Number(row.outQuantity || 0), closing: sum.closing + Number(row.closingQuantity || 0), }), { opening: 0, inbound: 0, outbound: 0, closing: 0 } )); function formatNumber(value?: number) { const number = Number(value || 0); return Number.isInteger(number) ? String(number) : number.toFixed(2); } /** æå + å ¥åº - åºåº 䏿æ«ä¸ä¸è´æ¶ç»åºæç¤ºï¼é¿å æé误ç账读æåç */ function mismatch(row: StockSummary) { const expected = Number(row.openingQuantity || 0) + Number(row.inQuantity || 0) - Number(row.outQuantity || 0); return Math.abs(expected - Number(row.closingQuantity || 0)) > 0.0001; } async function loadWarehouses() { if (!state.accessToken || warehouses.value.length) return; warehouses.value = await getWarehouseSimpleList(state.accessToken); } async function load(reset = false) { if (!state.accessToken || loading.value || !canQuery.value) return; if (reset) { pageNo.value = 1; rows.value = []; } loading.value = true; loadError.value = ""; try { await loadWarehouses(); const data = await getStockSummaryPage(state.accessToken, { pageNo: pageNo.value, pageSize: 15, warehouseId: warehouseId.value, startTime: startDate.value ? `${startDate.value} 00:00:00` : undefined, endTime: endDate.value ? `${endDate.value} 23:59:59` : undefined, }); total.value = data.total; rows.value = reset ? data.list : [...rows.value, ...data.list]; pageNo.value += 1; } catch (error) { loadError.value = error instanceof Error ? error.message : "æ¶ååæ±æ»å 载失败"; } finally { loading.value = false; } } function pickWarehouse(event: { detail: { value: string } }) { const index = Number(event.detail.value); warehouseId.value = index === 0 ? undefined : warehouses.value[index - 1]?.id; load(true); } function pickStart(event: { detail: { value: string } }) { startDate.value = event.detail.value; if (endDate.value && startDate.value > endDate.value) { uni.showToast({ title: `å¼å§æ¥æ ${startDate.value} æäºç»ææ¥æ ${endDate.value}ï¼è¯·éæ°éæ©`, icon: "none" }); return; } load(true); } function pickEnd(event: { detail: { value: string } }) { endDate.value = event.detail.value; if (startDate.value && endDate.value < startDate.value) { uni.showToast({ title: `ç»ææ¥æ ${endDate.value} æ©äºå¼å§æ¥æ ${startDate.value}ï¼è¯·éæ°éæ©`, icon: "none" }); return; } load(true); } function resetFilter() { warehouseId.value = undefined; startDate.value = ""; endDate.value = ""; load(true); } onShow(() => load(true)); onPullDownRefresh(async () => { await load(true); uni.stopPullDownRefresh(); }); </script> <template> <view class="page"> <template v-if="isLoggedIn"> <view v-if="canQuery" class="filters"> <picker :range="warehouseOptions" @change="pickWarehouse"> <view class="picker"><text>{{ warehouseLabel }}</text><text>âº</text></view> </picker> <view class="dates"> <picker mode="date" :value="startDate" @change="pickStart"> <view class="picker"><text>{{ startDate || "å¼å§æ¥æ" }}</text><text>âº</text></view> </picker> <text class="dash">è³</text> <picker mode="date" :value="endDate" @change="pickEnd"> <view class="picker"><text>{{ endDate || "ç»ææ¥æ" }}</text><text>âº</text></view> </picker> </view> <view class="filter-actions"> <text>é»è®¤ç»è®¡è¿ä¸ä¸ªæï¼å·²å è½½ {{ rows.length }} / {{ total }} è¡</text> <text class="reset" @click="resetFilter">éç½®</text> </view> </view> <view v-if="canQuery && rows.length" class="totals"> <view><text>æå</text><text>{{ formatNumber(totals.opening) }}</text></view> <view><text>å ¥åº</text><text class="pos">{{ formatNumber(totals.inbound) }}</text></view> <view><text>åºåº</text><text class="neg">{{ formatNumber(totals.outbound) }}</text></view> <view><text>ææ«</text><text>{{ formatNumber(totals.closing) }}</text></view> </view> <scroll-view v-if="canQuery" class="content" scroll-y lower-threshold="80" @scrolltolower="hasMore && load()"> <view v-if="loadError" class="state error" @click="load(true)">{{ loadError }}ï¼ç¹å»éè¯</view> <view v-else-if="rows.length"> <view v-for="row in rows" :key="`${row.itemId}-${row.warehouseId}`" class="row"> <view class="head"> <view><text class="name">{{ row.itemName || "æªå½åç©æ" }}</text><text>{{ row.itemCode || "ææ ç¼ç " }} / {{ row.warehouseName || "æªæå®ä»åº" }}</text></view> <text :class="['status', mismatch(row) ? 'tag-danger' : 'tag-success']">{{ mismatch(row) ? "è´¦å®å¾ æ ¸" : "è´¦å®ç¸ç¬¦" }}</text> </view> <view class="grid"> <text>è§æ ¼åå·ï¼{{ row.specification || "--" }}</text> <text>åä½ï¼{{ row.unitMeasureName || "--" }}</text> <text>æåæ°éï¼{{ formatNumber(row.openingQuantity) }}</text> <text>æ¬æå ¥åºï¼{{ formatNumber(row.inQuantity) }}</text> <text>æ¬æåºåºï¼{{ formatNumber(row.outQuantity) }}</text> <text>ææ«æ°éï¼{{ formatNumber(row.closingQuantity) }}</text> </view> </view> <text class="more">{{ loading ? "å è½½ä¸..." : hasMore ? "䏿å è½½æ´å¤" : "æ²¡ææ´å¤äº" }}</text> </view> <view v-else class="state">{{ loading ? "å è½½ä¸..." : "æéæ¡ä»¶ä¸ææ æ¶ååæ°æ®" }}</view> </scroll-view> <view v-else class="state">ææ æ¶ååæ±æ»æ¥çæé</view> </template> <view v-else class="state">ç»å½åæ¥çæ¶ååæ±æ»</view> </view> </template> <style lang="scss" scoped> .page{height:100vh;display:flex;flex-direction:column;padding:0 24rpx;box-sizing:border-box;overflow:hidden;background:#f3f6fb}.filters{flex:none;padding:20rpx;margin:20rpx 0 14rpx;background:#fff;border-radius:16rpx;box-shadow:0 8rpx 20rpx rgba(43,69,107,.06)}.picker{display:flex;align-items:center;justify-content:space-between;gap:12rpx;min-height:66rpx;padding:0 18rpx;color:#31445c;font-size:25rpx;background:#f6f8fb;border-radius:12rpx}.picker text:first-child{min-width:0;flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.picker text:last-child{flex:none;color:#8092a7;font-size:32rpx}.dates{display:flex;align-items:center;gap:12rpx;margin-top:12rpx}.dates picker{flex:1;min-width:0}.dash{flex:none;color:#8795a8;font-size:24rpx}.filter-actions{display:flex;align-items:center;justify-content:space-between;margin-top:12rpx;color:#8795a8;font-size:22rpx}.filter-actions .reset{color:var(--color-primary, #1769d8);font-size:24rpx}.totals{flex:none;display:flex;margin-bottom:14rpx;background:#fff;border-radius:16rpx;box-shadow:0 8rpx 20rpx rgba(43,69,107,.06)}.totals view{flex:1;display:flex;flex-direction:column;align-items:center;gap:6rpx;padding:16rpx 0;border-right:1rpx solid #f1f4f8}.totals view:last-child{border-right:0}.totals text:first-child{color:#8795a8;font-size:22rpx}.totals text:last-child{color:#20334c;font-size:27rpx;font-weight:600}.totals .pos{color:#17865a}.totals .neg{color:#c65050}.content{height:0;flex:1;padding-bottom:80rpx;box-sizing:border-box}.row{padding:24rpx 22rpx 20rpx;margin-bottom:18rpx;background:#fff;border-radius:16rpx;box-shadow:0 10rpx 24rpx rgba(43,69,107,.08)}.head{display:flex;align-items:flex-start;justify-content:space-between;gap:16rpx}.head>view:first-child{display:flex;min-width:0;flex:1;flex-direction:column;gap:8rpx}.head>view:first-child text:last-child{color:#78879b;font-size:23rpx}.name{overflow:hidden;color:#20334c;font-size:30rpx;font-weight:600;text-overflow:ellipsis;white-space:nowrap}.status{display:inline-flex;align-items:center;flex:none;height:44rpx;padding:0 18rpx;font-size:23rpx;border-radius:999rpx}.tag-success{color:#17865a;background:#eafaf2;border:1rpx solid #b8efd5}.tag-danger{color:#c65050;background:#fff7f7;border:1rpx solid #e3a0a0}.grid{display:grid;grid-template-columns:1fr 1fr;gap:10rpx;padding-top:16rpx;margin-top:16rpx;color:#66778c;font-size:23rpx;border-top:1rpx solid #edf1f6}.grid text{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.more,.state{display:block;padding:35rpx;color:#8795a8;font-size:26rpx;text-align:center}.error{color:#c3654d} </style> src/pages/workbench/index.vue
@@ -159,6 +159,8 @@ */ function resolveModuleGroup(app: WorkbenchApp): string { if (MODULE_GROUPS.includes(app.group)) return app.group; // è´¨é管çï¼æ¥æ/è¿ç¨/åºè´§/éè´§æ£éªãä¸åæ ¼åãæ¹æ¬¡è¿½æº¯ï¼å±äºç产管æ§ï¼æ¹æ¡éä¸å忍¡å if (app.group === "è´¨é管ç") return "ç产管æ§"; if (app.group === "åºç¡æ°æ®") { return /设å¤ç±»å|mes\/dv\//.test(`${app.name} ${app.component}`) ? "设å¤ç®¡ç"