From 088e1ba6624c7523ee2566110b2c4721a37204a5 Mon Sep 17 00:00:00 2001
From: zhou zhou <3272660260@qq.com>
Date: 星期四, 09 四月 2026 14:22:09 +0800
Subject: [PATCH] #生成波次
---
rsf-open-api/src/main/resources/application-prod.yml | 17 ++--
rsf-server/src/main/resources/application-prod.yml | 15 ++-
rsf-design/.env.development | 2
rsf-open-api/src/main/resources/application-dev.yml | 11 +-
.claude/settings.local.json | 5 +
rsf-design/src/locales/langs/zh.json | 6 +
rsf-server/src/main/java/com/vincent/rsf/server/common/exception/GlobalExceptionHandler.java | 43 ++++++++++
rsf-design/src/api/wave.js | 7 +
rsf-design/src/views/orders/out-stock/index.vue | 73 ++++++++++++++---
rsf-design/src/views/orders/out-stock/outStockPage.helpers.js | 22 +++++
rsf-server/src/main/resources/application-dev.yml | 9 +-
11 files changed, 169 insertions(+), 41 deletions(-)
diff --git a/.claude/settings.local.json b/.claude/settings.local.json
index 6683a8f..0bb830b 100644
--- a/.claude/settings.local.json
+++ b/.claude/settings.local.json
@@ -11,7 +11,10 @@
"Read(//d/Program Files/Git/bin/**)",
"Read(//d/Program Files/Git/usr/bin/**)",
"Bash(CLAUDE_CODE_GIT_BASH_PATH=\"D:/Program Files/Git/bin/bash.exe\" claude mcp:*)",
- "Bash(CLAUDE_CODE_GIT_BASH_PATH='D:\\\\Program Files\\\\Git\\\\bin\\\\bash.exe' claude mcp:*)"
+ "Bash(CLAUDE_CODE_GIT_BASH_PATH='D:\\\\Program Files\\\\Git\\\\bin\\\\bash.exe' claude mcp:*)",
+ "Bash(git:*)",
+ "Bash(python:*)",
+ "Bash(npm:*)"
]
}
}
diff --git a/rsf-design/.env.development b/rsf-design/.env.development
index 8b3e566..3e69e34 100644
--- a/rsf-design/.env.development
+++ b/rsf-design/.env.development
@@ -13,4 +13,4 @@
VITE_DROP_CONSOLE = false
# 鏄惁寮�鍚� Vue DevTools / Inspector锛堝紑鍚悗浼氬鍔犳湰鍦版ā鍧楄浆鎹㈣�楁椂锛�
-VITE_ENABLE_VUE_DEVTOOLS = false
+VITE_ENABLE_VUE_DEVTOOLS = true
diff --git a/rsf-design/src/api/wave.js b/rsf-design/src/api/wave.js
index 4695632..84b69ff 100644
--- a/rsf-design/src/api/wave.js
+++ b/rsf-design/src/api/wave.js
@@ -103,6 +103,13 @@
})
}
+export function fetchCreateOutStockWave(payload = {}) {
+ return request.post({
+ url: '/outStock/generate/wave',
+ data: payload
+ })
+}
+
export async function fetchExportWaveReport(payload = {}, options = {}) {
return fetch(`${import.meta.env.VITE_API_URL}/wave/export`, {
method: 'POST',
diff --git a/rsf-design/src/locales/langs/zh.json b/rsf-design/src/locales/langs/zh.json
index cc520d7..7b7b4ba 100644
--- a/rsf-design/src/locales/langs/zh.json
+++ b/rsf-design/src/locales/langs/zh.json
@@ -1415,6 +1415,7 @@
"view": "鏌ョ湅璇︽儏",
"items": "鏄庣粏",
"print": "鎵撳嵃",
+ "createWave": "鐢熸垚娉㈡",
"complete": "瀹屾垚",
"cancel": "鍙栨秷",
"delete": "鍒犻櫎"
@@ -1466,6 +1467,11 @@
"detailTimeout": "鍑哄簱鍗曡鎯呭姞杞借秴鏃讹紝宸插仠姝㈢瓑寰�",
"itemsTimeout": "鍑哄簱鍗曟槑缁嗗姞杞借秴鏃讹紝宸插仠姝㈢瓑寰�",
"detailLoadFailed": "鑾峰彇鍑哄簱鍗曡鎯呭け璐�",
+ "createWaveTitle": "鐢熸垚娉㈡",
+ "createWaveConfirm": "纭畾涓哄凡閫夋嫨鐨� {count} 鏉″嚭搴撳崟鐢熸垚娉㈡鍚楋紵",
+ "createWaveSuccess": "娉㈡鐢熸垚鎴愬姛",
+ "createWaveFailed": "娉㈡鐢熸垚澶辫触",
+ "createWaveSelectionRequired": "璇峰厛閫夋嫨鍑哄簱鍗�",
"completeTitle": "瀹屾垚纭",
"completeConfirm": "纭畾瀹屾垚鍑哄簱鍗� {code} 鍚楋紵",
"completeSuccess": "瀹屾垚鎴愬姛",
diff --git a/rsf-design/src/views/orders/out-stock/index.vue b/rsf-design/src/views/orders/out-stock/index.vue
index c3d5018..77e0798 100644
--- a/rsf-design/src/views/orders/out-stock/index.vue
+++ b/rsf-design/src/views/orders/out-stock/index.vue
@@ -11,20 +11,25 @@
<ElCard class="art-table-card">
<ArtTableHeader v-model:columns="columnChecks" :loading="loading" @refresh="refreshData">
<template #left>
- <ListExportPrint
- :preview-visible="previewVisible"
- @update:previewVisible="handlePreviewVisibleChange"
- :report-title="reportTitle"
- :selected-rows="selectedRows"
- :query-params="reportQueryParams"
- :columns="columns"
- :preview-rows="previewRows"
- :preview-meta="resolvedPreviewMeta"
- :total="pagination.total"
- :disabled="loading"
- @export="handleExport"
- @print="handlePrint"
- />
+ <ElSpace wrap>
+ <ElButton type="primary" :loading="createWaveLoading" :disabled="loading || selectedRows.length === 0" @click="handleCreateWave">
+ {{ t('pages.orders.outStock.actions.createWave') }}
+ </ElButton>
+ <ListExportPrint
+ :preview-visible="previewVisible"
+ @update:previewVisible="handlePreviewVisibleChange"
+ :report-title="reportTitle"
+ :selected-rows="selectedRows"
+ :query-params="reportQueryParams"
+ :columns="columns"
+ :preview-rows="previewRows"
+ :preview-meta="resolvedPreviewMeta"
+ :total="pagination.total"
+ :disabled="loading"
+ @export="handleExport"
+ @print="handlePrint"
+ />
+ </ElSpace>
</template>
</ArtTableHeader>
@@ -56,7 +61,7 @@
<script setup>
import { computed, reactive, ref } from 'vue'
- import { ElMessage, ElMessageBox } from 'element-plus'
+ import { ElButton, ElMessage, ElMessageBox, ElSpace } from 'element-plus'
import { useRouter } from 'vue-router'
import { useI18n } from 'vue-i18n'
import { useUserStore } from '@/store/modules/user'
@@ -75,14 +80,17 @@
fetchGetOutStockMany,
fetchOutStockPage
} from '@/api/out-stock'
+ import { fetchCreateOutStockWave } from '@/api/wave'
import OutStockDetailDrawer from './modules/out-stock-detail-drawer.vue'
import {
OUT_STOCK_REPORT_STYLE,
+ buildCreateWavePayload,
buildOutStockPageQueryParams,
buildOutStockPrintRows,
buildOutStockReportMeta,
buildOutStockSearchParams,
createOutStockSearchState,
+ getCreateWaveValidationMessage,
getOutStockReportTitle,
normalizeOutStockRow
} from './outStockPage.helpers'
@@ -104,6 +112,7 @@
const detailData = ref({})
const detailItemRows = ref([])
const activeOutStockId = ref(null)
+ const createWaveLoading = ref(false)
const detailItemPagination = reactive({
current: 1,
size: 20,
@@ -269,6 +278,40 @@
loadDetailResources()
}
+ async function handleCreateWave() {
+ const validationMessage = getCreateWaveValidationMessage(selectedRows.value, t)
+ if (validationMessage) {
+ ElMessage.warning(validationMessage)
+ return
+ }
+
+ try {
+ await ElMessageBox.confirm(
+ t('pages.orders.outStock.messages.createWaveConfirm', {
+ count: selectedRows.value.length
+ }),
+ t('pages.orders.outStock.messages.createWaveTitle'),
+ {
+ confirmButtonText: t('common.confirm'),
+ cancelButtonText: t('common.cancel'),
+ type: 'warning'
+ }
+ )
+
+ createWaveLoading.value = true
+ await fetchCreateOutStockWave(buildCreateWavePayload(selectedRows.value))
+ ElMessage.success(t('pages.orders.outStock.messages.createWaveSuccess'))
+ selectedRows.value = []
+ await refreshData()
+ router.push('/orders/wave')
+ } catch (error) {
+ if (error === 'cancel' || error?.message === 'cancel') return
+ ElMessage.error(error?.message || t('pages.orders.outStock.messages.createWaveFailed'))
+ } finally {
+ createWaveLoading.value = false
+ }
+ }
+
async function handleComplete(row) {
try {
await ElMessageBox.confirm(t('pages.orders.outStock.messages.completeConfirm', { code: row.code || '' }), t('pages.orders.outStock.messages.completeTitle'), {
diff --git a/rsf-design/src/views/orders/out-stock/outStockPage.helpers.js b/rsf-design/src/views/orders/out-stock/outStockPage.helpers.js
index 7e213da..b35e20d 100644
--- a/rsf-design/src/views/orders/out-stock/outStockPage.helpers.js
+++ b/rsf-design/src/views/orders/out-stock/outStockPage.helpers.js
@@ -35,6 +35,14 @@
return String(value ?? '').trim()
}
+function normalizeIds(rows = []) {
+ return Array.isArray(rows)
+ ? rows
+ .map((row) => Number(row?.id))
+ .filter((id) => Number.isFinite(id))
+ : []
+}
+
function normalizeNumber(value, fallback = void 0) {
if (value === '' || value === null || value === undefined) {
return fallback
@@ -196,6 +204,20 @@
}
}
+export function buildCreateWavePayload(rows = []) {
+ return {
+ ids: normalizeIds(rows)
+ }
+}
+
+export function getCreateWaveValidationMessage(rows = [], t) {
+ const ids = normalizeIds(rows)
+ if (ids.length === 0) {
+ return translate(t, 'pages.orders.outStock.messages.createWaveSelectionRequired')
+ }
+ return ''
+}
+
export function getOutStockActionList(row = {}, t) {
const normalizedRow = normalizeOutStockRow(row, t)
return [
diff --git a/rsf-open-api/src/main/resources/application-dev.yml b/rsf-open-api/src/main/resources/application-dev.yml
index ad49eca..6536f74 100644
--- a/rsf-open-api/src/main/resources/application-dev.yml
+++ b/rsf-open-api/src/main/resources/application-dev.yml
@@ -27,23 +27,24 @@
min-idle: 5
max-active: 20
max-wait: 30000
+ keep-alive: true
time-between-eviction-runs-millis: 60000
min-evictable-idle-time-millis: 300000
test-while-idle: true
- test-on-borrow: true
+ test-on-borrow: false
test-on-return: false
remove-abandoned: true
remove-abandoned-timeout: 1800
#pool-prepared-statements: false
#max-pool-prepared-statement-per-connection-size: 20
filters: stat, wall
- validation-query: SELECT 'x'
- aop-patterns: com.zy.*.*.service.*
+ validation-query: SELECT 1
+ aop-patterns: com.vincent.rsf.openApi.*.service.*
stat-view-servlet:
url-pattern: /druid/*
reset-enable: true
- login-username: admin
- login-password: admin
+ login-username: ${DRUID_STAT_USER:admin}
+ login-password: ${DRUID_STAT_PWD:admin123}
enabled: true
servlet:
multipart:
diff --git a/rsf-open-api/src/main/resources/application-prod.yml b/rsf-open-api/src/main/resources/application-prod.yml
index 9c850fa..9501c2b 100644
--- a/rsf-open-api/src/main/resources/application-prod.yml
+++ b/rsf-open-api/src/main/resources/application-prod.yml
@@ -19,24 +19,25 @@
min-idle: 5
max-active: 20
max-wait: 30000
+ keep-alive: true
time-between-eviction-runs-millis: 60000
min-evictable-idle-time-millis: 300000
test-while-idle: true
- test-on-borrow: true
+ test-on-borrow: false
test-on-return: false
- remove-abandoned: true
+ remove-abandoned: false
remove-abandoned-timeout: 1800
#pool-prepared-statements: false
#max-pool-prepared-statement-per-connection-size: 20
filters: stat, wall
- validation-query: SELECT 'x'
- aop-patterns: com.zy.*.*.service.*
+ validation-query: SELECT 1
+ aop-patterns: com.vincent.rsf.openApi.*.service.*
stat-view-servlet:
url-pattern: /druid/*
reset-enable: true
- login-username: admin
- login-password: admin
- enabled: true
+ login-username: ${DRUID_STAT_USER:admin}
+ login-password: ${DRUID_STAT_PWD:admin123}
+ enabled: false
servlet:
multipart:
maxFileSize: 100MB
@@ -84,4 +85,4 @@
#閾炬帴
host: http://www.itsdg.cn
#绔彛
- port: 3741
\ No newline at end of file
+ port: 3741
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/common/exception/GlobalExceptionHandler.java b/rsf-server/src/main/java/com/vincent/rsf/server/common/exception/GlobalExceptionHandler.java
index 558bec6..ea691e6 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/common/exception/GlobalExceptionHandler.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/common/exception/GlobalExceptionHandler.java
@@ -12,6 +12,7 @@
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
+import org.springframework.web.context.request.async.AsyncRequestNotUsableException;
import jakarta.servlet.http.HttpServletResponse;
import java.util.regex.Matcher;
@@ -79,10 +80,18 @@
return R.error(out);
}
+ @ExceptionHandler(AsyncRequestNotUsableException.class)
+ public void asyncRequestNotUsableExceptionHandler(AsyncRequestNotUsableException e) {
+ logger.warn("Client connection aborted: {}", resolveAbortMessage(e));
+ }
@ResponseBody
@ExceptionHandler(RuntimeException.class)
public R runtimeExceptionHandler(RuntimeException e, HttpServletResponse response) {
+ if (isClientAbortException(e)) {
+ logger.warn("Client connection aborted: {}", resolveAbortMessage(e));
+ return null;
+ }
CommonUtil.addCrossHeaders(response);
Throwable cause = e.getCause();
if (cause instanceof CoolException) {
@@ -95,10 +104,44 @@
@ResponseBody
@ExceptionHandler(Throwable.class)
public R exceptionHandler(Throwable e, HttpServletResponse response) {
+ if (isClientAbortException(e)) {
+ logger.warn("Client connection aborted: {}", resolveAbortMessage(e));
+ return null;
+ }
logger.error(e.getMessage(), e);
CommonUtil.addCrossHeaders(response);
return R.error(Constants.RESULT_ERROR_MSG);
}
+ private boolean isClientAbortException(Throwable throwable) {
+ Throwable current = throwable;
+ while (current != null) {
+ String message = current.getMessage();
+ if (message != null) {
+ String normalized = message.toLowerCase();
+ if (normalized.contains("broken pipe")
+ || normalized.contains("connection reset")
+ || normalized.contains("forcibly closed")
+ || normalized.contains("abort")) {
+ return true;
+ }
+ }
+ current = current.getCause();
+ }
+ return false;
+ }
+
+ private String resolveAbortMessage(Throwable throwable) {
+ Throwable current = throwable;
+ while (current != null) {
+ String message = current.getMessage();
+ if (message != null && !message.isBlank()) {
+ return message;
+ }
+ current = current.getCause();
+ }
+ return "client aborted connection";
+ }
+
}
diff --git a/rsf-server/src/main/resources/application-dev.yml b/rsf-server/src/main/resources/application-dev.yml
index 5e7940f..ce088af 100644
--- a/rsf-server/src/main/resources/application-dev.yml
+++ b/rsf-server/src/main/resources/application-dev.yml
@@ -25,10 +25,11 @@
min-idle: 5
max-active: 20
max-wait: 30000
+ keep-alive: true
time-between-eviction-runs-millis: 60000
min-evictable-idle-time-millis: 300000
test-while-idle: true
- test-on-borrow: true
+ test-on-borrow: false
test-on-return: false
remove-abandoned: true
remove-abandoned-timeout: 1800
@@ -36,12 +37,12 @@
#max-pool-prepared-statement-per-connection-size: 20
filters: stat, wall
validation-query: SELECT 1
- aop-patterns: com.zy.*.*.service.*
+ aop-patterns: com.vincent.rsf.server.*.service.*
stat-view-servlet:
url-pattern: /druid/*
reset-enable: true
- login-username: admin
- login-password: admin
+ login-username: ${DRUID_STAT_USER:admin}
+ login-password: ${DRUID_STAT_PWD:admin123}
enabled: true
servlet:
multipart:
diff --git a/rsf-server/src/main/resources/application-prod.yml b/rsf-server/src/main/resources/application-prod.yml
index 569d601..fc96c6b 100644
--- a/rsf-server/src/main/resources/application-prod.yml
+++ b/rsf-server/src/main/resources/application-prod.yml
@@ -19,24 +19,25 @@
min-idle: 5
max-active: 20
max-wait: 30000
+ keep-alive: true
time-between-eviction-runs-millis: 60000
min-evictable-idle-time-millis: 300000
test-while-idle: true
- test-on-borrow: true
+ test-on-borrow: false
test-on-return: false
- remove-abandoned: true
+ remove-abandoned: false
remove-abandoned-timeout: 1800
#pool-prepared-statements: false
#max-pool-prepared-statement-per-connection-size: 20
filters: stat, wall
- validation-query: SELECT 'x'
- aop-patterns: com.zy.*.*.service.*
+ validation-query: SELECT 1
+ aop-patterns: com.vincent.rsf.server.*.service.*
stat-view-servlet:
url-pattern: /druid/*
reset-enable: true
- login-username: admin
- login-password: admin
- enabled: true
+ login-username: ${DRUID_STAT_USER:admin}
+ login-password: ${DRUID_STAT_PWD:admin123}
+ enabled: false
servlet:
multipart:
maxFileSize: 100MB
--
Gitblit v1.9.1