feat(CoilSelector): 新增入场卷号字段并调整当前卷号显示

feat(customer): 新增客户相关配卷和财务信息查询接口

fix(base.vue): 修复发货单时间条件显示问题

refactor(CustomerEdit): 替换地址选择组件为普通输入框

feat(CoilSelector): 增加入场卷号查询条件并调整对话框宽度

style(OrderEdit): 调整客户名称和销售员选择框宽度

refactor(ChinaAreaSelect): 优化地址解析逻辑并支持空对象处理

feat(FileUpload/FileList): 新增文件预览功能组件

refactor(KLPService/CustomerSelect): 优化客户选择组件并支持自定义字段绑定

fix(AbnormalForm): 修复异常位置校验逻辑并保留当前卷号

feat(ContractTabs): 新增合同附件展示功能

refactor(warehouse/record): 重构操作记录统计展示方式

feat(contract): 集成客户选择组件并优化合同信息填充

refactor(order): 调整订单表单布局并集成合同信息

feat(FilePreview): 新增文件预览组件

feat(customer): 新增财务状态和发货配卷展示

refactor(CustomerOrder): 移除冗余代码并优化布局

feat(PlanDetailForm): 新增合同附件查看功能

feat(dict): 新增字典管理页面
This commit is contained in:
砂糖
2026-04-06 13:16:45 +08:00
parent 4075ead84e
commit 1fa4c55869
21 changed files with 1158 additions and 192 deletions

View File

@@ -42,3 +42,19 @@ export function delCustomer(customerId) {
method: 'delete'
})
}
// 查询该客户相关的配卷情况
export function listCoilByCustomerId(customerId) {
return request({
url: '/crm/orderItem/coils/customer/' + customerId,
method: 'get'
})
}
// 查询该项目相关的订单异议和财务信息
export function listFinanceByCustomerId(customerId) {
return request({
url: '/crm/orderItem/customerFinance/' + customerId,
method: 'get'
})
}

View File

@@ -47,12 +47,17 @@ function formatAreaText(value) {
return { standard, custom };
}
// 非组合格式(纯标准地址/纯自定义地址)→ 默认归为standard
return { standard: trimVal, custom: '' };
// 非组合格式(纯任意字符串)→ 归为custom
return { standard: '', custom: trimVal };
}
// ========== 场景3输入是对象 → 格式化为组合字符串 ==========
if (typeof value === 'object' && !Array.isArray(value)) {
// 处理空对象
if (Object.keys(value).length === 0) {
return '[]()';
}
const { standard = '', custom = '' } = value;
// 转字符串并去空格
const standardStr = String(standard).trim();
@@ -71,11 +76,19 @@ function formatAreaText(value) {
*/
function formatAreaTextEnhanced(value, keyType = 'name') {
// 先调用基础版解析/格式化
const result = formatAreaText(value);
let result = formatAreaText(value);
// 如果是解析模式返回对象且keyType为name转换code为name
// 如果是解析模式返回对象且keyType为name检查是否为代码输入
if (typeof result === 'object' && keyType === 'name') {
result.standard = convertCodeToName(result.standard);
// 检查custom是否可能是代码不包含中文
if (result.custom && !/[\u4e00-\u9fa5]/.test(result.custom)) {
// 尝试转换code为name
const convertedName = convertCodeToName(result.custom);
// 如果转换成功返回非空字符串则将其移到standard字段
if (convertedName) {
result = { standard: convertedName, custom: '' };
}
}
}
return result;

View File

@@ -26,6 +26,7 @@
<script>
import areaData from './data.js'
import { formatAreaTextEnhanced } from './index.js'
export default {
name: 'ChinaAreaSelectWithDetail',
@@ -105,32 +106,9 @@ export default {
* @param {String} val - 外部传入的「[标准地址](自定义地址)」格式值
*/
parseCombineValue(val) {
if (!val) {
this.areaValue = []
this.detailAddress = ''
return
}
// 核心正则:匹配「[xxx](yyy)」格式分组提取xxx和yyy无匹配则为空
const reg = /\[([^\]]*)\]\(([^)]*)\)/
const matchResult = val.match(reg)
// 提取标准地址(第一个分组)和自定义地址(第二个分组)
const standardAddress = matchResult?.[1] || ''
const customAddress = matchResult?.[2] || ''
// 1. 处理自定义地址:直接赋值
this.detailAddress = customAddress.trim()
// 2. 处理标准地址转换为区域选择器的code数组
if (standardAddress) {
const standardArr = standardAddress.split('/').filter(Boolean)
this.areaValue = this.keyType === 'name'
? this.convertNameToCode(standardArr) // name转code
: standardArr.filter(code => !!areaData[code]) // code直接过滤无效值
} else {
this.areaValue = []
}
const formattedAddress = formatAreaTextEnhanced(val)
this.areaValue = formattedAddress.standard.split('/').filter(Boolean)
this.detailAddress = formattedAddress.custom.trim()
},
/**

View File

@@ -1,6 +1,12 @@
export const defaultColumns = [
{
label: '卷号',
label: '入场卷号',
align: 'center',
prop: 'enterCoilNo',
showOverflowTooltip: true
},
{
label: '当前卷号',
align: 'center',
prop: 'currentCoilNo',
showOverflowTooltip: true

View File

@@ -27,8 +27,12 @@
<el-option label="原料" value="raw_material" />
</el-select>
</el-form-item> -->
<el-form-item label="卷号">
<el-input v-model="queryParams.currentCoilNo" placeholder="请输入卷号" clearable size="small"
<el-form-item label="入场卷号">
<el-input v-model="queryParams.enterCoilNo" placeholder="请输入入场卷号" clearable size="small"
@keyup.enter.native="handleQuery" />
</el-form-item>
<el-form-item label="当前卷号">
<el-input v-model="queryParams.currentCoilNo" placeholder="请输入当前卷号" clearable size="small"
@keyup.enter.native="handleQuery" />
</el-form-item>
<el-form-item label="物料">
@@ -192,7 +196,7 @@ export default {
},
dialogWidth: {
type: String,
default: '1000px'
default: '1200px'
},
// 过滤条件(可以预设一些查询条件)
filters: {

View File

@@ -15,25 +15,47 @@
<i class="el-icon-document"></i>
<span class="file-name">{{ file.originalName }}</span>
</div>
<el-button
type="text"
icon="el-icon-download"
@click="downloadFile(file)"
size="small"
class="download-btn"
>
下载
</el-button>
<div class="file-actions">
<el-button
type="text"
icon="el-icon-view"
@click="handlePreview(file)"
size="small"
class="preview-btn"
>
预览
</el-button>
<el-button
type="text"
icon="el-icon-download"
@click="downloadFile(file)"
size="small"
class="download-btn"
>
下载
</el-button>
</div>
</div>
</div>
<!-- 文件预览组件 -->
<file-preview
:visible.sync="previewVisible"
:file-url="previewFileUrl"
:file-name="previewFileName"
/>
</div>
</template>
<script>
import { listByIds } from "@/api/system/oss";
import FilePreview from "../FilePreview";
export default {
name: "FileList",
components: {
FilePreview
},
props: {
ossIds: {
type: String,
@@ -43,7 +65,11 @@ export default {
data() {
return {
fileList: [],
loading: false // 加载状态
loading: false, // 加载状态
// 预览相关
previewVisible: false,
previewFileUrl: '',
previewFileName: ''
}
},
watch: {
@@ -81,6 +107,12 @@ export default {
return;
}
this.$download.oss(file.ossId);
},
// 预览文件
handlePreview(file) {
this.previewFileUrl = file.url;
this.previewFileName = file.originalName;
this.previewVisible = true;
}
}
}
@@ -115,6 +147,12 @@ export default {
transition: background-color 0.3s;
}
.file-actions {
display: flex;
align-items: center;
gap: 16px;
}
.file-item:last-child {
border-bottom: none;
}

View File

@@ -0,0 +1,185 @@
<template>
<el-dialog
:title="title"
:visible.sync="dialogVisible"
:width="width"
:close-on-click-modal="false"
destroy-on-close
append-to-body
@close="handleClose"
>
<!-- 图片预览 -->
<div v-if="fileType === 'image'" class="preview-image">
<div class="image-controls">
<el-button type="primary" size="small" @click="zoomIn">放大</el-button>
<el-button type="primary" size="small" @click="zoomOut">缩小</el-button>
<el-button type="primary" size="small" @click="resetZoom">重置</el-button>
</div>
<div class="image-container" ref="imageContainer">
<img
:src="fileUrl"
:style="{ transform: `scale(${scale})` }"
class="preview-image-content"
@wheel="handleWheel"
/>
</div>
</div>
<!-- PDF预览 -->
<div v-else-if="fileType === 'pdf'" class="preview-pdf">
<iframe
:src="fileUrl"
class="preview-pdf-content"
frameborder="0"
/>
</div>
<!-- 不支持的文件类型 -->
<div v-else class="preview-not-supported">
<el-empty description="暂不支持预览此文件类型"></el-empty>
</div>
</el-dialog>
</template>
<script>
export default {
name: "FilePreview",
props: {
visible: {
type: Boolean,
default: false
},
fileUrl: {
type: String,
required: true
},
fileName: {
type: String,
default: "文件预览"
},
width: {
type: String,
default: "80%"
}
},
data() {
return {
dialogVisible: false,
scale: 1
};
},
watch: {
visible: {
handler(val) {
this.dialogVisible = val;
},
immediate: true
},
dialogVisible(val) {
if (!val) {
this.$emit('update:visible', false);
}
}
},
computed: {
title() {
return this.fileName || "文件预览";
},
fileType() {
const fileName = this.fileName || '';
const ext = fileName.split('.').pop()?.toLowerCase();
if (['png', 'jpg', 'jpeg', 'bmp', 'webp'].includes(ext)) {
return 'image';
} else if (ext === 'pdf') {
return 'pdf';
} else {
return 'other';
}
}
},
methods: {
handleClose() {
this.$emit('update:visible', false);
},
// 放大图片
zoomIn() {
if (this.scale < 3) {
this.scale += 0.1;
}
},
// 缩小图片
zoomOut() {
if (this.scale > 0.1) {
this.scale -= 0.1;
}
},
// 重置缩放
resetZoom() {
this.scale = 1;
},
// 鼠标滚轮缩放
handleWheel(event) {
event.preventDefault();
const delta = event.deltaY > 0 ? -0.1 : 0.1;
if ((this.scale > 0.1 || delta > 0) && (this.scale < 3 || delta < 0)) {
this.scale += delta;
}
}
}
};
</script>
<style scoped>
.preview-image {
width: 100%;
height: 70vh;
background-color: #f5f7fa;
display: flex;
flex-direction: column;
}
.image-controls {
padding: 10px;
display: flex;
gap: 10px;
border-bottom: 1px solid #e4e7ed;
background-color: #ffffff;
}
.image-container {
flex: 1;
overflow: auto;
display: flex;
justify-content: center;
align-items: flex-start;
padding: 20px;
}
.preview-image-content {
transition: transform 0.3s ease;
cursor: zoom-in;
max-width: 100%;
max-height: 100%;
}
.preview-image-content:hover {
cursor: zoom-in;
}
.preview-pdf {
width: 100%;
height: 70vh;
}
.preview-pdf-content {
width: 100%;
height: 100%;
}
.preview-not-supported {
display: flex;
justify-content: center;
align-items: center;
height: 400px;
}
</style>

View File

@@ -32,19 +32,31 @@
<span class="el-icon-document"> {{ getFileName(file.name) }} </span>
</el-link>
<div class="ele-upload-list__item-content-action">
<el-link :underline="false" @click="handlePreview(file)" type="primary">预览</el-link>
<el-link :underline="false" @click="handleDelete(index)" type="danger">删除</el-link>
</div>
</li>
</transition-group>
<!-- 文件预览组件 -->
<file-preview
:visible.sync="previewVisible"
:file-url="previewFileUrl"
:file-name="previewFileName"
/>
</div>
</template>
<script>
import { getToken } from "@/utils/auth";
import { listByIds, delOss } from "@/api/system/oss";
import FilePreview from "../FilePreview";
export default {
name: "FileUpload",
components: {
FilePreview
},
props: {
// 值
value: [String, Object, Array],
@@ -85,6 +97,10 @@ export default {
},
fileList: [],
loading: false,
// 预览相关
previewVisible: false,
previewFileUrl: '',
previewFileName: ''
};
},
watch: {
@@ -232,6 +248,12 @@ export default {
}
return strs != "" ? strs.substr(0, strs.length - 1) : "";
},
// 预览文件
handlePreview(file) {
this.previewFileUrl = file.url;
this.previewFileName = file.name;
this.previewVisible = true;
},
},
};
</script>

View File

@@ -1,6 +1,6 @@
<template>
<el-select filterable v-model="_customerId" remote :remote-method="remoteSearchCustomer" :loading="customerLoading" placeholder="请选择客户">
<el-option v-for="item in customerList" :key="item.customerId" :label="item.name" :value="item.customerId" />
<el-select filterable v-model="_customerId" remote :remote-method="remoteSearchCustomer" :style="style" :loading="customerLoading" placeholder="请选择客户">
<el-option v-for="item in customerList" :key="item[bindField]" :label="item.companyName" :value="item[bindField]" />
</el-select>
</template>
@@ -13,6 +13,14 @@
value: {
type: String,
default: ''
},
bindField: {
type: String,
default: 'customerId'
},
style: {
type: Object,
default: () => ({})
}
},
computed: {
@@ -22,6 +30,12 @@
},
set(value) {
this.$emit('input', value);
// 找到对应的客户信息
const customer = this.customerList.find(item => item[this.bindField] === value);
// 触发 change 事件,传递客户信息
if (customer) {
this.$emit('change', customer);
}
}
}
},
@@ -37,7 +51,7 @@
methods: {
remoteSearchCustomer(query) {
this.customerLoading = true;
listCustomer({ name: query, pageNum: 1, pageSize: 10 }).then(response => {
listCustomer({ companyName: query, pageNum: 1, pageSize: 10 }).then(response => {
this.customerList = response.rows;
}).finally(() => {
this.customerLoading = false;

View File

@@ -7,9 +7,14 @@
<template slot="title">
<!-- 在这里选择订单后快速填写相关信息 -->
<span>订单信息</span>
<el-button @click.stop="openOrderDialog" style="margin-left: 10px;" plain :type="formData.orderId ? 'success' : 'default'">
{{ formData.orderId ? formData.orderCode : '选择订单' }}
</el-button>
<el-button @click.stop="openOrderDialog" style="margin-left: 10px;" plain
:type="formData.orderId ? 'success' : 'default'">
{{ formData.orderId ? formData.orderCode : '选择订单' }}
</el-button>
<div v-if="formData.orderId" @click.stop="openOrderAttachmentDialog" style="margin-left: 10px; cursor: pointer; color: #409eff;"
type="primary">
查看附件
</div>
</template>
<el-row>
<el-col :span="12">
@@ -207,12 +212,8 @@
<el-form :model="orderQueryParams" ref="orderQueryForm" size="small" :inline="true" label-width="80px">
<el-form-item label="合同号">
<el-select v-model="orderQueryParams.contractId" placeholder="请选择合同">
<el-option
v-for="contract in contractList"
:key="contract.contractId"
:label="contract.contractNo"
:value="contract.contractId"
/>
<el-option v-for="contract in contractList" :key="contract.contractId" :label="contract.contractNo"
:value="contract.contractId" />
</el-select>
</el-form-item>
<el-form-item label="订单号">
@@ -250,14 +251,26 @@
<!-- 分页 -->
<div class="pagination-container">
<el-pagination
background
layout="prev, pager, next, jumper"
:total="orderTotal"
:page-size="orderQueryParams.pageSize"
:current-page.sync="orderQueryParams.pageNum"
@current-change="getOrderList"
/>
<el-pagination background layout="prev, pager, next, jumper" :total="orderTotal"
:page-size="orderQueryParams.pageSize" :current-page.sync="orderQueryParams.pageNum"
@current-change="getOrderList" />
</div>
</div>
</el-dialog>
<el-dialog title="合同附件" :visible.sync="attachmentOpen" width="50%" append-to-body>
<div class="attachment-section" v-loading="loading">
<!-- <div class="attachment-item">
<h4>商务附件</h4>
<FileList :oss-ids="contractAttachment" />
</div> -->
<div class="attachment-item">
<h4>技术附件</h4>
<FileList :oss-ids="contract.techAnnex" />
</div>
<div class="attachment-item">
<h4>排产函</h4>
<FileList :oss-ids="contract.productionSchedule" />
</div>
</div>
</el-dialog>
@@ -265,8 +278,9 @@
</template>
<script>
import { listOrder } from '@/api/crm/order';
import { listContract } from '@/api/crm/contract';
import { listOrder, getOrder } from '@/api/crm/order';
import { listContract, getContract } from '@/api/crm/contract';
import FileList from '@/components/FileList'
export default {
name: "PlanDetailForm",
@@ -284,6 +298,9 @@ export default {
default: false
}
},
components: {
FileList
},
computed: {
formData: {
get() {
@@ -330,7 +347,10 @@ export default {
contractId: undefined,
contractCode: undefined,
customerName: undefined
}
},
attachmentOpen: false,
contract: {},
loading: false,
};
},
methods: {
@@ -349,6 +369,29 @@ export default {
// 加载订单列表
this.getOrderList();
},
async openOrderAttachmentDialog() {
this.loading = true;
this.attachmentOpen = true;
// 根据订单id获取订单详情拿到合同id
try {
if (!this.formData.orderId) {
this.$message.error('订单不存在')
return;
}
const order = await getOrder(this.formData.orderId);
if (!order.data.contractId) {
this.$message.error('未找到合同')
return;
}
// 根据合同id拿到合同详情
const contract = await getContract(order.data.contractId);
this.contract = contract.data;
} catch {
this.$message.error('获取合同附件失败')
} finally {
this.loading = false;
}
},
/** 获取合同列表 */
getContractList() {
listContract().then(response => {
@@ -388,6 +431,7 @@ export default {
this.formData.contractCode = row.contractCode;
this.formData.customerName = row.companyName;
this.formData.salesman = row.salesman;
this.formData.orderId = row.orderId;
this.dialogVisible = false;
},
/** 处理订单行点击 */
@@ -411,4 +455,24 @@ export default {
margin-top: 20px;
text-align: right;
}
.attachment-section {
padding: 20px;
}
.attachment-item {
margin-bottom: 10px;
}
.attachment-item h4 {
font-size: 16px;
font-weight: 600;
color: #303133;
}
.attachment-item .file-list-container {
border: 1px solid #ebeef5;
border-radius: 4px;
overflow: hidden;
}
</style>

View File

@@ -95,17 +95,16 @@
</el-col>
<el-col :span="24" v-hasPermi="['crm:customer:address']">
<el-form-item label="客户地址" prop="address">
<!-- <el-input
type="textarea"
<el-input
v-model="customer.address"
placeholder="请输入客户地址"
@input="handleInputChange"
/> -->
<ChinaAreaSelect
/>
<!-- <ChinaAreaSelect
v-model="customer.address"
placeholder="请选择客户地址"
@change="handleInputChange"
/>
/> -->
</el-form-item>
</el-col>
<el-col :span="24" v-hasPermi="['crm:customer:bank']">

View File

@@ -1,12 +1,5 @@
<template>
<div>
<el-descriptions :column="2" border title="订单统计">
<el-descriptions-item label="订单总数">{{ currentCustomer.totalCount || 0 }}</el-descriptions-item>
<el-descriptions-item label="已成交订单数">{{ currentCustomer.dealCount || 0 }}</el-descriptions-item>
<el-descriptions-item label="待成交订单数">{{ currentCustomer.waitCount || 0 }}</el-descriptions-item>
<el-descriptions-item label="取消订单数">{{ currentCustomer.cancelCount || 0 }}</el-descriptions-item>
</el-descriptions>
<el-descriptions border title="订单详情">
</el-descriptions>
@@ -21,6 +14,10 @@
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
</el-form-item>
<el-form-item label="订单总数" prop="totalCount" style="margin-left: 10px; float: right;">
<div>{{ total || 0 }}</div>
<!-- <el-input v-model="queryParams.totalCount" placeholder="请输入订单总数" clearable @keyup.enter.native="handleQuery" /> -->
</el-form-item>
</el-form>
<el-table v-loading="loading" :data="orderList" height="400px" highlight-current-row @row-click="handleRowClick">
@@ -38,90 +35,15 @@
<span v-else-if="scope.row.orderType === ORDER_TYPE['正式订单']">正式订单</span>
</template>
</el-table-column>
<!-- <el-table-column v-if="orderType === ORDER_TYPE['预订单']" label="审核状态" align="center" prop="preOrderStatus">
<template slot-scope="scope">
<span v-if="scope.row.preOrderStatus === 0">待审核</span>
<span v-else-if="scope.row.preOrderStatus === 1">已审核</span>
<span v-else-if="scope.row.preOrderStatus === 2">已取消</span>
<span v-else>未知状态</span>
</template>
</el-table-column> -->
<!-- <el-table-column label="审核人" align="center" prop="auditUser" />
<el-table-column label="审核时间" align="center" prop="auditTime" width="180">
<template slot-scope="scope">
<span>{{ parseTime(scope.row.auditTime, '{y}-{m}-{d}') }}</span>
</template>
</el-table-column> -->
<!-- <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-check"
@click="handleApprove(scope.row)"
>审批</el-button>
<el-button
size="mini"
type="text"
icon="el-icon-edit"
@click="handleUpdate(scope.row)"
>修改</el-button>
<el-button
size="mini"
type="text"
icon="el-icon-delete"
@click="handleDelete(scope.row)"
>删除</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" />
<!-- 正式订单明细列表组件 -->
<!-- <OrderDetailList ref="orderDetailList" :orderId="orderId" /> -->
<!-- 添加或修改正式订单主对话框 -->
<!-- <el-dialog :title="title" :visible.sync="open" width="500px" append-to-body>
<el-form ref="form" :model="form" :rules="rules" label-width="80px">
<el-form-item label="订单编号" prop="orderCode">
<el-input v-model="form.orderCode" placeholder="请输入订单编号" />
</el-form-item>
<el-form-item label="客户" prop="customerId">
<el-select v-model="form.customerId" placeholder="请选择客户">
<el-option v-for="item in customerList" :key="item.customerId" :label="item.customerCode" :value="item.customerId" />
</el-select>
</el-form-item>
<el-form-item label="订单总金额" prop="orderAmount">
<el-input v-model="form.orderAmount" placeholder="请输入订单总金额" />
</el-form-item>
<el-form-item label="销售员" prop="salesman">
<el-input v-model="form.salesman" placeholder="请输入销售员" />
</el-form-item>
<el-form-item label="交货日期" prop="deliveryDate">
<el-date-picker clearable
v-model="form.deliveryDate"
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>
<div slot="footer" class="dialog-footer">
<el-button :loading="buttonLoading" type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
</div>
</el-dialog> -->
</div>
</template>
<script>
import { listOrder, getOrder, delOrder, addOrder, updateOrder } from "@/api/crm/order";
// import { listCustomer } from "@/api/crm/customer";
import OrderDetailList from '@/views/crm/components/OrderDetail.vue'
import { ORDER_TYPE } from "../js/enum";

View File

@@ -12,8 +12,8 @@
<el-row>
<el-col :span="12">
<el-form-item label="客户名称" prop="customerId">
<el-select v-model="form.customerId" placeholder="请选择客户名称">
<el-option v-for="item in customerList" :key="item.customerId" :label="item.customerCode"
<el-select style="width: 100%;" v-model="form.customerId" placeholder="请选择客户名称">
<el-option v-for="item in customerList" :key="item.customerId" :label="item.companyName"
:value="item.customerId"></el-option>
</el-select>
</el-form-item>
@@ -27,7 +27,7 @@
<el-col :span="12">
<el-form-item label="销售员" prop="salesman">
<el-select v-model="form.salesman" placeholder="请选择销售员">
<el-select style="width: 100%;" v-model="form.salesman" placeholder="请选择销售员">
<el-option v-for="item in dict.type.wip_pack_saleman" :key="item.value" :label="item.label"
:value="item.value" />
</el-select>

View File

@@ -3,7 +3,7 @@
<div v-if="contractId" class="tabs-content">
<el-tabs v-model="activeTab" type="card" tab-position="top" v-loading="tabLoading">
<el-tab-pane label="下发订单" name="second">
<OrderPage v-if="activeTab === 'second'" :contractId="contractId" />
<OrderPage v-if="activeTab === 'second'" :contractId="contractId" :customerId="customerId" />
</el-tab-pane>
<el-tab-pane label="财务状态" name="third">
<KLPTable v-loading="loading" :data="financeList">
@@ -38,6 +38,22 @@
<el-tab-pane label="发货配卷" name="fifth">
<CoilTable :data="coilList" />
</el-tab-pane>
<el-tab-pane label="合同附件" name="sixth">
<div class="attachment-section">
<div class="attachment-item">
<h4>商务附件</h4>
<FileList :oss-ids="contractAttachment" />
</div>
<div class="attachment-item">
<h4>技术附件</h4>
<FileList :oss-ids="technicalAgreement" />
</div>
<div class="attachment-item">
<h4>排产函</h4>
<FileList :oss-ids="otherAttachment" />
</div>
</div>
</el-tab-pane>
</el-tabs>
</div>
<div v-else class="no-selection" style="display: flex; align-items: center; justify-content: center; height: 100%;">
@@ -49,18 +65,24 @@
<script>
import OrderPage from "@/views/crm/order/index.vue";
import CoilTable from "../../components/CoilTable.vue";
import FileList from "@/components/FileList";
export default {
name: "ContractTabs",
components: {
OrderPage,
CoilTable
CoilTable,
FileList
},
props: {
contractId: {
type: [Number, String],
default: null
},
customerId: {
type: String,
default: ''
},
financeList: {
type: Array,
default: () => []
@@ -80,6 +102,19 @@ export default {
tabLoading: {
type: Boolean,
default: false
},
// 附件字段
contractAttachment: {
type: String,
default: ''
},
technicalAgreement: {
type: String,
default: ''
},
otherAttachment: {
type: String,
default: ''
}
},
data() {
@@ -116,4 +151,24 @@ export default {
height: 100%;
overflow-y: auto;
}
.attachment-section {
padding: 20px;
}
.attachment-item {
margin-bottom: 10px;
}
.attachment-item h4 {
font-size: 16px;
font-weight: 600;
color: #303133;
}
.attachment-item .file-list-container {
border: 1px solid #ebeef5;
border-radius: 4px;
overflow: hidden;
}
</style>

View File

@@ -24,8 +24,12 @@
<!-- 右侧下方Tab标签页 -->
<div class="tab-panel" ref="tabPanel" style="flex: 1; overflow-y: auto;">
<ContractTabs :contractId="form.contractId" :financeList="financeList" :objectionList="objectionList"
:coilList="coilList" :tabLoading="tabLoading" />
<ContractTabs :contractId="form.contractId"
:customerId="form.customerId" :financeList="financeList" :objectionList="objectionList"
:coilList="coilList" :tabLoading="tabLoading"
:contract-attachment="form.businessAnnex"
:technical-agreement="form.techAnnex"
:other-attachment="form.productionSchedule" />
</div>
</div>
<div v-else style="flex: 1; display: flex; flex-direction: column;">
@@ -102,7 +106,8 @@
<!-- 需方信息 -->
<el-col :span="12">
<el-form-item label="需方" prop="customer">
<el-input v-model="form.customer" placeholder="请输入需方" />
<CustomerSelect v-model="form.customer" bindField="companyName" @change="handleCustomerChange" :style="{ width: '100%' }" />
<!-- <el-input v-model="form.customer" placeholder="请输入需方" /> -->
</el-form-item>
<el-form-item label="需方地址" prop="customerAddress">
<el-input v-model="form.customerAddress" placeholder="请输入需方地址" />
@@ -151,6 +156,8 @@ import ContractList from "./components/ContractList.vue";
import ContractPreview from "./components/ContractPreview.vue";
import ContractTabs from "./components/ContractTabs.vue";
import ProductContent from "./components/ProductContent.vue";
import CustomerSelect from "@/components/KLPService/CustomerSelect/index.vue";
import { formatAreaTextEnhanced } from '@/components/ChinaAreaSelect/index.js'
export default {
name: "Contract",
@@ -158,7 +165,8 @@ export default {
ContractList,
ContractPreview,
ContractTabs,
ProductContent
ProductContent,
CustomerSelect
},
data() {
return {
@@ -258,6 +266,29 @@ export default {
},
methods: {
/** 处理客户选择 */
handleCustomerChange(customer) {
console.log(customer);
this.form.customerAddress = customer.address;
this.form.customerPhone = customer.contactWay;
this.form.customerTaxNo = customer.taxNumber;
this.form.customerId = customer.customerId;
// 处理银行信息
if (customer.bankInfo) {
try {
const bankList = JSON.parse(customer.bankInfo);
if (bankList && bankList.length > 0) {
const firstBank = bankList[0];
this.form.customerBank = firstBank.bankName;
this.form.customerAccount = firstBank.bankAccount;
}
} catch (error) {
console.error('解析银行信息失败:', error);
}
}
// 开户行和银行账号在客户信息中是一个json数组字符串如果存在则默认选中第一个将输入框改为下拉选可以快速切换
console.log(customer);
},
/** 处理合同状态更新 */
handleStatusChange(status) {

View File

@@ -65,6 +65,39 @@
<CustomerOrder :customer="currentCustomer" :dict="dict" />
</div>
</el-tab-pane>
<el-tab-pane label="财务状态" name="third">
<KLPTable v-loading="loading" :data="financeList">
<el-table-column label="收款日期" align="center" prop="dueDate" width="180">
<template slot-scope="scope">
<span>{{ parseTime(scope.row.dueDate, '{y}-{m}-{d}') }}</span>
</template>
</el-table-column>
<el-table-column label="收款金额" align="center" prop="amount" />
<el-table-column label="备注" align="center" prop="remark" />
</KLPTable>
</el-tab-pane>
<el-tab-pane label="订单异议" name="fourth">
<el-table v-loading="loading" :data="objectionList">
<el-table-column label="编号" align="center" prop="objectionCode" />
<el-table-column label="状态" align="center" prop="objectionStatus">
<template slot-scope="scope">
<el-tag v-if="scope.row.objectionStatus === 0" type="danger">待处理</el-tag>
<el-tag v-else-if="scope.row.objectionStatus === 1" type="success">已处理</el-tag>
<el-tag v-else-if="scope.row.objectionStatus === 2" type="info">已关闭</el-tag>
</template>
</el-table-column>
<el-table-column label="处理人" align="center" prop="handleUser" />
<el-table-column label="处理时间" align="center" prop="handleTime" width="180">
<template slot-scope="scope">
<span>{{ parseTime(scope.row.handleTime, '{y}-{m}-{d}') }}</span>
</template>
</el-table-column>
<el-table-column label="备注" align="center" prop="remark" />
</el-table>
</el-tab-pane>
<el-tab-pane label="发货配卷" name="fifth">
<CoilTable :data="coilList" />
</el-tab-pane>
</el-tabs>
<el-empty v-else style="margin-top: 20px;" description="选择客户查看详情"></el-empty>
</el-col>
@@ -101,7 +134,8 @@
</el-select>
</el-form-item>
<el-form-item label="客户地址" prop="address">
<ChinaAreaSelect v-model="form.address" placeholder="请选择客户地址" />
<el-input v-model="form.address" placeholder="请输入客户地址" />
<!-- <ChinaAreaSelect v-model="form.address" placeholder="请选择客户地址" /> -->
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input v-model="form.remark" placeholder="请输入备注" />
@@ -126,8 +160,9 @@ import CustomerDetail from '../components/CustomerInfo.vue'
import CustomerEdit from '../components/CustomerEdit.vue'
import CustomerOrder from '../components/CustomerOrder.vue'
import ChinaAreaSelect from '@/components/ChinaAreaSelect/index.vue'
import CoilTable from '../components/CoilTable.vue'
import { listCustomer, addCustomer, updateCustomer, delCustomer } from '@/api/crm/customer'
import { listCustomer, addCustomer, updateCustomer, delCustomer, listCoilByCustomerId, listFinanceByCustomerId } from '@/api/crm/customer'
export default {
name: 'CustomerPage',
@@ -137,12 +172,16 @@ export default {
CustomerDetail,
CustomerEdit,
CustomerOrder,
ChinaAreaSelect
ChinaAreaSelect,
CoilTable
},
dicts: ['customer_industry', 'customer_level'],
data() {
return {
customerList: [],
financeList: [],
objectionList: [],
coilList: [],
showQuery: false,
queryParams: {
industry: '',
@@ -187,6 +226,24 @@ export default {
this.showQuery = !this.showQuery
},
// 获取客户财务状态
getFinanceList() {
if (!this.currentCustomerId) return;
listFinanceByCustomerId(this.currentCustomerId).then(response => {
this.financeList = response.data.financeList || [];
this.objectionList = response.data.oobjectionList || [];
}).catch(() => {
this.$message.error('获取客户财务状态失败');
});
},
/** 查询合同配卷列表 */
getCoilList() {
listContractPackaging(this.form.contractId).then(response => {
this.coilList = response.data || [];
})
},
debounce(fn, delay) {
return (...args) => {
if (this.debounceTimer) clearTimeout(this.debounceTimer);

View File

@@ -0,0 +1,497 @@
<template>
<div class="app-container">
<el-row :gutter="20">
<!-- 左侧字典类型管理 -->
<el-col :span="6" style="height: calc(100vh - 124px); overflow-y: auto;">
<div>
<div class="dict-type-list">
<div
v-for="item in typeList"
:key="item.dictId"
:class="['dict-type-item', { 'active': selectedDictType === item.dictType }]"
@click="selectDictType(item.dictType, item.dictId)"
>
<div class="dict-type-name">{{ item.dictName }}</div>
<div class="dict-type-info">
<span class="dict-type-code">{{ item.dictType }}</span>
<span class="dict-type-id">#{{ item.dictId }}</span>
</div>
</div>
</div>
</div>
</el-col>
<!-- 右侧字典数据管理 -->
<el-col :span="18" style="height: 100%; overflow-y: auto;">
<div class="dict-data-container">
<h3>字典数据管理</h3>
<el-form :model="dataQueryParams" ref="dataQueryForm" size="small" :inline="true" v-show="dataShowSearch" label-width="68px">
<el-form-item label="字典标签" prop="dictLabel">
<el-input
v-model="dataQueryParams.dictLabel"
placeholder="请输入字典标签"
clearable
@keyup.enter.native="dataHandleQuery"
/>
</el-form-item>
<el-form-item label="状态" prop="status">
<el-select v-model="dataQueryParams.status" placeholder="数据状态" clearable>
<el-option
v-for="dict in dict.type.sys_normal_disable"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" size="mini" @click="dataHandleQuery">搜索</el-button>
<el-button icon="el-icon-refresh" size="mini" @click="dataResetQuery">重置</el-button>
</el-form-item>
</el-form>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button
type="primary"
plain
icon="el-icon-plus"
size="mini"
@click="dataHandleAdd"
v-hasPermi="['system:dict:add']"
>新增</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="success"
plain
icon="el-icon-edit"
size="mini"
:disabled="dataSingle"
@click="dataHandleUpdate"
v-hasPermi="['system:dict:edit']"
>修改</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="danger"
plain
icon="el-icon-delete"
size="mini"
:disabled="dataMultiple"
@click="dataHandleDelete"
v-hasPermi="['system:dict:remove']"
>删除</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="warning"
plain
icon="el-icon-download"
size="mini"
@click="dataHandleExport"
v-hasPermi="['system:dict:export']"
>导出</el-button>
</el-col>
<right-toolbar :showSearch.sync="dataShowSearch" @queryTable="dataGetList"></right-toolbar>
</el-row>
<el-empty v-if="!dataQueryParams.dictType" description="请选择一个字典类型" />
<template v-else>
<el-table v-loading="dataLoading" max-height="calc(100vh - 300px)" :data="dataList" @selection-change="dataHandleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="字典编码" align="center" prop="dictCode" />
<el-table-column label="字典标签" align="center" prop="dictLabel">
<template slot-scope="scope">
<span v-if="scope.row.listClass == '' || scope.row.listClass == 'default'">{{scope.row.dictLabel}}</span>
<el-tag v-else :type="scope.row.listClass == 'primary' ? '' : scope.row.listClass">{{scope.row.dictLabel}}</el-tag>
</template>
</el-table-column>
<el-table-column label="字典键值" align="center" prop="dictValue" />
<el-table-column label="字典排序" align="center" prop="dictSort" />
<el-table-column label="状态" align="center" prop="status">
<template slot-scope="scope">
<dict-tag :options="dict.type.sys_normal_disable" :value="scope.row.status"/>
</template>
</el-table-column>
<el-table-column label="备注" align="center" prop="remark" :show-overflow-tooltip="true" />
<el-table-column label="创建时间" align="center" prop="createTime" width="180">
<template slot-scope="scope">
<span>{{ parseTime(scope.row.createTime) }}</span>
</template>
</el-table-column>
<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="dataHandleUpdate(scope.row)"
v-hasPermi="['system:dict:edit']"
>修改</el-button>
<el-button
size="mini"
type="text"
icon="el-icon-delete"
@click="dataHandleDelete(scope.row)"
v-hasPermi="['system:dict:remove']"
>删除</el-button>
</template>
</el-table-column>
</el-table>
<pagination
v-show="dataTotal>0"
:total="dataTotal"
:page.sync="dataQueryParams.pageNum"
:limit.sync="dataQueryParams.pageSize"
@pagination="dataGetList"
/>
</template>
<!-- 添加或修改字典数据对话框 -->
<el-dialog :title="dataTitle" :visible.sync="dataOpen" width="500px" append-to-body>
<el-form ref="dataForm" :model="dataForm" :rules="dataRules" label-width="80px">
<el-form-item label="字典类型">
<el-input v-model="dataForm.dictType" :disabled="true" />
</el-form-item>
<el-form-item label="数据标签" prop="dictLabel">
<el-input v-model="dataForm.dictLabel" placeholder="请输入数据标签" />
</el-form-item>
<el-form-item label="数据键值" prop="dictValue">
<el-input v-model="dataForm.dictValue" placeholder="请输入数据键值" />
</el-form-item>
<el-form-item label="样式属性" prop="cssClass">
<el-input v-model="dataForm.cssClass" placeholder="请输入样式属性" />
</el-form-item>
<el-form-item label="显示排序" prop="dictSort">
<el-input-number :controls=false controls-position="right" v-model="dataForm.dictSort" :min="0" />
</el-form-item>
<el-form-item label="回显样式" prop="listClass">
<el-select v-model="dataForm.listClass">
<el-option
v-for="item in listClassOptions"
:key="item.value"
:label="item.label + '(' + item.value + ')'"
:value="item.value"
></el-option>
</el-select>
</el-form-item>
<el-form-item label="状态" prop="status">
<el-radio-group v-model="dataForm.status">
<el-radio
v-for="dict in dict.type.sys_normal_disable"
:key="dict.value"
:label="dict.value"
>{{dict.label}}</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input v-model="dataForm.remark" type="textarea" placeholder="请输入内容"></el-input>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="dataSubmitForm"> </el-button>
<el-button @click="dataCancel"> </el-button>
</div>
</el-dialog>
</div>
</el-col>
</el-row>
</div>
</template>
<script>
import { listType, getType, delType, addType, updateType, refreshCache, optionselect as getDictOptionselect } from "@/api/system/dict/type";
import { listData, getData, delData, addData, updateData } from "@/api/system/dict/data";
export default {
name: "Dict",
dicts: ['sys_normal_disable'],
data() {
return {
// 字典类型管理相关
typeLoading: false,
typeIds: [],
typeSingle: true,
typeMultiple: true,
typeShowSearch: true,
typeTotal: 0,
selectedDictType: '',
typeList: [
{ dictId: '1996470503006347266', dictName: "钢卷异常位置", dictType: "coil_abnormal_position" },
{ dictId: '1996471384858763265', dictName: "钢卷异常缺陷代码", dictType: "coil_abnormal_code" },
{ dictId: '1996471559400529922', dictName: "钢卷异常程度", dictType: "coil_abnormal_degree" },
{ dictId: '2000385565177729025', dictName: "钢卷厂家", dictType: "coil_manufacturer" },
{ dictId: '2000385689094246401', dictName: "钢卷材质", dictType: "coil_material" },
{ dictId: '2000385875539447809', dictName: "钢卷名称", dictType: "coil_itemname" },
{ dictId: '2000445660494381058', dictName: "客户行业", dictType: "customer_industry" },
{ dictId: '2000446466396340226', dictName: "客户等级", dictType: "customer_level" },
{ dictId: '2010954150756622337', dictName: "钢卷质量状态", dictType: "coil_quality_status" },
{ dictId: '2034910283200458753', dictName: "机组产线", dictType: "sys_lines" },
],
typeTitle: "",
typeOpen: false,
typeDateRange: [],
typeQueryParams: {
pageNum: 1,
pageSize: 20,
dictName: undefined,
dictType: undefined,
status: undefined
},
typeForm: {},
typeRules: {
dictName: [
{ required: true, message: "字典名称不能为空", trigger: "blur" }
],
dictType: [
{ required: true, message: "字典类型不能为空", trigger: "blur" }
]
},
// 字典数据管理相关
dataLoading: true,
dataIds: [],
dataSingle: true,
dataMultiple: true,
dataShowSearch: true,
dataTotal: 0,
dataList: [],
dataTitle: "",
dataOpen: false,
listClassOptions: [
{
value: "default",
label: "默认"
},
{
value: "primary",
label: "主要"
},
{
value: "success",
label: "成功"
},
{
value: "info",
label: "信息"
},
{
value: "warning",
label: "警告"
},
{
value: "danger",
label: "危险"
}
],
typeOptions: [],
dataQueryParams: {
pageNum: 1,
pageSize: 20,
dictName: undefined,
dictType: undefined,
status: undefined
},
dataForm: {},
dataRules: {
dictLabel: [
{ required: true, message: "数据标签不能为空", trigger: "blur" }
],
dictValue: [
{ required: true, message: "数据键值不能为空", trigger: "blur" }
],
dictSort: [
{ required: true, message: "数据顺序不能为空", trigger: "blur" }
]
}
};
},
created() {
// 初始化时设置类型选项
this.typeOptions = this.typeList.map(item => ({
dictId: item.dictId,
dictName: item.dictName,
dictType: item.dictType
}));
},
methods: {
// 字典数据管理方法
/** 查询字典数据列表 */
dataGetList() {
this.dataLoading = true;
listData(this.dataQueryParams).then(response => {
this.dataList = response.rows;
this.dataTotal = response.total;
this.dataLoading = false;
});
},
// 取消按钮
dataCancel() {
this.dataOpen = false;
this.dataReset();
},
// 表单重置
dataReset() {
this.dataForm = {
dictCode: undefined,
dictLabel: undefined,
dictValue: undefined,
cssClass: undefined,
listClass: 'default',
dictSort: 0,
status: "0",
remark: undefined
};
this.resetForm("dataForm");
},
/** 搜索按钮操作 */
dataHandleQuery() {
this.dataQueryParams.pageNum = 1;
this.dataGetList();
},
/** 重置按钮操作 */
dataResetQuery() {
this.resetForm("dataQueryForm");
this.dataHandleQuery();
},
/** 新增按钮操作 */
dataHandleAdd() {
this.dataReset();
this.dataOpen = true;
this.dataTitle = "添加字典数据";
this.dataForm.dictType = this.dataQueryParams.dictType;
},
// 多选框选中数据
dataHandleSelectionChange(selection) {
this.dataIds = selection.map(item => item.dictCode)
this.dataSingle = selection.length!=1
this.dataMultiple = !selection.length
},
/** 修改按钮操作 */
dataHandleUpdate(row) {
this.dataReset();
const dictCode = row.dictCode || this.dataIds
getData(dictCode).then(response => {
this.dataForm = response.data;
this.dataOpen = true;
this.dataTitle = "修改字典数据";
});
},
/** 提交按钮 */
dataSubmitForm: function() {
this.$refs["dataForm"].validate(valid => {
if (valid) {
if (this.dataForm.dictCode != undefined) {
updateData(this.dataForm).then(response => {
this.$store.dispatch('dict/removeDict', this.dataQueryParams.dictType);
this.$modal.msgSuccess("修改成功");
this.dataOpen = false;
this.dataGetList();
});
} else {
addData(this.dataForm).then(response => {
this.$store.dispatch('dict/removeDict', this.dataQueryParams.dictType);
this.$modal.msgSuccess("新增成功");
this.dataOpen = false;
this.dataGetList();
});
}
}
});
},
/** 删除按钮操作 */
dataHandleDelete(row) {
const dictCodes = row.dictCode || this.dataIds;
this.$modal.confirm('是否确认删除字典编码为"' + dictCodes + '"的数据项?').then(function() {
return delData(dictCodes);
}).then(() => {
this.dataGetList();
this.$modal.msgSuccess("删除成功");
this.$store.dispatch('dict/removeDict', this.dataQueryParams.dictType);
}).catch(() => {});
},
/** 导出按钮操作 */
dataHandleExport() {
this.download('system/dict/data/export', {
...this.dataQueryParams
}, `data_${new Date().getTime()}.xlsx`)
},
/** 选择字典类型 */
selectDictType(dictType, dictId) {
this.selectedDictType = dictType;
this.dataQueryParams.dictType = dictType;
this.dataGetList();
}
}
};
</script>
<style scoped>
.dict-type-container,
.dict-data-container {
padding: 20px;
}
h3 {
margin-bottom: 20px;
font-size: 18px;
font-weight: bold;
color: #303133;
}
.dict-type-list {
display: flex;
flex-direction: column;
gap: 10px;
}
.dict-type-item {
padding: 15px;
border: 1px solid #e4e7ed;
border-radius: 4px;
cursor: pointer;
transition: all 0.3s ease;
}
.dict-type-item:hover {
border-color: #409eff;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
}
.dict-type-item.active {
border-color: #409eff;
background-color: #ecf5ff;
box-shadow: 0 2px 12px 0 rgba(64, 158, 255, 0.1);
}
.dict-type-item.active .dict-type-name {
color: #409eff;
}
.dict-type-name {
font-size: 16px;
font-weight: bold;
margin-bottom: 5px;
color: #303133;
}
.dict-type-info {
display: flex;
justify-content: space-between;
font-size: 14px;
color: #606266;
}
.dict-type-code {
flex: 1;
}
.dict-type-id {
color: #909399;
}
</style>

View File

@@ -102,28 +102,28 @@
<el-form ref="form" :model="form" :rules="rules" label-width="80px">
<el-form-item label="订单编号" prop="orderCode">
<el-input v-model="form.orderCode" placeholder="请输入订单编号" />
</el-form-item>
<el-form-item label="合同号" prop="contractId">
<el-select style="width: 100%;" v-model="form.contractId" placeholder="请选择合同号" filterable clearable @change="handleContractChange">
<el-option v-for="item in contractList" :key="item.contractId" :label="item.contractNo"
:value="item.contractId" />
</el-select>
</el-form-item>
<el-form-item label="客户" prop="customerId">
<!-- <el-input v-model="form.customerId" placeholder="请输入客户" /> -->
<el-select v-model="form.customerId" placeholder="请选择客户">
<el-option v-for="item in customerList" :key="item.customerId" :label="item.customerCode"
<el-select style="width: 100%;" v-model="form.customerId" placeholder="请选择客户">
<el-option v-for="item in customerList" :key="item.customerId" :label="item.companyName"
:value="item.customerId" />
</el-select>
</el-form-item>
<el-form-item label="订单总金额" prop="orderAmount">
<el-input v-model="form.orderAmount" placeholder="请输入订单总金额" />
</el-form-item>
<el-form-item label="销售员" prop="salesman">
<el-select v-model="form.salesman" placeholder="请选择销售员">
<el-select style="width: 100%;" v-model="form.salesman" placeholder="请选择销售员">
<el-option v-for="item in dict.type.wip_pack_saleman" :key="item.value" :label="item.label"
:value="item.value" />
</el-select>
</el-form-item>
<el-form-item label="合同号" prop="contractId">
<el-select v-model="form.contractId" placeholder="请选择合同号" filterable clearable @change="handleContractChange">
<el-option v-for="item in contractList" :key="item.contractId" :label="item.contractNo"
:value="item.contractId" />
</el-select>
<el-form-item label="订单总金额" prop="orderAmount">
<el-input v-model="form.orderAmount" placeholder="请输入订单总金额" />
</el-form-item>
<el-form-item label="交货日期" prop="deliveryDate">
<el-date-picker clearable v-model="form.deliveryDate" type="datetime" value-format="yyyy-MM-dd HH:mm:ss"
@@ -177,6 +177,10 @@ export default {
contractId: {
type: String,
default: ''
},
customerId: {
type: String,
default: ''
}
},
data() {
@@ -186,7 +190,7 @@ export default {
showQuery: false,
queryParams: {
orderCode: '',
customerId: '',
customerId: this.customerId,
salesman: '',
orderStatus: '',
contractId: this.contractId,
@@ -243,7 +247,10 @@ export default {
handleContractChange(contractId) {
const contract = this.contractList.find(item => item.contractId === contractId)
if (contract) {
console.log(contract)
this.form.contractCode = contract.contractNo
this.form.deliveryDate = contract.deliveryDate
this.form.customerId = contract.customerId
}
},
@@ -284,7 +291,7 @@ export default {
orderId: undefined,
orderCode: undefined,
orderType: ORDER_TYPE['正式订单'],
customerId: undefined,
customerId: this.customerId,
orderAmount: undefined,
salesman: undefined,
deliveryDate: undefined,

View File

@@ -73,12 +73,12 @@ export default {
{ required: true, message: '请选择位置', trigger: 'change' }
],
startPosition: [
{ required: true, message: '请输入开始位置', trigger: 'blur' },
// { type: 'number', message: '请输入数字', trigger: 'blur' }
{ required: true, message: '请输入开始位置', trigger: ['blur', 'change'] },
{ validator: this.validateStartPosition, trigger: ['blur', 'change'] }
],
endPosition: [
{ required: true, message: '请输入结束位置', trigger: 'blur' },
// { type: 'number', message: '请输入数字', trigger: 'blur' }
{ required: true, message: '请输入结束位置', trigger: ['blur', 'change'] },
{ validator: this.validateEndPosition, trigger: ['blur', 'change'] }
],
defectCode: [
{ required: true, message: '请选择缺陷代码', trigger: 'change' }
@@ -102,14 +102,19 @@ export default {
methods: {
/** 表单验证 */
validate(callback) {
if (this.formData.startPosition > this.formData.endPosition) {
this.$message.error('开始位置必须小于结束位置');
return false;
}
return this.$refs.form.validate(callback);
},
/** 重置表单 */
resetFields() {
this.$refs.form.resetFields();
const currentCoilId = this.formData.coilId;
this.formData = {
abnormalId: undefined,
coilId: undefined,
coilId: currentCoilId,
position: undefined,
startPosition: undefined,
endPosition: undefined,
@@ -124,6 +129,22 @@ export default {
if (this.formData.startPosition && this.formData.endPosition) {
this.formData.length = this.formData.endPosition - this.formData.startPosition;
}
},
/** 校验开始位置 */
validateStartPosition(rule, value, callback) {
if (value <= 0) {
callback(new Error('开始位置必须为正数'));
} else {
callback();
}
},
/** 校验结束位置 */
validateEndPosition(rule, value, callback) {
if (value <= 0) {
callback(new Error('结束位置必须为正数'));
} else {
callback();
}
}
}
};

View File

@@ -48,8 +48,8 @@
<MaterialSelect :hideType="hideType" :itemId.sync="queryParams.itemIds" :itemType.sync="queryParams.itemType"
:multiple="true" />
<el-form-item label="发货单时间">
<el-date-picker v-if="showWaybill" v-model="queryParams.shipmentTime" type="daterange" value-format="yyyy-MM-dd HH:mm:ss"
<el-form-item v-if="showWaybill" label="发货单时间">
<el-date-picker v-model="queryParams.shipmentTime" type="daterange" value-format="yyyy-MM-dd HH:mm:ss"
range-separator="" start-placeholder="开始日期" end-placeholder="结束日期" />
</el-form-item>

View File

@@ -28,6 +28,8 @@
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<!-- 统计指标卡 -->
<el-row :gutter="10" class="mb10">
<el-col :span="8">
@@ -71,14 +73,27 @@
</el-col>
</el-row>
<!-- 操作人汇总表格 -->
<el-card shadow="hover" :body-style="{ padding: '10px' }" class="mb10">
<div slot="header" class="clearfix" style="padding-bottom: 5px;">
<span>操作记录趋势</span>
</div>
<div id="trendChart" style="height: 250px;"></div>
</el-card>
<!-- 图表区域 -->
<el-row :gutter="10" class="mb10">
<el-col :span="12">
<el-card shadow="hover" :body-style="{ padding: '10px' }">
<div slot="header" class="clearfix" style="padding-bottom: 5px;">
<span>操作记录趋势</span>
</div>
<div id="trendChart" style="height: 250px;"></div>
<el-table :data="userSummaryData" style="width: 100%" height="280px">
<el-table-column prop="createBy" label="操作人" width="180"></el-table-column>
<el-table-column prop="coilCount" label="操作卷数" width="120"></el-table-column>
<el-table-column prop="totalWeight" label="总重量(kg)">
<template slot-scope="scope">
{{ scope.row.totalWeight.toFixed(2) }}
</template>
</el-table-column>
</el-table>
</el-card>
</el-col>
<el-col :span="6">
@@ -212,6 +227,8 @@ export default {
totalWeight: 0,
warehouseCount: 0
},
// 操作人汇总数据
userSummaryData: [],
// 图表实例
trendChart: null,
pieChart: null,
@@ -258,7 +275,7 @@ export default {
this.queryParams.createEndTime = this.queryParams.createTimeRange[1];
}
// 移除分页参数,获取全部数据
const params = { ...this.queryParams, pageNum:1, pageSize: 10000 };
const params = { ...this.queryParams, pageNum: 1, pageSize: 10000 };
listCoilWarehouseOperationLog(params).then(response => {
this.allData = response.rows;
@@ -296,6 +313,26 @@ export default {
});
this.stats.warehouseCount = warehouseIds.size;
// 按操作人汇总数据
const userMap = {};
this.allData.forEach(item => {
const user = item.createBy || '未知';
if (!userMap[user]) {
userMap[user] = {
createBy: user,
coilCount: 0,
totalWeight: 0
};
}
userMap[user].coilCount++;
if (item.coil && item.coil.netWeight) {
userMap[user].totalWeight += parseFloat(item.coil.netWeight);
}
});
// 转换为数组并按操作卷数降序排序
this.userSummaryData = Object.values(userMap).sort((a, b) => b.coilCount - a.coilCount);
// 更新图表数据
this.updateCharts();
},