| | |
| | | "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:*)" |
| | | ] |
| | | } |
| | | } |
| | |
| | | VITE_DROP_CONSOLE = false |
| | | |
| | | # 是否开启 Vue DevTools / Inspector(开启后会增加本地模块转换耗时) |
| | | VITE_ENABLE_VUE_DEVTOOLS = false |
| | | VITE_ENABLE_VUE_DEVTOOLS = true |
| | |
| | | }) |
| | | } |
| | | |
| | | 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', |
| | |
| | | "view": "查看详情", |
| | | "items": "明细", |
| | | "print": "打印", |
| | | "createWave": "生成波次", |
| | | "complete": "完成", |
| | | "cancel": "取消", |
| | | "delete": "删除" |
| | |
| | | "detailTimeout": "出库单详情加载超时,已停止等待", |
| | | "itemsTimeout": "出库单明细加载超时,已停止等待", |
| | | "detailLoadFailed": "获取出库单详情失败", |
| | | "createWaveTitle": "生成波次", |
| | | "createWaveConfirm": "确定为已选择的 {count} 条出库单生成波次吗?", |
| | | "createWaveSuccess": "波次生成成功", |
| | | "createWaveFailed": "波次生成失败", |
| | | "createWaveSelectionRequired": "请先选择出库单", |
| | | "completeTitle": "完成确认", |
| | | "completeConfirm": "确定完成出库单 {code} 吗?", |
| | | "completeSuccess": "完成成功", |
| | |
| | | <ElCard class="art-table-card"> |
| | | <ArtTableHeader v-model:columns="columnChecks" :loading="loading" @refresh="refreshData"> |
| | | <template #left> |
| | | <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" |
| | |
| | | @export="handleExport" |
| | | @print="handlePrint" |
| | | /> |
| | | </ElSpace> |
| | | </template> |
| | | </ArtTableHeader> |
| | | |
| | |
| | | |
| | | <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' |
| | |
| | | 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' |
| | |
| | | const detailData = ref({}) |
| | | const detailItemRows = ref([]) |
| | | const activeOutStockId = ref(null) |
| | | const createWaveLoading = ref(false) |
| | | const detailItemPagination = reactive({ |
| | | current: 1, |
| | | size: 20, |
| | |
| | | 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'), { |
| | |
| | | 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 |
| | |
| | | } |
| | | } |
| | | |
| | | 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 [ |
| | |
| | | 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: |
| | |
| | | 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 |
| | |
| | | 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; |
| | |
| | | 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) { |
| | |
| | | @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"; |
| | | } |
| | | |
| | | } |
| | | |
| | |
| | | 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 |
| | |
| | | #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: |
| | |
| | | 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 |