Merge remote-tracking branch 'origin/0.8.X' into 0.8.X
This commit is contained in:
@@ -44,7 +44,7 @@
|
|||||||
<el-form-item>
|
<el-form-item>
|
||||||
<el-radio v-model='radioValue' :label="7">
|
<el-radio v-model='radioValue' :label="7">
|
||||||
指定
|
指定
|
||||||
<el-select clearable v-model="checkboxList" placeholder="可多选" multiple style="width:100%">
|
<el-select filterable clearable v-model="checkboxList" placeholder="可多选" multiple style="width:100%">
|
||||||
<el-option v-for="item in 31" :key="item" :value="item">{{item}}</el-option>
|
<el-option v-for="item in 31" :key="item" :value="item">{{item}}</el-option>
|
||||||
</el-select>
|
</el-select>
|
||||||
</el-radio>
|
</el-radio>
|
||||||
|
|||||||
@@ -25,7 +25,7 @@
|
|||||||
<el-form-item>
|
<el-form-item>
|
||||||
<el-radio v-model='radioValue' :label="4">
|
<el-radio v-model='radioValue' :label="4">
|
||||||
指定
|
指定
|
||||||
<el-select clearable v-model="checkboxList" placeholder="可多选" multiple style="width:100%">
|
<el-select filterable clearable v-model="checkboxList" placeholder="可多选" multiple style="width:100%">
|
||||||
<el-option v-for="item in 24" :key="item" :value="item-1">{{item-1}}</el-option>
|
<el-option v-for="item in 24" :key="item" :value="item-1">{{item-1}}</el-option>
|
||||||
</el-select>
|
</el-select>
|
||||||
</el-radio>
|
</el-radio>
|
||||||
|
|||||||
@@ -1,5 +1,13 @@
|
|||||||
<template>
|
<template>
|
||||||
<el-select remote filterable v-model="_value" :remote-method="remoteSearchVendor" :loading="vendorLoading" placeholder="请选择供应商">
|
<el-select
|
||||||
|
remote
|
||||||
|
clearable
|
||||||
|
filterable
|
||||||
|
v-model="_value"
|
||||||
|
:remote-method="remoteSearchVendor"
|
||||||
|
:loading="vendorLoading"
|
||||||
|
placeholder="请选择供应商"
|
||||||
|
>
|
||||||
<el-option v-for="item in vendorList" :key="item.supplierId" :label="item.name" :value="item.supplierId" />
|
<el-option v-for="item in vendorList" :key="item.supplierId" :label="item.name" :value="item.supplierId" />
|
||||||
</el-select>
|
</el-select>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
|
import QRCode from 'qrcode';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 通用js方法封装处理
|
* 通用js方法封装处理
|
||||||
@@ -231,3 +231,117 @@ export function tansParams(params) {
|
|||||||
export function blobValidate(data) {
|
export function blobValidate(data) {
|
||||||
return data.type !== 'application/json'
|
return data.type !== 'application/json'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 保存二维码为图片
|
||||||
|
* @param {string} code 二维码内容
|
||||||
|
* @param {string} text 下方文字
|
||||||
|
* @param {number} index 索引,用于生成文件名
|
||||||
|
* @param {Object} context Vue组件实例上下文(需要包含$message、barcodeWidth、barcodeHeight)
|
||||||
|
*/
|
||||||
|
export async function saveAsImage(code, text, index, context) {
|
||||||
|
// 确保上下文存在
|
||||||
|
if (!context) {
|
||||||
|
console.error('缺少组件上下文,请传入Vue实例');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 检查QRCode库是否加载
|
||||||
|
if (typeof QRCode === 'undefined') {
|
||||||
|
throw new Error('QRCode库未加载,请确保已引入');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建临时canvas用于绘制二维码和文字
|
||||||
|
const canvas = document.createElement('canvas');
|
||||||
|
const ctx = canvas.getContext('2d');
|
||||||
|
if (!ctx) {
|
||||||
|
throw new Error('无法获取canvas上下文');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 从上下文获取尺寸参数,提供默认值
|
||||||
|
const barcodeWidth = context.barcodeWidth || 200;
|
||||||
|
const barcodeHeight = context.barcodeHeight || 200;
|
||||||
|
const textHeight = 30; // 文字区域高度
|
||||||
|
|
||||||
|
canvas.width = barcodeWidth;
|
||||||
|
canvas.height = barcodeHeight + textHeight;
|
||||||
|
|
||||||
|
// 绘制二维码
|
||||||
|
const qrCanvas = document.createElement('canvas');
|
||||||
|
qrCanvas.width = barcodeWidth;
|
||||||
|
qrCanvas.height = barcodeHeight;
|
||||||
|
|
||||||
|
// 等待二维码绘制完成
|
||||||
|
await new Promise((resolve, reject) => {
|
||||||
|
QRCode.toCanvas(qrCanvas, code, {
|
||||||
|
width: barcodeWidth,
|
||||||
|
height: barcodeHeight,
|
||||||
|
margin: 0,
|
||||||
|
errorCorrectionLevel: 'M'
|
||||||
|
}, (error) => {
|
||||||
|
if (error) reject(error);
|
||||||
|
else resolve();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// 将二维码绘制到主canvas
|
||||||
|
ctx.drawImage(qrCanvas, 0, 0);
|
||||||
|
|
||||||
|
// 绘制文字(处理文字过长情况)
|
||||||
|
ctx.fillStyle = '#000';
|
||||||
|
ctx.font = '14px Arial, sans-serif';
|
||||||
|
ctx.textAlign = 'center';
|
||||||
|
ctx.textBaseline = 'top';
|
||||||
|
|
||||||
|
// 处理过长文字,最多显示2行
|
||||||
|
const maxTextWidth = barcodeWidth - 20; // 预留边距
|
||||||
|
let displayText = text;
|
||||||
|
if (ctx.measureText(text).width > maxTextWidth) {
|
||||||
|
// 尝试截断并添加省略号
|
||||||
|
let i = text.length;
|
||||||
|
while (ctx.measureText(text.substring(0, i)).width > maxTextWidth && i > 0) {
|
||||||
|
i--;
|
||||||
|
}
|
||||||
|
displayText = text.substring(0, i) + '...';
|
||||||
|
}
|
||||||
|
ctx.fillText(displayText, barcodeWidth / 2, barcodeHeight + 5);
|
||||||
|
|
||||||
|
// 创建图片链接并下载
|
||||||
|
canvas.toBlob(blob => {
|
||||||
|
if (!blob) {
|
||||||
|
throw new Error('无法生成图片blob对象');
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const url = URL.createObjectURL(blob);
|
||||||
|
const a = document.createElement('a');
|
||||||
|
|
||||||
|
// 处理文件名特殊字符
|
||||||
|
a.download = `二维码_${index + 1}.png`;
|
||||||
|
a.href = url;
|
||||||
|
|
||||||
|
// 模拟点击下载
|
||||||
|
document.body.appendChild(a);
|
||||||
|
const event = new MouseEvent('click');
|
||||||
|
a.dispatchEvent(event);
|
||||||
|
|
||||||
|
// 清理资源
|
||||||
|
setTimeout(() => {
|
||||||
|
document.body.removeChild(a);
|
||||||
|
URL.revokeObjectURL(url);
|
||||||
|
}, 100);
|
||||||
|
|
||||||
|
context.$message.success('图片保存成功');
|
||||||
|
} catch (urlError) {
|
||||||
|
console.error('创建下载链接失败:', urlError);
|
||||||
|
context.$message.error('创建下载链接失败');
|
||||||
|
}
|
||||||
|
}, 'image/png'); // 明确指定MIME类型
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('保存图片失败:', error);
|
||||||
|
context.$message.error(`保存图片失败: ${error.message || '未知错误'}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,50 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="all-applications-container">
|
<div class="all-applications-container">
|
||||||
<h3 class="title">全部应用</h3>
|
<!-- 常用应用区域 -->
|
||||||
<el-tabs v-model="activeTabName" class="app-tabs">
|
<div class="frequently-used-section">
|
||||||
|
<div class="frequently-used-header">
|
||||||
|
<h3 class="frequently-title">常用应用</h3>
|
||||||
|
<el-button
|
||||||
|
type="text"
|
||||||
|
size="small"
|
||||||
|
class="edit-btn"
|
||||||
|
@click="isEditingFavorites = !isEditingFavorites"
|
||||||
|
>
|
||||||
|
{{ isEditingFavorites ? '完成' : '编辑' }}
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="frequently-used-grid" v-if="frequentlyUsedApps.length > 0">
|
||||||
|
<div
|
||||||
|
v-for="app in frequentlyUsedApps"
|
||||||
|
:key="`fav-${app.parentPath}-${app.childPath}`"
|
||||||
|
class="frequently-app-item"
|
||||||
|
@click="handleAppClick(getParentMenu(app.parentPath), getChildMenu(app.parentPath, app.childPath))"
|
||||||
|
>
|
||||||
|
<div class="app-icon-wrapper">
|
||||||
|
<svg-icon :icon-class="getChildMenu(app.parentPath, app.childPath).meta.icon || 'documentation'" class="app-icon" />
|
||||||
|
</div>
|
||||||
|
<span class="app-name">{{ getChildMenu(app.parentPath, app.childPath).meta.title }}</span>
|
||||||
|
|
||||||
|
<!-- 删除按钮 - 仅在编辑模式显示 -->
|
||||||
|
<div
|
||||||
|
class="remove-btn"
|
||||||
|
v-if="isEditingFavorites"
|
||||||
|
@click.stop="removeFromFavorites(app.parentPath, app.childPath)"
|
||||||
|
>
|
||||||
|
<i class="el-icon-close"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-else>
|
||||||
|
<el-empty description="暂无常用应用" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 原有全部应用区域 -->
|
||||||
|
<h3 class="title" v-if="isEditingFavorites">全部应用</h3>
|
||||||
|
<el-tabs v-model="activeTabName" class="app-tabs" v-if="isEditingFavorites">
|
||||||
<el-tab-pane
|
<el-tab-pane
|
||||||
v-for="menu in filteredMenus"
|
v-for="menu in filteredMenus"
|
||||||
:key="menu.path"
|
:key="menu.path"
|
||||||
@@ -13,10 +56,27 @@
|
|||||||
v-for="child in menu.children"
|
v-for="child in menu.children"
|
||||||
:key="child.path"
|
:key="child.path"
|
||||||
class="app-item"
|
class="app-item"
|
||||||
@click="handleAppClick(menu, child)"
|
|
||||||
>
|
>
|
||||||
|
<!-- @click="handleAppClick(menu, child)" -->
|
||||||
<div class="app-icon-wrapper">
|
<div class="app-icon-wrapper">
|
||||||
<svg-icon :icon-class="child.meta.icon || 'documentation'" class="app-icon" />
|
<svg-icon :icon-class="child.meta.icon || 'documentation'" class="app-icon" />
|
||||||
|
|
||||||
|
<!-- 添加到常用按钮 -->
|
||||||
|
<div
|
||||||
|
class="add-to-favorite-btn"
|
||||||
|
@click.stop="addToFavorites(menu.path, child.path)"
|
||||||
|
v-if="!isInFavorites(menu.path, child.path)"
|
||||||
|
>
|
||||||
|
<i class="el-icon-star-off"></i>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 已在常用中的标识 -->
|
||||||
|
<div
|
||||||
|
class="in-favorite-indicator"
|
||||||
|
v-if="isInFavorites(menu.path, child.path)"
|
||||||
|
>
|
||||||
|
<i class="el-icon-star-on"></i>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<span class="app-name">{{ child.meta.title }}</span>
|
<span class="app-name">{{ child.meta.title }}</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -35,14 +95,16 @@ export default {
|
|||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
allMenus: [],
|
allMenus: [],
|
||||||
activeTabName: ''
|
activeTabName: '',
|
||||||
|
frequentlyUsedApps: [], // 存储常用应用
|
||||||
|
isEditingFavorites: false // 是否处于编辑常用应用模式
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
filteredMenus() {
|
filteredMenus() {
|
||||||
const filterHidden = (menus) => {
|
const filterHidden = (menus) => {
|
||||||
return menus
|
return menus
|
||||||
.filter(menu => menu.hidden !== true)
|
.filter(menu => menu.hidden!== true)
|
||||||
.map(menu => {
|
.map(menu => {
|
||||||
if (menu.children) {
|
if (menu.children) {
|
||||||
menu.children = filterHidden(menu.children)
|
menu.children = filterHidden(menu.children)
|
||||||
@@ -58,6 +120,7 @@ export default {
|
|||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
this.fetchMenus()
|
this.fetchMenus()
|
||||||
|
this.loadFrequentlyUsedApps()
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
fetchMenus() {
|
fetchMenus() {
|
||||||
@@ -68,7 +131,72 @@ export default {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// 加载常用应用(从localStorage)
|
||||||
|
loadFrequentlyUsedApps() {
|
||||||
|
const saved = localStorage.getItem('frequentlyUsedApps')
|
||||||
|
if (saved) {
|
||||||
|
try {
|
||||||
|
this.frequentlyUsedApps = JSON.parse(saved)
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Failed to parse frequently used apps', e)
|
||||||
|
this.frequentlyUsedApps = []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// 保存常用应用到localStorage
|
||||||
|
saveFrequentlyUsedApps() {
|
||||||
|
localStorage.setItem('frequentlyUsedApps', JSON.stringify(this.frequentlyUsedApps))
|
||||||
|
},
|
||||||
|
|
||||||
|
// 添加到常用应用
|
||||||
|
addToFavorites(parentPath, childPath) {
|
||||||
|
if (!this.isInFavorites(parentPath, childPath)) {
|
||||||
|
// 限制最多10个常用应用
|
||||||
|
if (this.frequentlyUsedApps.length >= 10) {
|
||||||
|
this.$message.warning('常用应用最多只能添加10个')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this.frequentlyUsedApps.unshift({ parentPath, childPath })
|
||||||
|
this.saveFrequentlyUsedApps()
|
||||||
|
this.$message.success('已添加到常用应用')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// 从常用应用中移除
|
||||||
|
removeFromFavorites(parentPath, childPath) {
|
||||||
|
this.frequentlyUsedApps = this.frequentlyUsedApps.filter(
|
||||||
|
app =>!(app.parentPath === parentPath && app.childPath === childPath)
|
||||||
|
)
|
||||||
|
this.saveFrequentlyUsedApps()
|
||||||
|
},
|
||||||
|
|
||||||
|
// 检查应用是否在常用列表中
|
||||||
|
isInFavorites(parentPath, childPath) {
|
||||||
|
return this.frequentlyUsedApps.some(
|
||||||
|
app => app.parentPath === parentPath && app.childPath === childPath
|
||||||
|
)
|
||||||
|
},
|
||||||
|
|
||||||
|
// 根据路径获取父菜单
|
||||||
|
getParentMenu(parentPath) {
|
||||||
|
return this.filteredMenus.find(menu => menu.path === parentPath)
|
||||||
|
},
|
||||||
|
|
||||||
|
// 根据路径获取子菜单
|
||||||
|
getChildMenu(parentPath, childPath) {
|
||||||
|
const parent = this.getParentMenu(parentPath)
|
||||||
|
if (parent && parent.children) {
|
||||||
|
return parent.children.find(child => child.path === childPath)
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
},
|
||||||
|
|
||||||
handleAppClick(parentMenu, childMenu) {
|
handleAppClick(parentMenu, childMenu) {
|
||||||
|
if (!childMenu) return
|
||||||
|
|
||||||
const basePath = parentMenu.path
|
const basePath = parentMenu.path
|
||||||
const fullPath = path.resolve(basePath, childMenu.path)
|
const fullPath = path.resolve(basePath, childMenu.path)
|
||||||
this.$router.push(fullPath)
|
this.$router.push(fullPath)
|
||||||
@@ -80,23 +208,73 @@ export default {
|
|||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.all-applications-container {
|
.all-applications-container {
|
||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
padding: 20px;
|
padding: 16px; /* 调整间距,更贴近飞书紧凑感 */
|
||||||
border-radius: 8px;
|
border-radius: 12px; /* 飞书常用较大圆角 */
|
||||||
margin-top: 20px;
|
margin-top: 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 常用应用样式
|
||||||
|
.frequently-used-section {
|
||||||
|
margin-bottom: 32px; /* 增大间距 */
|
||||||
|
padding-bottom: 20px;
|
||||||
|
border-bottom: 1px solid #ebeef5; /* 飞书浅灰边框色 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.frequently-used-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
// margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.frequently-title {
|
||||||
|
font-size: 16px; /* 稍小字体,飞书风格更简洁 */
|
||||||
|
font-weight: 500; /* 调整 FontWeight */
|
||||||
|
color: #1f2329; /* 深一点的标题色 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.edit-btn {
|
||||||
|
color: #0078ff; /* 飞书常用的主题蓝 */
|
||||||
|
padding: 0;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.frequently-used-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(112px, 1fr)); /* 调整卡片宽度 */
|
||||||
|
gap: 20px; /* 增大间距 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.frequently-app-item {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 16px; /* 调整内边距 */
|
||||||
|
border-radius: 12px; /* 大圆角 */
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
position: relative;
|
||||||
|
background-color: #f7f8fa; /* 飞书卡片常用浅灰底色 */
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: #eaeef7; /* hover 时的浅灰 */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 全部应用标题
|
||||||
.title {
|
.title {
|
||||||
font-size: 18px;
|
font-size: 16px;
|
||||||
font-weight: 600;
|
font-weight: 500;
|
||||||
margin-bottom: 16px;
|
margin-bottom: 20px;
|
||||||
color: #303133;
|
color: #1f2329;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 应用网格布局
|
||||||
.app-grid {
|
.app-grid {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
|
grid-template-columns: repeat(auto-fill, minmax(128px, 1fr)); /* 调整卡片宽度 */
|
||||||
gap: 20px;
|
gap: 24px; /* 增大间距 */
|
||||||
padding-top: 10px;
|
padding-top: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.app-item {
|
.app-item {
|
||||||
@@ -104,37 +282,110 @@ export default {
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
padding: 10px;
|
padding: 16px;
|
||||||
border-radius: 8px;
|
border-radius: 12px;
|
||||||
transition: background-color 0.3s ease;
|
transition: background-color 0.3s ease;
|
||||||
|
position: relative;
|
||||||
|
background-color: #f7f8fa;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: #f5f7fa;
|
background-color: #eaeef7;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 应用图标样式
|
||||||
.app-icon-wrapper {
|
.app-icon-wrapper {
|
||||||
width: 56px;
|
width: 48px; /* 稍小图标容器 */
|
||||||
height: 56px;
|
height: 48px;
|
||||||
border-radius: 12px;
|
border-radius: 10px; /* 图标容器圆角 */
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
margin-bottom: 10px;
|
margin-bottom: 12px;
|
||||||
background-color: #f0f5ff;
|
background-color: #e6f0ff; /* 飞书风格的浅蓝底色 */
|
||||||
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.app-icon {
|
.app-icon {
|
||||||
font-size: 28px;
|
font-size: 24px; /* 调整图标大小 */
|
||||||
color: #409eff;
|
color: #0078ff; /* 主题蓝 */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 应用名称样式
|
||||||
.app-name {
|
.app-name {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
color: #606266;
|
color: #515767; /* 飞书常用的文本色 */
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 添加到常用按钮
|
||||||
|
.add-to-favorite-btn {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 4px;
|
||||||
|
right: 4px;
|
||||||
|
width: 24px; /* 稍大按钮 */
|
||||||
|
height: 24px;
|
||||||
|
border-radius: 6px; /* 小圆角 */
|
||||||
|
background-color: rgba(255, 255, 255, 0.9);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 14px;
|
||||||
|
color: #c0c4cc;
|
||||||
|
transition: all 0.2s;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: #ffb400; /* 飞书常用的强调色 */
|
||||||
|
background-color: #fff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 已在常用中的标识
|
||||||
|
.in-favorite-indicator {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 4px;
|
||||||
|
right: 4px;
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
border-radius: 6px;
|
||||||
|
background-color: rgba(255, 255, 255, 0.9);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 14px;
|
||||||
|
color: #ffb400;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除常用应用按钮
|
||||||
|
.remove-btn {
|
||||||
|
position: absolute;
|
||||||
|
top: -6px;
|
||||||
|
right: -6px;
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
border-radius: 6px;
|
||||||
|
background-color: #ff4d4f; /* 飞书删除按钮红 */
|
||||||
|
color: white;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 14px;
|
||||||
|
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1);
|
||||||
|
transition: all 0.2s;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: #ff3839;
|
||||||
|
transform: scale(1.05);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 标签页样式
|
||||||
::v-deep .el-tabs__header {
|
::v-deep .el-tabs__header {
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
@@ -142,8 +393,4 @@ export default {
|
|||||||
::v-deep .el-tabs__nav-wrap::after {
|
::v-deep .el-tabs__nav-wrap::after {
|
||||||
height: 1px;
|
height: 1px;
|
||||||
}
|
}
|
||||||
|
|
||||||
::v-deep .el-tabs__item {
|
|
||||||
font-size: 15px;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
170
klp-ui/src/views/components/OrderDashboard.vue
Normal file
170
klp-ui/src/views/components/OrderDashboard.vue
Normal file
@@ -0,0 +1,170 @@
|
|||||||
|
<template>
|
||||||
|
<div
|
||||||
|
class="order-analysis-dashboard"
|
||||||
|
v-loading="loading"
|
||||||
|
>
|
||||||
|
<!-- 业绩区 -->
|
||||||
|
<el-tabs v-model="activeTab" type="card">
|
||||||
|
<el-tab-pane label="业绩总览" name="performance">
|
||||||
|
<PerformanceArea mode="mini" :performance-area="dashboardData.performanceArea" />
|
||||||
|
</el-tab-pane>
|
||||||
|
<el-tab-pane label="订单统计" name="currentSituation">
|
||||||
|
<CurrentSituationArea mode="mini" :current-situation-area="dashboardData.currentSituationArea" />
|
||||||
|
</el-tab-pane>
|
||||||
|
<el-tab-pane label="采购推荐" name="recommendation">
|
||||||
|
<RecommendationArea mode="mini" :recommendation-area="dashboardData.recommendationArea" />
|
||||||
|
</el-tab-pane>
|
||||||
|
</el-tabs>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import PerformanceArea from '@/views/wms/order/components/PerformanceArea.vue'
|
||||||
|
import CurrentSituationArea from '@/views/wms/order/components/CurrentSituationArea.vue'
|
||||||
|
import RecommendationArea from '@/views/wms/order/components/RecommendationArea.vue'
|
||||||
|
import { getDashboardData } from '@/api/wms/order'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'OrderAnalysisDashboard',
|
||||||
|
components: {
|
||||||
|
PerformanceArea,
|
||||||
|
CurrentSituationArea,
|
||||||
|
RecommendationArea,
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
// 新的数据结构
|
||||||
|
dashboardData: {
|
||||||
|
performanceArea: {},
|
||||||
|
currentSituationArea: {},
|
||||||
|
recommendationArea: {}
|
||||||
|
},
|
||||||
|
// 新增定时刷新相关数据
|
||||||
|
drawerVisible: false,
|
||||||
|
autoRefresh: false,
|
||||||
|
refreshInterval: 30, // 默认30秒
|
||||||
|
refreshTimer: null,
|
||||||
|
loading: false,
|
||||||
|
activeTab: 'performance'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.fetchAllData()
|
||||||
|
this.loadRefreshSetting()
|
||||||
|
this.startAutoRefresh()
|
||||||
|
},
|
||||||
|
beforeDestroy() {
|
||||||
|
this.clearAutoRefresh()
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
async fetchAllData() {
|
||||||
|
this.loading = true
|
||||||
|
try {
|
||||||
|
const res = await getDashboardData()
|
||||||
|
const data = res
|
||||||
|
|
||||||
|
// 更新新的数据结构
|
||||||
|
this.dashboardData = {
|
||||||
|
performanceArea: data.performanceArea || {},
|
||||||
|
currentSituationArea: data.currentSituationArea || {},
|
||||||
|
recommendationArea: data.recommendationArea || {}
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取数据看板数据失败:', error)
|
||||||
|
this.$message.error('获取数据失败,请稍后重试')
|
||||||
|
} finally {
|
||||||
|
this.loading = false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handleRefresh() {
|
||||||
|
this.fetchAllData()
|
||||||
|
},
|
||||||
|
// 定时刷新相关
|
||||||
|
startAutoRefresh() {
|
||||||
|
this.clearAutoRefresh()
|
||||||
|
if (this.autoRefresh) {
|
||||||
|
this.refreshTimer = setInterval(() => {
|
||||||
|
this.fetchAllData()
|
||||||
|
}, this.refreshInterval * 1000)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
clearAutoRefresh() {
|
||||||
|
if (this.refreshTimer) {
|
||||||
|
clearInterval(this.refreshTimer)
|
||||||
|
this.refreshTimer = null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
saveRefreshSetting() {
|
||||||
|
// 可持久化到localStorage
|
||||||
|
localStorage.setItem('orderDashboardAutoRefresh', JSON.stringify({
|
||||||
|
autoRefresh: this.autoRefresh,
|
||||||
|
refreshInterval: this.refreshInterval
|
||||||
|
}))
|
||||||
|
this.drawerVisible = false
|
||||||
|
this.startAutoRefresh()
|
||||||
|
},
|
||||||
|
loadRefreshSetting() {
|
||||||
|
const setting = localStorage.getItem('orderDashboardAutoRefresh')
|
||||||
|
if (setting) {
|
||||||
|
const { autoRefresh, refreshInterval } = JSON.parse(setting)
|
||||||
|
this.autoRefresh = autoRefresh
|
||||||
|
this.refreshInterval = refreshInterval
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
autoRefresh(val) {
|
||||||
|
if (!val) {
|
||||||
|
this.clearAutoRefresh()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
refreshInterval(val) {
|
||||||
|
if (this.autoRefresh) {
|
||||||
|
this.startAutoRefresh()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.order-analysis-dashboard {
|
||||||
|
padding: 24px;
|
||||||
|
background-color: #f7f8fa;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-row {
|
||||||
|
margin-bottom: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-title {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
padding: 0 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-title h2 {
|
||||||
|
margin: 0 0 8px 0;
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #303133;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-title p {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 14px;
|
||||||
|
color: #909399;
|
||||||
|
}
|
||||||
|
|
||||||
|
.top-row,
|
||||||
|
.chart-row {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart-row > .el-col {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: stretch;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -10,8 +10,24 @@
|
|||||||
<div class="greeting-desc">愿你天黑有灯,下雨有伞</div>
|
<div class="greeting-desc">愿你天黑有灯,下雨有伞</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- 全部应用 -->
|
||||||
|
<el-row>
|
||||||
|
<AllApplications />
|
||||||
|
</el-row>
|
||||||
|
|
||||||
|
<el-row>
|
||||||
|
<el-col :span="12">
|
||||||
|
办公管理数据
|
||||||
|
</el-col>
|
||||||
|
|
||||||
|
<el-col :span="12">
|
||||||
|
<OrderDashboard />
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
|
||||||
<!-- 数据概览区 -->
|
<!-- 数据概览区 -->
|
||||||
<div class="data-overview">
|
<!-- <div class="data-overview">
|
||||||
<div v-for="(card, index) in dataCards" :key="index"
|
<div v-for="(card, index) in dataCards" :key="index"
|
||||||
class="data-card">
|
class="data-card">
|
||||||
<div class="data-card-header">
|
<div class="data-card-header">
|
||||||
@@ -25,10 +41,10 @@
|
|||||||
<div ref="charts" class="chart-inner"></div>
|
<div ref="charts" class="chart-inner"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div> -->
|
||||||
|
|
||||||
<!-- 业务功能区 -->
|
<!-- 业务功能区 -->
|
||||||
<div class="business-modules">
|
<!-- <div class="business-modules">
|
||||||
<div v-for="(module, index) in businessModules" :key="index"
|
<div v-for="(module, index) in businessModules" :key="index"
|
||||||
class="business-module" @click="handleLink(module.link)">
|
class="business-module" @click="handleLink(module.link)">
|
||||||
<div :class="['business-module-icon', getModuleBg(module.bgColor)]">
|
<div :class="['business-module-icon', getModuleBg(module.bgColor)]">
|
||||||
@@ -36,20 +52,19 @@
|
|||||||
</div>
|
</div>
|
||||||
<h3 class="business-module-title">{{ module.title }}</h3>
|
<h3 class="business-module-title">{{ module.title }}</h3>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div> -->
|
||||||
|
|
||||||
<!-- 全部应用 -->
|
|
||||||
<AllApplications />
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import * as echarts from 'echarts';
|
import * as echarts from 'echarts';
|
||||||
import AllApplications from '@/views/components/AllApplications.vue';
|
import AllApplications from '@/views/components/AllApplications.vue';
|
||||||
|
import OrderDashboard from '@/views/components/OrderDashboard.vue';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
AllApplications
|
AllApplications,
|
||||||
|
OrderDashboard
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
@@ -346,50 +361,71 @@ export default {
|
|||||||
background: linear-gradient(135deg, #f9fafb 0%, #f3f4f6 100%);
|
background: linear-gradient(135deg, #f9fafb 0%, #f3f4f6 100%);
|
||||||
padding: 32px;
|
padding: 32px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.data-overview {
|
.data-overview {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(4, 1fr);
|
grid-template-columns: repeat(4, 1fr);
|
||||||
gap: 24px;
|
gap: 24px;
|
||||||
margin-bottom: 32px;
|
margin-bottom: 32px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.data-card {
|
.data-card {
|
||||||
padding: 24px;
|
padding: 24px;
|
||||||
border-radius: 16px;
|
border-radius: 16px;
|
||||||
backdrop-filter: blur(4px);
|
backdrop-filter: blur(4px);
|
||||||
background: rgba(255,255,255,0.8);
|
background: rgba(255, 255, 255, 0.8);
|
||||||
box-shadow: 0 4px 24px 0 rgba(0,0,0,0.06);
|
box-shadow: 0 4px 24px 0 rgba(0, 0, 0, 0.06);
|
||||||
border: 1px solid rgba(255,255,255,0.2);
|
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||||
transition: transform 0.2s;
|
transition: transform 0.2s;
|
||||||
}
|
}
|
||||||
|
|
||||||
.data-card:hover {
|
.data-card:hover {
|
||||||
transform: scale(1.02);
|
transform: scale(1.02);
|
||||||
}
|
}
|
||||||
|
|
||||||
.data-card-header {
|
.data-card-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
margin-bottom: 16px;
|
margin-bottom: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.data-card-title {
|
.data-card-title {
|
||||||
color: #6b7280;
|
color: #6b7280;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
margin-bottom: 4px;
|
margin-bottom: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.data-card-value {
|
.data-card-value {
|
||||||
font-size: 24px;
|
font-size: 24px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
.data-card-icon {
|
.data-card-icon {
|
||||||
font-size: 24px;
|
font-size: 24px;
|
||||||
}
|
}
|
||||||
.icon-blue { color: #3b82f6; }
|
|
||||||
.icon-green { color: #22c55e; }
|
.icon-blue {
|
||||||
.icon-yellow { color: #eab308; }
|
color: #3b82f6;
|
||||||
.icon-purple { color: #a855f7; }
|
}
|
||||||
|
|
||||||
|
.icon-green {
|
||||||
|
color: #22c55e;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-yellow {
|
||||||
|
color: #eab308;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-purple {
|
||||||
|
color: #a855f7;
|
||||||
|
}
|
||||||
|
|
||||||
.data-card-chart {
|
.data-card-chart {
|
||||||
height: 48px;
|
height: 48px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.chart-inner {
|
.chart-inner {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
@@ -401,20 +437,23 @@ export default {
|
|||||||
gap: 24px;
|
gap: 24px;
|
||||||
margin-bottom: 32px;
|
margin-bottom: 32px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.business-module {
|
.business-module {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 16px;
|
padding: 16px;
|
||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
background: #fff;
|
background: #fff;
|
||||||
box-shadow: 0 2px 12px 0 rgba(0,0,0,0.05);
|
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05);
|
||||||
transition: all 0.2s;
|
transition: all 0.2s;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.business-module:hover {
|
.business-module:hover {
|
||||||
box-shadow: 0 4px 16px 0 rgba(0,0,0,0.1);
|
box-shadow: 0 4px 16px 0 rgba(0, 0, 0, 0.1);
|
||||||
transform: translateY(-2px);
|
transform: translateY(-2px);
|
||||||
}
|
}
|
||||||
|
|
||||||
.business-module-icon {
|
.business-module-icon {
|
||||||
width: 40px;
|
width: 40px;
|
||||||
height: 40px;
|
height: 40px;
|
||||||
@@ -425,17 +464,40 @@ export default {
|
|||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
margin-right: 12px;
|
margin-right: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.business-module-icon i {
|
.business-module-icon i {
|
||||||
font-size: 20px;
|
font-size: 20px;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
}
|
}
|
||||||
.bg-blue { background: #3b82f6; }
|
|
||||||
.bg-green { background: #22c55e; }
|
.bg-blue {
|
||||||
.bg-yellow { background: #eab308; }
|
background: #3b82f6;
|
||||||
.bg-purple { background: #a855f7; }
|
}
|
||||||
.bg-red { background: #ef4444; }
|
|
||||||
.bg-indigo { background: #6366f1; }
|
.bg-green {
|
||||||
.bg-teal { background: #14b8a6; }
|
background: #22c55e;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bg-yellow {
|
||||||
|
background: #eab308;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bg-purple {
|
||||||
|
background: #a855f7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bg-red {
|
||||||
|
background: #ef4444;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bg-indigo {
|
||||||
|
background: #6366f1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bg-teal {
|
||||||
|
background: #14b8a6;
|
||||||
|
}
|
||||||
|
|
||||||
.business-module-title {
|
.business-module-title {
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
@@ -447,30 +509,37 @@ export default {
|
|||||||
grid-template-columns: repeat(2, 1fr);
|
grid-template-columns: repeat(2, 1fr);
|
||||||
gap: 24px;
|
gap: 24px;
|
||||||
}
|
}
|
||||||
.monitor-resource, .monitor-records {
|
|
||||||
|
.monitor-resource,
|
||||||
|
.monitor-records {
|
||||||
padding: 24px;
|
padding: 24px;
|
||||||
border-radius: 16px;
|
border-radius: 16px;
|
||||||
background: #fff;
|
background: #fff;
|
||||||
box-shadow: 0 4px 24px 0 rgba(0,0,0,0.06);
|
box-shadow: 0 4px 24px 0 rgba(0, 0, 0, 0.06);
|
||||||
}
|
}
|
||||||
|
|
||||||
.monitor-title {
|
.monitor-title {
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
margin-bottom: 24px;
|
margin-bottom: 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.monitor-resource-charts {
|
.monitor-resource-charts {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(2, 1fr);
|
grid-template-columns: repeat(2, 1fr);
|
||||||
gap: 16px;
|
gap: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.monitor-resource-chart {
|
.monitor-resource-chart {
|
||||||
height: 160px;
|
height: 160px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.monitor-records-list {
|
.monitor-records-list {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 16px;
|
gap: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.monitor-record-item {
|
.monitor-record-item {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -478,9 +547,11 @@ export default {
|
|||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
transition: background 0.2s;
|
transition: background 0.2s;
|
||||||
}
|
}
|
||||||
|
|
||||||
.monitor-record-item:hover {
|
.monitor-record-item:hover {
|
||||||
background: #f9fafb;
|
background: #f9fafb;
|
||||||
}
|
}
|
||||||
|
|
||||||
.monitor-record-icon {
|
.monitor-record-icon {
|
||||||
width: 32px;
|
width: 32px;
|
||||||
height: 32px;
|
height: 32px;
|
||||||
@@ -490,24 +561,29 @@ export default {
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
margin-right: 16px;
|
margin-right: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.monitor-record-icon-inner {
|
.monitor-record-icon-inner {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
.monitor-record-action {
|
.monitor-record-action {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
.monitor-record-time {
|
.monitor-record-time {
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
color: #6b7280;
|
color: #6b7280;
|
||||||
}
|
}
|
||||||
|
|
||||||
.user-greeting-row {
|
.user-greeting-row {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 24px;
|
gap: 24px;
|
||||||
margin-bottom: 24px;
|
margin-bottom: 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.user-avatar {
|
.user-avatar {
|
||||||
width: 80px;
|
width: 80px;
|
||||||
height: 80px;
|
height: 80px;
|
||||||
@@ -516,16 +592,19 @@ export default {
|
|||||||
border: 2px solid #e0e0e0;
|
border: 2px solid #e0e0e0;
|
||||||
background: #fff;
|
background: #fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
.greeting-text {
|
.greeting-text {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.greeting-title {
|
.greeting-title {
|
||||||
font-size: 28px;
|
font-size: 28px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: #333;
|
color: #333;
|
||||||
}
|
}
|
||||||
|
|
||||||
.greeting-desc {
|
.greeting-desc {
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
color: #888;
|
color: #888;
|
||||||
@@ -539,4 +618,3 @@ export default {
|
|||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="current-situation-area">
|
<div class="current-situation-area">
|
||||||
<el-row :gutter="20">
|
<el-row :gutter="20" v-if="mode === 'normal'">
|
||||||
<!-- 订单所需的产品统计 -->
|
<!-- 订单所需的产品统计 -->
|
||||||
<el-col :span="8">
|
<el-col :span="8">
|
||||||
<el-card shadow="hover" class="situation-card">
|
<el-card shadow="hover" class="situation-card">
|
||||||
@@ -8,7 +8,8 @@
|
|||||||
<span>订单所需的产品统计</span>
|
<span>订单所需的产品统计</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="table-container">
|
<div class="table-container">
|
||||||
<el-table :data="orderProductStatistics" size="small" height="320" v-loading="!orderProductStatistics.length">
|
<el-table :data="orderProductStatistics" size="small" height="320"
|
||||||
|
v-loading="!orderProductStatistics.length">
|
||||||
<el-table-column prop="productName" label="产品名称" width="120" />
|
<el-table-column prop="productName" label="产品名称" width="120" />
|
||||||
<el-table-column prop="orderDemandQuantity" label="需求数量" width="80" />
|
<el-table-column prop="orderDemandQuantity" label="需求数量" width="80" />
|
||||||
<el-table-column prop="currentStockQuantity" label="库存数量" width="80" />
|
<el-table-column prop="currentStockQuantity" label="库存数量" width="80" />
|
||||||
@@ -36,7 +37,8 @@
|
|||||||
<span>BOM原料需求</span>
|
<span>BOM原料需求</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="table-container">
|
<div class="table-container">
|
||||||
<el-table :data="productMaterialRequirements" size="small" height="320" v-loading="!productMaterialRequirements.length">
|
<el-table :data="productMaterialRequirements" size="small" height="320"
|
||||||
|
v-loading="!productMaterialRequirements.length">
|
||||||
<el-table-column prop="productName" label="产品" width="100" />
|
<el-table-column prop="productName" label="产品" width="100" />
|
||||||
<el-table-column prop="materialName" label="原料" width="100" />
|
<el-table-column prop="materialName" label="原料" width="100" />
|
||||||
<el-table-column prop="requiredQuantity" label="需求数量" width="80" />
|
<el-table-column prop="requiredQuantity" label="需求数量" width="80" />
|
||||||
@@ -93,6 +95,33 @@
|
|||||||
</el-card>
|
</el-card>
|
||||||
</el-col>
|
</el-col>
|
||||||
</el-row>
|
</el-row>
|
||||||
|
|
||||||
|
<div class="current-situation-area-mini" v-else>
|
||||||
|
<el-card shadow="hover" class="situation-card">
|
||||||
|
<div slot="header" class="card-header">
|
||||||
|
<span>订单所需的产品统计</span>
|
||||||
|
</div>
|
||||||
|
<div class="table-container">
|
||||||
|
<el-table :data="orderProductStatistics" size="small" height="320" v-loading="!orderProductStatistics.length">
|
||||||
|
<el-table-column prop="productName" label="产品名称" width="120" />
|
||||||
|
<el-table-column prop="orderDemandQuantity" label="需求数量" width="80" />
|
||||||
|
<el-table-column prop="currentStockQuantity" label="库存数量" width="80" />
|
||||||
|
<el-table-column prop="stockGap" label="库存缺口" width="80">
|
||||||
|
<template slot-scope="scope">
|
||||||
|
<span :class="getStockGapClass(scope.row.stockGap)">
|
||||||
|
{{ scope.row.stockGap }}
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="relatedOrderCount" label="相关订单" width="80" />
|
||||||
|
</el-table>
|
||||||
|
<div v-if="!orderProductStatistics.length" class="empty-data">
|
||||||
|
<i class="el-icon-warning-outline"></i>
|
||||||
|
<p>暂无数据</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-card>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -104,6 +133,10 @@ export default {
|
|||||||
type: Object,
|
type: Object,
|
||||||
required: true,
|
required: true,
|
||||||
default: () => ({})
|
default: () => ({})
|
||||||
|
},
|
||||||
|
mode: {
|
||||||
|
type: String,
|
||||||
|
default: 'normal'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="performance-area">
|
<div class="performance-area">
|
||||||
<el-row :gutter="20">
|
<el-row :gutter="20" v-if="mode === 'normal'">
|
||||||
<!-- 产品销售情况 -->
|
<!-- 产品销售情况 -->
|
||||||
<el-col :span="8">
|
<el-col :span="8">
|
||||||
<el-card shadow="hover" class="performance-card">
|
<el-card shadow="hover" class="performance-card">
|
||||||
@@ -56,6 +56,45 @@
|
|||||||
</el-card>
|
</el-card>
|
||||||
</el-col>
|
</el-col>
|
||||||
</el-row>
|
</el-row>
|
||||||
|
|
||||||
|
<div class="performance-area-mini" v-else>
|
||||||
|
<!-- <el-tabs tab-position="left"> -->
|
||||||
|
<!-- <el-tab-pane label="产品销售情况" name="productSales">
|
||||||
|
<div class="chart-container" ref="productSalesChart" style="width: 100%;"></div>
|
||||||
|
</el-tab-pane>
|
||||||
|
<el-tab-pane label="销售人员业绩" name="salesPerson">
|
||||||
|
<div class="chart-container" ref="salesPersonChart" style="width: 100%;"></div>
|
||||||
|
</el-tab-pane> -->
|
||||||
|
<!-- <el-tab-pane label="总订单数量统计" name="orderCount"> -->
|
||||||
|
<div class="stats-container">
|
||||||
|
<div class="stat-item">
|
||||||
|
<div class="stat-number">{{ orderCountStatistics.totalOrderCount || 0 }}</div>
|
||||||
|
<div class="stat-label">总订单数</div>
|
||||||
|
</div>
|
||||||
|
<div class="stat-item">
|
||||||
|
<div class="stat-number">{{ orderCountStatistics.completedOrderCount || 0 }}</div>
|
||||||
|
<div class="stat-label">已完成</div>
|
||||||
|
</div>
|
||||||
|
<div class="stat-item">
|
||||||
|
<div class="stat-number">{{ orderCountStatistics.inProgressOrderCount || 0 }}</div>
|
||||||
|
<div class="stat-label">进行中</div>
|
||||||
|
</div>
|
||||||
|
<div class="stat-item">
|
||||||
|
<div class="stat-number">{{ orderCountStatistics.pendingOrderCount || 0 }}</div>
|
||||||
|
<div class="stat-label">待处理</div>
|
||||||
|
</div>
|
||||||
|
<div class="stat-item">
|
||||||
|
<div class="stat-number">{{ orderCountStatistics.monthlyNewOrderCount || 0 }}</div>
|
||||||
|
<div class="stat-label">本月新增</div>
|
||||||
|
</div>
|
||||||
|
<div class="stat-item">
|
||||||
|
<div class="stat-number">{{ ((orderCountStatistics.completionRate || 0) * 100).toFixed(1) }}%</div>
|
||||||
|
<div class="stat-label">完成率</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- </el-tab-pane> -->
|
||||||
|
<!-- </el-tabs> -->
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -69,6 +108,10 @@ export default {
|
|||||||
type: Object,
|
type: Object,
|
||||||
required: true,
|
required: true,
|
||||||
default: () => ({})
|
default: () => ({})
|
||||||
|
},
|
||||||
|
mode: {
|
||||||
|
type: String,
|
||||||
|
default: 'normal'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="recommendation-area">
|
<div class="recommendation-area">
|
||||||
<el-row :gutter="20">
|
<el-row :gutter="20" v-if="mode === 'normal'">
|
||||||
<!-- 订单维度推荐 -->
|
<!-- 订单维度推荐 -->
|
||||||
<el-col :span="12">
|
<el-col :span="12">
|
||||||
<el-card shadow="hover" class="recommendation-card">
|
<el-card shadow="hover" class="recommendation-card">
|
||||||
@@ -61,6 +61,32 @@
|
|||||||
</el-card>
|
</el-card>
|
||||||
</el-col>
|
</el-col>
|
||||||
</el-row>
|
</el-row>
|
||||||
|
|
||||||
|
<div class="recommendation-area-mini" v-else>
|
||||||
|
<el-card shadow="hover" class="recommendation-card">
|
||||||
|
<div class="table-container">
|
||||||
|
<el-table :data="materialRecommendations" size="small" height="320" v-loading="!materialRecommendations.length">
|
||||||
|
<el-table-column prop="materialName" label="原料名称" width="120" />
|
||||||
|
<el-table-column prop="recommendedPurchaseQuantity" label="推荐采购数量" width="120" />
|
||||||
|
<el-table-column prop="recommendedSupplier" label="推荐供应商" width="100" show-overflow-tooltip />
|
||||||
|
<el-table-column prop="urgencyLevel" label="紧急程度" width="80">
|
||||||
|
<template slot-scope="scope">
|
||||||
|
<el-tag :type="getUrgencyType(scope.row.urgencyLevel)" size="mini">
|
||||||
|
{{ scope.row.urgencyLevel }}
|
||||||
|
</el-tag>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="recommendationReason" label="推荐原因" width="150" show-overflow-tooltip />
|
||||||
|
<el-table-column prop="suggestedAction" label="建议操作" width="100" />
|
||||||
|
<el-table-column prop="estimatedArrivalTime" label="预计到货时间" width="120" />
|
||||||
|
</el-table>
|
||||||
|
<div v-if="!materialRecommendations.length" class="empty-data">
|
||||||
|
<i class="el-icon-warning-outline"></i>
|
||||||
|
<p>暂无推荐数据</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-card>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -72,6 +98,10 @@ export default {
|
|||||||
type: Object,
|
type: Object,
|
||||||
required: true,
|
required: true,
|
||||||
default: () => ({})
|
default: () => ({})
|
||||||
|
},
|
||||||
|
mode: {
|
||||||
|
type: String,
|
||||||
|
default: 'normal'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
|||||||
295
klp-ui/src/views/wms/print/components/CodeRenderer.vue
Normal file
295
klp-ui/src/views/wms/print/components/CodeRenderer.vue
Normal file
@@ -0,0 +1,295 @@
|
|||||||
|
<template>
|
||||||
|
<div class="barcode-simple-layout">
|
||||||
|
<!-- 仅保留预览区 -->
|
||||||
|
<div class="barcode-preview-area">
|
||||||
|
<div class="iframe-wrapper">
|
||||||
|
<iframe ref="previewIframe" class="barcode-iframe" frameborder="0" :style="iframeStyle"></iframe>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import QRCode from 'qrcode';
|
||||||
|
export default {
|
||||||
|
name: 'BarcodeRenderer',
|
||||||
|
props: {
|
||||||
|
barcodes: {
|
||||||
|
type: Array,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
// 可通过props传入配置,保持一定灵活性
|
||||||
|
layoutConfig: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({
|
||||||
|
perRow: 3,
|
||||||
|
barcodeWidth: 180,
|
||||||
|
paperSize: 'A4',
|
||||||
|
paperOrientation: 'portrait',
|
||||||
|
customPaperWidth: 210,
|
||||||
|
customPaperHeight: 297
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
barcodeConfigs: [], // 二维码配置数组 [{code, count, textTpl}]
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
// 从传入的配置中获取参数
|
||||||
|
perRow() {
|
||||||
|
return this.layoutConfig.perRow || 3;
|
||||||
|
},
|
||||||
|
barcodeWidth() {
|
||||||
|
return this.layoutConfig.barcodeWidth || 180;
|
||||||
|
},
|
||||||
|
barcodeHeight() {
|
||||||
|
return this.layoutConfig.barcodeWidth || 180; // 保持宽高一致
|
||||||
|
},
|
||||||
|
paperSize() {
|
||||||
|
return this.layoutConfig.paperSize || 'A4';
|
||||||
|
},
|
||||||
|
paperOrientation() {
|
||||||
|
return this.layoutConfig.paperOrientation || 'portrait';
|
||||||
|
},
|
||||||
|
customPaperWidth() {
|
||||||
|
return this.layoutConfig.customPaperWidth || 210;
|
||||||
|
},
|
||||||
|
customPaperHeight() {
|
||||||
|
return this.layoutConfig.customPaperHeight || 297;
|
||||||
|
},
|
||||||
|
// 展开后的二维码列表(考虑生成数量)
|
||||||
|
expandedBarcodes() {
|
||||||
|
let arr = [];
|
||||||
|
this.barcodeConfigs.forEach(cfg => {
|
||||||
|
for (let i = 0; i < (cfg.count || 1); i++) {
|
||||||
|
arr.push(cfg.code);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return arr;
|
||||||
|
},
|
||||||
|
// 展开后的文字列表
|
||||||
|
expandedBarcodeTexts() {
|
||||||
|
let arr = [];
|
||||||
|
this.barcodeConfigs.forEach(cfg => {
|
||||||
|
for (let i = 0; i < (cfg.count || 1); i++) {
|
||||||
|
let text = cfg.textTpl || cfg.code;
|
||||||
|
text = text.replace(/\{\{n\}\}/g, i + 1); // 替换序号变量
|
||||||
|
arr.push(text);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return arr;
|
||||||
|
},
|
||||||
|
// 按行分组的二维码
|
||||||
|
barcodeRows() {
|
||||||
|
const rows = [];
|
||||||
|
const arr = this.expandedBarcodes;
|
||||||
|
for (let i = 0; i < arr.length; i += this.perRow) {
|
||||||
|
rows.push(arr.slice(i, i + this.perRow));
|
||||||
|
}
|
||||||
|
return rows;
|
||||||
|
},
|
||||||
|
// 按行分组的文字
|
||||||
|
barcodeTextRows() {
|
||||||
|
const rows = [];
|
||||||
|
const arr = this.expandedBarcodeTexts;
|
||||||
|
for (let i = 0; i < arr.length; i += this.perRow) {
|
||||||
|
rows.push(arr.slice(i, i + this.perRow));
|
||||||
|
}
|
||||||
|
return rows;
|
||||||
|
},
|
||||||
|
// iframe样式
|
||||||
|
iframeStyle() {
|
||||||
|
const { width, height } = this.getPaperPx();
|
||||||
|
return {
|
||||||
|
width: width + 'px',
|
||||||
|
minHeight: height + 'px',
|
||||||
|
height: height + 'px',
|
||||||
|
background: '#fff',
|
||||||
|
border: 'none',
|
||||||
|
display: 'block',
|
||||||
|
margin: 0,
|
||||||
|
padding: 0
|
||||||
|
};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
// 监听barcodes变化,重新初始化和渲染
|
||||||
|
barcodes: {
|
||||||
|
handler(newVal) {
|
||||||
|
this.barcodeConfigs = newVal.map(b => ({
|
||||||
|
code: b.code || b,
|
||||||
|
count: b.count || 1,
|
||||||
|
textTpl: b.textTpl || (b.code || b)
|
||||||
|
}));
|
||||||
|
this.$nextTick(this.renderPreviewIframe);
|
||||||
|
},
|
||||||
|
immediate: true,
|
||||||
|
deep: true
|
||||||
|
},
|
||||||
|
// 监听配置变化,重新渲染
|
||||||
|
layoutConfig: {
|
||||||
|
handler() {
|
||||||
|
this.$nextTick(this.renderPreviewIframe);
|
||||||
|
},
|
||||||
|
deep: true
|
||||||
|
},
|
||||||
|
// 监听二维码配置变化,重新渲染
|
||||||
|
barcodeConfigs: {
|
||||||
|
handler() {
|
||||||
|
this.$nextTick(this.renderPreviewIframe);
|
||||||
|
},
|
||||||
|
deep: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
// 生成二维码ID
|
||||||
|
getBarcodeId(row, col) {
|
||||||
|
return `barcode-canvas-${row}-${col}`;
|
||||||
|
},
|
||||||
|
// 获取二维码下方文字
|
||||||
|
getBarcodeText(row, col, code) {
|
||||||
|
if (
|
||||||
|
this.barcodeTextRows[row] &&
|
||||||
|
typeof this.barcodeTextRows[row][col] !== 'undefined' &&
|
||||||
|
this.barcodeTextRows[row][col] !== null &&
|
||||||
|
this.barcodeTextRows[row][col] !== ''
|
||||||
|
) {
|
||||||
|
return this.barcodeTextRows[row][col];
|
||||||
|
}
|
||||||
|
return code;
|
||||||
|
},
|
||||||
|
// 将纸张尺寸从mm转换为px(1mm ≈ 3.78px)
|
||||||
|
getPaperPx() {
|
||||||
|
let width, height;
|
||||||
|
if (this.paperSize === 'A4') {
|
||||||
|
width = 210 * 3.78;
|
||||||
|
height = 297 * 3.78;
|
||||||
|
} else if (this.paperSize === 'A5') {
|
||||||
|
width = 148 * 3.78;
|
||||||
|
height = 210 * 3.78;
|
||||||
|
} else if (this.paperSize === 'A6') {
|
||||||
|
width = 105 * 3.78;
|
||||||
|
height = 148 * 3.78;
|
||||||
|
} else {
|
||||||
|
width = this.customPaperWidth * 3.78;
|
||||||
|
height = this.customPaperHeight * 3.78;
|
||||||
|
}
|
||||||
|
// 处理横向/纵向
|
||||||
|
if (this.paperOrientation === 'landscape') {
|
||||||
|
return { width: height, height: width };
|
||||||
|
}
|
||||||
|
return { width, height };
|
||||||
|
},
|
||||||
|
// 生成打印用的HTML
|
||||||
|
getPrintHtml() {
|
||||||
|
const { width, height } = this.getPaperPx();
|
||||||
|
let html = `
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>二维码预览</title>
|
||||||
|
<style>
|
||||||
|
body { margin: 0; padding: 0; background: #fff; }
|
||||||
|
.barcode-list { width: ${width}px; min-height: ${height}px; margin: 0 auto; background: #fff; padding: 20px; box-sizing: border-box; }
|
||||||
|
.barcode-row { display: flex; margin-bottom: 24px; justify-content: space-around; }
|
||||||
|
.barcode-item { flex: 1; text-align: center; padding: 0 10px; box-sizing: border-box; }
|
||||||
|
.barcode-text { margin-top: 8px; font-size: 14px; word-break: break-all; }
|
||||||
|
html, body { overflow-x: hidden; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="barcode-list">
|
||||||
|
`;
|
||||||
|
// 生成二维码行
|
||||||
|
this.barcodeRows.forEach((row, rowIndex) => {
|
||||||
|
html += '<div class="barcode-row">';
|
||||||
|
row.forEach((code, colIndex) => {
|
||||||
|
const id = this.getBarcodeId(rowIndex, colIndex);
|
||||||
|
const text = this.getBarcodeText(rowIndex, colIndex, code);
|
||||||
|
html += `<div class="barcode-item">
|
||||||
|
<canvas id="${id}" width="${this.barcodeWidth}" height="${this.barcodeHeight}"></canvas>
|
||||||
|
<div class="barcode-text">${text}</div>
|
||||||
|
</div>`;
|
||||||
|
});
|
||||||
|
html += '</div>';
|
||||||
|
});
|
||||||
|
html += '</div></body></html>';
|
||||||
|
return html;
|
||||||
|
},
|
||||||
|
// 渲染预览iframe
|
||||||
|
async renderPreviewIframe() {
|
||||||
|
const iframe = this.$refs.previewIframe;
|
||||||
|
if (!iframe) return;
|
||||||
|
|
||||||
|
const doc = iframe.contentDocument || iframe.contentWindow.document;
|
||||||
|
doc.open();
|
||||||
|
doc.write(this.getPrintHtml());
|
||||||
|
doc.close();
|
||||||
|
|
||||||
|
// 绘制二维码
|
||||||
|
setTimeout(async () => {
|
||||||
|
for (let rowIndex = 0; rowIndex < this.barcodeRows.length; rowIndex++) {
|
||||||
|
const row = this.barcodeRows[rowIndex];
|
||||||
|
for (let colIndex = 0; colIndex < row.length; colIndex++) {
|
||||||
|
const code = row[colIndex];
|
||||||
|
const id = this.getBarcodeId(rowIndex, colIndex);
|
||||||
|
const el = doc.getElementById(id);
|
||||||
|
if (el) {
|
||||||
|
await QRCode.toCanvas(el, code, {
|
||||||
|
width: this.barcodeWidth,
|
||||||
|
height: this.barcodeHeight,
|
||||||
|
margin: 0
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, 50);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
// 初始化二维码配置
|
||||||
|
this.barcodeConfigs = this.barcodes.map(b => ({
|
||||||
|
code: b.code || b,
|
||||||
|
count: b.count || 1,
|
||||||
|
textTpl: b.textTpl || (b.code || b)
|
||||||
|
}));
|
||||||
|
this.renderPreviewIframe();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.barcode-simple-layout {
|
||||||
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
min-height: 400px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.barcode-preview-area {
|
||||||
|
flex: 1;
|
||||||
|
background: #fff;
|
||||||
|
padding: 24px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 100vh;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.iframe-wrapper {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: flex-start;
|
||||||
|
background: #f8f8f8;
|
||||||
|
overflow: auto;
|
||||||
|
padding: 20px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.barcode-iframe {
|
||||||
|
border-radius: 4px;
|
||||||
|
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
70
klp-ui/src/views/wms/print/index-simple.vue
Normal file
70
klp-ui/src/views/wms/print/index-simple.vue
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
<template>
|
||||||
|
<div style="position: relative;">
|
||||||
|
<!-- 录入二维码列表 -->
|
||||||
|
<el-form inline style="position: relative;">
|
||||||
|
<el-form-item label="物料类型">
|
||||||
|
<el-select v-model="itemType" placeholder="请选择物料类型" clearable>
|
||||||
|
<el-option v-for="dict in dict.type.stock_item_type" :key="dict.value" :label="dict.label"
|
||||||
|
:value="dict.value" />
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="物料信息">
|
||||||
|
<ProductSelect can-add v-if="itemType === 'product'" v-model="itemId" placeholder="请选择产品"
|
||||||
|
@change="onItemChange" />
|
||||||
|
<SemiSelect can-add v-else-if="itemType === 'semi'" v-model="itemId" placeholder="请选择半成品"
|
||||||
|
@change="onItemChange" />
|
||||||
|
<RawMaterialSelect can-add v-else-if="itemType === 'raw_material'" v-model="itemId" placeholder="请选择原材料"
|
||||||
|
@change="onItemChange" />
|
||||||
|
<el-input v-else disabled v-model="itemId" placeholder="请先选择物料类型" :disabled="true" style="width: 100%;" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
|
||||||
|
<div class="print-container" v-loading="loading">
|
||||||
|
<BarCode :barcodes="drawerBarcodeData" @delete="handleDelete" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import BarCode from '../stockIo/panels/barcode.vue';
|
||||||
|
import ProductSelect from '@/components/KLPService/ProductSelect/index.vue';
|
||||||
|
import SemiSelect from '@/components/KLPService/SemiSelect/index.vue';
|
||||||
|
import RawMaterialSelect from '@/components/KLPService/RawMaterialSelect/index.vue';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'Print',
|
||||||
|
components: { BarCode, ProductSelect, SemiSelect, RawMaterialSelect },
|
||||||
|
dicts: ['stock_item_type'],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
drawerBarcodeData: [], // 条码数据
|
||||||
|
loading: false, // 加载状态
|
||||||
|
itemId: undefined, // 物料ID
|
||||||
|
itemType: undefined, // 物料类型
|
||||||
|
};
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
onItemChange(item) {
|
||||||
|
// 选中后构造条码数据并插入
|
||||||
|
console.log(item);
|
||||||
|
const itemType = this.itemType;
|
||||||
|
const name = (itemType == 'semi' || itemType == 'product') ? item.productName : item.rawMaterialName;
|
||||||
|
const code = (itemType == 'semi' || itemType == 'product') ? item.productCode : item.rawMaterialCode;
|
||||||
|
const itemId = (itemType == 'semi' || itemType == 'product') ? item.productId : item.rawMaterialId;
|
||||||
|
const o = {
|
||||||
|
code: encodeURIComponent(`${itemType}__${itemId || ''}`),
|
||||||
|
count: 1,
|
||||||
|
textTpl: `${name}[${code}]`
|
||||||
|
}
|
||||||
|
this.drawerBarcodeData.push(o);
|
||||||
|
},
|
||||||
|
handleDelete(cfg, idx) {
|
||||||
|
console.log(cfg, idx);
|
||||||
|
this.drawerBarcodeData.splice(idx, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
</style>
|
||||||
@@ -1,69 +1,238 @@
|
|||||||
<template>
|
<template>
|
||||||
<div style="position: relative;">
|
<div>
|
||||||
<!-- 录入二维码列表 -->
|
<el-row>
|
||||||
<el-form inline style="position: relative;">
|
<el-col :span="8">
|
||||||
<el-form-item label="物料类型">
|
<div class="left-container">
|
||||||
<el-select v-model="itemType" placeholder="请选择物料类型" clearable>
|
<el-button type="primary" @click="handleAdd">添加二维码</el-button>
|
||||||
|
<el-form label-width="80px" size="small" label-position="top">
|
||||||
|
<div v-for="(cfg, idx) in drawerBarcodeData" :key="idx"
|
||||||
|
style="margin-bottom: 16px; border: 1px solid #eee; border-radius: 4px; padding: 12px 16px; background: #fafbfc;">
|
||||||
|
<div style="margin-bottom: 8px; display: flex; justify-content: space-between; align-items: center;">
|
||||||
|
<span style="font-weight: bold; color: #666;">二维码 {{ idx + 1 }}</span>
|
||||||
|
<el-button type="text" size="mini" @click="saveAsImage(idx)"
|
||||||
|
icon="el-icon-download">
|
||||||
|
另存为图片
|
||||||
|
</el-button>
|
||||||
|
<el-button type="text" size="mini" @click="handleDelete(cfg, idx)" icon="el-icon-delete">
|
||||||
|
删除
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
<el-row>
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-form-item label="操作类型" style="margin-bottom: 8px;">
|
||||||
|
<el-select v-model="cfg.ioType" placeholder="请选择操作类型">
|
||||||
|
<el-option label="入库" value="in" />
|
||||||
|
<el-option label="出库" value="out" />
|
||||||
|
<el-option label="移库" value="transfer" />
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-form-item label="单据" style="margin-bottom: 8px;">
|
||||||
|
<el-select clearable filterable size="mini" v-model="cfg.stockIoId" placeholder="请选择挂载单据"
|
||||||
|
class="form-input">
|
||||||
|
<el-option v-for="item in masterList.filter(i => i.ioType === cfg.ioType)" :key="item.stockIoId"
|
||||||
|
:label="item.stockIoCode"
|
||||||
|
:value="item.stockIoId" />
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
<el-row>
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-form-item label="目标仓库" style="margin-bottom: 8px;">
|
||||||
|
<WarehouseSelect v-model="cfg.warehouseId" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-form-item label="源仓库" v-if="cfg.ioType === 'transfer'" style="margin-bottom: 8px;">
|
||||||
|
<WarehouseSelect v-model="cfg.fromWarehouseId" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
|
||||||
|
<el-row>
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-form-item label="物料类型" style="margin-bottom: 8px;">
|
||||||
|
<el-select v-model="cfg.itemType" placeholder="请选择物料类型">
|
||||||
<el-option v-for="dict in dict.type.stock_item_type" :key="dict.value" :label="dict.label"
|
<el-option v-for="dict in dict.type.stock_item_type" :key="dict.value" :label="dict.label"
|
||||||
:value="dict.value" />
|
:value="dict.value" />
|
||||||
</el-select>
|
</el-select>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="物料信息">
|
</el-col>
|
||||||
<ProductSelect can-add v-if="itemType === 'product'" v-model="itemId" placeholder="请选择产品"
|
<el-col :span="12">
|
||||||
@change="onItemChange" />
|
<el-form-item label="物料信息" style="margin-bottom: 8px;">
|
||||||
<SemiSelect can-add v-else-if="itemType === 'semi'" v-model="itemId" placeholder="请选择半成品"
|
<ProductSelect v-if="cfg.itemType === 'product'" v-model="cfg.itemId" placeholder="请选择产品" @change="onItemChange($event, idx)" />
|
||||||
@change="onItemChange" />
|
<SemiSelect v-else-if="cfg.itemType === 'semi'" v-model="cfg.itemId" placeholder="请选择半成品" @change="onItemChange($event, idx)" />
|
||||||
<RawMaterialSelect can-add v-else-if="itemType === 'raw_material'" v-model="itemId" placeholder="请选择原材料"
|
<RawMaterialSelect v-else-if="cfg.itemType === 'raw_material'" v-model="cfg.itemId"
|
||||||
@change="onItemChange" />
|
placeholder="请选择原材料" @change="onItemChange($event, idx)" />
|
||||||
<el-input v-else disabled v-model="itemId" placeholder="请先选择物料类型" :disabled="true" style="width: 100%;" />
|
<el-input v-else disabled v-model="cfg.itemId" placeholder="请先选择物料类型" :disabled="true"
|
||||||
|
style="width: 100%;" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-form>
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
|
||||||
<div class="print-container" v-loading="loading">
|
<el-row>
|
||||||
<BarCode :barcodes="drawerBarcodeData" @delete="handleDelete" />
|
<el-col :span="12">
|
||||||
|
<el-form-item label="数量" style="margin-bottom: 8px;">
|
||||||
|
<el-input-number v-model="cfg.count" :min="1" :max="100" size="mini" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-form-item label="下方文字" style="margin-bottom: 8px;">
|
||||||
|
<el-input v-model="cfg.text" size="mini" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
</div>
|
</div>
|
||||||
|
</el-form>
|
||||||
|
</div>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="16">
|
||||||
|
<div class="right-container">
|
||||||
|
<div v-loading="loading">
|
||||||
|
<BarCode :barcodes="barCodeConfigs" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import BarCode from '../stockIo/panels/barcode.vue';
|
import BarCode from './components/CodeRenderer.vue';
|
||||||
import ProductSelect from '@/components/KLPService/ProductSelect/index.vue';
|
import ProductSelect from '@/components/KLPService/ProductSelect/index.vue';
|
||||||
import SemiSelect from '@/components/KLPService/SemiSelect/index.vue';
|
import SemiSelect from '@/components/KLPService/SemiSelect/index.vue';
|
||||||
import RawMaterialSelect from '@/components/KLPService/RawMaterialSelect/index.vue';
|
import RawMaterialSelect from '@/components/KLPService/RawMaterialSelect/index.vue';
|
||||||
|
import WarehouseSelect from '@/components/WarehouseSelect/index.vue';
|
||||||
|
import { saveAsImage } from '@/utils/klp';
|
||||||
|
|
||||||
|
import { listStockIo } from '@/api/wms/stockIo';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'Print',
|
name: 'Print',
|
||||||
components: { BarCode, ProductSelect, SemiSelect, RawMaterialSelect },
|
components: { BarCode, ProductSelect, SemiSelect, RawMaterialSelect, WarehouseSelect },
|
||||||
dicts: ['stock_item_type'],
|
dicts: ['stock_item_type', 'stock_io_type'],
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
drawerBarcodeData: [], // 条码数据
|
drawerBarcodeData: [], // 条码数据
|
||||||
loading: false, // 加载状态
|
loading: false, // 加载状态
|
||||||
itemId: undefined, // 物料ID
|
itemId: undefined, // 物料ID
|
||||||
itemType: undefined, // 物料类型
|
itemType: undefined, // 物料类型
|
||||||
|
masterList: [],
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
methods: {
|
mounted() {
|
||||||
onItemChange(item) {
|
this.fetchMaster();
|
||||||
// 选中后构造条码数据并插入
|
},
|
||||||
console.log(item);
|
computed: {
|
||||||
const itemType = this.itemType;
|
barCodeConfigs() {
|
||||||
const name = (itemType == 'semi' || itemType == 'product') ? item.productName : item.rawMaterialName;
|
return this.drawerBarcodeData.map(b => ({
|
||||||
const code = (itemType == 'semi' || itemType == 'product') ? item.productCode : item.rawMaterialCode;
|
code: JSON.stringify({
|
||||||
const o = {
|
ioType: b.ioType,
|
||||||
code: encodeURIComponent(`${itemType}__${item.itemId || ''}`),
|
stockIoId: b.stockIoId,
|
||||||
count: 1,
|
fromWarehouseId: b.fromWarehouseId,
|
||||||
textTpl: `${name}[${code}]`
|
warehouseId: b.warehouseId,
|
||||||
|
itemType: b.itemType,
|
||||||
|
itemId: b.itemId,
|
||||||
|
batchNo: b.batchNo,
|
||||||
|
quantity: b.count,
|
||||||
|
unit: b.unit,
|
||||||
|
recordType: 1,
|
||||||
|
}),
|
||||||
|
count: b.count || 1,
|
||||||
|
textTpl: b.text || ''
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
this.drawerBarcodeData.push(o);
|
},
|
||||||
|
methods: {
|
||||||
|
onItemChange(item, idx) {
|
||||||
|
this.drawerBarcodeData[idx].unit = item.unit;
|
||||||
|
},
|
||||||
|
fetchMaster() {
|
||||||
|
listStockIo({ pageSize: 9999, pageNum: 1 }).then(res => {
|
||||||
|
console.log("获取挂载单据", res);
|
||||||
|
this.masterList = res.rows || [];
|
||||||
|
}).catch(error => {
|
||||||
|
console.error("获取挂载单据失败", error);
|
||||||
|
this.$message.error("获取挂载单据失败");
|
||||||
|
});
|
||||||
},
|
},
|
||||||
handleDelete(cfg, idx) {
|
handleDelete(cfg, idx) {
|
||||||
console.log(cfg, idx);
|
console.log(cfg, idx);
|
||||||
this.drawerBarcodeData.splice(idx, 1);
|
this.drawerBarcodeData.splice(idx, 1);
|
||||||
|
},
|
||||||
|
handleAdd() {
|
||||||
|
const o = {
|
||||||
|
ioType: undefined,
|
||||||
|
stockIoId: undefined,
|
||||||
|
|
||||||
|
fromWarehouseId: undefined,
|
||||||
|
warehouseId: undefined,
|
||||||
|
|
||||||
|
itemType: undefined,
|
||||||
|
itemId: undefined,
|
||||||
|
batchNo: 'auto',
|
||||||
|
count: 0,
|
||||||
|
unit: '',
|
||||||
|
|
||||||
|
text: '默认文字',
|
||||||
|
}
|
||||||
|
|
||||||
|
this.drawerBarcodeData.push(o);
|
||||||
|
},
|
||||||
|
// 补充saveAsImage方法的空实现,避免控制台报错
|
||||||
|
saveAsImage(index) {
|
||||||
|
saveAsImage(this.barCodeConfigs[index].code, this.barCodeConfigs[index].textTpl, index, this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
|
// 左侧容器样式
|
||||||
|
.left-container {
|
||||||
|
height: calc(100vh - 84px); // 高度为视口高度减去顶部间距
|
||||||
|
overflow-y: auto; // 纵向滚动
|
||||||
|
padding-right: 10px; // 避免内容与滚动条重叠
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 右侧容器样式
|
||||||
|
.right-container {
|
||||||
|
height: calc(100vh - 84px); // 与左侧保持一致的高度
|
||||||
|
overflow-y: auto;
|
||||||
|
padding-left: 10px; // 与左侧间距区分
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 美化滚动条
|
||||||
|
::-webkit-scrollbar {
|
||||||
|
width: 6px; // 滚动条宽度
|
||||||
|
height: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-thumb {
|
||||||
|
background: #ddd;
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-track {
|
||||||
|
background: #f5f5f5;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解决scoped样式无法穿透到滚动条的问题
|
||||||
|
::v-deep ::-webkit-scrollbar {
|
||||||
|
width: 6px;
|
||||||
|
height: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
::v-deep ::-webkit-scrollbar-thumb {
|
||||||
|
background: #ddd;
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
::v-deep ::-webkit-scrollbar-track {
|
||||||
|
background: #f5f5f5;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -20,14 +20,14 @@
|
|||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :span="20" class="main-panel">
|
<!-- <el-col :span="20" class="main-panel">
|
||||||
<div class="form-panel">
|
<div class="form-panel">
|
||||||
<el-form inline>
|
<el-form inline>
|
||||||
<el-form-item label="库位" class="form-item">
|
<el-form-item label="目标库位" class="form-item">
|
||||||
<WarehouseSelect size="mini" style="width: 200px;" v-model="defaultForm.warehouseId" />
|
<WarehouseSelect size="mini" style="width: 200px;" v-model="defaultForm.warehouseId" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="挂载单据" class="form-item">
|
<el-form-item label="挂载单据" class="form-item">
|
||||||
<el-select size="mini" v-model="defaultForm.stockIoId" placeholder="请选择挂载单据" clearable class="form-input">
|
<el-select size="mini" filterable v-model="defaultForm.stockIoId" placeholder="请选择挂载单据" clearable class="form-input">
|
||||||
<el-option
|
<el-option
|
||||||
v-for="item in masterList"
|
v-for="item in masterList"
|
||||||
:key="item.stockIoId"
|
:key="item.stockIoId"
|
||||||
@@ -52,9 +52,9 @@
|
|||||||
</el-form>
|
</el-form>
|
||||||
</div>
|
</div>
|
||||||
<div class="table-panel">
|
<div class="table-panel">
|
||||||
<el-table :data="messageList" style="width: 100%" height="100%" class="message-table" stripe @selection-change="handleSelectionChange">
|
<el-table height="100%" :data="messageList" style="width: 100%" class="message-table" stripe @selection-change="handleSelectionChange">
|
||||||
<el-table-column type="selection" width="55" align="center" />
|
<el-table-column type="selection" width="55" align="center" />
|
||||||
<el-table-column prop="time" label="时间" width="60" align="center" />
|
<el-table-column prop="time" label="时间" width="150" align="center" />
|
||||||
<el-table-column prop="itemId" label="物料" align="center">
|
<el-table-column prop="itemId" label="物料" align="center">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<ProductInfo v-if="scope.row.itemType == 'product' || scope.row.itemType == 'semi'" :productId="scope.row.itemId" />
|
<ProductInfo v-if="scope.row.itemType == 'product' || scope.row.itemType == 'semi'" :productId="scope.row.itemId" />
|
||||||
@@ -68,7 +68,7 @@
|
|||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column prop="stockIoId" label="挂载单据" align="center">
|
<el-table-column prop="stockIoId" label="挂载单据" align="center">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<el-select v-model="scope.row.stockIoId" placeholder="请选择挂载单据" clearable class="table-select">
|
<el-select v-model="scope.row.stockIoId" filterable placeholder="请选择挂载单据" clearable class="table-select">
|
||||||
<el-option
|
<el-option
|
||||||
v-for="item in masterList"
|
v-for="item in masterList"
|
||||||
:key="item.stockIoId"
|
:key="item.stockIoId"
|
||||||
@@ -100,15 +100,15 @@
|
|||||||
<el-input v-model="scope.row.batchNo" class="table-input" />
|
<el-input v-model="scope.row.batchNo" class="table-input" />
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="操作" align="center" width="120" fixed="right">
|
<el-table-column label="操作" align="center" width="140" fixed="right">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<el-button type="primary" size="mini" @click="handleDelete(scope.row)">删除</el-button>
|
<el-button size="mini" type="text" @click="handleDelete(scope.row)">删除</el-button>
|
||||||
<el-button type="primary" size="mini" @click="handleConfirm(scope.row)">确认</el-button>
|
<el-button size="mini" type="text" @click="handleConfirm(scope.row)">确认</el-button>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
</el-table>
|
</el-table>
|
||||||
</div>
|
</div>
|
||||||
</el-col>
|
</el-col> -->
|
||||||
</el-row>
|
</el-row>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -441,6 +441,7 @@ export default {
|
|||||||
|
|
||||||
.table-panel {
|
.table-panel {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
height: calc(100vh - 220px);
|
||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
|
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
|
||||||
|
|||||||
@@ -12,9 +12,9 @@
|
|||||||
<el-input v-model="queryParams.owner" placeholder="请输入负责人" />
|
<el-input v-model="queryParams.owner" placeholder="请输入负责人" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<!-- 远程搜索供应商 -->
|
<!-- 远程搜索供应商 -->
|
||||||
<el-form-item label="供应商" prop="supplierId">
|
<!-- <el-form-item label="供应商" prop="supplierId">
|
||||||
<VendorSelect v-model="queryParams.supplierId" />
|
<VendorSelect v-model="queryParams.supplierId" />
|
||||||
</el-form-item>
|
</el-form-item> -->
|
||||||
<el-form-item label="关联订单ID" prop="orderId">
|
<el-form-item label="关联订单ID" prop="orderId">
|
||||||
<el-input v-model="queryParams.orderId" placeholder="请输入关联订单ID" clearable @keyup.enter.native="handleQuery" />
|
<el-input v-model="queryParams.orderId" placeholder="请输入关联订单ID" clearable @keyup.enter.native="handleQuery" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|||||||
@@ -19,6 +19,41 @@
|
|||||||
<br />
|
<br />
|
||||||
<!-- 明细表格 -->
|
<!-- 明细表格 -->
|
||||||
<el-tabs v-model="activeTab" style="margin-bottom: 10px;">
|
<el-tabs v-model="activeTab" style="margin-bottom: 10px;">
|
||||||
|
<el-tab-pane label="扫码枪" name="scanner">
|
||||||
|
<el-table :data="scannerList" :show-header="true" :border="true" style="width: 100%;" :default-sort="{}" :highlight-current-row="true">
|
||||||
|
<el-table-column type="index" width="55" align="center" label="#" />
|
||||||
|
<el-table-column label="库区/库位" align="center" prop="warehouseName" />
|
||||||
|
<el-table-column v-if="stockIo.ioType === 'transfer'" label="源库区/库位" align="center" prop="fromWarehouseName" />
|
||||||
|
<el-table-column label="物品类型" align="center" prop="itemType">
|
||||||
|
<template slot-scope="scope">
|
||||||
|
<dict-tag :options="dict.type.stock_item_type" :value="scope.row.itemType" />
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="物品ID" align="center" prop="itemId" />
|
||||||
|
<el-table-column label="数量" align="center" prop="quantity" />
|
||||||
|
<el-table-column label="单位" align="center" prop="unit" />
|
||||||
|
<!-- <el-table-column label="批次号" align="center" prop="batchNo" /> -->
|
||||||
|
<el-table-column label="备注" align="center" prop="remark" />
|
||||||
|
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
|
||||||
|
<template slot-scope="scope">
|
||||||
|
<el-button
|
||||||
|
size="mini"
|
||||||
|
type="text"
|
||||||
|
icon="el-icon-edit"
|
||||||
|
@click="handleUpdate(scope.row)"
|
||||||
|
:disabled="stockIo.status >= 2"
|
||||||
|
>修改</el-button>
|
||||||
|
<el-button
|
||||||
|
size="mini"
|
||||||
|
type="text"
|
||||||
|
icon="el-icon-delete"
|
||||||
|
@click="handleDelete(scope.row)"
|
||||||
|
:disabled="stockIo.status >= 2"
|
||||||
|
>删除</el-button>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
</el-tab-pane>
|
||||||
<el-tab-pane label="手动录入" name="manual">
|
<el-tab-pane label="手动录入" name="manual">
|
||||||
<el-row :gutter="10" class="mb8">
|
<el-row :gutter="10" class="mb8">
|
||||||
<el-col :span="1.5">
|
<el-col :span="1.5">
|
||||||
@@ -90,7 +125,7 @@
|
|||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="数量" align="center" prop="quantity" />
|
<el-table-column label="数量" align="center" prop="quantity" />
|
||||||
<el-table-column label="单位" align="center" prop="unit" />
|
<el-table-column label="单位" align="center" prop="unit" />
|
||||||
<el-table-column label="批次号" align="center" prop="batchNo" />
|
<!-- <el-table-column label="批次号" align="center" prop="batchNo" /> -->
|
||||||
<el-table-column label="备注" align="center" prop="remark" />
|
<el-table-column label="备注" align="center" prop="remark" />
|
||||||
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
|
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
|
||||||
<template slot-scope="scope">
|
<template slot-scope="scope">
|
||||||
@@ -112,23 +147,7 @@
|
|||||||
</el-table-column>
|
</el-table-column>
|
||||||
</el-table>
|
</el-table>
|
||||||
</el-tab-pane>
|
</el-tab-pane>
|
||||||
<el-tab-pane label="扫码枪" name="scanner">
|
|
||||||
<el-table :data="scannerList" :show-header="true" :border="true" style="width: 100%;" :default-sort="{}" :highlight-current-row="true">
|
|
||||||
<el-table-column type="index" width="55" align="center" label="#" />
|
|
||||||
<el-table-column label="库区/库位" align="center" prop="warehouseName" />
|
|
||||||
<el-table-column v-if="stockIo.ioType === 'transfer'" label="源库区/库位" align="center" prop="fromWarehouseName" />
|
|
||||||
<el-table-column label="物品类型" align="center" prop="itemType">
|
|
||||||
<template slot-scope="scope">
|
|
||||||
<dict-tag :options="dict.type.stock_item_type" :value="scope.row.itemType" />
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column label="物品ID" align="center" prop="itemId" />
|
|
||||||
<el-table-column label="数量" align="center" prop="quantity" />
|
|
||||||
<el-table-column label="单位" align="center" prop="unit" />
|
|
||||||
<el-table-column label="批次号" align="center" prop="batchNo" />
|
|
||||||
<el-table-column label="备注" align="center" prop="remark" />
|
|
||||||
</el-table>
|
|
||||||
</el-tab-pane>
|
|
||||||
</el-tabs>
|
</el-tabs>
|
||||||
<pagination
|
<pagination
|
||||||
v-show="total>0"
|
v-show="total>0"
|
||||||
@@ -186,9 +205,9 @@
|
|||||||
<el-form-item label="单位" prop="unit">
|
<el-form-item label="单位" prop="unit">
|
||||||
<el-input v-model="form.unit" placeholder="请输入单位" :disabled="unitDisabled" />
|
<el-input v-model="form.unit" placeholder="请输入单位" :disabled="unitDisabled" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="批次号" prop="batchNo">
|
<!-- <el-form-item label="批次号" prop="batchNo">
|
||||||
<el-input v-model="form.batchNo" placeholder="请输入批次号" />
|
<el-input v-model="form.batchNo" placeholder="请输入批次号" />
|
||||||
</el-form-item>
|
</el-form-item> -->
|
||||||
<el-form-item label="备注" prop="remark">
|
<el-form-item label="备注" prop="remark">
|
||||||
<el-input v-model="form.remark" placeholder="请输入备注" />
|
<el-input v-model="form.remark" placeholder="请输入备注" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
@@ -275,7 +294,7 @@ export default {
|
|||||||
statusLoading: false, // 新增状态修改按钮加载状态
|
statusLoading: false, // 新增状态修改按钮加载状态
|
||||||
cancelLoading: false, // 撤回按钮加载状态
|
cancelLoading: false, // 撤回按钮加载状态
|
||||||
unitDisabled: false, // 新增:单位输入框是否禁用
|
unitDisabled: false, // 新增:单位输入框是否禁用
|
||||||
activeTab: 'manual' // 新增:当前激活的标签页
|
activeTab: 'scanner' // 新增:当前激活的标签页
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
@@ -443,7 +462,7 @@ export default {
|
|||||||
itemId: undefined,
|
itemId: undefined,
|
||||||
quantity: undefined,
|
quantity: undefined,
|
||||||
unit: undefined,
|
unit: undefined,
|
||||||
batchNo: undefined,
|
batchNo: 'auto',
|
||||||
remark: undefined
|
remark: undefined
|
||||||
};
|
};
|
||||||
this.unitDisabled = false; // 新增:重置单位输入框为可编辑
|
this.unitDisabled = false; // 新增:重置单位输入框为可编辑
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
<div style="max-height:60vh;overflow:auto;padding-right:8px;">
|
<div style="max-height:60vh;overflow:auto;padding-right:8px;">
|
||||||
<el-form :model="detailForm" :rules="detailRules" ref="detailForm" label-width="80px" style="overflow:visible;">
|
<el-form :model="detailForm" :rules="detailRules" ref="detailForm" label-width="80px" style="overflow:visible;">
|
||||||
<el-form-item label="产线" prop="lineId">
|
<el-form-item label="产线" prop="lineId">
|
||||||
<el-select v-model="detailForm.lineId" placeholder="请选择产线" filterable @change="onLineChange">
|
<el-select clearable v-model="detailForm.lineId" placeholder="请选择产线" filterable @change="onLineChange">
|
||||||
<el-option v-for="item in productionLineList" :key="item.lineId" :label="item.lineName"
|
<el-option v-for="item in productionLineList" :key="item.lineId" :label="item.lineName"
|
||||||
:value="item.lineId" />
|
:value="item.lineId" />
|
||||||
</el-select>
|
</el-select>
|
||||||
@@ -14,7 +14,7 @@
|
|||||||
<el-option v-for="item in productList" :key="item.productId" :label="item.productName"
|
<el-option v-for="item in productList" :key="item.productId" :label="item.productName"
|
||||||
:value="item.productId" />
|
:value="item.productId" />
|
||||||
</el-select> -->
|
</el-select> -->
|
||||||
<el-select v-model="detailForm.batchId" placeholder="请选择批次" filterable @change="onBatchChange">
|
<el-select clearable v-model="detailForm.batchId" placeholder="请选择批次" filterable @change="onBatchChange">
|
||||||
<el-option v-for="item in batchList" :key="item.batchId" :label="item.batchNo" :value="item.batchId" />
|
<el-option v-for="item in batchList" :key="item.batchId" :label="item.batchNo" :value="item.batchId" />
|
||||||
</el-select>
|
</el-select>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
@@ -70,7 +70,7 @@ export default {
|
|||||||
remark: ''
|
remark: ''
|
||||||
},
|
},
|
||||||
detailRules: {
|
detailRules: {
|
||||||
productId: [{ required: true, message: '请选择产品', trigger: 'change' }],
|
batchId: [{ required: true, message: '请选择产品', trigger: 'change' }],
|
||||||
lineId: [{ required: true, message: '请选择产线', trigger: 'change' }],
|
lineId: [{ required: true, message: '请选择产线', trigger: 'change' }],
|
||||||
quantity: [{ required: true, message: '请输入排产数量', trigger: 'blur' }],
|
quantity: [{ required: true, message: '请输入排产数量', trigger: 'blur' }],
|
||||||
dateRange: [{ required: true, type: 'array', len: 2, message: '请选择计划日期区间', trigger: 'change' }]
|
dateRange: [{ required: true, type: 'array', len: 2, message: '请选择计划日期区间', trigger: 'change' }]
|
||||||
|
|||||||
@@ -9,20 +9,17 @@ import java.util.ArrayList;
|
|||||||
import org.glassfish.tyrus.server.Server;
|
import org.glassfish.tyrus.server.Server;
|
||||||
|
|
||||||
import MvCodeReaderCtrlWrapper.*;
|
import MvCodeReaderCtrlWrapper.*;
|
||||||
import MvCodeReaderCtrlWrapper.MvCodeReaderCtrl.*;
|
|
||||||
import MvCodeReaderCtrlWrapper.MvCodeReaderCtrlDefine.*;
|
import MvCodeReaderCtrlWrapper.MvCodeReaderCtrlDefine.*;
|
||||||
import MvCodeReaderCtrlWrapper.ParameterException.*;
|
|
||||||
import java.net.*;
|
|
||||||
import java.io.*;
|
|
||||||
|
|
||||||
public class Main {
|
public class Main {
|
||||||
static Handle hHandle = null;
|
static HttpRequestUtil http = null;
|
||||||
|
|
||||||
// 保存所有读取到的设备列表
|
// 保存所有读取到的设备列表
|
||||||
private static ArrayList<MV_CODEREADER_DEVICE_INFO> allDevices = new ArrayList<>();
|
private static ArrayList<MV_CODEREADER_DEVICE_INFO> allDevices = new ArrayList<>();
|
||||||
|
|
||||||
// 保存正在监听的设备列表
|
// 保存正在监听的设备列表
|
||||||
private static ArrayList<MV_CODEREADER_DEVICE_INFO> activeDevices = new ArrayList<>();
|
private static final ArrayList<MV_CODEREADER_DEVICE_INFO> activeDevices = new ArrayList<>();
|
||||||
|
private static byte[] pdata;
|
||||||
|
|
||||||
// 获取所有读取到的设备列表JSON(推送给前端)
|
// 获取所有读取到的设备列表JSON(推送给前端)
|
||||||
public static String getAllDevicesJson() {
|
public static String getAllDevicesJson() {
|
||||||
@@ -94,8 +91,9 @@ public class Main {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 回调函数
|
// 回调函数, 当扫码枪扫码后触发
|
||||||
private static void printImgCBInfo(byte[] pdata, MV_CODEREADER_IMAGE_OUT_INFO_EX2 stOutInfo) {
|
private static void printImgCBInfo(byte[] pdata, MV_CODEREADER_IMAGE_OUT_INFO_EX2 stOutInfo) {
|
||||||
|
Main.pdata = pdata;
|
||||||
if (null == stOutInfo) {
|
if (null == stOutInfo) {
|
||||||
System.out.println("stOutInfo is null");
|
System.out.println("stOutInfo is null");
|
||||||
return;
|
return;
|
||||||
@@ -103,10 +101,10 @@ public class Main {
|
|||||||
|
|
||||||
System.out.print("/**CBpstOutInfo***************************************/\n");
|
System.out.print("/**CBpstOutInfo***************************************/\n");
|
||||||
|
|
||||||
System.out.print(String.format("获取一帧: nEventID[%d], nChannelID[%d], nWidth[%d], nHeight[%d], nFrameNum[%d], nTriggerIndex[%d], nFrameLen[%d], "
|
System.out.printf("获取一帧: nEventID[%d], nChannelID[%d], nWidth[%d], nHeight[%d], nFrameNum[%d], nTriggerIndex[%d], nFrameLen[%d], "
|
||||||
+ " nCodeNumber[%d] \r\n",
|
+ " nCodeNumber[%d] \r\n",
|
||||||
stOutInfo.nEventID, stOutInfo.nChannelID, stOutInfo.nWidth, stOutInfo.nHeight, stOutInfo.nFrameNum,
|
stOutInfo.nEventID, stOutInfo.nChannelID, stOutInfo.nWidth, stOutInfo.nHeight, stOutInfo.nFrameNum,
|
||||||
stOutInfo.nTriggerIndex, stOutInfo.nFrameLen, stOutInfo.pstCodeListEx.nCodeNum));
|
stOutInfo.nTriggerIndex, stOutInfo.nFrameLen, stOutInfo.pstCodeListEx.nCodeNum);
|
||||||
|
|
||||||
System.out.print("解码状态: bIsGetCode[" + stOutInfo.bIsGetCode + "]\r\n");
|
System.out.print("解码状态: bIsGetCode[" + stOutInfo.bIsGetCode + "]\r\n");
|
||||||
|
|
||||||
@@ -118,34 +116,44 @@ public class Main {
|
|||||||
obj.put("type", "scanMessage");
|
obj.put("type", "scanMessage");
|
||||||
// 解析码中的数据,用下划线分割,第一个为物料类型,第二个为物料id
|
// 解析码中的数据,用下划线分割,第一个为物料类型,第二个为物料id
|
||||||
String code = stOutInfo.pstCodeListEx.stBcrInfoEx.get(a).chCode;
|
String code = stOutInfo.pstCodeListEx.stBcrInfoEx.get(a).chCode;
|
||||||
String[] parts = code.split("__");
|
// String[] parts = code.split("__");
|
||||||
if (parts.length >= 2) {
|
// if (parts.length >= 2) {
|
||||||
String itemType = parts[0];
|
// String itemType = parts[0];
|
||||||
String itemId = parts[1];
|
// String itemId = parts[1];
|
||||||
obj.put("itemType", itemType);
|
// obj.put("itemType", itemType);
|
||||||
obj.put("itemId", itemId);
|
// obj.put("itemId", itemId);
|
||||||
} else {
|
// } else {
|
||||||
obj.put("rawCode", code);
|
// obj.put("rawCode", code);
|
||||||
obj.put("error", "格式不正确");
|
// obj.put("error", "格式不正确");
|
||||||
|
// }
|
||||||
|
// 直接将解析出的数据返回到页面上
|
||||||
|
// WsServer.broadcast(code);
|
||||||
|
// 发送http请求,存储信息
|
||||||
|
try {
|
||||||
|
String s = HttpRequestUtil.postJson("http://140.143.206.120:8080/wms/stockIoDetail", code);
|
||||||
|
System.out.println("返回值" + s);
|
||||||
|
System.out.println("明细记录创建成功" + code);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
WsServer.broadcast(obj.toString());
|
|
||||||
|
|
||||||
System.out.print(String.format("解码信息: TheCodeID[%d], CodeString[%s], nCodeLen[%d], nAngle[%d], nBarType[%d],"
|
|
||||||
|
System.out.printf("解码信息: TheCodeID[%d], CodeString[%s], nCodeLen[%d], nAngle[%d], nBarType[%d],"
|
||||||
+ "sAlgoCost[%d], nIDRScore[%d], n1DIsGetQuality[%d]\r\n",
|
+ "sAlgoCost[%d], nIDRScore[%d], n1DIsGetQuality[%d]\r\n",
|
||||||
a, stOutInfo.pstCodeListEx.stBcrInfoEx.get(a).chCode, stOutInfo.pstCodeListEx.stBcrInfoEx.get(a).nLen,
|
a, stOutInfo.pstCodeListEx.stBcrInfoEx.get(a).chCode, stOutInfo.pstCodeListEx.stBcrInfoEx.get(a).nLen,
|
||||||
stOutInfo.pstCodeListEx.stBcrInfoEx.get(a).nAngle, stOutInfo.pstCodeListEx.stBcrInfoEx.get(a).nBarType,
|
stOutInfo.pstCodeListEx.stBcrInfoEx.get(a).nAngle, stOutInfo.pstCodeListEx.stBcrInfoEx.get(a).nBarType,
|
||||||
stOutInfo.pstCodeListEx.stBcrInfoEx.get(a).sAlgoCost, stOutInfo.pstCodeListEx.stBcrInfoEx.get(a).nIDRScore,
|
stOutInfo.pstCodeListEx.stBcrInfoEx.get(a).sAlgoCost, stOutInfo.pstCodeListEx.stBcrInfoEx.get(a).nIDRScore,
|
||||||
stOutInfo.pstCodeListEx.stBcrInfoEx.get(a).n1DIsGetQuality));
|
stOutInfo.pstCodeListEx.stBcrInfoEx.get(a).n1DIsGetQuality);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void PrintDeviceInfo(MV_CODEREADER_DEVICE_INFO stCamInfo) {
|
private static void PrintDeviceInfo(MV_CODEREADER_DEVICE_INFO stCamInfo) {
|
||||||
if (stCamInfo.nTLayerType == MvCodeReaderCtrlDefine.MV_CODEREADER_GIGE_DEVICE) {
|
if (stCamInfo.nTLayerType == MvCodeReaderCtrlDefine.MV_CODEREADER_GIGE_DEVICE) {
|
||||||
System.out.print("IP地址: " + getIpAddress(stCamInfo.stGigEInfo.nCurrentIp) + "\r\n");
|
System.out.print("IP地址: " + getIpAddress(stCamInfo.stGigEInfo.nCurrentIp) + "\r\n");
|
||||||
System.out.print(String.format("设备信息: 自定义名称:[%s], 序列号:[%s], 型号:[%s] \r\n\r\n",
|
System.out.printf("设备信息: 自定义名称:[%s], 序列号:[%s], 型号:[%s] \r\n\r\n",
|
||||||
stCamInfo.stGigEInfo.chUserDefinedName,
|
stCamInfo.stGigEInfo.chUserDefinedName,
|
||||||
stCamInfo.stGigEInfo.chSerialNumber,
|
stCamInfo.stGigEInfo.chSerialNumber,
|
||||||
stCamInfo.stGigEInfo.chModelName));
|
stCamInfo.stGigEInfo.chModelName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -221,6 +229,8 @@ public class Main {
|
|||||||
|
|
||||||
System.out.println("所有设备处理完毕。活跃设备数量: " + activeDevices.size() + "\r\n");
|
System.out.println("所有设备处理完毕。活跃设备数量: " + activeDevices.size() + "\r\n");
|
||||||
|
|
||||||
|
http = new HttpRequestUtil();
|
||||||
|
System.out.println("http请求工具创建完成");
|
||||||
// 启动WebSocket服务器,用于向前端推送设备列表和扫描结果
|
// 启动WebSocket服务器,用于向前端推送设备列表和扫描结果
|
||||||
Server server = new Server("localhost", 9000, "/", null, WsServer.class);
|
Server server = new Server("localhost", 9000, "/", null, WsServer.class);
|
||||||
try {
|
try {
|
||||||
@@ -242,4 +252,8 @@ public class Main {
|
|||||||
System.out.println("程序已退出");
|
System.out.println("程序已退出");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static byte[] getPdata() {
|
||||||
|
return pdata;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ public class ScanDataUtil {
|
|||||||
JSONObject obj = new JSONObject();
|
JSONObject obj = new JSONObject();
|
||||||
obj.put("stockIoId", Long.valueOf(parts[0]));
|
obj.put("stockIoId", Long.valueOf(parts[0]));
|
||||||
obj.put("warehouseId", Long.valueOf(parts[1]));
|
obj.put("warehouseId", Long.valueOf(parts[1]));
|
||||||
|
obj.put("fromWarehouseId", Long.valueOf(parts[1]));
|
||||||
obj.put("itemId", Long.valueOf(parts[2]));
|
obj.put("itemId", Long.valueOf(parts[2]));
|
||||||
obj.put("quantity", new BigDecimal(parts[3]));
|
obj.put("quantity", new BigDecimal(parts[3]));
|
||||||
obj.put("itemType", itemType);
|
obj.put("itemType", itemType);
|
||||||
|
|||||||
Reference in New Issue
Block a user