<template>
|
<div class="app-container">
|
<div class="search_form">
|
<el-form :model="searchForm"
|
:inline="true">
|
<el-form-item label="规程资质名称:">
|
<el-input v-model="searchForm.name"
|
placeholder="请输入"
|
clearable
|
prefix-icon="Search"
|
@change="handleQuery" />
|
</el-form-item>
|
<el-form-item label="规程资质类型:">
|
<el-select v-model="searchForm.type"
|
placeholder="请选择"
|
clearable
|
style="width: 200px"
|
prefix-icon="Search"
|
@change="handleQuery">
|
<el-option v-for="item in type_qualification"
|
:key="item.value"
|
:label="item.label"
|
:value="item.value" />
|
</el-select>
|
</el-form-item>
|
<el-form-item label="规程资质编号:">
|
<el-input v-model="searchForm.code"
|
placeholder="请输入"
|
clearable
|
prefix-icon="Search"
|
@change="handleQuery" />
|
</el-form-item>
|
<el-form-item>
|
<el-button type="primary"
|
@click="handleQuery"> 搜索 </el-button>
|
</el-form-item>
|
</el-form>
|
</div>
|
<div class="table_list">
|
<div class="actions">
|
<div></div>
|
<div>
|
<el-button type="primary"
|
@click="openForm('add')">
|
新增规程资质
|
</el-button>
|
<!-- <el-button type="primary"
|
plain
|
@click="handleImport">导入</el-button>
|
<el-button @click="handleOut">导出</el-button> -->
|
<el-button type="danger"
|
plain
|
@click="handleDelete">删除</el-button>
|
<!-- <el-button type="primary"
|
plain
|
@click="handlePrint">打印</el-button> -->
|
</div>
|
</div>
|
<el-table :data="tableData"
|
border
|
v-loading="tableLoading"
|
@selection-change="handleSelectionChange"
|
:expand-row-keys="expandedRowKeys"
|
:row-key="(row) => row.id"
|
:row-class-name="getRowClass"
|
style="width: 100%"
|
height="calc(100vh - 18.5em)">
|
<el-table-column align="center"
|
type="selection"
|
width="55"
|
fixed="left" />
|
<el-table-column align="center"
|
label="序号"
|
type="index"
|
width="60" />
|
<el-table-column label="规程资质名称"
|
prop="name"
|
width="180"
|
show-overflow-tooltip />
|
<el-table-column label="规程资质编号"
|
prop="code"
|
show-overflow-tooltip />
|
<el-table-column label="规程资质类型"
|
prop="type"
|
show-overflow-tooltip />
|
<el-table-column label="版本号"
|
prop="version"
|
width="180"
|
show-overflow-tooltip />
|
<el-table-column label="备注"
|
prop="remark"
|
show-overflow-tooltip />
|
<el-table-column label="有效期"
|
prop="effectiveTime"
|
width="120"
|
show-overflow-tooltip />
|
<el-table-column fixed="right"
|
label="操作"
|
min-width="100"
|
align="center">
|
<template #default="scope">
|
<el-button link
|
type="primary"
|
size="small"
|
@click="openForm('edit', scope.row)">编辑</el-button>
|
<el-button link
|
type="primary"
|
size="small"
|
@click="downLoadFile(scope.row)">附件</el-button>
|
</template>
|
</el-table-column>
|
</el-table>
|
<pagination v-show="total > 0"
|
:total="total"
|
layout="total, sizes, prev, pager, next, jumper"
|
:page="page.current"
|
:limit="page.size"
|
@pagination="paginationChange" />
|
</div>
|
<FormDialog v-model="dialogFormVisible"
|
:title="operationType === 'add' ? '新增规程资质页面' : '编辑规程资质页面'"
|
:width="'70%'"
|
:operation-type="operationType"
|
@close="closeDia"
|
@confirm="submitForm"
|
@cancel="closeDia">
|
<el-form :model="form"
|
label-width="140px"
|
label-position="top"
|
:rules="rules"
|
ref="formRef">
|
<el-row :gutter="30">
|
<el-col :span="12">
|
<el-form-item label="规程资质名称:"
|
prop="name">
|
<el-input v-model="form.name"
|
placeholder="请输入"
|
clearable />
|
</el-form-item>
|
</el-col>
|
<el-col :span="12">
|
<el-form-item label="规程资质编号:"
|
prop="code">
|
<el-input v-model="form.code"
|
placeholder="请输入"
|
clearable />
|
</el-form-item>
|
</el-col>
|
</el-row>
|
<el-row :gutter="30">
|
<el-col :span="12">
|
<el-form-item label="规程资质类型:"
|
prop="type">
|
<el-select v-model="form.type"
|
placeholder="请选择"
|
clearable>
|
<el-option v-for="item in type_qualification"
|
:key="item.value"
|
:label="item.label"
|
:value="item.value" />
|
</el-select>
|
</el-form-item>
|
</el-col>
|
<el-col :span="12">
|
<el-form-item label="版本号:"
|
prop="version">
|
<el-input v-model="form.version"
|
placeholder="请输入"
|
clearable />
|
</el-form-item>
|
</el-col>
|
</el-row>
|
<el-row :gutter="30">
|
<el-col :span="12">
|
<el-form-item label="有效期:"
|
prop="effectiveTime">
|
<el-date-picker style="width: 100%"
|
v-model="form.effectiveTime"
|
value-format="YYYY-MM-DD"
|
format="YYYY-MM-DD"
|
type="date"
|
placeholder="请选择"
|
clearable
|
:disabled="operationType === 'view'" />
|
</el-form-item>
|
</el-col>
|
<el-col :span="12">
|
<el-form-item label="备注·:"
|
prop="remark">
|
<el-input v-model="form.remark"
|
placeholder="请输入"
|
clearable
|
type="textarea"
|
:rows="1"
|
:disabled="operationType === 'view'" />
|
</el-form-item>
|
</el-col>
|
</el-row>
|
</el-form>
|
</FormDialog>
|
<!-- 附件列表弹窗 -->
|
<FileListDialog ref="fileListRef"
|
v-model="fileListDialogVisible"
|
:show-upload-button="true"
|
:show-delete-button="true"
|
:upload-method="handleUpload"
|
:delete-method="handleFileDelete"
|
title="附件列表" />
|
</div>
|
</template>
|
|
<script setup>
|
import { getToken } from "@/utils/auth";
|
import pagination from "@/components/PIMTable/Pagination.vue";
|
import { onMounted, ref, getCurrentInstance } from "vue";
|
import { ElMessageBox, ElMessage } from "element-plus";
|
import useUserStore from "@/store/modules/user";
|
import { userListNoPage } from "@/api/system/user.js";
|
import FileListDialog from "@/components/Dialog/FileListDialog.vue";
|
import FormDialog from "@/components/Dialog/FormDialog.vue";
|
import { getQuotationList } from "@/api/salesManagement/salesQuotation.js";
|
import {
|
qualificationsListPage,
|
safeCertificationAdd,
|
safeCertificationUpdate,
|
safeCertificationDel,
|
fileListPage,
|
safeCertificationFileAdd,
|
safeCertificationFileDel,
|
} from "@/api/safeProduction/safeQualifications.js";
|
import useFormData from "@/hooks/useFormData.js";
|
import request from "@/utils/request";
|
import dayjs from "dayjs";
|
|
const userStore = useUserStore();
|
const { proxy } = getCurrentInstance();
|
const tableData = ref([]);
|
const selectedRows = ref([]);
|
const userList = ref([]);
|
const tableLoading = ref(false);
|
const page = reactive({
|
current: 1,
|
size: 100,
|
});
|
const total = ref(0);
|
|
// 用户信息表单弹框数据
|
const operationType = ref("");
|
const dialogFormVisible = ref(false);
|
const data = reactive({
|
searchForm: {
|
name: "", // 规程资质名称
|
code: "", // 规程资质编号
|
type: "", // 规程资质类型
|
},
|
form: {
|
salesContractNo: "",
|
salesman: "",
|
customerId: "",
|
entryPerson: "",
|
entryDate: "",
|
maintenanceTime: "",
|
executionDate: "",
|
},
|
rules: {
|
code: [{ required: true, message: "请输入", trigger: "blur" }],
|
name: [{ required: true, message: "请输入", trigger: "blur" }],
|
type: [{ required: true, message: "请选择", trigger: "change" }],
|
effectiveTime: [{ required: true, message: "请选择", trigger: "change" }],
|
version: [{ required: true, message: "请输入", trigger: "blur" }],
|
},
|
});
|
// 规程资质类型选项
|
const { type_qualification } = proxy.useDict("type_qualification");
|
const { form, rules } = toRefs(data);
|
const { form: searchForm } = useFormData(data.searchForm);
|
// 产品表单弹框数据
|
const productFormVisible = ref(false);
|
|
const quotationLoading = ref(false);
|
const quotationList = ref([]);
|
const quotationSearchForm = reactive({
|
quotationNo: "",
|
customer: "",
|
});
|
|
// 导入相关
|
const importUploadRef = ref(null);
|
const importUpload = reactive({
|
title: "导入销售台账",
|
open: false,
|
url: import.meta.env.VITE_APP_BASE_API + "/sales/ledger/import",
|
headers: { Authorization: "Bearer " + getToken() },
|
isUploading: false,
|
beforeUpload: file => {
|
const isExcel = file.name.endsWith(".xlsx") || file.name.endsWith(".xls");
|
const isLt10M = file.size / 1024 / 1024 < 10;
|
if (!isExcel) {
|
proxy.$modal.msgError("上传文件只能是 xlsx/xls 格式!");
|
return false;
|
}
|
if (!isLt10M) {
|
proxy.$modal.msgError("上传文件大小不能超过 10MB!");
|
return false;
|
}
|
return true;
|
},
|
onChange: (file, fileList) => {
|
console.log("文件状态改变", file, fileList);
|
},
|
onProgress: (event, file, fileList) => {
|
console.log("上传中...", event.percent);
|
},
|
onSuccess: (response, file, fileList) => {
|
console.log("上传成功", response, file, fileList);
|
importUpload.isUploading = false;
|
if (response.code === 200) {
|
proxy.$modal.msgSuccess("导入成功");
|
importUpload.open = false;
|
if (importUploadRef.value) {
|
importUploadRef.value.clearFiles();
|
}
|
getList();
|
} else {
|
proxy.$modal.msgError(response.msg || "导入失败");
|
}
|
},
|
onError: (error, file, fileList) => {
|
console.error("上传失败", error, file, fileList);
|
importUpload.isUploading = false;
|
proxy.$modal.msgError("导入失败,请重试");
|
},
|
});
|
|
// 查询列表
|
/** 搜索按钮操作 */
|
const handleQuery = () => {
|
// 只有在点击搜索按钮时才重置页码到第一页
|
// 避免表单字段change事件干扰分页
|
if (arguments.length === 0) {
|
page.current = 1;
|
}
|
expandedRowKeys.value = [];
|
getList();
|
};
|
const paginationChange = obj => {
|
page.current = obj.page;
|
page.size = obj.limit;
|
getList();
|
};
|
const getList = () => {
|
tableLoading.value = true;
|
const { entryDate, ...rest } = searchForm;
|
// 将范围日期字段传递给后端
|
const params = { ...rest, ...page };
|
// 移除录入日期的默认值设置,只保留范围日期字段
|
delete params.entryDate;
|
return qualificationsListPage(params)
|
.then(res => {
|
tableLoading.value = false;
|
tableData.value = res.data.records;
|
total.value = res.data.total;
|
return res;
|
})
|
.catch(() => {
|
tableLoading.value = false;
|
});
|
};
|
|
const findNodeById = (nodes, productId) => {
|
for (let i = 0; i < nodes.length; i++) {
|
if (nodes[i].value === productId) {
|
return nodes[i].label; // 找到节点,返回该节点
|
}
|
if (nodes[i].children && nodes[i].children.length > 0) {
|
const foundNode = findNodeById(nodes[i].children, productId);
|
if (foundNode) {
|
return foundNode; // 在子节点中找到,返回该节点
|
}
|
}
|
}
|
return null; // 没有找到节点,返回null
|
};
|
// 表格选择数据
|
const handleSelectionChange = selection => {
|
selectedRows.value = selection;
|
console.log("selection", selectedRows.value);
|
};
|
const expandedRowKeys = ref([]);
|
// 打开弹框
|
const openForm = async (type, row) => {
|
operationType.value = type;
|
if (type === "add") {
|
form.value = {
|
salesContractNo: "",
|
salesman: "",
|
customerId: "",
|
entryPerson: "",
|
entryDate: "",
|
maintenanceTime: "",
|
executionDate: "",
|
};
|
} else {
|
form.value = row;
|
}
|
dialogFormVisible.value = true;
|
};
|
|
const fetchQuotationList = async () => {
|
quotationLoading.value = true;
|
try {
|
const params = {
|
// 兼容后端分页字段:这里沿用报价页面已有可用的字段命名
|
currentPage: 1,
|
pageSize: 100,
|
...quotationSearchForm,
|
status: "通过",
|
};
|
const res = await getQuotationList(params);
|
quotationList.value = res?.data?.records || [];
|
} finally {
|
quotationLoading.value = false;
|
}
|
};
|
|
// 提交表单
|
const submitForm = () => {
|
proxy.$refs["formRef"].validate(valid => {
|
if (valid) {
|
if (operationType.value === "add") {
|
safeCertificationAdd(form.value).then(res => {
|
proxy.$modal.msgSuccess("提交成功");
|
closeDia();
|
getList();
|
});
|
} else {
|
safeCertificationUpdate(form.value).then(res => {
|
proxy.$modal.msgSuccess("提交成功");
|
closeDia();
|
getList();
|
});
|
}
|
}
|
});
|
};
|
// 关闭弹框
|
const closeDia = () => {
|
proxy.resetForm("formRef");
|
dialogFormVisible.value = false;
|
};
|
// 关闭产品弹框
|
const closeProductDia = () => {
|
proxy.resetForm("productFormRef");
|
productFormVisible.value = false;
|
};
|
// 删除
|
const handleDelete = () => {
|
let ids = [];
|
if (selectedRows.value.length > 0) {
|
ids = selectedRows.value.map(item => item.id);
|
} else {
|
proxy.$modal.msgWarning("请选择数据");
|
return;
|
}
|
ElMessageBox.confirm("选中的内容将被删除,是否确认删除?", "导出", {
|
confirmButtonText: "确认",
|
cancelButtonText: "取消",
|
type: "warning",
|
})
|
.then(() => {
|
safeCertificationDel(ids).then(res => {
|
proxy.$modal.msgSuccess("删除成功");
|
getList();
|
});
|
})
|
.catch(() => {
|
proxy.$modal.msg("已取消");
|
});
|
};
|
|
/**
|
* 判断是否可以发货
|
* 只有在产品状态是充足,发货状态是待发货和审核拒绝的时候才可以发货
|
* @param row 行数据
|
*/
|
const canShip = row => {
|
// 产品状态必须是充足(approveStatus === 1)
|
if (row.approveStatus !== 1) {
|
return false;
|
}
|
|
// 获取发货状态
|
const shippingStatus = row.shippingStatus;
|
|
// 如果已发货(有发货日期或车牌号),不能再次发货
|
if (row.shippingDate || row.shippingCarNumber) {
|
return false;
|
}
|
|
// 发货状态必须是"待发货"或"审核拒绝"
|
const statusStr = shippingStatus ? String(shippingStatus).trim() : "";
|
return statusStr === "待发货" || statusStr === "审核拒绝";
|
};
|
|
/**
|
* 下载文件
|
*
|
* @param row 下载文件的相关信息对象
|
*/
|
const fileListRef = ref(null);
|
const fileListDialogVisible = ref(false);
|
const currentFileRow = ref(null);
|
const downLoadFile = row => {
|
currentFileRow.value = row;
|
fileListPage({ safeCertificationId: row.id }).then(res => {
|
if (fileListRef.value) {
|
fileListRef.value.open(res.data.records);
|
}
|
});
|
};
|
const currentFactoryName = ref("");
|
const getCurrentFactoryName = async () => {
|
let res = await userStore.getInfo();
|
currentFactoryName.value = res.user.currentFactoryName;
|
};
|
|
/**
|
* 获取行类名,用于判断当前日期是否接近有效期15天内
|
* @param row 行数据
|
* @returns 类名
|
*/
|
const getRowClass = ({ row }) => {
|
if (!row.effectiveTime) return "";
|
|
const now = new Date();
|
const effectiveTime = new Date(row.effectiveTime);
|
const diffTime = effectiveTime - now;
|
const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
|
|
if (diffDays >= 0 && diffDays <= 15) {
|
return "warning-row";
|
}
|
|
return "";
|
};
|
|
onMounted(() => {
|
getList();
|
userListNoPage().then(res => {
|
userList.value = res.data;
|
});
|
getCurrentFactoryName();
|
});
|
// 上传附件
|
const handleUpload = async () => {
|
if (!currentFileRow.value) {
|
proxy.$modal.msgWarning("请先选择数据");
|
return null;
|
}
|
|
return new Promise(resolve => {
|
// 创建一个隐藏的文件输入元素
|
const input = document.createElement("input");
|
input.type = "file";
|
input.style.display = "none";
|
input.onchange = async e => {
|
const file = e.target.files[0];
|
if (!file) {
|
resolve(null);
|
return;
|
}
|
|
try {
|
// 使用 FormData 上传文件
|
const formData = new FormData();
|
formData.append("file", file);
|
|
const uploadRes = await request({
|
url: "/file/upload",
|
method: "post",
|
data: formData,
|
headers: {
|
"Content-Type": "multipart/form-data",
|
Authorization: `Bearer ${getToken()}`,
|
},
|
});
|
|
if (uploadRes.code === 200) {
|
// 保存附件信息
|
const fileData = {
|
safeCertificationId: currentFileRow.value.id,
|
name: uploadRes.data.originalName || file.name,
|
url: uploadRes.data.tempPath || uploadRes.data.url,
|
};
|
|
const saveRes = await safeCertificationFileAdd(fileData);
|
if (saveRes.code === 200) {
|
proxy.$modal.msgSuccess("文件上传成功");
|
// 重新加载文件列表
|
const listRes = await fileListPage({
|
safeCertificationId: currentFileRow.value.id,
|
});
|
if (listRes.code === 200 && fileListRef.value) {
|
const fileList = (listRes.data?.records || []).map(item => ({
|
name: item.name,
|
url: item.url,
|
id: item.id,
|
...item,
|
}));
|
fileListRef.value.setList(fileList);
|
}
|
// 返回新文件信息
|
resolve({
|
name: fileData.name,
|
url: fileData.url,
|
id: saveRes.data?.id,
|
});
|
} else {
|
proxy.$modal.msgError(saveRes.msg || "文件保存失败");
|
resolve(null);
|
}
|
} else {
|
proxy.$modal.msgError(uploadRes.msg || "文件上传失败");
|
resolve(null);
|
}
|
} catch (error) {
|
proxy.$modal.msgError("文件上传失败");
|
resolve(null);
|
} finally {
|
document.body.removeChild(input);
|
}
|
};
|
|
document.body.appendChild(input);
|
input.click();
|
});
|
};
|
// 删除附件
|
const handleFileDelete = async row => {
|
try {
|
const res = await safeCertificationFileDel([row.id]);
|
if (res.code === 200) {
|
proxy.$modal.msgSuccess("删除成功");
|
// 重新加载文件列表
|
if (currentFileRow.value && fileListRef.value) {
|
const listRes = await fileListPage({
|
safeCertificationId: currentFileRow.value.id,
|
});
|
if (listRes.code === 200) {
|
const fileList = (listRes.data?.records || []).map(item => ({
|
name: item.name,
|
url: item.url,
|
id: item.id,
|
...item,
|
}));
|
fileListRef.value.setList(fileList);
|
}
|
}
|
return true; // 返回 true 表示删除成功,组件会更新列表
|
} else {
|
proxy.$modal.msgError(res.msg || "删除失败");
|
return false;
|
}
|
} catch (error) {
|
proxy.$modal.msgError("删除失败");
|
return false;
|
}
|
};
|
</script>
|
|
<style scoped lang="scss">
|
.ml-10 {
|
margin-left: 10px;
|
}
|
|
.table_list {
|
margin-top: unset;
|
}
|
|
:deep(.warning-row) {
|
background-color: #fef0f0 !important;
|
}
|
|
:deep(.warning-row td) {
|
// color: #cf1322 !important;
|
}
|
|
.actions {
|
display: flex;
|
justify-content: space-between;
|
margin-bottom: 10px;
|
}
|
.print-preview-dialog {
|
.el-dialog__body {
|
padding: 0;
|
max-height: 80vh;
|
overflow-y: auto;
|
}
|
}
|
|
.print-preview-container {
|
.print-preview-header {
|
padding: 15px;
|
border-bottom: 1px solid #e4e7ed;
|
text-align: center;
|
|
.el-button {
|
margin: 0 10px;
|
}
|
}
|
|
.print-preview-content {
|
padding: 20px;
|
background-color: #f5f5f5;
|
min-height: 400px;
|
}
|
}
|
|
.print-page {
|
width: 220mm;
|
height: 90mm;
|
padding: 10mm;
|
margin: 0 auto;
|
background: white;
|
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
|
margin-bottom: 10px;
|
box-sizing: border-box;
|
}
|
|
.delivery-note {
|
width: 100%;
|
height: 100%;
|
font-family: "SimSun", serif;
|
font-size: 10px;
|
line-height: 1.2;
|
display: flex;
|
flex-direction: column;
|
}
|
|
.header {
|
text-align: center;
|
margin-bottom: 8px;
|
|
.company-name {
|
font-size: 18px;
|
font-weight: bold;
|
margin-bottom: 4px;
|
}
|
|
.document-title {
|
font-size: 16px;
|
font-weight: bold;
|
}
|
}
|
|
.info-section {
|
margin-bottom: 8px;
|
display: flex;
|
justify-content: space-between;
|
align-items: center;
|
|
.info-row {
|
line-height: 20px;
|
|
.label {
|
font-weight: bold;
|
width: 60px;
|
font-size: 14px;
|
}
|
|
.value {
|
margin-right: 20px;
|
min-width: 80px;
|
font-size: 14px;
|
}
|
}
|
}
|
|
.table-section {
|
margin-bottom: 4px;
|
flex: 1;
|
|
.product-table {
|
width: 100%;
|
border-collapse: collapse;
|
border: 1px solid #000;
|
|
th,
|
td {
|
border: 1px solid #000;
|
padding: 6px;
|
text-align: center;
|
font-size: 14px;
|
line-height: 1.4;
|
}
|
|
th {
|
font-weight: bold;
|
}
|
|
.total-label {
|
text-align: right;
|
font-weight: bold;
|
}
|
|
.total-value {
|
font-weight: bold;
|
}
|
}
|
}
|
|
.footer-section {
|
.footer-row {
|
display: flex;
|
margin-bottom: 3px;
|
line-height: 20px;
|
justify-content: space-between;
|
|
.footer-item {
|
display: flex;
|
margin-right: 20px;
|
|
.label {
|
font-weight: bold;
|
width: 80px;
|
font-size: 14px;
|
}
|
|
.value {
|
min-width: 80px;
|
font-size: 14px;
|
}
|
|
&.address-item {
|
.address-value {
|
min-width: 200px;
|
}
|
}
|
}
|
}
|
}
|
|
@media print {
|
.app-container {
|
display: none;
|
}
|
|
.print-page {
|
box-shadow: none;
|
margin: 0;
|
padding: 10mm;
|
padding-left: 20mm;
|
page-break-inside: avoid;
|
page-break-after: always;
|
}
|
.print-page:last-child {
|
page-break-after: avoid;
|
}
|
}
|
</style>
|