| .claude/settings.local.json | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| rsf-design/.env.development | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| rsf-design/src/api/wave.js | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| rsf-design/src/locales/langs/zh.json | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| rsf-design/src/views/orders/out-stock/index.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| rsf-design/src/views/orders/out-stock/outStockPage.helpers.js | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| rsf-open-api/src/main/resources/application-dev.yml | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| rsf-open-api/src/main/resources/application-prod.yml | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| rsf-server/src/main/java/com/vincent/rsf/server/common/exception/GlobalExceptionHandler.java | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| rsf-server/src/main/resources/application-dev.yml | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| rsf-server/src/main/resources/application-prod.yml | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 |
.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:*)" ] } } 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 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', 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": "完成成功", 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'), { 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 [ 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: 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 port: 3741 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"; } } 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: 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