Files
FT517-WMS/ruoyi-ui/src/views/bas/BasProductCode/index.vue
2025-09-15 16:34:26 +08:00

939 lines
23 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div class="app-container">
<el-row :gutter="20">
<!-- 左侧区域 -->
<el-col :span="8">
<!-- 表单区域 -->
<div class="form-container">
<div class="section-header">
<h3>生成条码</h3>
</div>
<el-form ref="form" :model="form" :rules="rules" label-width="80px">
<el-row :gutter="20">
<el-col
v-for="field in formFields"
:key="field.prop"
:span="24"
>
<el-form-item :label="field.label" :prop="field.prop">
<el-input
v-model="form[field.prop]"
:placeholder="field.placeholder"
:disabled="loading"
/>
</el-form-item>
</el-col>
</el-row>
<el-form-item class="form-actions">
<el-button
type="warning"
@click="handlePreview"
:loading="previewLoading"
icon="el-icon-view"
>
预览
</el-button>
<el-button
@click="resetForm"
icon="el-icon-refresh"
>
重置
</el-button>
</el-form-item>
</el-form>
</div>
</el-col>
<!-- 右侧预览区域 -->
<el-col :span="16">
<!-- 预览弹框 -->
<el-dialog
title="标签预览"
:visible.sync="showPreview"
width="900px"
:close-on-click-modal="false"
append-to-body
>
<div class="preview-container">
<div class="label-content">
<div class="preview-box">
<div class="preview-content">
<div class="preview-row">
<span class="preview-label">MODEL:</span>
<span class="preview-value">{{ previewData.model }}</span>
</div>
<div class="preview-row">
<span class="preview-label">SERIAL:</span>
<span class="preview-value">{{ previewData.serial }}</span>
</div>
<div class="preview-row">
<span class="preview-label">COLOR:</span>
<span class="preview-value">{{ previewData.color }}</span>
</div>
<div class="preview-row">
<div class="preview-col">
<span class="preview-label">V:</span>
<span class="preview-value">{{ previewData.v }}</span>
</div>
<div class="preview-col">
<span class="preview-label">Hz:</span>
<span class="preview-value">{{ previewData.hz }}</span>
</div>
<div class="preview-col">
<span class="preview-label">PH:</span>
<span class="preview-value">{{ previewData.ph }}</span>
</div>
</div>
<div class="preview-row">
<div class="preview-col">
<span class="preview-label">N.W.:</span>
<span class="preview-value">{{ previewData.nW }}kg</span>
</div>
<div class="preview-col">
<span class="preview-label">G.W.:</span>
<span class="preview-value">{{ previewData.gW }}kg</span>
</div>
</div>
</div>
<!-- 二维码区域 -->
<div class="qrcode-container">
<vue-qr
:text="qrCodeValue"
:size="180"
:margin="0"
:correctLevel="2"
error-correction-level="M"
/>
</div>
</div>
</div>
</div>
<!-- 打印专用区域(隐藏) -->
<vue-easy-print style="display: none;" ref="easyPrint2">
<div class="vue-easy-print" style="page-break-after:always">
<div class="label-content">
<div class="preview-box">
<div class="preview-content">
<div class="preview-row">
<span class="preview-label">MODEL:</span>
<span class="preview-value">{{ previewData.model }}</span>
</div>
<div class="preview-row">
<span class="preview-label">SERIAL:</span>
<span class="preview-value">{{ previewData.serial }}</span>
</div>
<div class="preview-row">
<span class="preview-label">COLOR:</span>
<span class="preview-value">{{ previewData.color }}</span>
</div>
<div class="preview-row">
<div class="preview-col">
<span class="preview-label">V:</span>
<span class="preview-value">{{ previewData.v }}</span>
</div>
<div class="preview-col">
<span class="preview-label">Hz:</span>
<span class="preview-value">{{ previewData.hz }}</span>
</div>
<div class="preview-col">
<span class="preview-label">PH:</span>
<span class="preview-value">{{ previewData.ph }}</span>
</div>
</div>
<div class="preview-row">
<div class="preview-col">
<span class="preview-label">N.W.:</span>
<span class="preview-value">{{ previewData.nW }}</span>
</div>
<div class="preview-col">
<span class="preview-label">G.W.:</span>
<span class="preview-value">{{ previewData.gW }}</span>
</div>
</div>
</div>
<!-- 二维码区域 -->
<div class="qrcode-container">
<vue-qr
:text="qrCodeValue"
:size="180"
:margin="0"
:correctLevel="2"
error-correction-level="M"
/>
</div>
</div>
</div>
</div>
</vue-easy-print>
<div slot="footer" class="dialog-footer">
<el-button @click="showPreview = false">关 闭</el-button>
<el-button type="primary" @click="submitForm" :loading="submitLoading">保存并打印</el-button>
</div>
</el-dialog>
<!-- 历史数据区域 -->
<div class="list-container" style="padding-bottom: 60px;">
<div class="section-header" style="margin-bottom: 40px;">
<h3>历史数据</h3>
<div class="header-actions">
<el-button
type="danger"
size="small"
@click="handleBatchDelete"
:disabled="!hasSelection"
icon="el-icon-delete"
>
批量删除
</el-button>
</div>
</div>
<el-table
ref="table"
v-loading="loading"
:data="tableData"
@selection-change="handleSelectionChange"
stripe
border
style="width: 100%;margin-bottom: 40px;height: 550px;"
>
<el-table-column type="selection" width="55" align="center"/>
<el-table-column
v-for="column in tableColumns"
:key="column.prop"
:label="column.label"
:prop="column.prop"
:width="column.width"
:formatter="column.formatter"
show-overflow-tooltip
/>
<el-table-column label="操作" width="180" align="center" fixed="right">
<template slot-scope="scope">
<el-button size="mini" type="text" @click="handleEdit(scope.row)" icon="el-icon-edit">编辑</el-button>
<el-button size="mini" type="text" @click="handlePrint(scope.row)" icon="el-icon-printer">打印</el-button>
<el-button size="mini" type="text" @click="handleDelete(scope.row)" icon="el-icon-delete" class="danger-text">删除</el-button>
</template>
</el-table-column>
</el-table>
<pagination
v-show="total > 0"
:total="total"
:page.sync="queryParams.pageNum"
:limit.sync="queryParams.pageSize"
@pagination="getList"
/>
</div>
</el-col>
</el-row>
</div>
</template>
<script>
import VueQr from 'vue-qr'
import VueEasyPrint from 'vue-easy-print'
import {
listBasProductCode,
getBasProductCode,
delBasProductCode,
addBasProductCode,
updateBasProductCode
} from "@/api/bas/BasProductCode"
// 常量配置
const FORM_FIELDS = [
{ prop: 'model', label: '型号', placeholder: '请输入型号', required: true },
{ prop: 'serial', label: '序列号', placeholder: '请输入序列号', required: true },
{ prop: 'color', label: '颜色', placeholder: '请输入颜色', required: true },
{ prop: 'v', label: '电压', placeholder: '请输入电压' },
{ prop: 'hz', label: '功率', placeholder: '请输入功率' },
{ prop: 'ph', label: '相位', placeholder: '请输入相位' },
{ prop: 'nW', label: '净重', placeholder: '请输入净重', required: true },
{ prop: 'gW', label: '毛重', placeholder: '请输入毛重', required: true },
{ prop: 'spare1', label: '订单号', placeholder: '请输入订单号' },
{ prop: 'spare2', label: '机型', placeholder: '请输入机型' },
{ prop: 'spare3', label: '分销', placeholder: '请输入分销' }
]
const TABLE_COLUMNS = [
{ prop: 'model', label: '型号', width: 120 },
{ prop: 'color', label: '颜色', width: 100 },
{ prop: 'serial', label: '序列号', width: 150 },
{ prop: 'v', label: '电压', width: 100 },
{ prop: 'hz', label: '功率', width: 100 },
{ prop: 'ph', label: '相位', width: 100 },
{ prop: 'nW', label: '净重', width: 100 },
{ prop: 'gW', label: '毛重', width: 100 },
{
prop: 'type',
label: '状态',
width: 100,
formatter: (row) => row.type === '1' ? '未打印' : '已打印'
},
{ prop: 'spare1', label: '订单号', width: 150 },
{ prop: 'spare2', label: '机型', width: 120 },
{ prop: 'spare3', label: '分箱', width: 100 }
]
const DEFAULT_FORM = {
model: '',
color: '',
serial: '',
v: '',
hz: '',
ph: '',
nW: '',
gW: '',
type: '1',
spare1: '',
spare2: '',
spare3: ''
}
const DEFAULT_PREVIEW_DATA = {
model: '',
color: '',
serial: '',
v: '',
hz: '',
ph: '',
nW: '',
gW: ''
}
export default {
name: 'BasProductCode',
components: {
VueQr,
VueEasyPrint
},
data() {
return {
// 表单对象
form: { ...DEFAULT_FORM },
// 表单校验规则
rules: this.generateRules(),
// 查询参数
queryParams: {
pageNum: 1,
pageSize: 10,
searchText: ''
},
// 表格数据
tableData: [],
// 加载状态
loading: false,
previewLoading: false,
submitLoading: false,
// 总条数
total: 0,
// 预览数据
previewData: { ...DEFAULT_PREVIEW_DATA },
// 选中数据
selectedRows: [],
// 是否显示预览
showPreview: false,
// 表单字段配置
formFields: FORM_FIELDS,
// 表格列配置
tableColumns: TABLE_COLUMNS
}
},
computed: {
// 二维码内容
qrCodeValue() {
console.log(this.previewData)
const text = `${this.previewData.spare1}@${this.previewData.model}@${this.previewData.spare2}@${this.previewData.spare3}@${this.previewData.serial}`
return this.procode(text)
},
// 是否有选中项
hasSelection() {
return this.selectedRows.length > 0
}
},
created() {
this.init()
},
methods: {
// 初始化
async init() {
try {
await this.getList()
} catch (error) {
this.$message.error('初始化失败:' + error.message)
}
},
// 生成表单验证规则
generateRules() {
const rules = {}
FORM_FIELDS.forEach(field => {
if (field.required) {
rules[field.prop] = [
{ required: true, message: `请输入${field.label}`, trigger: 'blur' }
]
}
})
return rules
},
// 获取列表数据
async getList() {
this.loading = true
try {
const response = await listBasProductCode(this.queryParams)
this.tableData = response.rows || []
this.total = response.total || 0
} catch (error) {
this.$message.error('获取数据失败:' + error.message)
this.tableData = []
this.total = 0
} finally {
this.loading = false
}
},
// 查询按钮操作
handleQuery() {
this.queryParams.pageNum = 1
this.getList()
},
// 重置表单
resetForm() {
this.$refs.form.resetFields()
this.form = { ...DEFAULT_FORM }
this.previewData = { ...DEFAULT_PREVIEW_DATA }
this.showPreview = false
},
// 预览按钮操作
async handlePreview() {
try {
await this.$refs.form.validate()
this.previewLoading = true
// 模拟预览加载
setTimeout(() => {
this.previewData = { ...this.form }
this.showPreview = true
this.previewLoading = false
this.$message.success('预览生成成功')
}, 300)
} catch (error) {
this.$message.warning('请完善必填信息')
}
},
// 表单提交
async submitForm() {
try {
await this.$refs.form.validate()
this.submitLoading = true
const apiCall = this.form.id != null
? updateBasProductCode(this.form)
: addBasProductCode(this.form)
await apiCall
const message = this.form.id != null ? '修改成功' : '新增成功'
this.$message.success(message)
await this.getList()
await this.autoPrint()
} catch (error) {
this.$message.error('保存失败:' + error.message)
} finally {
this.submitLoading = false
}
},
// 新增按钮操作
handleAdd() {
this.resetForm()
},
// 编辑按钮操作
handleEdit(row) {
this.form = { ...row }
this.previewData = { ...row }
// this.showPreview = true
},
// 删除按钮操作
async handleDelete(row) {
try {
await this.$confirm(
`是否确认删除条码打印编号为"${row.id}"的数据项?`,
'警告',
{
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}
)
await delBasProductCode(row.id)
this.$message.success('删除成功')
await this.getList()
} catch (error) {
if (error !== 'cancel') {
this.$message.error('删除失败:' + error.message)
}
}
},
// 批量删除
async handleBatchDelete() {
if (!this.hasSelection) {
this.$message.warning('请选择要删除的数据')
return
}
try {
await this.$confirm(
`是否确认删除选中的${this.selectedRows.length}条数据?`,
'警告',
{
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}
)
const ids = this.selectedRows.map(row => row.id)
await delBasProductCode(ids.join(','))
this.$message.success('批量删除成功')
await this.getList()
} catch (error) {
if (error !== 'cancel') {
this.$message.error('批量删除失败:' + error.message)
}
}
},
// 打印按钮操作
async handlePrint(row) {
try {
this.previewData = { ...row }
this.showPreview = true
await this.$nextTick()
setTimeout(() => {
this.$refs.easyPrint2.print()
}, 100)
} catch (error) {
this.$message.error('打印失败:' + error.message)
}
},
// 自动打印功能
async autoPrint() {
try {
this.previewData = { ...this.form }
this.showPreview = true
await this.$nextTick()
setTimeout(() => {
this.$refs.easyPrint2.print()
}, 500)
} catch (error) {
this.$message.error('自动打印失败:' + error.message)
}
},
// 多选框选中数据
handleSelectionChange(selection) {
this.selectedRows = selection
},
// 二维码编码方法
procode(text) {
const key = "0"
const mystr = text.split("@")
const text1 = `@${mystr[1]}@${mystr[2]}@${mystr[3]}@`
let str = key + text1
let str1 = ""
for (let i = 0; i < str.length; i++) {
// 获取字符的Unicode编码并转换为16进制
let str2 = str.charCodeAt(i).toString(16).toUpperCase()
// 补齐到4位
while (str2.length < 4) {
str2 = "0" + str2
}
str1 += str2
}
// 反转字符串
str1 = str1.split('').reverse().join('')
// 替换操作
str1 = str1.replace(/100/g, "Q")
str1 = str1.replace(/200/g, "R")
str1 = str1.replace(/300/g, "S")
str1 = str1.replace(/400/g, "T")
str1 = str1.replace(/500/g, "U")
str1 = str1.replace(/600/g, "V")
str1 = str1.replace(/700/g, "W")
str1 = str1.replace(/800/g, "X")
str1 = str1.replace(/900/g, "Y")
str1 = str1.replace(/00/g, "Z")
// 最终结果反转
const result = `${mystr[0]}$${str1}$${mystr[4]}`
return result.split('').reverse().join('')
}
}
}
</script>
<style scoped>
/* 基础布局 */
.app-container {
padding: 20px;
background-color: #f5f5f5;
}
.list-container {
background: #fff;
padding: 20px;
border-radius: 4px;
box-shadow: 0 2px 12px 0 rgba(0,0,0,0.1);
}
.section-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
}
.section-header h3 {
margin: 0;
font-size: 16px;
font-weight: 500;
}
.header-actions {
display: flex;
gap: 10px;
}
.el-table {
margin-bottom: 20px;
}
.el-table .danger-text {
color: #F56C6C;
}
.el-table .danger-text:hover {
color: #f78989;
}
/* 容器样式 */
.form-container,
.list-container,
.preview-container {
background-color: #fff;
border-radius: 8px;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
overflow: hidden;
}
.form-container {
margin-bottom: 20px;
}
.preview-container {
padding: 20px;
}
.preview-box {
display: flex;
justify-content: space-between;
align-items: flex-start;
padding: 20px;
border: 1px solid #dcdfe6;
border-radius: 4px;
}
.preview-content {
flex: 1;
margin-right: 20px;
}
.preview-row {
margin-bottom: 15px;
}
.preview-label {
font-weight: bold;
margin-right: 10px;
}
.qrcode-container {
width: 180px;
height: 180px;
}
.list-container .el-table {
height: 100%;
}
.preview-container {
position: sticky;
top: 20px;
height: 450px;
overflow: hidden;
}
.preview-display {
height: 100%;
overflow: hidden;
}
/* 区域标题 */
.section-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 20px 20px 0;
margin-bottom: 20px;
}
.section-header h3 {
margin: 0;
font-size: 16px;
font-weight: 600;
color: #303133;
}
.header-actions {
display: flex;
gap: 10px;
}
/* 表单样式 */
.form-container .el-form {
padding: 0 20px 20px;
}
.form-actions {
margin-top: 20px;
text-align: center;
}
.form-actions .el-button {
margin: 0 10px;
}
/* 表格样式 */
.list-container .el-table {
margin: 0 20px;
}
.list-container .pagination {
margin: 20px;
text-align: center;
}
.danger-text {
color: #f56c6c !important;
}
.danger-text:hover {
color: #f78989 !important;
}
/* 预览区域样式 */
.preview-container {
padding: 0;
}
.empty-preview {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 300px;
margin: 0 20px 20px;
border: 2px dashed #dcdfe6;
border-radius: 8px;
color: #909399;
font-size: 14px;
}
.empty-preview i {
font-size: 48px;
margin-bottom: 16px;
color: #c0c4cc;
}
.empty-preview p {
margin: 0;
}
.preview-display {
padding: 0 20px 20px;
}
.label-content {
border: 1px solid #e4e7ed;
border-radius: 8px;
overflow: hidden;
}
.preview-box {
padding: 20px;
font-family: 'Arial', sans-serif;
position: relative;
background-color: #fff;
height: 350px;
width: 100%;
box-sizing: border-box;
}
.preview-content {
margin-right: 200px;
}
.preview-row {
display: flex;
align-items: center;
margin-bottom: 25px;
min-height: 40px;
}
.preview-row:last-child {
margin-bottom: 0;
}
.preview-col {
flex: 1;
display: flex;
align-items: center;
}
.preview-label {
font-weight: 600;
margin-right: 15px;
min-width: 80px;
font-size: 24px;
color: #303133;
}
.preview-value {
flex: 1;
font-size: 24px;
color: #606266;
word-break: break-all;
}
.qrcode-container {
position: absolute;
top: 20px;
right: 20px;
padding: 0;
background-color: transparent;
border: none;
}
/* 响应式设计 */
@media (max-width: 1200px) {
.app-container {
padding: 10px;
}
.preview-container {
position: static;
max-height: none;
}
}
@media (max-width: 768px) {
.section-header {
flex-direction: column;
align-items: flex-start;
gap: 10px;
}
.header-actions {
width: 100%;
justify-content: flex-end;
}
.preview-content {
margin-right: 0;
margin-bottom: 140px;
}
.qrcode-container {
position: static;
margin-top: 20px;
text-align: center;
}
}
/* 动画效果 */
.preview-display {
animation: fadeIn 0.3s ease-in-out;
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
/* 加载状态 */
.el-button.is-loading {
pointer-events: none;
}
/* 打印样式 */
@media print {
@page {
margin: 0;
size: auto;
}
body {
margin: 0;
}
.preview-box {
border: none;
box-shadow: none;
position: relative;
}
.qrcode-container {
border: none;
position: absolute !important;
top: 20px !important;
right: 20px !important;
padding: 0;
background-color: transparent;
}
}
</style>