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:
@@ -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;
|
||||
|
||||
@@ -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()
|
||||
},
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
export const defaultColumns = [
|
||||
{
|
||||
label: '卷号',
|
||||
label: '入场卷号',
|
||||
align: 'center',
|
||||
prop: 'enterCoilNo',
|
||||
showOverflowTooltip: true
|
||||
},
|
||||
{
|
||||
label: '当前卷号',
|
||||
align: 'center',
|
||||
prop: 'currentCoilNo',
|
||||
showOverflowTooltip: true
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
185
klp-ui/src/components/FilePreview/index.vue
Normal file
185
klp-ui/src/components/FilePreview/index.vue
Normal 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>
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user