feat(订单明细): 添加删除订单明细功能

feat(辅料管理): 新增辅料变动记录功能

feat(仪表拓扑): 在仪表标签中显示上次抄表记录

fix(设备管理): 修复图片预览空值问题

refactor(员工管理): 部门树显示负责人信息

style(备件变动): 优化备件变动页面布局

docs(订单记录): 更新操作类型映射表

test(销售异议): 新增销售异议管理模块

chore: 更新.gitignore文件
This commit is contained in:
砂糖
2026-01-04 14:42:51 +08:00
parent aeecf4bcf7
commit 42dbbf79ae
16 changed files with 1724 additions and 316 deletions

View File

@@ -49,6 +49,7 @@
<div style="width: 100%; height: 1px; background: #ebeef5; margin: 8px 0;"></div>
<div class="legend-item">
<span style="font-size: 11px; color: #909399;">不同能源类型使用不同图案三角星形等</span>
<span style="font-size: 11px; color: #909399;">括号内的数字为上次抄表时的记录值没有括号则表示未抄过表</span>
</div>
</div>
</div>
@@ -445,10 +446,15 @@ export default {
const deviceShape = this.getDeviceShape(meter.energyTypeId);
// 根据设备状态选择不同的颜色
const deviceColor = this.getDeviceColor(meter.status);
let label = meter.meterCode;
if (meter.lastReading) {
label += `(${meter.lastReading || '-'})`
}
nodesData.push({
id: deviceId,
label: meter.meterCode,
label,
title: `${meter.meterCode} (${energyType?.name || '-'})`,
shape: deviceShape,
size: 20,
@@ -457,7 +463,7 @@ export default {
data: {
type: 'device',
...meter,
energyType: energyType?.name || '-'
energyType: energyType?.name || '-',
}
});

View File

@@ -1,163 +1,201 @@
<template>
<div class="app-container">
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="68px">
<el-form-item label="文档编号" prop="docNo">
<el-input
v-model="queryParams.docNo"
placeholder="请输入文档编号"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="文档名称" prop="docName">
<el-input
v-model="queryParams.docName"
placeholder="请输入文档名称"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="文档分类" prop="docCategory">
<el-select v-model="queryParams.docCategory" placeholder="请选择文档分类" clearable>
<el-option
v-for="dict in dict.type.doc_category"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="文档类型" prop="docType">
<el-select v-model="queryParams.docType" placeholder="请选择文档类型" clearable>
<el-option
v-for="dict in dict.type.doc_type"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="发布状态" prop="publishStatus">
<el-select v-model="queryParams.publishStatus" placeholder="请选择发布状态" clearable>
<el-option
v-for="dict in dict.type.publish_status"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="发布日期" prop="publishTime">
<el-date-picker clearable
v-model="queryParams.publishTime"
type="date"
value-format="yyyy-MM-dd"
placeholder="请选择发布日期">
</el-date-picker>
</el-form-item>
<el-form-item>
<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>
<!-- 左右分栏布局左侧自定义列表右侧预览 -->
<el-row :gutter="20" class="layout-row">
<!-- 左侧自定义列表区域占10列 -->
<el-col :span="6" class="left-list-container">
<!-- 本地搜索表单改造后 -->
<el-form
@submit.native.prevent
:model="searchForm"
ref="searchForm"
size="small"
:inline="true"
v-show="showSearch"
label-width="68px"
class="query-form"
>
<!-- 关键字搜索文档编号+文档名称联合搜索 -->
<el-form-item label="关键字搜索" prop="keyword">
<el-input
v-model="searchForm.keyword"
placeholder="请输入文档编号/文档名称"
clearable
@change="handleLocalSearch"
/>
</el-form-item>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button
type="primary"
plain
icon="el-icon-plus"
size="mini"
@click="handleAdd"
>新增</el-button>
<!-- 文档分类el-radio-button 单选 -->
<el-form-item label="文档分类" prop="docCategory">
<el-radio-group v-model="searchForm.docCategory" @change="handleLocalSearch">
<el-radio-button label="" value="">全部</el-radio-button>
<el-radio-button
v-for="dict in dict.type.doc_category"
:key="dict.value"
:label="dict.value"
:value="dict.value"
>{{ dict.label }}</el-radio-button>
</el-radio-group>
</el-form-item>
<!-- 文档类型el-radio-button 单选 -->
<el-form-item label="文档类型" prop="docType">
<el-radio-group v-model="searchForm.docType" @change="handleLocalSearch">
<el-radio-button label="" value="">全部</el-radio-button>
<el-radio-button
v-for="dict in dict.type.doc_type"
:key="dict.value"
:label="dict.value"
:value="dict.value"
>{{ dict.label }}</el-radio-button>
</el-radio-group>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleLocalSearch">搜索</el-button>
<el-button icon="el-icon-refresh" size="mini" @click="resetLocalSearch">重置</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="handleAdd"
>新增</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="success"
plain
icon="el-icon-edit"
size="mini"
:disabled="single"
@click="handleUpdate"
>修改</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="danger"
plain
icon="el-icon-delete"
size="mini"
:disabled="multiple"
@click="handleDelete"
>删除</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="warning"
plain
icon="el-icon-download"
size="mini"
@click="handleExport"
>导出</el-button>
</el-col>
<right-toolbar :showSearch.sync="showSearch" @queryTable="handleLocalSearch"></right-toolbar>
</el-row>
<!-- 自定义文档列表替代表格无分页 -->
<div v-loading="loading" class="custom-doc-list">
<!-- 单个文档项 -->
<div
v-for="item in safetyEnvDocList"
:key="item.docId"
class="doc-item"
:class="{ 'active': currentSelectDoc && currentSelectDoc.docId === item.docId }"
@click="handleDocItemClick(item)"
>
<div class="doc-status">
<dict-tag :options="dict.type.publish_status" :value="item.publishStatus"/>
</div>
<div class="doc-base-info">
<p class="doc-no">{{ item.docName || '-' }}</p>
<p class="doc-name">{{ item.docNo || '-' }}</p>
</div>
<div class="doc-operate">
<el-button
size="mini"
type="text"
icon="el-icon-edit"
@click.stop="handleUpdate(item)"
>修改</el-button>
<el-button
size="mini"
type="text"
icon="el-icon-delete"
@click.stop="handleDelete(item)"
>删除</el-button>
</div>
</div>
<!-- 无数据提示 -->
<div v-if="!loading && safetyEnvDocList.length === 0" class="empty-list">
<el-empty description="暂无文档数据" :image-size="100"></el-empty>
</div>
</div>
</el-col>
<el-col :span="1.5">
<el-button
type="success"
plain
icon="el-icon-edit"
size="mini"
:disabled="single"
@click="handleUpdate"
>修改</el-button>
<!-- 右侧预览区域占14列增加iframe附件预览 -->
<el-col :span="18" class="right-preview-container">
<el-card title="文档详情预览" class="preview-card" :body-style="{ padding: '20px', height: '100%', 'box-sizing': 'border-box' }">
<!-- 无选中数据时的提示 -->
<div v-if="!previewData" class="empty-preview">
<el-empty description="请选择左侧文档进行预览" :image-size="120"></el-empty>
</div>
<!-- 有选中数据时的预览内容 -->
<div v-else class="preview-content">
<!-- 文档基本信息 -->
<el-descriptions :column="3" border class="doc-descriptions">
<el-descriptions-item label="文档编号">
{{ previewData.docNo || '-' }}
</el-descriptions-item>
<el-descriptions-item label="文档名称">
{{ previewData.docName || '-' }}
</el-descriptions-item>
<el-descriptions-item label="文档分类">
<dict-tag :options="dict.type.doc_category" :value="previewData.docCategory"/>
</el-descriptions-item>
<el-descriptions-item label="文档类型">
<dict-tag :options="dict.type.doc_type" :value="previewData.docType"/>
</el-descriptions-item>
<el-descriptions-item label="发布状态">
<dict-tag :options="dict.type.publish_status" :value="previewData.publishStatus"/>
</el-descriptions-item>
<el-descriptions-item label="发布日期">
{{ parseTime(previewData.publishTime, '{y}-{m}-{d} {h}:{i}:{s}') || '-' }}
</el-descriptions-item>
<el-descriptions-item label="备注">
{{ previewData.remark || '-' }}
</el-descriptions-item>
</el-descriptions>
<!-- iframe PDF附件预览 -->
<div class="pdf-preview-area">
<!-- 有附件时显示iframe -->
<iframe
v-if="previewData.accessory"
:src="pdfUrl"
class="pdf-iframe"
frameborder="0"
title="PDF附件预览"
></iframe>
<!-- 无附件时显示提示 -->
<div v-else class="no-pdf-tip">
<el-empty description="暂无PDF附件可供预览" :image-size="80"></el-empty>
</div>
</div>
</div>
</el-card>
</el-col>
<el-col :span="1.5">
<el-button
type="danger"
plain
icon="el-icon-delete"
size="mini"
:disabled="multiple"
@click="handleDelete"
>删除</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="warning"
plain
icon="el-icon-download"
size="mini"
@click="handleExport"
>导出</el-button>
</el-col>
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
<el-table v-loading="loading" :data="safetyEnvDocList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="文档编号" align="center" prop="docNo" />
<el-table-column label="文档名称" align="center" prop="docName" />
<el-table-column label="文档分类" align="center" prop="docCategory">
<template slot-scope="scope">
<dict-tag :options="dict.type.doc_category" :value="scope.row.docCategory"/>
</template>
</el-table-column>
<el-table-column label="文档类型" align="center" prop="docType">
<template slot-scope="scope">
<dict-tag :options="dict.type.doc_type" :value="scope.row.docType"/>
</template>
</el-table-column>
<el-form-item label="附件" prop="accessory">
<file-upload v-model="form.accessory" :limit="1" :fileType="fileType" type="textarea" placeholder="请输入内容" />
</el-form-item>
<el-table-column label="发布状态" align="center" prop="publishStatus">
<template slot-scope="scope">
<dict-tag :options="dict.type.publish_status" :value="scope.row.publishStatus"/>
</template>
</el-table-column>
<el-table-column label="发布日期" align="center" prop="publishTime" width="180">
<template slot-scope="scope">
<span>{{ parseTime(scope.row.publishTime, '{y}-{m}-{d}') }}</span>
</template>
</el-table-column>
<el-table-column label="备注" align="center" prop="remark" />
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template slot-scope="scope">
<el-button
size="mini"
type="text"
icon="el-icon-edit"
@click="handleUpdate(scope.row)"
>修改</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"
/>
<!-- 添加或修改安全环保制度文档对话框 -->
<!-- 添加或修改安全环保制度文档对话框保留原有功能附件仅支持PDF -->
<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="docNo">
@@ -186,8 +224,8 @@
></el-option>
</el-select>
</el-form-item>
<el-form-item label="附件" prop="accessory">
<file-upload v-model="form.accessory" :limit="1" :fileType="fileType" type="textarea" placeholder="请输入内容" />
<el-form-item label="PDF附件" prop="accessory">
<file-upload v-model="form.accessory" :limit="1" :fileType="fileType" type="textarea" placeholder="请上传PDF格式附件" />
</el-form-item>
<el-form-item label="发布状态" prop="publishStatus">
<el-radio-group v-model="form.publishStatus">
@@ -199,11 +237,13 @@
</el-radio-group>
</el-form-item>
<el-form-item label="发布日期" prop="publishTime">
<el-date-picker clearable
<el-date-picker
clearable
v-model="form.publishTime"
type="datetime"
value-format="yyyy-MM-dd HH:mm:ss"
placeholder="请选择发布日期">
placeholder="请选择发布日期"
>
</el-date-picker>
</el-form-item>
<el-form-item label="备注" prop="remark">
@@ -220,6 +260,7 @@
<script>
import { listSafetyEnvDoc, getSafetyEnvDoc, delSafetyEnvDoc, addSafetyEnvDoc, updateSafetyEnvDoc } from "@/api/ems/safetyEnvDoc";
import { listByIds } from '@/api/system/oss'
export default {
name: "SafetyEnvDoc",
@@ -238,18 +279,44 @@ export default {
multiple: true,
// 显示搜索条件
showSearch: true,
// 总条数
total: 0,
// 安全环保制度文档表格数据
// 原始全部文档数据(用于本地搜索过滤)
originSafetyEnvDocList: [],
// 过滤后展示的文档数据
safetyEnvDocList: [],
// 弹出层标题
title: "",
// 是否显示弹出层
open: false,
// 查询参数
queryParams: {
// 本地搜索表单参数
searchForm: {
keyword: "", // 关键字(文档编号+文档名称)
docCategory: "", // 文档分类
docType: "" // 文档类型
},
// 表单参数
form: {},
// 表单校验
rules: {},
// 右侧预览数据
previewData: null,
// 当前选中的文档(左侧列表高亮用)
currentSelectDoc: null,
// 文件类型限制仅支持PDF
fileType: ['pdf'],
pdfUrl: '',
};
},
created() {
this.getList();
},
methods: {
/** 获取所有文档数据pageSize=1000无分页 */
getList() {
this.loading = true;
// 查询参数设置pageSize=1000获取全部数据
const queryParams = {
pageNum: 1,
pageSize: 10,
pageSize: 1000, // 无分页,获取所有数据
docNo: undefined,
docName: undefined,
docCategory: undefined,
@@ -257,24 +324,12 @@ export default {
accessory: undefined,
publishStatus: undefined,
publishTime: undefined,
},
// 表单参数
form: {},
// 表单校验
rules: {
}
};
},
created() {
this.getList();
},
methods: {
/** 查询安全环保制度文档列表 */
getList() {
this.loading = true;
listSafetyEnvDoc(this.queryParams).then(response => {
this.safetyEnvDocList = response.rows;
this.total = response.total;
};
listSafetyEnvDoc(queryParams).then(response => {
// 存储原始全部数据
this.originSafetyEnvDocList = response.rows || [];
// 初始化展示全部数据
this.safetyEnvDocList = [...this.originSafetyEnvDocList];
this.loading = false;
});
},
@@ -304,21 +359,77 @@ export default {
};
this.resetForm("form");
},
/** 搜索按钮操作 */
handleQuery() {
this.queryParams.pageNum = 1;
this.getList();
/** 本地搜索(关键字+文档分类+文档类型过滤) */
handleLocalSearch() {
const { keyword, docCategory, docType } = this.searchForm;
// 基于原始数据进行过滤
let filterList = [...this.originSafetyEnvDocList];
// 1. 关键字过滤(文档编号+文档名称,模糊匹配,忽略大小写)
if (keyword.trim()) {
const key = keyword.trim().toLowerCase();
filterList = filterList.filter(item => {
const docNo = (item.docNo || "").toLowerCase();
const docName = (item.docName || "").toLowerCase();
return docNo.includes(key) || docName.includes(key);
});
}
// 2. 文档分类过滤(精确匹配,非全部时过滤)
if (docCategory) {
filterList = filterList.filter(item => item.docCategory === docCategory);
}
// 3. 文档类型过滤(精确匹配,非全部时过滤)
if (docType) {
filterList = filterList.filter(item => item.docType === docType);
}
// 更新展示列表
this.safetyEnvDocList = filterList;
// 过滤后清空选中和预览数据
if (filterList.length === 0) {
this.currentSelectDoc = null;
this.previewData = null;
this.ids = [];
this.single = true;
this.multiple = true;
}
},
/** 重置按钮操作 */
resetQuery() {
this.resetForm("queryForm");
this.handleQuery();
/** 重置本地搜索表单 */
resetLocalSearch() {
this.resetForm("searchForm");
// 重置后重新展示全部数据
this.safetyEnvDocList = [...this.originSafetyEnvDocList];
// 清空选中和预览
this.currentSelectDoc = null;
this.previewData = null;
this.ids = [];
this.single = true;
this.multiple = true;
},
// 多选框选中数据
handleSelectionChange(selection) {
this.ids = selection.map(item => item.docId)
this.single = selection.length!==1
this.multiple = !selection.length
// 自定义列表项点击事件
handleDocItemClick(item) {
// 更新当前选中文档(高亮)
this.currentSelectDoc = item;
// 更新预览数据
this.previewData = item;
// 更新选中ids用于修改/删除)
this.ids = [item.docId];
// 生成PDF预览URL
if (item.accessory) {
listByIds(item.accessory).then(response => {
if (response.data && response.data.length > 0) {
// 检查是否是PDF文件
if (response.data[0].fileSuffix === '.pdf') {
this.pdfUrl = response.data[0].url;
console.log(this.pdfUrl)
}
}
});
}
this.single = false; // 单个选中,取消修改按钮禁用
this.multiple = true; // 非多个选中,删除按钮禁用(单个可删除,此处保持原有逻辑)
},
/** 新增按钮操作 */
handleAdd() {
@@ -330,7 +441,8 @@ export default {
handleUpdate(row) {
this.loading = true;
this.reset();
const docId = row.docId || this.ids
const docId = row?.docId || this.ids[0];
if (!docId) return;
getSafetyEnvDoc(docId).then(response => {
this.loading = false;
this.form = response.data;
@@ -343,20 +455,22 @@ export default {
this.$refs["form"].validate(valid => {
if (valid) {
this.buttonLoading = true;
const handleSuccess = () => {
this.$modal.msgSuccess(this.form.docId ? "修改成功" : "新增成功");
this.open = false;
// 重新获取全部数据并刷新列表
this.getList();
// 清空选中和预览
this.currentSelectDoc = null;
this.previewData = null;
};
if (this.form.docId != null) {
updateSafetyEnvDoc(this.form).then(response => {
this.$modal.msgSuccess("修改成功");
this.open = false;
this.getList();
}).finally(() => {
updateSafetyEnvDoc(this.form).then(handleSuccess).finally(() => {
this.buttonLoading = false;
});
} else {
addSafetyEnvDoc(this.form).then(response => {
this.$modal.msgSuccess("新增成功");
this.open = false;
this.getList();
}).finally(() => {
addSafetyEnvDoc(this.form).then(handleSuccess).finally(() => {
this.buttonLoading = false;
});
}
@@ -365,14 +479,21 @@ export default {
},
/** 删除按钮操作 */
handleDelete(row) {
const docIds = row.docId || this.ids;
const docIds = row?.docId || this.ids;
if (!docIds || docIds.length === 0) return;
this.$modal.confirm('是否确认删除安全环保制度文档编号为"' + docIds + '"的数据项?').then(() => {
this.loading = true;
return delSafetyEnvDoc(docIds);
}).then(() => {
this.loading = false;
this.getList();
this.$modal.msgSuccess("删除成功");
// 重新获取全部数据并刷新列表
this.getList();
// 清空选中和预览
this.currentSelectDoc = null;
this.previewData = null;
this.ids = [];
this.single = true;
this.multiple = true;
}).catch(() => {
}).finally(() => {
this.loading = false;
@@ -381,9 +502,174 @@ export default {
/** 导出按钮操作 */
handleExport() {
this.download('ems/safetyEnvDoc/export', {
...this.queryParams
pageNum: 1,
pageSize: 1000,
...this.searchForm
}, `safetyEnvDoc_${new Date().getTime()}.xlsx`)
}
}
};
</script>
<style scoped>
/* 整体布局样式 */
.layout-row {
height: calc(100vh - 124px); /* 适配页面高度 */
}
/* 左侧列表容器 */
.left-list-container {
height: 100%;
overflow: auto;
box-sizing: border-box;
border-radius: 4px;
}
/* 搜索表单样式 */
.query-form {
margin-bottom: 10px;
padding: 10px;
border-radius: 4px;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.04);
}
/* 自定义文档列表样式 */
.custom-doc-list {
border-radius: 4px;
padding: 10px;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.04);
}
/* 单个文档项样式 */
.doc-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 12px 10px;
background-color: #fff;
border: 1px solid #f0f0f0;
cursor: pointer;
border-radius: 4px;
margin-bottom: 5px;
transition: background-color 0.2s ease;
}
.doc-item:hover {
background-color: #f5fafe;
}
/* 选中项高亮样式 */
.doc-item.active {
background-color: #e6f7ff;
border: 1px solid #91d5ff;
}
.doc-base-info {
flex: 1;
}
.doc-no {
font-size: 13px;
color: #606266;
margin: 0 0 4px 0;
}
.doc-name {
font-size: 14px;
color: #303133;
margin: 0;
font-weight: 500;
}
.doc-status {
margin: 0 15px;
}
.doc-operate {
display: flex;
}
/* 无列表数据提示 */
.empty-list {
display: flex;
justify-content: center;
align-items: center;
padding: 20px 0;
}
/* 右侧预览容器 */
.right-preview-container {
height: 100%;
overflow: auto;
padding: 10px;
box-sizing: border-box;
}
/* 预览卡片样式 */
.preview-card {
height: 100%;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.04);
}
/* 无数据预览样式 */
.empty-preview {
display: flex;
justify-content: center;
align-items: center;
height: 80%;
}
/* 预览内容样式 */
.preview-content {
height: 100%; /* 关键继承父级el-card body的100%高度 */
overflow: auto;
display: flex;
flex-direction: column; /* 垂直布局让pdf区域占据剩余空间 */
box-sizing: border-box; /* 防止内边距撑破容器 */
}
/* 文档详情描述样式 */
.doc-descriptions {
margin-bottom: 5px;
}
.pdf-preview-area {
flex: 1; /* 关键:占据父级 preview-content 的剩余空间 */
width: 100%;
overflow: hidden; /* 防止内部元素溢出容器 */
box-sizing: border-box;
}
.preview-title {
font-size: 15px;
color: #303133;
font-weight: 500;
margin: 0 0 10px 0;
padding-bottom: 5px;
border-bottom: 1px solid #e4e7ed;
}
/* PDF iframe样式 */
.pdf-iframe {
width: 100%;
height: 100%;
border-radius: 4px;
border: 1px solid #e4e7ed;
}
/* 无PDF提示样式 */
.no-pdf-tip {
display: flex;
justify-content: center;
align-items: center;
height: 500px;
background-color: #fafafa;
border-radius: 4px;
border: 1px dashed #dcdfe6;
}
/* 间距样式 */
.mb8 {
margin-bottom: 8px;
}
</style>