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,53 @@
|
||||
<script setup>
|
||||
// 物料下拉选择器(公共组件)
|
||||
// 规范:数据量不确定的下拉只加载最近 100 条(created_at desc),支持远程模糊搜索(keyword 查询,结果仍封顶 100)。
|
||||
// 选中后通过 item-change 事件抛出完整物料对象(无匹配为 null),供页面校验 manageMode 等。
|
||||
import { ref, onMounted } from 'vue'
|
||||
import request from '../utils/request'
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: { type: [String, Array], default: '' },
|
||||
multiple: { type: Boolean, default: false },
|
||||
manageMode: { type: Number, default: 0 }, // 0=全部 1=结构件 2=精密件
|
||||
placeholder: { type: String, default: '选择物料' },
|
||||
clearable: { type: Boolean, default: true },
|
||||
width: { type: String, default: '100%' }
|
||||
})
|
||||
const emit = defineEmits(['update:modelValue', 'item-change'])
|
||||
|
||||
const options = ref([])
|
||||
const loading = ref(false)
|
||||
|
||||
// 最近 100 条 + keyword 模糊搜索(后端 /material/query 兼容空 keyword)
|
||||
async function load(kw = '') {
|
||||
loading.value = true
|
||||
try {
|
||||
const data = await request.get('/material/query', {
|
||||
params: { page: 1, pageSize: 100, keyword: kw || '', manageMode: props.manageMode || '' }
|
||||
})
|
||||
options.value = data?.list || []
|
||||
} catch {
|
||||
options.value = []
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function onChange(v) {
|
||||
emit('update:modelValue', v)
|
||||
if (props.multiple) {
|
||||
emit('item-change', options.value.filter((o) => v.includes(o.code)))
|
||||
} else {
|
||||
emit('item-change', options.value.find((o) => o.code === v) || null)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<el-select :model-value="modelValue" :multiple="multiple" filterable remote clearable
|
||||
:remote-method="load" :loading="loading" :placeholder="placeholder"
|
||||
:style="{ width }" @change="onChange">
|
||||
<el-option v-for="m in options" :key="m.id" :value="m.code"
|
||||
:label="`${m.code} ${m.name || ''}`" />
|
||||
</el-select>
|
||||
</template>
|
||||
@@ -0,0 +1,133 @@
|
||||
<template>
|
||||
<!-- 帮助内容通过右上角「帮助」按钮触发(MainLayout 派发 wms:open-page-help 事件) -->
|
||||
<el-drawer v-model="show" :title="data.title || '操作说明'" size="520px" append-to-body class="ph-drawer">
|
||||
<div class="ph-inner">
|
||||
<div class="ph-banner">
|
||||
<div class="ph-banner-title">{{ data.title || '操作说明' }}</div>
|
||||
<div class="ph-banner-sub">页面整体说明</div>
|
||||
</div>
|
||||
<div class="ph-overview" v-if="data.overview">{{ data.overview }}</div>
|
||||
|
||||
<div class="ph-legend">
|
||||
<span class="lg"><i class="ic lg-source"></i>来源:这个字段从哪来、谁填</span>
|
||||
<span class="lg"><i class="ic lg-purpose"></i>用途:它起什么作用</span>
|
||||
<span class="lg"><i class="ic lg-fill"></i>怎么填:具体填什么</span>
|
||||
</div>
|
||||
|
||||
<!-- 顶层字段(主表单说明) -->
|
||||
<div v-if="data.fields && data.fields.length" class="ph-section">
|
||||
<div class="ph-field" v-for="(f, fi) in data.fields" :key="'top'+fi">
|
||||
<div class="ph-fname">
|
||||
{{ f.name }}
|
||||
<span class="ph-fsource">{{ f.source }}</span>
|
||||
</div>
|
||||
<div class="ph-fpurpose">{{ f.purpose }}</div>
|
||||
<div class="ph-ffill">👉 {{ f.fill }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 分组(多组) -->
|
||||
<template v-if="data.sections && data.sections.length">
|
||||
<div v-for="(sec, si) in data.sections" :key="si" class="ph-section">
|
||||
<div class="ph-sec-title" v-if="sec.title">{{ sec.title }}</div>
|
||||
<div class="ph-sec-overview" v-if="sec.overview">{{ sec.overview }}</div>
|
||||
<div class="ph-field" v-for="(f, fi) in sec.fields" :key="fi">
|
||||
<div class="ph-fname">
|
||||
{{ f.name }}
|
||||
<span class="ph-fsource">{{ f.source }}</span>
|
||||
</div>
|
||||
<div class="ph-fpurpose">{{ f.purpose }}</div>
|
||||
<div class="ph-ffill">👉 {{ f.fill }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- 既无顶层字段也无分组 -->
|
||||
<el-empty v-if="!data.fields && !(data.sections && data.sections.length)"
|
||||
description="该页面暂未编写操作说明" :image-size="70" />
|
||||
</div>
|
||||
</el-drawer>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted, onBeforeUnmount } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
|
||||
const props = defineProps({
|
||||
data: { type: Object, default: () => ({ title: '操作说明', overview: '', fields: [] }) },
|
||||
// 归属路由名(route.name):仅当其为当前激活页时才弹出,避免 keep-alive 下多页抽屉同时弹出
|
||||
page: { type: String, default: '' }
|
||||
})
|
||||
|
||||
const route = useRoute()
|
||||
const show = ref(false)
|
||||
function open() {
|
||||
// 未声明归属页 → 始终打开;声明了则必须与当前路由一致才打开(保证只弹自己)
|
||||
if (props.page && route.name !== props.page) return
|
||||
show.value = true
|
||||
}
|
||||
|
||||
// 右上角「帮助」按钮(MainLayout)通过 wms:open-page-help 事件触发当前页帮助抽屉
|
||||
onMounted(() => window.addEventListener('wms:open-page-help', open))
|
||||
onBeforeUnmount(() => window.removeEventListener('wms:open-page-help', open))
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.page-help { position: fixed; left: 18px; bottom: 18px; z-index: 2050; }
|
||||
.help-fab {
|
||||
width: 42px; height: 42px; background: #1668dc; color: #fff; font-weight: 700;
|
||||
font-size: 20px; box-shadow: 0 3px 10px rgba(22,50,79,.3);
|
||||
transition: transform .15s;
|
||||
}
|
||||
.help-fab:hover { background: #0e57c3; transform: scale(1.06); }
|
||||
|
||||
.ph-inner { padding: 0 6px 24px; }
|
||||
|
||||
.ph-banner {
|
||||
background: linear-gradient(120deg, #1668dc, #3a98f0);
|
||||
color: #fff; border-radius: 10px; padding: 16px 18px; margin-bottom: 16px;
|
||||
}
|
||||
.ph-banner-title { font-size: 20px; font-weight: 700; }
|
||||
.ph-banner-sub { font-size: 12px; opacity: .85; margin-top: 4px; }
|
||||
|
||||
.ph-overview {
|
||||
color: #3f536e; line-height: 1.8; font-size: 14px; margin-bottom: 16px;
|
||||
background: #f4f7fb; border: 1px solid #e4ecf5; border-radius: 8px;
|
||||
padding: 12px 14px; white-space: pre-line;
|
||||
}
|
||||
|
||||
.ph-legend {
|
||||
display: flex; flex-wrap: wrap; gap: 12px 16px; margin-bottom: 16px;
|
||||
padding: 10px 14px; background: #fafbfc; border: 1px dashed #d9e0e8; border-radius: 8px;
|
||||
font-size: 12px; color: #6b7785;
|
||||
}
|
||||
.ph-legend .lg { display: inline-flex; align-items: center; gap: 6px; }
|
||||
.ph-legend .ic { display: inline-block; width: 10px; height: 10px; border-radius: 3px; }
|
||||
.lg-source { background: #8fa5f5; }
|
||||
.lg-purpose { background: #22b07d; }
|
||||
.lg-fill { background: #f5a623; }
|
||||
|
||||
.ph-section { margin-bottom: 18px; }
|
||||
.ph-sec-title {
|
||||
font-size: 15px; font-weight: 700; color: #1b2a3a; margin: 0 0 4px;
|
||||
padding-left: 10px; border-left: 4px solid #1668dc; line-height: 1.4;
|
||||
}
|
||||
.ph-sec-overview { color: #7a8694; font-size: 13px; margin: 4px 0 10px; line-height: 1.7; }
|
||||
|
||||
.ph-field {
|
||||
background: #fff; border: 1px solid #eef1f6; border-radius: 8px;
|
||||
padding: 10px 14px; margin-bottom: 8px;
|
||||
}
|
||||
.ph-fname {
|
||||
display: flex; align-items: center; gap: 8px;
|
||||
font-weight: 700; color: #1b2a3a; font-size: 14px;
|
||||
}
|
||||
.ph-fsource {
|
||||
font-weight: 400; font-size: 11px; color: #1668dc;
|
||||
background: #e8f1ff; border-radius: 4px; padding: 1px 8px;
|
||||
}
|
||||
.ph-fpurpose { color: #22b07d; font-size: 13px; line-height: 1.6; margin-top: 6px; }
|
||||
.ph-fpurpose::before { content: '用途:'; font-weight: 600; color: #1b8d63; }
|
||||
.ph-ffill { color: #8a5b00; font-size: 13px; line-height: 1.6; margin-top: 4px; }
|
||||
.ph-ffill::before { content: '怎么填:'; font-weight: 600; color: #8a5b00; }
|
||||
</style>
|
||||
Reference in New Issue
Block a user