refactor: 合并原库房客户端到主WMS服务,退役8891端口
1. 将bj_power_wms_client的前端代码、配置及网关逻辑全部迁移到bj_power_wms主项目 2. 删除原库房客户端相关的所有文件与配置 3. 更新README与部署文档,说明合并后的服务架构 4. 新增Web配置项支持自动打开浏览器开关,统一服务端口为8890
This commit is contained in:
@@ -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>
|
||||
Reference in New Issue
Block a user