✨ feat: 退款管理
This commit is contained in:
44
gear-ui3/src/api/oms/workDeduction.js
Normal file
44
gear-ui3/src/api/oms/workDeduction.js
Normal file
@@ -0,0 +1,44 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
// 查询订单下人员工作及扣款信息列表
|
||||
export function listWorkDeduction(query) {
|
||||
return request({
|
||||
url: '/oa/workDeduction/list',
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
}
|
||||
|
||||
// 查询订单下人员工作及扣款信息详细
|
||||
export function getWorkDeduction(deductionId) {
|
||||
return request({
|
||||
url: '/oa/workDeduction/' + deductionId,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
// 新增订单下人员工作及扣款信息
|
||||
export function addWorkDeduction(data) {
|
||||
return request({
|
||||
url: '/oa/workDeduction',
|
||||
method: 'post',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
// 修改订单下人员工作及扣款信息
|
||||
export function updateWorkDeduction(data) {
|
||||
return request({
|
||||
url: '/oa/workDeduction',
|
||||
method: 'put',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
// 删除订单下人员工作及扣款信息
|
||||
export function delWorkDeduction(deductionId) {
|
||||
return request({
|
||||
url: '/oa/workDeduction/' + deductionId,
|
||||
method: 'delete'
|
||||
})
|
||||
}
|
||||
@@ -5,17 +5,14 @@
|
||||
<!-- 左侧:使用klp-list组件(占6列) -->
|
||||
<el-col :span="6" style="display: table-cell;">
|
||||
<!-- 搜索表单 - 精简搜索项 -->
|
||||
<el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px"
|
||||
class="mb-4">
|
||||
<el-row :gutter="10">
|
||||
<el-col :span="16">
|
||||
<el-input v-model="queryParams.orderCode" placeholder="请输入订单编号" clearable @change="handleQuery" />
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-button type="primary" plain icon="Plus" size="small" @click="handleAdd" class="w-full">新增订单</el-button>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
<el-row :gutter="10">
|
||||
<el-col :span="16">
|
||||
<el-input v-model="queryParams.orderCode" placeholder="请输入订单编号" clearable @change="handleQuery" />
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-button type="primary" plain icon="Plus" size="small" @click="handleAdd" class="w-full">新增订单</el-button>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- klp-list组件 -->
|
||||
<klp-list :list-data="orderList" :model-value="selectedIds" title-field="orderCode" list-key="orderId"
|
||||
@@ -105,7 +102,7 @@
|
||||
</el-tab-pane>
|
||||
|
||||
<el-tab-pane label="退换货" name="returnExchange">
|
||||
<div class="mt-4">
|
||||
<div class="mt-4">
|
||||
<ReturnExchange :orderId="selectedOrderId" />
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
|
||||
@@ -67,8 +67,12 @@
|
||||
<!-- 添加或修改退换货管理对话框 -->
|
||||
<el-dialog :title="title" v-model="open" width="500px" append-to-body>
|
||||
<el-form ref="returnExchangeRef" :model="form" :rules="rules" label-width="80px">
|
||||
<el-form-item label="关联订单明细ID" prop="orderDetailId">
|
||||
<el-input v-model="form.orderDetailId" placeholder="请输入关联订单明细ID" />
|
||||
<el-form-item label="订单明细" prop="orderDetailId">
|
||||
<el-select v-model="form.orderDetailId" placeholder="请选择订单明细">
|
||||
<el-option v-for="item in orderDetailList" :key="item.detailId" :value="item.detailId">
|
||||
<ProductInfo :productId="item.productId" />
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="客户ID" prop="customerId">
|
||||
<CustomerSelect v-model="form.customerId" />
|
||||
@@ -95,7 +99,9 @@
|
||||
|
||||
<script setup name="ReturnExchange">
|
||||
import { listReturnExchange, getReturnExchange, delReturnExchange, addReturnExchange, updateReturnExchange } from "@/api/oa/returnExchange";
|
||||
import { listOrderDetail } from "@/api/oms/orderDetail";
|
||||
import CustomerSelect from '@/components/CustomerSelect/index.vue';
|
||||
import ProductInfo from '@/components/Renderer/ProductInfo.vue';
|
||||
|
||||
const { proxy } = getCurrentInstance();
|
||||
|
||||
@@ -140,6 +146,11 @@ const data = reactive({
|
||||
|
||||
const { queryParams, form, rules } = toRefs(data);
|
||||
|
||||
watch(() => props.orderId, () => {
|
||||
getOrderDetailList();
|
||||
getList();
|
||||
}, { immediate: true });
|
||||
|
||||
/** 查询退换货管理列表 */
|
||||
function getList() {
|
||||
loading.value = true;
|
||||
@@ -150,6 +161,15 @@ function getList() {
|
||||
});
|
||||
}
|
||||
|
||||
const orderDetailList = ref([]);
|
||||
function getOrderDetailList() {
|
||||
listOrderDetail({
|
||||
orderId: props.orderId,
|
||||
}).then(response => {
|
||||
orderDetailList.value = response.rows;
|
||||
});
|
||||
}
|
||||
|
||||
// 取消按钮
|
||||
function cancel() {
|
||||
open.value = false;
|
||||
|
||||
328
gear-ui3/src/views/peoples/workDeduction/index.vue
Normal file
328
gear-ui3/src/views/peoples/workDeduction/index.vue
Normal file
@@ -0,0 +1,328 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch" label-width="68px">
|
||||
<el-form-item label="订单编号" prop="orderId">
|
||||
<el-select style="width: 150px;" clearable v-model="queryParams.orderId" placeholder="请选择订单编号" filterable :loading="loading">
|
||||
<el-option v-for="item in orderList" :key="item.orderId" :value="item.orderId" :label="item.orderCode"/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="人员名字" prop="personName">
|
||||
<el-input
|
||||
v-model="queryParams.personName"
|
||||
placeholder="请输入人员名字"
|
||||
clearable
|
||||
@keyup.enter="handleQuery"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="扣款原因" prop="deductionReason">
|
||||
<el-input
|
||||
v-model="queryParams.deductionReason"
|
||||
placeholder="请输入扣款原因"
|
||||
clearable
|
||||
@keyup.enter="handleQuery"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="工作日期" prop="workDate">
|
||||
<el-date-picker clearable
|
||||
v-model="queryParams.workDate"
|
||||
type="date"
|
||||
value-format="YYYY-MM-DD 00:00:00"
|
||||
placeholder="请选择工作日期">
|
||||
</el-date-picker>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
|
||||
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<el-row :gutter="10" class="mb8">
|
||||
<el-col :span="1.5">
|
||||
<el-button
|
||||
type="primary"
|
||||
plain
|
||||
icon="Plus"
|
||||
@click="handleAdd"
|
||||
>新增</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button
|
||||
type="success"
|
||||
plain
|
||||
icon="Edit"
|
||||
:disabled="single"
|
||||
@click="handleUpdate"
|
||||
>修改</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button
|
||||
type="danger"
|
||||
plain
|
||||
icon="Delete"
|
||||
:disabled="multiple"
|
||||
@click="handleDelete"
|
||||
>删除</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button
|
||||
type="warning"
|
||||
plain
|
||||
icon="Download"
|
||||
@click="handleExport"
|
||||
>导出</el-button>
|
||||
</el-col>
|
||||
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
|
||||
</el-row>
|
||||
|
||||
<el-table v-loading="loading" :data="workDeductionList" @selection-change="handleSelectionChange">
|
||||
<el-table-column type="selection" width="55" align="center" />
|
||||
<el-table-column label="订单编号" align="center" prop="orderId" />
|
||||
<el-table-column label="人员名字" align="center" prop="personName" />
|
||||
<el-table-column label="工作内容" align="center" prop="workContent" />
|
||||
<el-table-column label="扣款金额" align="center" prop="deductionAmount" />
|
||||
<el-table-column label="扣款原因" align="center" prop="deductionReason" />
|
||||
<el-table-column label="工作日期" align="center" prop="workDate" width="180">
|
||||
<template #default="scope">
|
||||
<span>{{ formatterTime(scope.row.workDate) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<!-- <el-table-column label="状态:0-有效,1-无效" align="center" prop="status" /> -->
|
||||
<el-table-column label="备注" align="center" prop="remark" />
|
||||
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
|
||||
<template #default="scope">
|
||||
<el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)">修改</el-button>
|
||||
<el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<pagination
|
||||
v-show="total>0"
|
||||
:total="total"
|
||||
v-model:page="queryParams.pageNum"
|
||||
v-model:limit="queryParams.pageSize"
|
||||
@pagination="getList"
|
||||
/>
|
||||
|
||||
<!-- 添加或修改订单下人员工作及扣款信息对话框 -->
|
||||
<el-dialog :title="title" v-model="open" width="500px" append-to-body>
|
||||
<el-form ref="workDeductionRef" :model="form" :rules="rules" label-width="80px">
|
||||
<el-form-item label="关联订单" prop="orderId">
|
||||
<el-select style="width: 150px;" v-model="form.orderId" placeholder="请选择关联订单" filterable remote :loading="loading" @remote-change="remoteOrderList">
|
||||
<!-- 支持远程搜索 -->
|
||||
<el-option v-for="item in orderList" :key="item.orderId" :value="item.orderId" :label="item.orderCode" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="人员名字" prop="personName">
|
||||
<el-input v-model="form.personName" placeholder="请输入人员名字" />
|
||||
</el-form-item>
|
||||
<el-form-item label="工作内容">
|
||||
<el-input v-model="form.workContent" placeholder="请输入工作内容" />
|
||||
</el-form-item>
|
||||
<el-form-item label="扣款金额" prop="deductionAmount">
|
||||
<el-input v-model="form.deductionAmount" placeholder="请输入扣款金额" />
|
||||
</el-form-item>
|
||||
<el-form-item label="扣款原因" prop="deductionReason">
|
||||
<el-input v-model="form.deductionReason" placeholder="请输入扣款原因" />
|
||||
</el-form-item>
|
||||
<el-form-item label="工作日期" prop="workDate">
|
||||
<el-date-picker clearable
|
||||
v-model="form.workDate"
|
||||
type="datetime"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
placeholder="请选择工作日期">
|
||||
</el-date-picker>
|
||||
</el-form-item>
|
||||
<el-form-item label="备注" prop="remark">
|
||||
<el-input v-model="form.remark" placeholder="请输入备注" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button :loading="buttonLoading" type="primary" @click="submitForm">确 定</el-button>
|
||||
<el-button @click="cancel">取 消</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup name="WorkDeduction">
|
||||
import { listWorkDeduction, getWorkDeduction, delWorkDeduction, addWorkDeduction, updateWorkDeduction } from "@/api/oms/workDeduction";
|
||||
import { listOrder } from "@/api/oms/order";
|
||||
|
||||
const { proxy } = getCurrentInstance();
|
||||
|
||||
const workDeductionList = ref([]);
|
||||
const open = ref(false);
|
||||
const buttonLoading = ref(false);
|
||||
const loading = ref(true);
|
||||
const showSearch = ref(true);
|
||||
const ids = ref([]);
|
||||
const single = ref(true);
|
||||
const multiple = ref(true);
|
||||
const total = ref(0);
|
||||
const title = ref("");
|
||||
|
||||
const formatterTime = (time) => {
|
||||
return proxy.parseTime(time, '{y}-{m}-{d}')
|
||||
}
|
||||
|
||||
const data = reactive({
|
||||
form: {},
|
||||
queryParams: {
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
orderId: undefined,
|
||||
personName: undefined,
|
||||
workContent: undefined,
|
||||
deductionAmount: undefined,
|
||||
deductionReason: undefined,
|
||||
workDate: undefined,
|
||||
status: undefined,
|
||||
},
|
||||
rules: {
|
||||
}
|
||||
});
|
||||
|
||||
const { queryParams, form, rules } = toRefs(data);
|
||||
|
||||
/** 查询订单下人员工作及扣款信息列表 */
|
||||
function getList() {
|
||||
loading.value = true;
|
||||
listWorkDeduction(queryParams.value).then(response => {
|
||||
workDeductionList.value = response.rows;
|
||||
total.value = response.total;
|
||||
loading.value = false;
|
||||
});
|
||||
}
|
||||
|
||||
const orderList = ref([]);
|
||||
function remoteOrderList(value) {
|
||||
listOrder({
|
||||
orderCode: value,
|
||||
pageNum: 1,
|
||||
pageSize: 1000,
|
||||
}).then(response => {
|
||||
orderList.value = response.rows;
|
||||
});
|
||||
}
|
||||
|
||||
// 取消按钮
|
||||
function cancel() {
|
||||
open.value = false;
|
||||
reset();
|
||||
}
|
||||
|
||||
// 表单重置
|
||||
function reset() {
|
||||
form.value = {
|
||||
deductionId: null,
|
||||
orderId: null,
|
||||
personName: null,
|
||||
workContent: null,
|
||||
deductionAmount: null,
|
||||
deductionReason: null,
|
||||
workDate: null,
|
||||
status: null,
|
||||
remark: null,
|
||||
createTime: null,
|
||||
createBy: null,
|
||||
updateTime: null,
|
||||
updateBy: null,
|
||||
delFlag: null
|
||||
};
|
||||
proxy.resetForm("workDeductionRef");
|
||||
}
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
function handleQuery() {
|
||||
queryParams.value.pageNum = 1;
|
||||
getList();
|
||||
}
|
||||
|
||||
/** 重置按钮操作 */
|
||||
function resetQuery() {
|
||||
proxy.resetForm("queryRef");
|
||||
handleQuery();
|
||||
}
|
||||
|
||||
// 多选框选中数据
|
||||
function handleSelectionChange(selection) {
|
||||
ids.value = selection.map(item => item.deductionId);
|
||||
single.value = selection.length != 1;
|
||||
multiple.value = !selection.length;
|
||||
}
|
||||
|
||||
/** 新增按钮操作 */
|
||||
function handleAdd() {
|
||||
reset();
|
||||
open.value = true;
|
||||
title.value = "添加订单下人员工作及扣款信息";
|
||||
}
|
||||
|
||||
/** 修改按钮操作 */
|
||||
function handleUpdate(row) {
|
||||
loading.value = true
|
||||
reset();
|
||||
const _deductionId = row.deductionId || ids.value
|
||||
getWorkDeduction(_deductionId).then(response => {
|
||||
loading.value = false;
|
||||
form.value = response.data;
|
||||
open.value = true;
|
||||
title.value = "修改订单下人员工作及扣款信息";
|
||||
});
|
||||
}
|
||||
|
||||
/** 提交按钮 */
|
||||
function submitForm() {
|
||||
proxy.$refs["workDeductionRef"].validate(valid => {
|
||||
if (valid) {
|
||||
buttonLoading.value = true;
|
||||
if (form.value.deductionId != null) {
|
||||
updateWorkDeduction(form.value).then(response => {
|
||||
proxy.$modal.msgSuccess("修改成功");
|
||||
open.value = false;
|
||||
getList();
|
||||
}).finally(() => {
|
||||
buttonLoading.value = false;
|
||||
});
|
||||
} else {
|
||||
addWorkDeduction(form.value).then(response => {
|
||||
proxy.$modal.msgSuccess("新增成功");
|
||||
open.value = false;
|
||||
getList();
|
||||
}).finally(() => {
|
||||
buttonLoading.value = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/** 删除按钮操作 */
|
||||
function handleDelete(row) {
|
||||
const _deductionIds = row.deductionId || ids.value;
|
||||
proxy.$modal.confirm('是否确认删除订单下人员工作及扣款信息编号为"' + _deductionIds + '"的数据项?').then(function() {
|
||||
loading.value = true;
|
||||
return delWorkDeduction(_deductionIds);
|
||||
}).then(() => {
|
||||
loading.value = true;
|
||||
getList();
|
||||
proxy.$modal.msgSuccess("删除成功");
|
||||
}).catch(() => {
|
||||
}).finally(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
}
|
||||
|
||||
/** 导出按钮操作 */
|
||||
function handleExport() {
|
||||
proxy.download('oa/workDeduction/export', {
|
||||
...queryParams.value
|
||||
}, `workDeduction_${new Date().getTime()}.xlsx`)
|
||||
}
|
||||
|
||||
getList();
|
||||
remoteOrderList()
|
||||
</script>
|
||||
@@ -226,14 +226,12 @@
|
||||
|
||||
<script>
|
||||
import { listStockIo, getStockIo, delStockIo, addStockIo, updateStockIo } from "@/api/wms/stockIo";
|
||||
import { listStockIoDetail } from "@/api/wms/stockIoDetail";
|
||||
import StockIoDetailPanel from './panels/detail.vue';
|
||||
import BarcodeGenerator from './panels/barcode.vue';
|
||||
|
||||
export default {
|
||||
name: "StockIo",
|
||||
dicts: ['stock_biz_type', 'stock_io_type', 'stock_status'],
|
||||
components: { StockIoDetailPanel, BarcodeGenerator },
|
||||
components: { StockIoDetailPanel },
|
||||
data() {
|
||||
return {
|
||||
// 按钮loading
|
||||
|
||||
@@ -1,424 +0,0 @@
|
||||
<template>
|
||||
<div class="barcode-3col-layout">
|
||||
<!-- 预览区 -->
|
||||
<div class="barcode-preview-col">
|
||||
<div class="iframe-wrapper">
|
||||
<iframe ref="previewIframe" class="barcode-iframe" frameborder="0" :style="iframeStyle"></iframe>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 右侧控制+设置区 -->
|
||||
<div class="barcode-right-col">
|
||||
<!-- 控制区 -->
|
||||
<div class="barcode-control-bar">
|
||||
<el-tabs v-model="activeTab" stretch>
|
||||
<el-tab-pane label="排版设置" name="layout" />
|
||||
<el-tab-pane label="二维码明细" name="detail" />
|
||||
</el-tabs>
|
||||
</div>
|
||||
<!-- 设置区 -->
|
||||
<div class="barcode-settings-panel">
|
||||
<el-form v-if="activeTab==='layout'" label-width="80px" size="small" label-position="top">
|
||||
<!-- 排版设置内容保持不变 -->
|
||||
<el-form-item label="每行数量">
|
||||
<el-input-number :controls=false controls-position="right" v-model="perRow" size="mini" :min="1" :max="10" />
|
||||
</el-form-item>
|
||||
<el-form-item label="二维码尺寸">
|
||||
<el-input-number :controls=false controls-position="right" v-model="barcodeWidth" size="mini" :min="60" :max="600" />
|
||||
</el-form-item>
|
||||
<el-form-item label="纸张尺寸">
|
||||
<el-select v-model="paperSize" placeholder="请选择纸张尺寸" style="width: 160px">
|
||||
<el-option label="A4 (210mm x 297mm)" value="A4" />
|
||||
<el-option label="A5 (148mm x 210mm)" value="A5" />
|
||||
<el-option label="A6 (105mm x 148mm)" value="A6" />
|
||||
<el-option label="自定义" value="custom" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="paperSize==='custom'" label="自定义宽度(mm)">
|
||||
<el-input-number :controls=false controls-position="right" v-model="customPaperWidth" size="mini" :min="50" :max="500" />
|
||||
</el-form-item>
|
||||
<el-form-item v-if="paperSize==='custom'" label="自定义高度(mm)">
|
||||
<el-input-number :controls=false controls-position="right" v-model="customPaperHeight" size="mini" :min="50" :max="500" />
|
||||
</el-form-item>
|
||||
<el-form-item label="方向">
|
||||
<el-radio-group v-model="paperOrientation">
|
||||
<el-radio label="portrait">纵向</el-radio>
|
||||
<el-radio label="landscape">横向</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="handlePrint">打印</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<el-form v-else label-width="80px" size="small" label-position="top">
|
||||
<el-form-item label="二维码明细">
|
||||
<div v-for="(cfg, idx) in barcodeConfigs" :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(cfg.code, cfg.textTpl || cfg.code, idx)"
|
||||
icon="Download"
|
||||
>
|
||||
另存为图片
|
||||
</el-button>
|
||||
<el-button
|
||||
type="text"
|
||||
size="mini"
|
||||
@click="handleDelete(cfg, idx)"
|
||||
icon="Delete"
|
||||
>
|
||||
删除
|
||||
</el-button>
|
||||
</div>
|
||||
<el-form-item label="二维码内容" label-width="70px" style="margin-bottom: 8px;">
|
||||
<el-input disabled type="textarea" v-model="cfg.code" size="mini" :autosize="{ minRows: 1, maxRows: 3 }" placeholder="请输入条码内容" />
|
||||
</el-form-item>
|
||||
<el-form-item label="生成数量" label-width="70px" style="margin-bottom: 8px;">
|
||||
<el-input-number :controls=false controls-position="right" v-model.number="cfg.count" :min="1" :max="100" size="mini" />
|
||||
</el-form-item>
|
||||
<el-form-item label="下方文字" label-width="70px" style="margin-bottom: 0;">
|
||||
<el-input type="textarea" v-model="cfg.textTpl" size="mini" placeholder="如 箱号" />
|
||||
</el-form-item>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import QRCode from 'qrcode';
|
||||
export default {
|
||||
name: 'BarcodeGenerator',
|
||||
props: {
|
||||
barcodes: {
|
||||
type: Array,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
perRow: 3,
|
||||
barcodeWidth: 180,
|
||||
barcodeHeight: 180, // 二维码建议宽高一致
|
||||
barcodeConfigs: [], // [{code, count, textTpl}]
|
||||
previewScale: 1, // 预览缩放比例
|
||||
activeTab: 'layout',
|
||||
paperSize: 'A4',
|
||||
paperOrientation: 'portrait',
|
||||
customPaperWidth: 210,
|
||||
customPaperHeight: 297
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
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++) {
|
||||
// 模板替换 {{n}}
|
||||
let text = cfg.textTpl || cfg.code;
|
||||
text = text.replace(/\{\{n\}\}/g, i + 1);
|
||||
arr.push(text);
|
||||
}
|
||||
});
|
||||
return arr;
|
||||
},
|
||||
expandedBarcodeTableData() {
|
||||
return this.expandedBarcodes.map((code, idx) => ({ code, text: this.expandedBarcodeTexts[idx] }));
|
||||
},
|
||||
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;
|
||||
},
|
||||
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: {
|
||||
handler(newVal) {
|
||||
// 初始化barcodeConfigs
|
||||
this.barcodeConfigs = newVal;
|
||||
this.$nextTick(this.renderPreviewIframe);
|
||||
},
|
||||
immediate: true
|
||||
},
|
||||
barcodeConfigs: {
|
||||
handler() { this.$nextTick(this.renderPreviewIframe); },
|
||||
deep: true
|
||||
},
|
||||
perRow() { this.$nextTick(this.renderPreviewIframe); },
|
||||
barcodeWidth() { this.$nextTick(this.renderPreviewIframe); },
|
||||
barcodeHeight() { this.$nextTick(this.renderPreviewIframe); },
|
||||
previewScale() { this.$nextTick(this.renderPreviewIframe); },
|
||||
paperSize() { this.$nextTick(this.renderPreviewIframe); },
|
||||
paperOrientation() { this.$nextTick(this.renderPreviewIframe); },
|
||||
customPaperWidth() { this.$nextTick(this.renderPreviewIframe); },
|
||||
customPaperHeight() { this.$nextTick(this.renderPreviewIframe); }
|
||||
},
|
||||
methods: {
|
||||
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;
|
||||
},
|
||||
handleDelete(cfg, idx) {
|
||||
// this.barcodeConfigs.splice(idx, 1);
|
||||
this.$emit('delete', cfg, idx);
|
||||
},
|
||||
getPaperPx() {
|
||||
// mm to px, 1mm ≈ 3.78px
|
||||
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 };
|
||||
},
|
||||
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; }
|
||||
.barcode-row { display: flex; margin-bottom: 24px; }
|
||||
.barcode-item { flex: 1; text-align: center; }
|
||||
.barcode-text { margin-top: 8px; font-size: 14px; }
|
||||
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;
|
||||
},
|
||||
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);
|
||||
},
|
||||
handlePrint() {
|
||||
const iframe = this.$refs.previewIframe;
|
||||
if (!iframe) return;
|
||||
iframe.contentWindow.focus();
|
||||
iframe.contentWindow.print();
|
||||
},
|
||||
/**
|
||||
* 保存二维码为图片
|
||||
* @param {string} code 二维码内容
|
||||
* @param {string} text 下方文字
|
||||
* @param {number} index 索引,用于生成文件名
|
||||
*/
|
||||
async saveAsImage(code, text, index) {
|
||||
try {
|
||||
// 创建临时canvas用于绘制二维码和文字
|
||||
const canvas = document.createElement('canvas');
|
||||
const ctx = canvas.getContext('2d');
|
||||
|
||||
// 计算总高度(二维码高度 + 文字区域高度)
|
||||
const textHeight = 30; // 文字区域高度
|
||||
canvas.width = this.barcodeWidth;
|
||||
canvas.height = this.barcodeHeight + textHeight;
|
||||
|
||||
// 绘制二维码
|
||||
const qrCanvas = document.createElement('canvas');
|
||||
qrCanvas.width = this.barcodeWidth;
|
||||
qrCanvas.height = this.barcodeHeight;
|
||||
await QRCode.toCanvas(qrCanvas, code, {
|
||||
width: this.barcodeWidth,
|
||||
height: this.barcodeHeight,
|
||||
margin: 0
|
||||
});
|
||||
|
||||
// 将二维码绘制到主canvas
|
||||
ctx.drawImage(qrCanvas, 0, 0);
|
||||
|
||||
// 绘制文字
|
||||
ctx.fillStyle = '#000';
|
||||
ctx.font = '14px Arial';
|
||||
ctx.textAlign = 'center';
|
||||
ctx.textBaseline = 'top';
|
||||
ctx.fillText(text, this.barcodeWidth / 2, this.barcodeHeight + 5);
|
||||
|
||||
// 创建图片链接并下载
|
||||
canvas.toBlob(blob => {
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
// 生成文件名(使用索引和内容摘要)
|
||||
const fileName = `二维码_${index + 1}_${code.substring(0, 10)}.png`;
|
||||
a.download = fileName;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
URL.revokeObjectURL(url);
|
||||
});
|
||||
|
||||
this.$message.success('图片保存成功');
|
||||
} catch (error) {
|
||||
console.error('保存图片失败:', error);
|
||||
this.$message.error('保存图片失败,请重试');
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.barcodeConfigs = this.barcodes?.map(b => ({ code: b, count: 1, textTpl: b })) || [];
|
||||
this.renderPreviewIframe();
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 样式保持不变 */
|
||||
.barcode-3col-layout {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
width: 100%;
|
||||
min-height: 400px;
|
||||
}
|
||||
.barcode-preview-col {
|
||||
flex: 1 1 0;
|
||||
min-width: 0;
|
||||
background: #fff;
|
||||
border-right: 1px solid #eee;
|
||||
padding: 24px 0 24px 24px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 90vh;
|
||||
overflow: auto;
|
||||
}
|
||||
.barcode-right-col {
|
||||
width: 420px;
|
||||
min-width: 320px;
|
||||
padding: 16px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background: #fafbfc;
|
||||
height: 90vh;
|
||||
}
|
||||
.barcode-control-bar {
|
||||
border-bottom: 1px solid #eee;
|
||||
background: #fafbfc;
|
||||
padding: 0 16px;
|
||||
}
|
||||
.barcode-settings-panel {
|
||||
flex: 1;
|
||||
overflow: auto;
|
||||
min-height: 0;
|
||||
max-height: calc(90vh - 48px); /* 48px 约为tabs高度,可根据实际调整 */
|
||||
}
|
||||
.preview-toolbar {
|
||||
margin-bottom: 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.iframe-wrapper {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: flex-start;
|
||||
background: #f8f8f8;
|
||||
overflow: auto;
|
||||
}
|
||||
.barcode-iframe {
|
||||
/* width、height 由:style绑定动态控制 */
|
||||
min-width: 0;
|
||||
min-height: 0;
|
||||
border: none;
|
||||
background: #fff;
|
||||
border-radius: 4px;
|
||||
display: block;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user