| | |
| | | method: "get", |
| | | params: query, |
| | | }); |
| | | } |
| | | |
| | | // 导出销售合同 |
| | | // /sales/ledger/exportProcessContract/id |
| | | |
| | | export function exportSalesContract(query) { |
| | | console.log(query); |
| | | return request({ |
| | | url: "/sales/ledger/exportProcessContract/" + query.id, |
| | | method: "get", |
| | | responseType: "blob", |
| | | }); |
| | | } |
| | |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="开户银行:" |
| | | prop="bankName"> |
| | | <el-input v-model="form.bankName" |
| | | placeholder="请输入" |
| | | clearable /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="开户行号:" |
| | | prop="bankCode"> |
| | | <el-input v-model="form.bankCode" |
| | |
| | | clearable /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="客户分类:" |
| | | prop="customerType"> |
| | |
| | | <el-option label="进销商客户" |
| | | value="进销商客户" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="法人" |
| | | prop="corporation"> |
| | | <el-input v-model="form.corporation" |
| | | placeholder="请输入" |
| | | clearable /> |
| | | </el-form-item> |
| | | </el-col> |
| | | |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="代理人" |
| | | prop="agent"> |
| | | <el-select v-model="form.agent" |
| | | placeholder="请选择代理人" |
| | | clearable |
| | | filterable |
| | | :disabled="agentOptions.length === 0" |
| | | :no-data-text="agentNoDataText"> |
| | | <el-option v-for="(contact, index) in agentOptions" |
| | | :key="getAgentOptionKey(contact, index)" |
| | | :label="getAgentLabel(contact)" |
| | | :value="contact.contactPhone" |
| | | :disabled="!contact.contactPhone"> |
| | | <span>{{ contact.contactPerson || "-" }}</span> |
| | | <span style="float: right; color: var(--el-text-color-secondary); font-size: 12px;"> |
| | | {{ contact.contactPhone || "" }} |
| | | </span> |
| | | </el-option> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="传真" |
| | | prop="fax"> |
| | | <el-input v-model="form.fax" |
| | | placeholder="请输入" |
| | | clearable /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <div class="info-item"> |
| | | <span class="info-label">银行账号:</span> |
| | | <span class="info-value">{{ detailForm.bankAccount }}</span> |
| | | </div> |
| | | </el-col> |
| | | |
| | | <el-col :span="12"> |
| | | <div class="info-item"> |
| | | <span class="info-label">开户行号:</span> |
| | |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { onMounted, ref, reactive, getCurrentInstance, toRefs } from "vue"; |
| | | import { onMounted, ref, reactive, getCurrentInstance, toRefs, computed, watch } from "vue"; |
| | | import { Search, Paperclip, Upload } from "@element-plus/icons-vue"; |
| | | import { |
| | | addCustomer, |
| | |
| | | companyPhone: "", |
| | | companyAddress: "", |
| | | basicBankAccount: "", |
| | | corporation: "", |
| | | fax: "", |
| | | bankAccount: "", |
| | | bankCode: "", |
| | | contactPerson: "", |
| | |
| | | { |
| | | label: "开户行号", |
| | | prop: "bankCode", |
| | | width: 220, |
| | | }, |
| | | { |
| | | label: "法人代表", |
| | | prop: "corporation", |
| | | width: 220, |
| | | }, |
| | | { |
| | | label: "传真", |
| | | prop: "fax", |
| | | width: 220, |
| | | }, |
| | | { |
| | |
| | | maintenanceTime: "", |
| | | basicBankAccount: "", |
| | | bankAccount: "", |
| | | fax: "", |
| | | corporation: "", |
| | | bankCode: "", |
| | | customerType: "", |
| | | }, |
| | |
| | | ], |
| | | basicBankAccount: [{ required: true, message: "请输入", trigger: "blur" }], |
| | | bankAccount: [{ required: true, message: "请输入", trigger: "blur" }], |
| | | corporation: [{ required: true, message: "请输入法人代表", trigger: "blur" }], |
| | | agent: [{ required: true, message: "请选择代理人", trigger: "change" }], |
| | | bankName: [{ required: true, message: "请输入", trigger: "blur" }], |
| | | bankCode: [{ required: true, message: "请输入", trigger: "blur" }], |
| | | customerType: [{ required: true, message: "请选择", trigger: "change" }], |
| | | }, |
| | |
| | | }, |
| | | }); |
| | | const { searchForm, form, rules } = toRefs(data); |
| | | |
| | | const agentOptions = computed(() => { |
| | | const list = formYYs.value?.contactList || []; |
| | | return list.filter(item => item && (item.contactPerson || item.contactPhone)); |
| | | }); |
| | | |
| | | const agentNoDataText = computed(() => { |
| | | if (agentOptions.value.length === 0) return "请先新增联系人"; |
| | | return "无匹配联系人"; |
| | | }); |
| | | |
| | | const getAgentLabel = contact => { |
| | | const person = (contact?.contactPerson || "").trim(); |
| | | const phone = (contact?.contactPhone || "").trim(); |
| | | if (person && phone) return `${person}(${phone})`; |
| | | return person || phone || "-"; |
| | | }; |
| | | |
| | | const getAgentOptionKey = (contact, index) => { |
| | | return contact?.contactPhone || contact?.contactPerson || index; |
| | | }; |
| | | |
| | | watch( |
| | | () => agentOptions.value.map(item => item.contactPhone), |
| | | phones => { |
| | | const val = form.value?.agent; |
| | | if (!val) return; |
| | | if (!phones.includes(val)) { |
| | | form.value.agent = ""; |
| | | } |
| | | } |
| | | ); |
| | | |
| | | const addNewContact = () => { |
| | | formYYs.value.contactList.push({ |
| | | contactPerson: "", |
| | |
| | | message: '最多100个字符', |
| | | } |
| | | ]"> |
| | | <el-input v-model="formState.name" /> |
| | | <el-input v-model="formState.name" placeholder="请输入工序名称" /> |
| | | </el-form-item> |
| | | <el-form-item label="工序编号" prop="no"> |
| | | <el-input v-model="formState.no" /> |
| | | <el-form-item |
| | | label="工序机台" |
| | | prop="deviceId" |
| | | :rules="[ |
| | | { |
| | | required: true, |
| | | message: '请选择工序类型', |
| | | } |
| | | ]" |
| | | > |
| | | <el-select |
| | | v-model="formState.deviceId" |
| | | placeholder="请选择工序机台" |
| | | filterable |
| | | remote |
| | | clearable |
| | | reserve-keyword |
| | | :remote-method="handleDeviceRemoteSearch" |
| | | :loading="deviceLoading" |
| | | @clear="handleDeviceClear" |
| | | @change="handleDeviceChange" |
| | | @visible-change="handleDeviceDropdownVisible" |
| | | popper-class="device-select-popper" |
| | | > |
| | | <el-option v-for="item in equipmentList" :key="item.id" :label="item.deviceName" :value="item.id" /> |
| | | <el-option |
| | | v-if="equipmentList.length > 0 && deviceHasMore" |
| | | :value="__deviceLoadMoreSentinel" |
| | | label="加载更多…" |
| | | disabled |
| | | /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item |
| | | label="工序类型" |
| | |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, computed, getCurrentInstance } from "vue"; |
| | | import { ref, computed, onMounted, getCurrentInstance, reactive, nextTick, onBeforeUnmount } from "vue"; |
| | | import {add} from "@/api/productionManagement/productionProcess.js"; |
| | | import {getLedgerPage} from "@/api/equipmentManagement/ledger.js"; |
| | | |
| | | const props = defineProps({ |
| | | visible: { |
| | |
| | | isQuality: false, |
| | | }); |
| | | |
| | | // 分页查询参数 |
| | | const page = reactive({ |
| | | current: 1, |
| | | size: 10, |
| | | total: 0, |
| | | }); |
| | | |
| | | onMounted(() => { |
| | | resetDeviceOptions(); |
| | | }); |
| | | |
| | | const handleDeviceChange = (val) => { |
| | | formState.value.deviceName = equipmentList.value.find(item => item.id === val)?.deviceName || ''; |
| | | }; |
| | | |
| | | const isShow = computed({ |
| | | get() { |
| | | return props.visible; |
| | |
| | | }); |
| | | |
| | | let { proxy } = getCurrentInstance() |
| | | |
| | | |
| | | const equipmentList = ref([]); |
| | | const deviceLoading = ref(false); |
| | | const deviceQuery = ref(""); |
| | | const deviceScrollWrap = ref(null); |
| | | const __deviceLoadMoreSentinel = "__deviceLoadMoreSentinel"; |
| | | |
| | | const deviceHasMore = computed(() => { |
| | | const total = Number(page.total ?? 0); |
| | | if (!total) { |
| | | return false; |
| | | } |
| | | return equipmentList.value.length < total; |
| | | }); |
| | | |
| | | // 获取设备列表(分页查询) |
| | | const getLedgerPageS = async ({ reset = false } = {}) => { |
| | | if (deviceLoading.value) return; |
| | | deviceLoading.value = true; |
| | | try { |
| | | const res = await getLedgerPage({ |
| | | current: page.current, |
| | | size: page.size, |
| | | deviceName: deviceQuery.value ? deviceQuery.value : undefined, |
| | | }); |
| | | const data = res?.data || {}; |
| | | const records = Array.isArray(data.records) ? data.records : []; |
| | | |
| | | page.total = Number(data.total ?? page.total ?? 0); |
| | | page.current = Number(data.current ?? page.current); |
| | | page.size = Number(data.size ?? page.size); |
| | | |
| | | equipmentList.value = reset ? records : [...equipmentList.value, ...records]; |
| | | } finally { |
| | | deviceLoading.value = false; |
| | | } |
| | | }; |
| | | |
| | | const resetDeviceOptions = async () => { |
| | | page.current = 1; |
| | | page.size = 10; |
| | | page.total = 0; |
| | | equipmentList.value = []; |
| | | await getLedgerPageS({ reset: true }); |
| | | }; |
| | | |
| | | const loadMoreDevices = async () => { |
| | | if (deviceLoading.value) return; |
| | | if (!deviceHasMore.value) return; |
| | | page.current += 1; |
| | | await getLedgerPageS(); |
| | | }; |
| | | |
| | | let remoteTimer = null; |
| | | const handleDeviceRemoteSearch = (query) => { |
| | | const nextQuery = (query ?? "").trim(); |
| | | deviceQuery.value = nextQuery; |
| | | if (remoteTimer) clearTimeout(remoteTimer); |
| | | remoteTimer = setTimeout(() => { |
| | | resetDeviceOptions(); |
| | | }, 300); |
| | | }; |
| | | |
| | | const handleDeviceClear = () => { |
| | | deviceQuery.value = ""; |
| | | resetDeviceOptions(); |
| | | }; |
| | | |
| | | const onDeviceDropdownScroll = (e) => { |
| | | const el = e.target; |
| | | if (!el) return; |
| | | if (el.scrollHeight - el.scrollTop - el.clientHeight <= 20) { |
| | | loadMoreDevices(); |
| | | } |
| | | }; |
| | | |
| | | const unbindDeviceDropdownScroll = () => { |
| | | if (deviceScrollWrap.value) { |
| | | deviceScrollWrap.value.removeEventListener("scroll", onDeviceDropdownScroll); |
| | | deviceScrollWrap.value = null; |
| | | } |
| | | }; |
| | | |
| | | const bindDeviceDropdownScroll = () => { |
| | | unbindDeviceDropdownScroll(); |
| | | const wrap = |
| | | document.querySelector(".device-select-popper .el-scrollbar__wrap") || |
| | | document.querySelector(".device-select-popper .el-select-dropdown__wrap"); |
| | | if (wrap) { |
| | | deviceScrollWrap.value = wrap; |
| | | wrap.addEventListener("scroll", onDeviceDropdownScroll); |
| | | } |
| | | }; |
| | | |
| | | const handleDeviceDropdownVisible = async (visible) => { |
| | | if (!visible) { |
| | | unbindDeviceDropdownScroll(); |
| | | return; |
| | | } |
| | | if (equipmentList.value.length === 0) { |
| | | await resetDeviceOptions(); |
| | | } |
| | | await nextTick(); |
| | | bindDeviceDropdownScroll(); |
| | | }; |
| | | |
| | | const closeModal = () => { |
| | | isShow.value = false; |
| | |
| | | handleSubmit, |
| | | isShow, |
| | | }); |
| | | |
| | | onBeforeUnmount(() => { |
| | | unbindDeviceDropdownScroll(); |
| | | if (remoteTimer) clearTimeout(remoteTimer); |
| | | }); |
| | | </script> |
| | |
| | | </span> |
| | | </template> |
| | | </el-dialog> |
| | | <el-dialog |
| | | v-model="auditDialogVisible" |
| | | title="审核" |
| | | width="1000px" |
| | | :close-on-click-modal="false" |
| | | > |
| | | <el-table :data="auditTableData" border style="width: 100%" v-loading="auditLoading"> |
| | | <el-table-column label="产品名称" prop="productName" min-width="140" show-overflow-tooltip /> |
| | | <el-table-column label="规格" prop="model" min-width="120" show-overflow-tooltip /> |
| | | <el-table-column label="单位" prop="unit" width="80" /> |
| | | <el-table-column label="工序名称" prop="processName" min-width="120" show-overflow-tooltip /> |
| | | <el-table-column label="需求数量" prop="planQuantity" width="110" /> |
| | | <el-table-column label="完成数量" prop="completeQuantity" width="110" /> |
| | | <el-table-column label="完成进度" prop="completionStatus" width="140"> |
| | | <template #default="{ row }"> |
| | | <el-progress |
| | | :percentage="toProgressPercentage(row?.completionStatus)" |
| | | :color="progressColor(toProgressPercentage(row?.completionStatus))" |
| | | :status="toProgressPercentage(row?.completionStatus) >= 100 ? 'success' : ''" |
| | | /> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="计划开始时间" prop="planStartTime" width="140" /> |
| | | <el-table-column label="计划结束时间" prop="planEndTime" width="140" /> |
| | | </el-table> |
| | | <template #footer> |
| | | <span class="dialog-footer"> |
| | | <el-button type="primary" :loading="auditLoading" @click="submitAudit(1)">通过</el-button> |
| | | <el-button type="danger" :loading="auditLoading" @click="submitAudit(2)">不通过</el-button> |
| | | <el-button :disabled="auditLoading" @click="auditDialogVisible = false">取消</el-button> |
| | | </span> |
| | | </template> |
| | | </el-dialog> |
| | | <FilesDia ref="workOrderFilesRef" /> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { onMounted, ref, nextTick } from "vue"; |
| | | import { ElMessageBox } from "element-plus"; |
| | | import { ElMessageBox, ElMessage } from "element-plus"; |
| | | import dayjs from "dayjs"; |
| | | import { |
| | | productWorkOrderPage, |
| | |
| | | }, |
| | | disabled: row => row.planQuantity <= 0, |
| | | }, |
| | | { |
| | | name:"审核", |
| | | color: "#f56c6c", |
| | | clickFun: row => { |
| | | handleAudit(row); |
| | | }, |
| | | disabled: row => Number(row?.auditStatus) === 1, |
| | | } |
| | | ], |
| | | }, |
| | | ]); |
| | |
| | | const transferCardQrUrl = ref(""); |
| | | const transferCardRowData = ref(null); |
| | | const reportDialogVisible = ref(false); |
| | | const auditDialogVisible = ref(false); |
| | | const auditRowData = ref(null); |
| | | const auditTableData = ref([]); |
| | | const auditLoading = ref(false); |
| | | const workOrderFilesRef = ref(null); |
| | | const reportFormRef = ref(null); |
| | | const userOptions = ref([]); |
| | |
| | | callback(); |
| | | }; |
| | | |
| | | // 审核 |
| | | const handleAudit = (row) => { |
| | | if (Number(row?.auditStatus) === 1) { |
| | | ElMessage.warning("该工单已审核"); |
| | | return; |
| | | } |
| | | auditRowData.value = row; |
| | | const workOrderNo = row?.workOrderNo; |
| | | const related = workOrderNo |
| | | ? tableData.value.filter(r => r?.workOrderNo === workOrderNo) |
| | | : []; |
| | | auditTableData.value = related.length > 0 ? related : [row]; |
| | | auditDialogVisible.value = true; |
| | | }; |
| | | |
| | | const submitAudit = async (result) => { |
| | | const current = auditRowData.value; |
| | | if (!current) return; |
| | | if (auditLoading.value) return; |
| | | |
| | | const confirmText = result === 1 ? "确定审核通过吗?" : "确定审核不通过吗?"; |
| | | try { |
| | | await ElMessageBox.confirm(confirmText, "提示", { |
| | | confirmButtonText: "确定", |
| | | cancelButtonText: "取消", |
| | | type: "warning", |
| | | }); |
| | | } catch { |
| | | return; |
| | | } |
| | | |
| | | auditLoading.value = true; |
| | | try { |
| | | const updates = auditTableData.value.map(item => { |
| | | const id = item?.id; |
| | | if (!id) return Promise.resolve(); |
| | | return updateProductWorkOrder({ id, auditStatus: result }); |
| | | }); |
| | | await Promise.all(updates); |
| | | ElMessage.success("审核成功"); |
| | | auditDialogVisible.value = false; |
| | | getList(); |
| | | } finally { |
| | | auditLoading.value = false; |
| | | } |
| | | }; |
| | | |
| | | // 查看详情 |
| | | const handleView = (row) => { |
| | | const { workOrderId } = row; |
| | | router.push({ |
| | | path: "/productionManagement/workOrderDetail", |
| | | query: { workOrderId }, |
| | | }); |
| | | } |
| | | |
| | | // 验证规则 |
| | | const reportFormRules = { |
| | | quantity: [{ required: true, validator: validateQuantity, trigger: "blur" }], |
| | |
| | | v-for="item in statItems" |
| | | :key="item.name" |
| | | class="stat-card" |
| | | @click="handleClick(item)" |
| | | > |
| | | <img src="@/assets/BI/icon@2x.png" alt="图标" class="card-icon" /> |
| | | <div class="card-content"> |
| | |
| | | |
| | | <script setup> |
| | | import { ref, onMounted } from 'vue' |
| | | import { useRouter } from 'vue-router' |
| | | import { orderCount } from '@/api/viewIndex.js' |
| | | |
| | | const router = useRouter() |
| | | |
| | | |
| | | const statItems = ref([]) |
| | | |
| | |
| | | console.error('获取订单数量统计失败:', err) |
| | | }) |
| | | } |
| | | const handleClick = (item) => { |
| | | // 点击跳转页面 |
| | | console.log('点击了', item) |
| | | router.push({ |
| | | path: '/productionManagement/productionOrder', |
| | | query: { |
| | | name: item.name, |
| | | } |
| | | }) |
| | | } |
| | | |
| | | onMounted(() => { |
| | | fetchData() |
| | |
| | | } |
| | | |
| | | .stat-card { |
| | | cursor: pointer; |
| | | flex: 1; |
| | | display: flex; |
| | | align-items: center; |
| | |
| | | <el-table-column label="录入日期" prop="entryDate" width="120" show-overflow-tooltip /> |
| | | <el-table-column label="签订日期" prop="executionDate" width="120" show-overflow-tooltip /> |
| | | <el-table-column label="交付日期" prop="deliveryDate" width="120" show-overflow-tooltip /> |
| | | <el-table-column label="备注" prop="remarks" width="200" show-overflow-tooltip /> |
| | | <el-table-column fixed="right" label="操作" min-width="100" align="center"> |
| | | <el-table-column label="其它说明事项" prop="remarks" width="200" show-overflow-tooltip /> |
| | | <el-table-column fixed="right" label="操作" min-width="200" align="center"> |
| | | <template #default="scope"> |
| | | <el-button link type="primary" size="small" @click="openForm('edit', scope.row)" :disabled="!scope.row.isEdit">编辑</el-button> |
| | | <el-button link type="primary" size="small" @click="exportSalesContracts(scope.row)">导出销售合同</el-button> |
| | | <!-- <el-button link type="primary" size="small" @click="openForm('view', scope.row)">详情</el-button>--> |
| | | <el-button link type="primary" size="small" @click="downLoadFile(scope.row)">附件</el-button> |
| | | <!-- <el-button link type="primary" size="small" @click="openDeliveryForm(scope.row)">发货</el-button>--> |
| | |
| | | type="date" placeholder="请选择" clearable /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="签订地点:" prop="placeOfSinging"> |
| | | <el-input v-model="form.placeOfSinging" placeholder="请输入" clearable :disabled="operationType === 'view'" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row> |
| | | <el-form-item label="产品信息:" prop="entryDate"> |
| | |
| | | </el-table> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="24"> |
| | | <el-form-item label="备注:" prop="remarks"> |
| | | <el-form-item label="其它说明事项:" prop="remarks"> |
| | | <el-input v-model="form.remarks" placeholder="请输入" clearable type="textarea" :rows="2" :disabled="operationType === 'view'" /> |
| | | </el-form-item> |
| | | </el-col> |
| | |
| | | addOrUpdateSalesLedgerProduct, |
| | | delProduct, |
| | | delLedgerFile, getProductInventory, |
| | | exportSalesContract |
| | | } from "@/api/salesManagement/salesLedger.js"; |
| | | import { modelList, productTreeList } from "@/api/basicData/product.js"; |
| | | import useFormData from "@/hooks/useFormData.js"; |
| | |
| | | entryDate: [{ required: true, message: "请选择", trigger: "change" }], |
| | | deliveryDate: [{ required: true, message: "请选择", trigger: "change" }], |
| | | executionDate: [{ required: true, message: "请选择", trigger: "change" }], |
| | | placeOfSinging: [{ required: true, message: "请输入", trigger: "blur" }], |
| | | }, |
| | | }); |
| | | const { form, rules } = toRefs(data); |
| | |
| | | }; |
| | | |
| | | /** |
| | | * 导出销售合同 |
| | | * |
| | | * @param row 导出销售合同的相关信息对象 |
| | | */ |
| | | const exportSalesContracts = (row) => { |
| | | exportSalesContract({ id: row.id }).then((res) => { |
| | | if (res) { |
| | | const downloadUrl = window.URL.createObjectURL(res); |
| | | const link = document.createElement('a'); |
| | | link.href = downloadUrl; |
| | | console.log(row.executionDate) |
| | | link.download = row.projectName+row.executionDate + "销售合同.docx"; // 设置下载文件名 |
| | | link.style.display = 'none'; // 隐藏a标签 |
| | | document.body.appendChild(link); |
| | | link.click(); // 触发点击下载 |
| | | // 4. 清理资源(避免内存泄漏) |
| | | document.body.removeChild(link); |
| | | window.URL.revokeObjectURL(downloadUrl); |
| | | |
| | | // 5. 提示导出成功 |
| | | proxy.$modal.msgSuccess("导出销售合同成功"); |
| | | } else { |
| | | proxy.$modal.msgError(res.msg || "导出销售合同失败"); |
| | | } |
| | | }); |
| | | } |
| | | |
| | | /** |
| | | * 下载文件 |
| | | * |
| | | * @param row 下载文件的相关信息对象 |