refactor: 合并原库房客户端到主WMS服务,退役8891端口

1.  将bj_power_wms_client的前端代码、配置及网关逻辑全部迁移到bj_power_wms主项目
2.  删除原库房客户端相关的所有文件与配置
3.  更新README与部署文档,说明合并后的服务架构
4.  新增Web配置项支持自动打开浏览器开关,统一服务端口为8890
This commit is contained in:
SunYF
2026-09-07 15:55:30 +08:00
parent 649d2cc358
commit 2544c6141d
49 changed files with 115 additions and 488 deletions
@@ -0,0 +1,455 @@
<script setup>
defineOptions({ name: 'BaseData' })
import { computed, reactive, ref, watch } from 'vue'
import { ElMessage } from 'element-plus'
import request from '../utils/request'
import { fmtTime } from '../utils/format'
import { can } from '../utils/perm'
import { exportXlsxFetch } from '../utils/export'
import PageHelp from '../components/PageHelp.vue'
import { helpZone, helpMaterial } from '../help'
// mode: 'zone' = 区域维护;'material' = 物料档案(同一组件拆成两个独立菜单)
const props = defineProps({
mode: { type: String, default: 'zone' }
})
// 帮助文档按菜单各自独立
const helpData = computed(() => (props.mode === 'material' ? helpMaterial : helpZone))
// 默认近3个月起始日期
function defaultStart() {
const d = new Date()
d.setMonth(d.getMonth() - 3)
const y = d.getFullYear()
const m = String(d.getMonth() + 1).padStart(2, '0')
const day = String(d.getDate()).padStart(2, '0')
return `${y}-${m}-${day}`
}
/* ---------- 区域维护 ---------- */
const zones = ref([])
const zoneTotal = ref(0)
const zoneLoading = ref(false)
// 筛选拆分为独立条件(区域维护无需时间范围,已去除)
const zoneFilter = reactive({
zoneCode: '', zoneName: '', status: ''
})
const zonePage = reactive({ current: 1, size: 10 })
const zoneDialog = ref(false)
const zoneEditingId = ref(0)
const zoneForm = reactive({ zoneCode: '', zoneName: '', description: '' })
const zoneSaving = ref(false)
async function loadZones() {
zoneLoading.value = true
try {
const data = await request.get('/zone/list', {
params: {
zoneCode: zoneFilter.zoneCode.trim(),
zoneName: zoneFilter.zoneName.trim(),
status: zoneFilter.status,
page: zonePage.current,
pageSize: zonePage.size
}
})
zones.value = data?.list || []
zoneTotal.value = data?.total || 0
} finally {
zoneLoading.value = false
}
}
function searchZones() {
zonePage.current = 1
loadZones()
}
function openZoneCreate() {
zoneEditingId.value = 0
Object.assign(zoneForm, { zoneCode: '', zoneName: '', description: '' })
zoneDialog.value = true
}
function openZoneEdit(row) {
zoneEditingId.value = row.id
Object.assign(zoneForm, {
zoneCode: row.zoneCode, zoneName: row.zoneName, description: row.description || ''
})
zoneDialog.value = true
}
async function saveZone() {
if (!zoneForm.zoneCode || !zoneForm.zoneName) {
ElMessage.warning('区域编码和区域名称必填')
return
}
zoneSaving.value = true
try {
if (zoneEditingId.value) {
await request.post('/zone/update', { id: zoneEditingId.value, ...zoneForm })
ElMessage.success('区域修改成功')
} else {
await request.post('/zone/create', { ...zoneForm })
ElMessage.success('区域新增成功')
}
zoneDialog.value = false
await loadZones()
} finally {
zoneSaving.value = false
}
}
/* ---------- 物料档案维护 ---------- */
const materials = ref([])
const materialTotal = ref(0)
const materialLoading = ref(false)
const matFilter = reactive({
materialCode: '', name: '', spec: '', manageMode: '', startDate: defaultStart(), endDate: ''
})
const matPage = reactive({ current: 1, size: 10 })
const matDialog = ref(false)
const mEditingId = ref(0)
const mForm = reactive({
code: '', name: '', shortName: '', spec: '', unit: '',
manageMode: 1, templateCode: '', description: ''
})
const mSaving = ref(false)
async function loadMaterials() {
materialLoading.value = true
try {
const data = await request.get('/material/query', {
params: {
materialCode: matFilter.materialCode.trim(),
name: matFilter.name.trim(),
spec: matFilter.spec.trim(),
manageMode: matFilter.manageMode,
startDate: matFilter.startDate,
endDate: matFilter.endDate,
page: matPage.current,
pageSize: matPage.size
}
})
materials.value = data?.list || []
materialTotal.value = data?.total || 0
} finally {
materialLoading.value = false
}
}
function searchMaterials() {
matPage.current = 1
loadMaterials()
}
function resetMForm() {
mEditingId.value = 0
Object.assign(mForm, {
code: '', name: '', shortName: '', spec: '', unit: '',
manageMode: 1, templateCode: '', description: ''
})
}
function openMatCreate() {
resetMForm()
matDialog.value = true
}
function openMatEdit(row) {
mEditingId.value = row.id
Object.assign(mForm, {
code: row.code, name: row.name, shortName: row.shortName || '',
spec: row.spec || '', unit: row.unit || '', manageMode: row.manageMode,
templateCode: row.templateCode || '',
description: row.description || ''
})
matDialog.value = true
}
async function saveMaterial() {
if (!mForm.code || !mForm.name) {
ElMessage.warning('物料编码和名称必填')
return
}
mSaving.value = true
try {
if (mEditingId.value) {
await request.post('/material/update', {
id: mEditingId.value, name: mForm.name, shortName: mForm.shortName,
spec: mForm.spec, unit: mForm.unit, description: mForm.description,
manageMode: mForm.manageMode
})
ElMessage.success('物料修改成功')
} else {
await request.post('/material/create', { ...mForm })
ElMessage.success('物料新增成功')
}
matDialog.value = false
await loadMaterials()
} finally {
mSaving.value = false
}
}
// 区域维护 / 物料档案共用同一组件:切换菜单时复用,watch(mode, immediate) 保证进入即加载
watch(
() => props.mode,
(m) => { if (m === 'material') loadMaterials(); else loadZones() },
{ immediate: true }
)
/* ---------- 物料档案:导入 / 导出 ---------- */
const matExporting = ref(false)
const matImporting = ref(false)
const matFileRef = ref(null)
async function exportMaterials() {
matExporting.value = true
try {
const name = await exportXlsxFetch('/api/material/export', {
materialCode: matFilter.materialCode.trim(),
name: matFilter.name.trim(),
spec: matFilter.spec.trim(),
manageMode: matFilter.manageMode
}, '物料档案.xlsx')
ElMessage.success('已导出:' + name)
} catch (e) {
ElMessage.error('导出失败:' + (e?.message || e))
} finally {
matExporting.value = false
}
}
function pickMaterialFile() {
matFileRef.value?.click()
}
async function onMaterialFile(e) {
const file = e.target.files?.[0]
e.target.value = '' // 允许重复选择同一文件
if (!file) return
matImporting.value = true
try {
const fd = new FormData()
fd.append('file', file)
const token = JSON.parse(localStorage.getItem('token') || '""')
const res = await fetch('/api/material/import', {
method: 'POST',
headers: { Authorization: 'Bearer ' + token },
body: fd
})
const body = await res.json().catch(() => ({}))
const d = body?.data || {}
if (!res.ok || body?.code !== 0) {
ElMessage.error('导入失败:' + (body?.message || '未知错误'))
return
}
if (d.errors && d.errors.length) {
const lines = d.errors.slice(0, 10)
.map((x) => `${x.row}${x.materialCode || ''}${x.message}`).join('')
ElMessage.error('导入失败:' + lines + (d.errors.length > 10 ? ' …' : ''))
return
}
ElMessage.success(`导入成功 ${d.success || 0}`)
await loadMaterials()
} catch (err) {
ElMessage.error('导入异常:' + (err?.message || err))
} finally {
matImporting.value = false
}
}
</script>
<template>
<div>
<el-card shadow="never">
<!-- 区域维护 -->
<template v-if="mode !== 'material'">
<el-form inline style="margin-bottom:14px" @submit.prevent="searchZones">
<el-form-item label="区域编码">
<el-input v-model="zoneFilter.zoneCode" placeholder="区域编码" clearable style="width:140px"
@keyup.enter="searchZones" />
</el-form-item>
<el-form-item label="区域名称">
<el-input v-model="zoneFilter.zoneName" placeholder="区域名称" clearable style="width:150px"
@keyup.enter="searchZones" />
</el-form-item>
<el-form-item label="状态">
<el-select v-model="zoneFilter.status" placeholder="全部" clearable style="width:110px" @change="searchZones">
<el-option label="启用" value="启用" />
<el-option label="停用" value="停用" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" :loading="zoneLoading" @click="searchZones">查询</el-button>
<el-button type="success" @click="openZoneCreate">新增区域</el-button>
</el-form-item>
</el-form>
<el-table :data="zones" border size="small" v-loading="zoneLoading" empty-text="暂无区域,请点击【新增区域】">
<el-table-column label="ID" prop="id" width="60" align="center" />
<el-table-column prop="zoneCode" label="区域编码" min-width="120" />
<el-table-column prop="zoneName" label="区域名称" min-width="150" />
<el-table-column label="状态" width="90" align="center">
<template #default="{ row }">
<el-tag :type="row.status === '停用' ? 'info' : 'success'" size="small">
{{ row.status || '启用' }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="description" label="说明" min-width="180">
<template #default="{ row }">{{ row.description || '-' }}</template>
</el-table-column>
<el-table-column label="创建时间" width="155" align="center">
<template #default="{ row }">{{ fmtTime(row.createdAt) }}</template>
</el-table-column>
<el-table-column label="操作" width="100" align="center">
<template #default="{ row }">
<el-button link type="primary" size="small" @click="openZoneEdit(row)">编辑</el-button>
</template>
</el-table-column>
</el-table>
<div class="pager">
<el-pagination background layout="total, sizes, prev, pager, next" :total="zoneTotal"
:current-page="zonePage.current" :page-size="zonePage.size" :page-sizes="[10, 20, 50, 100]"
@current-change="(p) => { zonePage.current = p; loadZones() }"
@size-change="(s) => { zonePage.size = s; zonePage.current = 1; loadZones() }" />
</div>
<el-dialog v-model="zoneDialog" :title="zoneEditingId ? '编辑区域' : '新增区域'" width="460px"
append-to-body destroy-on-close>
<el-form label-width="90px">
<el-form-item label="区域编码" required>
<el-input v-model="zoneForm.zoneCode" placeholder=" Z05" :disabled="!!zoneEditingId" clearable />
</el-form-item>
<el-form-item label="区域名称" required>
<el-input v-model="zoneForm.zoneName" placeholder=" 待检区" clearable />
</el-form-item>
<el-form-item label="说明">
<el-input v-model="zoneForm.description" type="textarea" :rows="2" placeholder="备注可空" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="zoneDialog = false">取消</el-button>
<el-button type="primary" :loading="zoneSaving" @click="saveZone">保存</el-button>
</template>
</el-dialog>
</template>
<!-- 物料档案 -->
<template v-else>
<el-form inline style="margin-bottom:14px" @submit.prevent="searchMaterials">
<el-form-item label="物料编码">
<el-input v-model="matFilter.materialCode" placeholder="物料编码" clearable style="width:140px"
@keyup.enter="searchMaterials" />
</el-form-item>
<el-form-item label="名称">
<el-input v-model="matFilter.name" placeholder="物料名称" clearable style="width:140px"
@keyup.enter="searchMaterials" />
</el-form-item>
<el-form-item label="规格">
<el-input v-model="matFilter.spec" placeholder="规格型号" clearable style="width:130px"
@keyup.enter="searchMaterials" />
</el-form-item>
<el-form-item label="类型">
<el-select v-model="matFilter.manageMode" placeholder="全部" clearable style="width:120px" @change="searchMaterials">
<el-option label="结构件(批次)" :value="1" />
<el-option label="精密件(SN)" :value="2" />
</el-select>
</el-form-item>
<el-form-item label="创建时间">
<el-date-picker v-model="matFilter.startDate" type="date" value-format="YYYY-MM-DD" placeholder="开始"
style="width:130px" @change="searchMaterials" />
<span style="margin:0 6px">至</span>
<el-date-picker v-model="matFilter.endDate" type="date" value-format="YYYY-MM-DD" placeholder="结束"
style="width:130px" @change="searchMaterials" />
</el-form-item>
<el-form-item>
<el-button type="primary" :loading="materialLoading" @click="searchMaterials">查询</el-button>
<el-button type="success" @click="openMatCreate">新增物料</el-button>
<el-button v-if="can('material:import')" type="warning" :loading="matImporting" @click="pickMaterialFile">导入</el-button>
<el-button v-if="can('material:export')" :loading="matExporting" @click="exportMaterials">导出</el-button>
<el-tooltip v-if="can('material:import')" content="Excel 模板首行表头物料编码 | 名称 | 简称 | 规格 | 单位 | 类型(1结构件/2精密件) | 说明">
<el-link type="info" :underline="false" style="margin-left:4px">导入说明</el-link>
</el-tooltip>
<input ref="matFileRef" type="file" accept=".xlsx,.xls" style="display:none" @change="onMaterialFile" />
</el-form-item>
</el-form>
<el-table :data="materials" border size="small" v-loading="materialLoading"
empty-text="暂无物料请点击新增物料">
<el-table-column label="ID" prop="id" width="60" align="center" />
<el-table-column prop="code" label="物料编码" min-width="120" />
<el-table-column prop="name" label="名称" min-width="120" />
<el-table-column prop="spec" label="规格" min-width="90">
<template #default="{ row }">{{ row.spec || '-' }}</template>
</el-table-column>
<el-table-column prop="unit" label="单位" width="70" align="center">
<template #default="{ row }">{{ row.unit || '-' }}</template>
</el-table-column>
<el-table-column label="类型" width="140" align="center">
<template #default="{ row }">
<el-tag :type="row.manageMode === 2 ? 'warning' : 'info'" size="small">
{{ row.manageMode === 2 ? '精密件(SN)' : '结构件(批次)' }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="创建时间" width="155" align="center">
<template #default="{ row }">{{ fmtTime(row.createdAt) }}</template>
</el-table-column>
<el-table-column label="操作" width="90" align="center">
<template #default="{ row }">
<el-button link type="primary" size="small" @click="openMatEdit(row)">编辑</el-button>
</template>
</el-table-column>
</el-table>
<div class="pager">
<el-pagination background layout="total, sizes, prev, pager, next" :total="materialTotal"
:current-page="matPage.current" :page-size="matPage.size" :page-sizes="[10, 20, 50, 100]"
@current-change="(p) => { matPage.current = p; loadMaterials() }"
@size-change="(s) => { matPage.size = s; matPage.current = 1; loadMaterials() }" />
</div>
<el-dialog v-model="matDialog" :title="mEditingId ? '编辑物料' : '新增物料'" width="560px"
append-to-body destroy-on-close>
<el-form label-width="90px">
<el-form-item label="物料编码" required>
<el-input v-model="mForm.code" placeholder=" SCREW-M3" :disabled="!!mEditingId" clearable />
</el-form-item>
<el-form-item label="名称" required>
<el-input v-model="mForm.name" placeholder=" 螺丝 M3" clearable />
</el-form-item>
<el-form-item label="简称">
<el-input v-model="mForm.shortName" placeholder="简称可空" clearable />
</el-form-item>
<el-form-item label="规格">
<el-input v-model="mForm.spec" placeholder="规格可空" clearable />
</el-form-item>
<el-form-item label="单位">
<el-input v-model="mForm.unit" placeholder=" /" clearable />
</el-form-item>
<el-form-item label="类型">
<el-select v-model="mForm.manageMode" placeholder="请选择" style="width:100%">
<el-option :value="1" label="结构件批次管理" />
<el-option :value="2" label="精密件SN 管理" />
</el-select>
</el-form-item>
<el-form-item label="说明">
<el-input v-model="mForm.description" type="textarea" :rows="2" placeholder="备注可空" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="matDialog = false">取消</el-button>
<el-button type="primary" :loading="mSaving" @click="saveMaterial">保存</el-button>
</template>
</el-dialog>
</template>
</el-card>
<PageHelp :data="helpData" :page="props.mode === 'material' ? 'Material' : 'Zone'" />
</div>
</template>
<style scoped>
.pager { display: flex; justify-content: flex-end; margin-top: 10px; }
</style>