| 昨天 | gongchunyi | ![]() |
| 昨天 | gaoluyang | ![]() |
| 昨天 | huminmin | ![]() |
| 昨天 | huminmin | ![]() |
| 昨天 | gaoluyang | ![]() |
| 昨天 | gaoluyang | ![]() |
| 昨天 | yaowanxin | ![]() |
| 昨天 | yaowanxin | ![]() |
| 昨天 | yaowanxin | ![]() |
| 昨天 | zhangwencui | ![]() |
| 昨天 | spring | ![]() |
| 昨天 | spring | ![]() |
| 昨天 | spring | ![]() |
| 昨天 | gaoluyang | ![]() |
| 昨天 | gaoluyang | ![]() |
| 昨天 | gaoluyang | ![]() |
| 3 天以前 | gongchunyi | ![]() |
| 4 天以前 | liyong | ![]() |
| 4 天以前 | huminmin | ![]() |
| 4 天以前 | huminmin | ![]() |
| 4 天以前 | spring | ![]() |
| 4 天以前 | spring | ![]() |
| 4 天以前 | gongchunyi | ![]() |
| 4 天以前 | gongchunyi | ![]() |
| 4 天以前 | yaowanxin | ![]() |
| 4 天以前 | yaowanxin | ![]() |
| 4 天以前 | huminmin | ![]() |
| 4 天以前 | huminmin | ![]() |
src/api/inventoryManagement/stockInventory.js
@@ -25,3 +25,13 @@ data: params, }); }; export const exportStockInventory = (params) => { return request({ url: "/stockInventory/exportStockInventory", method: "post", data: params, }); }; src/api/procurementManagement/procurementInvoiceLedger.js
@@ -83,11 +83,18 @@ }); } export function getProductRecordById(params) { // export function getProductRecordById(params) { // return request({ // url: "/purchase/registration/getProductRecordById", // method: "get", // params: params, // }); // } export function getProductRecordById(data) { return request({ url: "/purchase/registration/getProductRecordById", method: "get", params: params, method: "post", data: data, }); } src/api/procurementManagement/procurementLedger.js
@@ -81,17 +81,24 @@ } // ä¿åéè´æ¨¡æ¿ // /purchase/ledger/addPurchaseTemplate export function addPurchaseTemplate(data) { return request({ url: "/purchase/ledger/addPurchaseTemplate", url: "/purchaseLedgerTemplate/add", method: "post", data: data, }); } // ä¿®æ¹éè´æ¨¡æ¿ export function updatePurchaseTemplate(data) { return request({ url: "/purchaseLedgerTemplate/update", method: "post", data: data, }); } // æ¥è¯¢éè´æ¨¡æ¿ // /purchase/ledger/getPurchaseTemplateList export function getPurchaseTemplateList(query) { return request({ url: "/purchase/ledger/getPurchaseTemplateList", @@ -99,3 +106,12 @@ params: query, }); } // å é¤éè´æ¨¡æ¿ export function delPurchaseTemplate(id) { return request({ url: "/purchaseLedgerTemplate/delete", method: "delete", data: id, }); } src/api/productionManagement/productBom.js
@@ -45,3 +45,22 @@ params: { productModelId }, }); } // 导åºBOM export function exportBom(bomId) { return request({ url: "/productBom/exportBom", method: "post", params: { bomId }, responseType: "blob", }); } // ä¸è½½æ¨¡æ¿ export function downloadTemplate() { return request({ url: "/productBom/downloadTemplate", method: "get", responseType: "blob", }); } src/assets/icons/png/circleBlue@2x.png
src/assets/icons/png/circleGreen@2x.png
src/assets/icons/png/circleOrange@2x.png
src/assets/icons/png/circleRed@2x.png
src/assets/icons/png/circleYellow@2x.png
src/assets/icons/png/walletBlue@2x.png
src/assets/icons/png/walletGreen@2x.png
src/assets/icons/png/walletOrange@2x.png
src/assets/icons/png/walletRed@2x.png
src/assets/icons/png/walletYellow@2x.png
src/views/basicData/product/ProductSelectDialog.vue
@@ -1,28 +1,12 @@ <template> <el-dialog v-model="visible" title="éæ©äº§å" width="900px" destroy-on-close :close-on-click-modal="false" > <el-dialog v-model="visible" title="éæ©äº§å" width="900px" destroy-on-close :close-on-click-modal="false"> <el-form :inline="true" :model="query" class="mb-2"> <el-form-item label="产å大类"> <el-input v-model="query.productName" placeholder="è¾å ¥äº§å大类" clearable @keyup.enter="onSearch" /> <el-input v-model="query.productName" placeholder="è¾å ¥äº§å大类" clearable @keyup.enter="onSearch" /> </el-form-item> <el-form-item label="åå·åç§°"> <el-input v-model="query.model" placeholder="è¾å ¥åå·åç§°" clearable @keyup.enter="onSearch" /> <el-input v-model="query.model" placeholder="è¾å ¥åå·åç§°" clearable @keyup.enter="onSearch" /> </el-form-item> <el-form-item> @@ -32,32 +16,19 @@ </el-form> <!-- å表 --> <el-table v-loading="loading" :data="tableData" height="420" highlight-current-row row-key="id" @selection-change="handleSelectionChange" > <el-table ref="tableRef" v-loading="loading" :data="tableData" height="420" highlight-current-row row-key="id" @selection-change="handleSelectionChange" @select="handleSelect"> <el-table-column type="selection" width="55" /> <el-table-column type="index" label="#" width="60"/> <el-table-column prop="productName" label="产å大类" min-width="160"/> <el-table-column prop="model" label="åå·åç§°" min-width="200"/> <el-table-column prop="unit" label="åä½" min-width="160"/> <el-table-column type="index" label="åºå·" width="60" /> <el-table-column prop="productName" label="产å大类" min-width="160" /> <el-table-column prop="model" label="åå·åç§°" min-width="200" /> <el-table-column prop="unit" label="åä½" min-width="160" /> </el-table> <div class="mt-3 flex justify-end"> <el-pagination background layout="total, sizes, prev, pager, next, jumper" :total="total" v-model:page-size="page.pageSize" v-model:current-page="page.pageNum" :page-sizes="[10, 20, 50, 100]" @size-change="onPageChange" @current-change="onPageChange" /> <el-pagination background layout="total, sizes, prev, pager, next, jumper" :total="total" v-model:page-size="page.pageSize" v-model:current-page="page.pageNum" :page-sizes="[10, 20, 50, 100]" @size-change="onPageChange" @current-change="onPageChange" /> </div> <template #footer> @@ -70,9 +41,9 @@ </template> <script setup lang="ts"> import {computed, onMounted, reactive, ref, watch} from "vue"; import {ElMessage} from "element-plus"; import {productModelList} from '@/api/basicData/productModel' import { computed, onMounted, reactive, ref, watch, nextTick } from "vue"; import { ElMessage } from "element-plus"; import { productModelList } from '@/api/basicData/productModel' export type ProductRow = { id: number; @@ -83,6 +54,7 @@ const props = defineProps<{ modelValue: boolean; single?: boolean; // æ¯å¦åªè½éæ©ä¸ä¸ªï¼é»è®¤falseï¼å¯éæ©å¤ä¸ªï¼ }>(); const emit = defineEmits(['update:modelValue', 'confirm']); @@ -105,14 +77,48 @@ const loading = ref(false); const tableData = ref<ProductRow[]>([]); const total = ref(0); const multipleSelection = ref<ProductRow[]>([]) const multipleSelection = ref<ProductRow[]>([]); const tableRef = ref(); function close() { visible.value = false; } const handleSelectionChange = (val: ProductRow[]) => { multipleSelection.value = val if (props.single && val.length > 1) { // 妿éå¶ä¸ºåä¸ªéæ©ï¼åªä¿çæåä¸ä¸ªéä¸ç const lastSelected = val[val.length - 1]; multipleSelection.value = [lastSelected]; // æ¸ ç©ºè¡¨æ ¼éä¸ç¶æï¼ç¶åéæ°é䏿åä¸ä¸ª nextTick(() => { if (tableRef.value) { tableRef.value.clearSelection(); tableRef.value.toggleRowSelection(lastSelected, true); } }); } else { multipleSelection.value = val; } } // å¤çåä¸ªéæ© const handleSelect = (selection: ProductRow[], row: ProductRow) => { if (props.single) { // 妿éå¶ä¸ºåä¸ªï¼æ¸ ç©ºå ¶ä»éæ©ï¼åªä¿çå½åè¡ if (selection.includes(row)) { // éä¸å½åè¡æ¶ï¼æ¸ ç©ºå ¶ä»éä¸ multipleSelection.value = [row]; nextTick(() => { if (tableRef.value) { tableData.value.forEach((item) => { if (item.id !== row.id) { tableRef.value.toggleRowSelection(item, false); } }); } }); } } } function onSearch() { @@ -136,7 +142,11 @@ ElMessage.warning("è¯·éæ©ä¸æ¡äº§å"); return; } emit("confirm", multipleSelection.value); if (props.single && multipleSelection.value.length > 1) { ElMessage.warning("åªè½éæ©ä¸ä¸ªäº§å"); return; } emit("confirm", props.single ? [multipleSelection.value[0]] : multipleSelection.value); close(); } @@ -144,7 +154,7 @@ loading.value = true; try { multipleSelection.value = []; // 翻页/æç´¢åæ¸ ç©ºéæ©æ´ç¬¦å颿 const res = await productModelList({ const res: any = await productModelList({ productName: query.productName.trim(), model: query.model.trim(), current: page.pageNum, @@ -157,6 +167,13 @@ } } // çå¬å¼¹çªæå¼ï¼éç½®éæ© watch(() => props.modelValue, (visible) => { if (visible) { multipleSelection.value = []; } }); onMounted(() => { loadData() }) src/views/collaborativeApproval/approvalProcess/index.vue
@@ -8,7 +8,7 @@ <el-tab-pane label="æ¥é管ç" name="4"></el-tab-pane> <el-tab-pane label="éè´å®¡æ¹" name="5"></el-tab-pane> <el-tab-pane label="æ¥ä»·å®¡æ¹" name="6"></el-tab-pane> <el-tab-pane label="åºåºå®¡æ¹" name="7"></el-tab-pane> <el-tab-pane label="å货审æ¹" name="7"></el-tab-pane> </el-tabs> <div class="search_form"> @@ -35,9 +35,18 @@ > </div> <div> <el-button type="primary" @click="openForm('add')" v-if="currentApproveType !== 6">æ°å¢</el-button> <el-button type="primary" @click="openForm('add')" v-if="currentApproveType !== 5 && currentApproveType !== 6 && currentApproveType !== 7" >æ°å¢</el-button> <el-button @click="handleOut">导åº</el-button> <el-button type="danger" plain @click="handleDelete">å é¤</el-button> <el-button type="danger" plain @click="handleDelete" v-if="currentApproveType !== 7" >å é¤</el-button> </div> </div> <div class="table_list"> @@ -205,7 +214,13 @@ clickFun: (row) => { openForm("edit", row); }, disabled: (row) => currentApproveType.value === 6 || row.approveStatus == 2 || row.approveStatus == 1 || row.approveStatus == 4 disabled: (row) => currentApproveType.value === 5 || currentApproveType.value === 6 || currentApproveType.value === 7 || row.approveStatus == 2 || row.approveStatus == 1 || row.approveStatus == 4 }, { name: "å®¡æ ¸", @@ -294,7 +309,7 @@ 4: "æ¥é管ç审æ¹è¡¨", 5: "éè´ç³è¯·å®¡æ¹è¡¨", 6: "æ¥ä»·å®¡æ¹è¡¨", 7: "åºåºå®¡æ¹è¡¨", 7: "å货审æ¹è¡¨", } const fileName = nameMap[type] || nameMap[0] proxy.download(url, {}, `${fileName}.xlsx`) src/views/collaborativeApproval/sealManagement/index.vue
@@ -27,7 +27,7 @@ <el-option label="å·²æç»" value="rejected" /> </el-select> </el-col> <el-col :span="8"> <el-col :span="6"> <el-button type="primary" @click="searchSealApplications">æç´¢</el-button> <el-button @click="resetSealSearch">éç½®</el-button> <el-button @click="handleExport">导åº</el-button> src/views/equipmentManagement/calibration/index.vue
@@ -34,6 +34,7 @@ <el-button type="primary" @click="handleQuery" style="margin-left: 10px" >æç´¢</el-button > <el-button @click="handleReset" style="margin-left: 10px">éç½®</el-button> </div> <div> <el-button @click="handleOut">导åº</el-button> @@ -56,7 +57,7 @@ </template> <script setup> import {onMounted, ref} from "vue"; import {onMounted, ref, reactive, toRefs, getCurrentInstance, nextTick} from "vue"; import {ElMessageBox, ElMessage} from "element-plus"; import useUserStore from "@/store/modules/user.js"; import CalibrationDia from "@/views/equipmentManagement/measurementEquipment/components/calibrationDia.vue"; @@ -179,6 +180,15 @@ page.current = 1; getList(); }; // éç½®æç´¢æ¡ä»¶ const handleReset = () => { searchForm.value.recordDate = ""; searchForm.value.entryDate = ""; searchForm.value.code = ""; page.current = 1; getList(); }; const pagination = (obj) => { page.current = obj.page; page.size = obj.limit; src/views/equipmentManagement/inspectionManagement/components/formDia.vue
@@ -114,7 +114,7 @@ </template> <script setup> import {reactive, ref} from "vue"; import {reactive, ref, getCurrentInstance, toRefs} from "vue"; import useUserStore from '@/store/modules/user' import {addOrEditTimingTask} from "@/api/inspectionManagement/index.js"; import {userListNoPageByTenantId} from "@/api/system/user.js"; @@ -142,7 +142,62 @@ rules: { taskId: [{ required: true, message: "è¯·éæ©è®¾å¤", trigger: "change" },], inspector: [{ required: true, message: "请è¾å ¥å·¡æ£äºº", trigger: "blur" },], dateStr: [{ required: true, message: "è¯·éæ©ç»è®°æ¶é´", trigger: "change" }] dateStr: [{ required: true, message: "è¯·éæ©ç»è®°æ¶é´", trigger: "change" }], frequencyType: [{ required: true, message: "è¯·éæ©ä»»å¡é¢ç", trigger: "change" }], frequencyDetail: [ { required: true, message: "è¯·éæ©æ¥æ", trigger: "change", validator: (rule, value, callback) => { if (!form.value.frequencyType) { callback() return } if (form.value.frequencyType === 'WEEKLY') { if (!form.value.week || !form.value.time) { callback(new Error("è¯·éæ©æ¥æåæ¶é´")) } else { callback() } } else { if (!value) { callback(new Error("è¯·éæ©æ¥æ")) } else { callback() } } } } ], week: [ { required: true, message: "è¯·éæ©ææ", trigger: "change", validator: (rule, value, callback) => { if (form.value.frequencyType === 'WEEKLY' && !value) { callback(new Error("è¯·éæ©ææ")) } else { callback() } } } ], time: [ { required: true, message: "è¯·éæ©æ¶é´", trigger: "change", validator: (rule, value, callback) => { if (form.value.frequencyType === 'WEEKLY' && !value) { callback(new Error("è¯·éæ©æ¶é´")) } else { callback() } } } ] } }) const { form, rules } = toRefs(data) src/views/equipmentManagement/inspectionManagement/index.vue
@@ -1,10 +1,10 @@ <template> <div class="app-container"> <el-form :inline="true" :model="queryParams" class="search-form"> <el-form-item label="æç´¢"> <el-form-item label="å·¡æ£ä»»å¡åç§°"> <el-input v-model="queryParams.searchAll" placeholder="请è¾å ¥å ³é®å" v-model="queryParams.taskName" placeholder="请è¾å ¥å·¡æ£ä»»å¡åç§°" clearable :style="{ width: '100%' }" /> @@ -100,7 +100,7 @@ // æ¥è¯¢åæ° const queryParams = reactive({ searchAll: "", taskName: "", }); // åéæ¡é ç½® src/views/equipmentManagement/measurementEquipment/components/calibrationDia.vue
@@ -125,7 +125,7 @@ </template> <script setup> import {ref} from "vue"; import {ref, reactive, toRefs, getCurrentInstance} from "vue"; import useUserStore from "@/store/modules/user.js"; import {userListNoPageByTenantId} from "@/api/system/user.js"; import {afterSalesServiceAdd, afterSalesServiceUpdate} from "@/api/customerService/index.js"; @@ -188,6 +188,10 @@ if(type === "add"){ fileList.value = row.commonFiles; } if(type === "verifying"){ form.value.valid = row.valid; form.value.recordDate = row.mostDate; } form.value.id = row.id; form.value.code = row.code; src/views/equipmentManagement/measurementEquipment/components/formDia.vue
@@ -27,9 +27,9 @@ </el-row> <el-row :gutter="30"> <el-col :span="12"> <el-form-item label="å®è£ ä½ç½®ï¼" prop="installationLocation"> <el-form-item label="å®è£ ä½ç½®ï¼" prop="instationLocation"> <el-input v-model="form.installationLocation" v-model="form.instationLocation" placeholder="请è¾å ¥" clearable /> @@ -171,7 +171,7 @@ const data = reactive({ form: { code: "", installationLocation: "", instationLocation: "", mostDate:"", model: "", cycle:"", @@ -179,6 +179,7 @@ nextDate: "", userId: "", recordDate: "", unit:"", tempFileIds: [] }, rules: { @@ -188,7 +189,7 @@ nextDate: [{required: true, message: "è¯·éæ©", trigger: "change"}], userId: [{required: true, message: "è¯·éæ©", trigger: "change"}], recordDate: [{required: true, message: "è¯·éæ©", trigger: "change"}], installationLocation: [{required: true, message: "请è¾å ¥", trigger: "blur"}], instationLocation: [{required: true, message: "请è¾å ¥", trigger: "blur"}], mostDate: [{required: true, message: "è¯·éæ©", trigger: "change"}], cycle: [{required: true, message: "è¯·éæ©", trigger: "blur"}], valid: [{required: true, message: "请è¾å ¥", trigger: "blur"}], src/views/equipmentManagement/measurementEquipment/filesDia.vue
@@ -26,20 +26,14 @@ rowKey="id" :column="tableColumn" :tableData="tableData" :page="page" :tableLoading="tableLoading" :isSelection="true" @selection-change="handleSelectionChange" @pagination="paginationSearch" height="500" > </PIMTable> <pagination style="margin: 10px 0" v-show="total > 0" @pagination="paginationSearch" :total="total" :page="page.current" :limit="page.size" /> <template #footer> <div class="dialog-footer"> <el-button @click="closeDia">åæ¶</el-button> @@ -51,16 +45,16 @@ </template> <script setup> import {ref} from "vue"; import {ref, reactive, getCurrentInstance} from "vue"; import {ElMessageBox} from "element-plus"; import {getToken} from "@/utils/auth.js"; import filePreview from '@/components/filePreview/index.vue' import PIMTable from "@/components/PIMTable/PIMTable.vue"; import { fileAdd, fileDel, fileListPage } from "@/api/financialManagement/revenueManagement.js"; import Pagination from "@/components/PIMTable/Pagination.vue"; const { proxy } = getCurrentInstance() const emit = defineEmits(['close']) @@ -98,8 +92,8 @@ const page = reactive({ current: 1, size: 100, total: 0, }); const total = ref(0); const tableData = ref([]); const fileList = ref([]); const tableLoading = ref(false); @@ -124,7 +118,7 @@ const getList = () => { fileListPage({accountId: currentId.value,accountType:accountType.value, ...page}).then(res => { tableData.value = res.data.records; total.value = res.data.total; page.total = res.data.total; }) } // è¡¨æ ¼éæ©æ°æ® src/views/equipmentManagement/measurementEquipment/index.vue
@@ -23,6 +23,7 @@ <el-button type="primary" @click="handleQuery" style="margin-left: 10px" >æç´¢</el-button > <el-button @click="handleReset" style="margin-left: 10px">éç½®</el-button> </div> <div> <el-button type="primary" @click="openForm('add')">æ°å¢è®¡éå¨å ·</el-button> @@ -51,7 +52,7 @@ </template> <script setup> import {onMounted, ref} from "vue"; import {onMounted, ref, reactive, toRefs, getCurrentInstance, nextTick} from "vue"; import FormDia from "@/views/equipmentManagement/measurementEquipment/components/formDia.vue"; import {ElMessageBox} from "element-plus"; import useUserStore from "@/store/modules/user.js"; @@ -82,14 +83,8 @@ align:"center" }, { label: "é¨é¨", prop: "deptName", width: 130, align:"center" }, { label: "å®è£ ä½ç½®", prop: "installationLocation", prop: "instationLocation", width: 150, align:"center" }, @@ -207,6 +202,15 @@ page.current = 1; getList(); }; // éç½®æç´¢æ¡ä»¶ const handleReset = () => { searchForm.value.recordDate = ""; searchForm.value.code = ""; searchForm.value.status = ""; page.current = 1; getList(); }; const pagination = (obj) => { page.current = obj.page; page.size = obj.limit; src/views/equipmentManagement/repair/index.vue
@@ -136,7 +136,6 @@ </template> <script setup> import { usePaginationApi } from "@/hooks/usePaginationApi"; import { onMounted, getCurrentInstance, computed } from "vue"; import {usePaginationApi} from "@/hooks/usePaginationApi"; import {getRepairPage, delRepair} from "@/api/equipmentManagement/repair"; src/views/equipmentManagement/upkeep/index.vue
@@ -171,13 +171,14 @@ <el-tag v-if="row.status === 0" type="warning">å¾ ä¿å »</el-tag> </template> <template #operation="{ row }"> <el-button <!-- è¿ä¸ªåè½è·æ°å¢ä¿å »åè½ä¸æ¨¡ä¸æ ·ï¼æå¥æä¹ï¼ --> <!-- <el-button type="primary" text @click="addMaintain(row)" > æ°å¢ä¿å » </el-button> </el-button> --> <el-button type="primary" link src/views/financialManagement/financialStatements/index.vue
@@ -28,103 +28,135 @@ <main class="container mx-auto px-4 pb-10"> <!-- è´¢å¡ææ å¡ç --> <div class="grid-container"> <!-- æ»æ¶å ¥ --> <el-card class="bg1"> <p>æ»æ¶å ¥</p> <h3> Â¥{{ pageInfo.totalIncome }} </h3> </el-card> <!-- æ¶å ¥ç¬æ° --> <el-card class="bg2"> <p>æ¶å ¥ç¬æ°</p> <h3> {{ pageInfo.incomeNumber }} </h3> </el-card> <div class="stats-cards"> <!-- æ»è¥æ¶ --> <div class="stat-card stat-card-blue"> <div class="stat-icon"> <img src="@/assets/icons/png/walletBlue@2x.png" alt="æ»è¥æ¶" /> </div> <div class="stat-content"> <div class="stat-label">æ»è¥æ¶</div> <div class="stat-value">{{ formatMoney(pageInfo.totalIncome || 0) }} å </div> </div> </div> <!-- æ»æ¯åº --> <el-card class="bg3"> <p>æ»æ¯åº</p> <h3> Â¥{{ pageInfo.totalExpense }} </h3> </el-card> <div class="stat-card stat-card-orange"> <div class="stat-icon"> <img src="@/assets/icons/png/walletOrange@2x.png" alt="æ»æ¯åº" /> </div> <div class="stat-content"> <div class="stat-label">æ»æ¯åº</div> <div class="stat-value">{{ formatMoney(pageInfo.totalExpense || 0) }} å </div> </div> </div> <!-- æ¯åºç¬æ° --> <el-card class="bg4"> <p>æ¯åºç¬æ°</p> <h3> {{ pageInfo.expenseNumber }} </h3> </el-card> <!-- æ»æ¶å ¥ç¬æ° --> <div class="stat-card stat-card-green"> <div class="stat-icon"> <img src="@/assets/icons/png/walletGreen@2x.png" alt="æ»æ¶å ¥ç¬æ°" /> </div> <div class="stat-content"> <div class="stat-label">æ»æ¶å ¥ç¬æ°</div> <div class="stat-value">{{ pageInfo.incomeNumber || 0 }} ç¬</div> </div> </div> <!-- æ»æ¯åºç¬æ° --> <div class="stat-card stat-card-red"> <div class="stat-icon"> <img src="@/assets/icons/png/walletRed@2x.png" alt="æ»æ¯åºç¬æ°" /> </div> <div class="stat-content"> <div class="stat-label">æ»æ¯åºç¬æ°</div> <div class="stat-value">{{ pageInfo.expenseNumber || 0 }} ç¬</div> </div> </div> <!-- åæ¶å ¥ --> <el-card class="bg5"> <p>åæ¶å ¥</p> <h3> Â¥{{ pageInfo.netRevenue }} </h3> <div class="stat-card stat-card-yellow"> <div class="stat-icon"> <img src="@/assets/icons/png/walletYellow@2x.png" alt="åæ¶å ¥" /> </div> <div class="stat-content"> <div class="stat-label">åæ¶å ¥</div> <div class="stat-value">{{ formatMoney(pageInfo.netRevenue || 0) }} å </div> </div> </div> </div> <!-- ä¸é´å¾è¡¨åºå --> <div class="charts-row"> <!-- å·¦ä¾§ï¼æ¶å ¥æ¯åºåæ --> <el-card class="chart-card"> <h2 class="section-title">æ¶å ¥æ¯åºåæ</h2> <div class="pie-chart-container"> <Echarts :legend="pieLegendIncomeExpense" :chartStyle="chartStylePie" :series="pieSeriesIncomeExpense" :tooltip="pieTooltipIncomeExpense" style="height: 320px; width: 100%;"> </Echarts> <div class="pie-stats"> <div class="bar-stat-item"> <span class="bar-stat-label">æ¶å ¥æ°é</span> <span class="bar-stat-value">{{ pageInfo.incomeNumber || 0 }}</span> </div> <div class="bar-stat-item"> <span class="bar-stat-label">æ¯åºæ°é</span> <span class="bar-stat-value">{{ pageInfo.expenseNumber || 0 }}</span> </div> </div> </div> </el-card> <!-- å³ä¾§ï¼è¡é¡¹çå©åæ --> <el-card class="chart-card"> <h2 class="section-title">è¡é¡¹çå©åæ</h2> <div class="bar-chart-header"> <div class="bar-stat-item"> <span class="bar-stat-label">å½åæ»ä¸ªæ°</span> <span class="bar-stat-value">{{ allBarTypes.value?.length || 0 }}</span> </div> <div class="bar-stat-item"> <span class="bar-stat-label">æ¯åºéé¢</span> <span class="bar-stat-value">{{ formatMoney(pageInfo.totalExpense || 0) }}</span> </div> <div class="bar-stat-item"> <span class="bar-stat-label">æ¶å ¥éé¢</span> <span class="bar-stat-value">{{ formatMoney(pageInfo.totalIncome || 0) }}</span> </div> </div> <Echarts ref="barChart" :chartStyle="chartStyle" :grid="barGrid" :legend="barLegend" :series="barSeries" :tooltip="barTooltip" :xAxis="barXAxis" :yAxis="barYAxis" style="height: 300px; width: 100%;"> </Echarts> </el-card> </div> <!-- æ¶å ¥ç»è®¡å¾è¡¨ --> <div class="grid-layout"> <el-card style="margin-bottom: 20px;"> <h2 class="section-title">æ¶å ¥ç»è®¡(å )</h2> <div class="echarts"> <Echarts :legend="pieLegend0" :chartStyle="chartStylePie" :series="materialPieSeries0" :tooltip="pieTooltip" style="height: 260px;width: 35%;"> <div class="chart-num"> <span style="font-size: 22px;">æ¶å ¥</span> <span style="font-size: 36px; font-weight: 500; font-family: 'MyCustomFont', sans-serif;">{{ pageInfo.totalIncome }}</span> </div> </Echarts> <Echarts ref="chart" :chartStyle="chartStyle" :grid="grid" :legend="lineLegend" :series="lineSeries0" :tooltip="tooltip" :xAxis="xAxis0" :yAxis="yAxis0" style="height: 260px;width: 64%;"></Echarts> </div> </el-card> <!-- æ¯åºç»è®¡å¾è¡¨ --> <el-card> <h2 class="section-title">æ¯åºç»è®¡(å )</h2> <div class="echarts"> <Echarts ref="chart" :legend="pieLegend1" :chartStyle="chartStylePie" :series="materialPieSeries1" :tooltip="pieTooltip" style="height: 260px;width: 35%;"> <div class="chart-num"> <span style="font-size: 22px;">æ¯åº</span> <span style="font-size: 36px; font-weight: 500; font-family: 'MyCustomFont', sans-serif;">{{ pageInfo.totalExpense }}</span> </div></Echarts> <Echarts ref="chart" :chartStyle="chartStyle" :grid="grid" :legend="lineLegend" :series="lineSeries1" :tooltip="tooltip" :xAxis="xAxis1" :yAxis="yAxis1" style="height: 260px;width: 64%;"></Echarts> </div> </el-card> </div> <!-- åºé¨ï¼è¥æ¶è¶å¿åæ --> <el-card class="trend-chart-card"> <h2 class="section-title">è¥æ¶è¶å¿åæ</h2> <Echarts ref="trendChart" :chartStyle="chartStyle" :grid="grid" :legend="trendLegend" :series="trendSeries" :tooltip="tooltip" :xAxis="xAxis0" :yAxis="trendYAxis" style="height: 350px; width: 100%;"> </Echarts> </el-card> </main> </div> </template> @@ -334,6 +366,262 @@ const pageInfo = ref({ }) // æ ¼å¼åéé¢ const formatMoney = (value) => { if (!value && value !== 0) return '0'; return Number(value).toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 }); }; // æ¶å ¥æ¯åºåæé¥¼å¾ const pieDataIncomeExpense = computed(() => { const totalIncome = Number(pageInfo.value.totalIncome) || 0; const totalExpense = Number(pageInfo.value.totalExpense) || 0; const total = totalIncome + totalExpense; if (total === 0) { return [ { name: 'æ¶å ¥', value: 0, percent: '0%' }, { name: 'æ¯åº', value: 0, percent: '0%' } ]; } const incomePercent = ((totalIncome / total) * 100).toFixed(0); const expensePercent = ((totalExpense / total) * 100).toFixed(0); return [ { name: 'æ¶å ¥', value: totalIncome, percent: `${incomePercent}%` }, { name: 'æ¯åº', value: totalExpense, percent: `${expensePercent}%` } ]; }); const pieLegendIncomeExpense = computed(() => ({ show: false })); const pieTooltipIncomeExpense = reactive({ trigger: 'item', formatter: function(params) { if (!params.data) return params.name; return `${params.name}å æ¯ ${params.percent}%`; } }); const pieSeriesIncomeExpense = computed(() => [ { type: 'pie', radius: ['0%', '70%'], center: ['50%', '50%'], avoidLabelOverlap: true, itemStyle: { borderColor: '#fff', borderWidth: 2 }, label: { show: true, position: 'outside', formatter: function(params) { return `${params.name}å æ¯ ${params.percent}%`; }, fontSize: 14, color: '#333' }, labelLine: { show: true, length: 15, length2: 10, lineStyle: { color: '#333' } }, emphasis: { label: { show: true, fontSize: 16, fontWeight: 'bold' } }, data: pieDataIncomeExpense.value, color: ['#1890FF', '#FACC14'] } ]); // è¡é¡¹çå©åææ±ç¶å¾ const barXAxis = computed(() => { return [{ type: 'category', data: (allBarTypes.value && allBarTypes.value.length > 0) ? allBarTypes.value : ['项ç®1', '项ç®2', '项ç®3', '项ç®4', '项ç®5', '项ç®6', '项ç®7'], axisTick: { show: true, alignWithLabel: true }, }]; }); const barYAxis = [{ type: 'value', name: 'åä½: å ', position: 'left', min: 0, nameTextStyle: { color: '#000', fontSize: 14, }, }]; const barGrid = { left: '3%', right: '4%', bottom: '3%', containLabel: true }; const barLegend = { show: true, top: 10, right: 10, }; // è·åææç±»ååç§° const allBarTypes = computed(() => { const incomeTypes = (lineSeries0.value || []).map(item => item.name || item.typeName).filter(Boolean); const expenseTypes = (lineSeries1.value || []).map(item => item.name || item.typeName).filter(Boolean); return [...new Set([...incomeTypes, ...expenseTypes])]; }); const barSeries = computed(() => { if (allBarTypes.value.length === 0) { return [ { name: 'æ¯åº', type: 'bar', data: [], itemStyle: { color: '#1890FF' } }, { name: 'æ¶å ¥', type: 'bar', data: [], itemStyle: { color: '#13C2C2' } } ]; } // è®¡ç®æ¯ä¸ªé¡¹ç®çæ»æ¶å ¥ï¼æ±æ»æææä»½ï¼ const incomeData = allBarTypes.value.map(typeName => { const incomeItem = (lineSeries0.value || []).find(item => (item.name || item.typeName) === typeName); if (incomeItem && incomeItem.data && Array.isArray(incomeItem.data)) { return incomeItem.data.reduce((sum, val) => sum + (Number(val) || 0), 0); } return 0; }); // è®¡ç®æ¯ä¸ªé¡¹ç®çæ»æ¯åºï¼æ±æ»æææä»½ï¼ const expenseData = allBarTypes.value.map(typeName => { const expenseItem = (lineSeries1.value || []).find(item => (item.name || item.typeName) === typeName); if (expenseItem && expenseItem.data && Array.isArray(expenseItem.data)) { return expenseItem.data.reduce((sum, val) => sum + (Number(val) || 0), 0); } return 0; }); return [ { name: 'æ¯åº', type: 'bar', data: expenseData, itemStyle: { color: '#1890FF' } }, { name: 'æ¶å ¥', type: 'bar', data: incomeData, itemStyle: { color: '#13C2C2' } } ]; }); const barTooltip = reactive({ trigger: 'axis', axisPointer: { type: 'shadow' }, formatter: function (params) { if (!params || !params.length) return ''; const axisLabel = params[0].axisValueLabel || params[0].axisValue || ''; const rows = params .map(p => { const colorDot = `<span style="display:inline-block;margin-right:6px;width:8px;height:8px;border-radius:50%;background:${p.color}"></span>`; const value = typeof p.value === 'number' ? p.value.toFixed(2) : p.value; return `${colorDot}${p.seriesName} ${value}`; }) .join('<br/>'); return `<div>${axisLabel}</div><div>${rows}</div>`; } }); // è¥æ¶è¶å¿åæ const trendLegend = { show: true, top: 10, right: 10, }; const trendYAxis = [{ type: 'value', name: 'åä½: å ', position: 'left', min: 0, nameTextStyle: { color: '#000', fontSize: 14, }, }]; const trendSeries = computed(() => { // æ±æ»æææ¯åºç±»åçæ°æ® let expenseTrend = []; if (lineSeries1.value.length > 0) { const monthCount = Math.max(...lineSeries1.value.map(item => item.data?.length || 0)); expenseTrend = Array(monthCount).fill(0); lineSeries1.value.forEach(item => { if (item.data && Array.isArray(item.data)) { item.data.forEach((val, index) => { if (index < monthCount) { expenseTrend[index] += Number(val) || 0; } }); } }); } // æ±æ»æææ¶å ¥ç±»åçæ°æ® let incomeTrend = []; if (lineSeries0.value.length > 0) { const monthCount = Math.max(...lineSeries0.value.map(item => item.data?.length || 0)); incomeTrend = Array(monthCount).fill(0); lineSeries0.value.forEach(item => { if (item.data && Array.isArray(item.data)) { item.data.forEach((val, index) => { if (index < monthCount) { incomeTrend[index] += Number(val) || 0; } }); } }); } return [ { name: 'æ¯åº', type: 'line', data: expenseTrend, itemStyle: { color: '#1890FF' }, smooth: true }, { name: 'æ¶å ¥', type: 'line', data: incomeTrend, itemStyle: { color: '#13C2C2' }, smooth: true } ]; }); // è·åæè¿å 个æçèå´ const getLastSixMonths = () => { const endMonth = dayjs().format('YYYY-MM'); @@ -487,111 +775,220 @@ :root { --el-color-primary: #4f46e5; } .el-card{ position: relative; border-radius: 12px; padding: 14px 10px 10px 10px; box-shadow: 0 2px 8px #eee; :deep(.el-card__body){ padding: 10px 20px !important; } &.bg1{ background: url(@/assets/icons/png/1.png) no-repeat 100% 100% !important; } &.bg2{ background: url(@/assets/icons/png/2.png) no-repeat 100% 100% !important; } &.bg3{ background: url(@/assets/icons/png/3.png) no-repeat 100% 100% !important; } &.bg4{ background: url(@/assets/icons/png/4.png) no-repeat 100% 100% !important; } &.bg5{ background: url(@/assets/icons/png/5.png) no-repeat 100% 100% !important; } } .grid-container { /* grid 容å¨åºç¡æ ·å¼ */ /* ç»è®¡å¡çæ ·å¼ */ .stats-cards { display: grid; gap: 1rem; /* gap-4 å¯¹åº 1rem (16px) */ margin-bottom: 2rem; /* mb-8 å¯¹åº 2rem (32px) */ grid-template-columns: repeat(5, 1fr); gap: 20px; margin-bottom: 20px; } .stat-card { background: #fff; border: 1px solid #e4e7ed; border-radius: 8px; padding: 20px; display: flex; align-items: center; gap: 15px; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05); transition: all 0.3s; p{ font-size: 22px; margin-top: 0px; color: #fff; } h3{ font-size: 36px; font-weight: 500; font-family: 'MyCustomFont', sans-serif; margin: 10px 0; color: #fff; &:hover { box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); transform: translateY(-2px); } } /* ç§»å¨ç«¯é»è®¤æ ·å¼ (grid-cols-1) */ .grid-container { grid-template-columns: repeat(1, minmax(0, 1fr)); } /* å°å±å¹åä»¥ä¸ (sm:grid-cols-2) */ @media (min-width: 640px) { .grid-container { grid-template-columns: repeat(2, minmax(0, 1fr)); .stat-icon { width: 48px; height: 48px; flex-shrink: 0; img { width: 100%; height: 100%; object-fit: contain; } } .stat-content { flex: 1; display: flex; flex-direction: column; gap: 8px; } .stat-label { font-size: 14px; color: #666; line-height: 1.2; } .stat-value { font-size: 24px; font-weight: 600; color: #333; line-height: 1.2; } .stat-trend { font-size: 12px; line-height: 1.2; &.trend-up { color: #f56c6c; } &.trend-down { color: #67c23a; } } } /* 大å±å¹åä»¥ä¸ (lg:grid-cols-5) */ @media (min-width: 1024px) { .grid-container { grid-template-columns: repeat(5, minmax(0, 1fr)); /* å¾è¡¨è¡å¸å± */ .charts-row { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; margin-bottom: 20px; } .chart-card { border: 1px solid #e4e7ed; border-radius: 8px; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05); :deep(.el-card__body) { padding: 20px !important; } } /* å¡çæ¬åææå¢å¼º */ .el-card:hover { transform: translateY(-2px); .trend-chart-card { border: 1px solid #e4e7ed; border-radius: 8px; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05); :deep(.el-card__body) { padding: 20px !important; } } .echarts{ /* 饼å¾å®¹å¨ */ .pie-chart-container { position: relative; .pie-stats { display: flex; justify-content: space-between; gap: 20px; margin-top: 20px; .bar-stat-item { display: flex; flex-direction: column; align-items: center; gap: 8px; padding: 15px; background: #f5f7fa; border-radius: 6px; flex: 1; .bar-stat-label { font-size: 14px; color: #666; } .bar-stat-value { font-size: 18px; font-weight: 600; color: #333; } } } } /* æ±ç¶å¾å¤´é¨ç»è®¡ */ .bar-chart-header { display: flex; justify-content: space-between; gap: 20px; margin-bottom: 20px; .bar-stat-item { display: flex; flex-direction: column; align-items: center; gap: 8px; padding: 15px; background: #f5f7fa; border-radius: 6px; flex: 1; .bar-stat-label { font-size: 14px; color: #666; } .bar-stat-value { font-size: 18px; font-weight: 600; color: #333; } } } /* å¾è¡¨å®¹å¨æ ·å¼ */ .el-chart { width: 100%; height: 100%; } /* æ 颿 ·å¼ */ .section-title { position: relative; font-size: 18px; color: #333; padding-left: 10px; margin-bottom: 10px; font-weight: 700; position: relative; font-size: 18px; color: #333; padding-left: 12px; margin-bottom: 20px; font-weight: 700; &::before { position: absolute; left: 0; top: 2px; content: ''; width: 4px; height: 18px; background-color: #002FA7; border-radius: 2px; } } .section-title::before { position: absolute; left: 0; top: 0px; content: ''; width: 4px; height: 18px; background-color: #002FA7; border-radius: 2px; /* ååºå¼è®¾è®¡ */ @media (max-width: 1400px) { .stats-cards { grid-template-columns: repeat(3, 1fr); } } .chart-num{ position: absolute; z-index: 3; top: 92px; left: 92px; display: flex; flex-direction: column; justify-content: center; @media (max-width: 1024px) { .stats-cards { grid-template-columns: repeat(2, 1fr); } .charts-row { grid-template-columns: 1fr; } } @media (max-width: 640px) { .stats-cards { grid-template-columns: 1fr; } } </style> src/views/inventoryManagement/dispatchLog/Record.vue
@@ -214,7 +214,7 @@ type: "warning", }) .then(() => { proxy.download("/stockmanagement/export", {}, "åºåºå°è´¦.xlsx"); proxy.download("/stockOutRecord/exportStockOutRecord", {type: props.type}, props.type === '0' ? "åæ ¼åºåºå°è´¦.xlsx" : "ä¸åæ ¼åºåºå°è´¦.xlsx"); }) .catch(() => { proxy.$modal.msg("已忶"); src/views/inventoryManagement/receiptManagement/Record.vue
@@ -203,8 +203,7 @@ }) .then(() => { // æ ¹æ®ä¸åç tab ç±»åè°ç¨ä¸åçå¯¼åºæ¥å£ let exportUrl = "/stockin/export"; proxy.download(exportUrl, {}, "å ¥åºå°è´¦.xlsx"); proxy.download("/stockInRecord/exportStockInRecord", {type: props.type}, props.type === '0' ? "åæ ¼å ¥åº.xlsx" : "ä¸åæ ¼å ¥åº.xlsx"); }) .catch(() => { proxy.$modal.msg("已忶"); src/views/inventoryManagement/stockManagement/Import.vue
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,86 @@ <template> <el-dialog v-model="isShow" title="å¯¼å ¥åºå" @close="closeModal"> <FileUpload ref="fileUploadRef" accept=".xlsx, .xls" :headers="upload.headers" :action="upload.url" :disabled="upload.isUploading" :showTip="false" @success="handleFileSuccess" /> <template #footer> <div class="dialog-footer"> <el-button type="primary" @click="submitFileForm">ç¡® å®</el-button> <el-button @click="closeModal">å æ¶</el-button> </div> </template> </el-dialog> </template> <script setup> import {computed, reactive} from "vue"; import { getToken } from "@/utils/auth.js"; import { FileUpload } from "@/components/Upload"; import { ElMessage } from "element-plus"; defineOptions({ name: "å¯¼å ¥åºå", }); const props = defineProps({ visible: { type: Boolean, required: true, }, type: { type: String, required: true, default: 'qualified', }, }); const emit = defineEmits(['update:visible', 'uploadSuccess']); const isShow = computed({ get() { return props.visible; }, set(val) { emit('update:visible', val); }, }); const fileUploadRef = ref(); const upload = reactive({ // æ¯å¦æ¾ç¤ºå¼¹åºå±ï¼åºåå¯¼å ¥ï¼ open: false, // æ¯å¦ç¦ç¨ä¸ä¼ isUploading: false, // 设置ä¸ä¼ ç请æ±å¤´é¨ headers: { Authorization: "Bearer " + getToken() }, // ä¸ä¼ çå°å url: import.meta.env.VITE_APP_BASE_API + "/stockInventory/importStockInventory", }); const submitFileForm = () => { fileUploadRef.value.uploadApi(); }; const handleFileSuccess = (response) => { const { code, msg } = response; if (code == 200) { ElMessage({ message: "å¯¼å ¥æå", type: "success" }); emit('uploadSuccess'); closeModal(); } else { ElMessage({ message: msg, type: "error" }); } }; const closeModal = () => { isShow.value = false; }; </script> src/views/inventoryManagement/stockManagement/Qualified.vue
@@ -11,6 +11,10 @@ </div> <div> <el-button type="primary" @click="isShowNewModal = true">æ°å¢åºå</el-button> <el-button @click="importTemplate">ä¸è½½å¯¼å ¥æ¨¡æ¿</el-button> <el-button type="info" plain icon="Upload" @click="isShowImportModal = true"> å¯¼å ¥åºå </el-button> <el-button @click="handleOut">导åº</el-button> </div> </div> @@ -45,17 +49,22 @@ v-model:visible="isShowSubtractModal" :record="record" @completed="handleQuery" /> <!-- å¯¼å ¥åºå--> <import-stock-inventory v-if="isShowImportModal" v-model:visible="isShowImportModal" type="qualified" @uploadSuccess="handleQuery" /> </div> </template> <script setup> import pagination from '@/components/PIMTable/Pagination.vue' import { ref, reactive, toRefs, onMounted, getCurrentInstance } from 'vue' import { ElMessageBox } from "element-plus"; import {ElMessage, ElMessageBox} from "element-plus"; import { getStockInventoryListPage } from "@/api/inventoryManagement/stockInventory.js"; const NewStockInventory = defineAsyncComponent(() => import("@/views/inventoryManagement/stockManagement/New.vue")); const SubtractStockInventory = defineAsyncComponent(() => import("@/views/inventoryManagement/stockManagement/Subtract.vue")); const ImportStockInventory = defineAsyncComponent(() => import("@/views/inventoryManagement/stockManagement/Import.vue")); const { proxy } = getCurrentInstance() const tableData = ref([]) const selectedRows = ref([]) @@ -70,6 +79,8 @@ const isShowNewModal = ref(false) // æ¯å¦æ¾ç¤ºé¢ç¨å¼¹æ¡ const isShowSubtractModal = ref(false) // æ¯å¦æ¾ç¤ºå¯¼å ¥å¼¹æ¡ const isShowImportModal = ref(false) const data = reactive({ searchForm: { productName: '', @@ -100,6 +111,17 @@ tableLoading.value = false }) } const handleFileSuccess = (response) => { const { code, msg } = response; if (code == 200) { ElMessage({ message: "å¯¼å ¥æå", type: "success" }); upload.open = false; emits("uploadSuccess"); } else { ElMessage({ message: msg, type: "error" }); } }; // ç¹å»é¢ç¨ const showSubtractModal = (row) => { @@ -135,12 +157,16 @@ type: 'warning', } ).then(() => { proxy.download("/stockin/exportCopy", {}, 'åºåä¿¡æ¯.xlsx') proxy.download("/stockInventory/exportStockInventory", {}, 'åæ ¼åºåä¿¡æ¯.xlsx') }).catch(() => { proxy.$modal.msg("已忶") }) } const importTemplate =() =>{ proxy.download("/stockInventory/downloadStockInventory", {}, "åºåå¯¼å ¥æ¨¡æ¿.xlsx"); } onMounted(() => { getList() }) src/views/inventoryManagement/stockManagement/Unqualified.vue
@@ -135,7 +135,7 @@ type: 'warning', } ).then(() => { proxy.download("/stockin/exportCopy", {}, 'åºåä¿¡æ¯.xlsx') proxy.download("/stockUninventory/exportStockUninventory", {}, 'ä¸åæ ¼åºåä¿¡æ¯.xlsx') }).catch(() => { proxy.$modal.msg("已忶") }) src/views/procurementManagement/procurementInvoiceLedger/Form/EditForm.vue
@@ -23,17 +23,28 @@ </el-col> <el-col :span="12"> <el-form-item label="å票å·ï¼"> <el-input disabled v-model="form.invoiceNumber" /> <el-input disabled v-model="form.invoiceNumber" /> </el-form-item> </el-col> <el-col :span="12"> <el-form-item label="æ¥ç¥¨æ°ï¼"> <el-input-number :step="0.1" :min="0" style="width: 100%" v-model="form.ticketsNum" @change="inputTicketsNum" :precision="2"/> <el-input-number :step="0.1" :min="0" style="width: 100%" v-model="form.ticketsNum" @change="inputTicketsNum" :precision="2" /> </el-form-item> </el-col> <el-col :span="12"> <el-form-item label="æ¬æ¬¡æ¥ç¥¨éé¢(å )ï¼"> <el-input-number :step="0.1" :min="0" style="width: 100%" v-model="form.ticketsAmount" @change="inputTicketsAmount" :precision="2"/> <el-input-number :step="0.1" :min="0" style="width: 100%" v-model="form.ticketsAmount" @change="inputTicketsAmount" :precision="2" /> </el-form-item> </el-col> <el-col :span="12"> @@ -46,83 +57,92 @@ </template> <script setup> import useFormData from "@/hooks/useFormData"; import { getProductRecordById } from "@/api/procurementManagement/procurementInvoiceLedger"; const { proxy } = getCurrentInstance() import useFormData from "@/hooks/useFormData"; import { getProductRecordById } from "@/api/procurementManagement/procurementInvoiceLedger"; const { proxy } = getCurrentInstance(); defineOptions({ name: "æ¥ç¥¨å°è´¦è¡¨å", }); const temFutureTickets = ref(0) const { form, resetForm } = useFormData({ id: undefined, purchaseContractNumber: undefined, // éè´ååå· salesContractNo: undefined, // éå®ååå· createdAt: undefined, // å建æ¶é´ invoiceNumber: undefined, // åç¥¨å· ticketsNum: undefined, // æ¥ç¥¨æ° ticketsAmount: undefined, // æ¥ç¥¨éé¢ taxInclusiveUnitPrice: undefined, // å«ç¨åä»· }); defineOptions({ name: "æ¥ç¥¨å°è´¦è¡¨å", }); const temFutureTickets = ref(0); const { form, resetForm } = useFormData({ id: undefined, purchaseContractNumber: undefined, // éè´ååå· salesContractNo: undefined, // éå®ååå· createdAt: undefined, // å建æ¶é´ invoiceNumber: undefined, // åç¥¨å· ticketsNum: undefined, // æ¥ç¥¨æ° ticketsAmount: undefined, // æ¥ç¥¨éé¢ taxInclusiveUnitPrice: undefined, // å«ç¨åä»· }); const load = async (id) => { const { code, data } = await getProductRecordById({ id }); if (code === 200) { form.id = data.id; form.purchaseContractNumber = data.purchaseContractNumber; form.salesContractNo = data.salesContractNo; form.createdAt = data.createdAt; form.invoiceNumber = data.invoiceNumber; form.ticketsNum = data.ticketsNum; form.ticketsAmount = data.ticketsAmount.toFixed(2); form.taxInclusiveUnitPrice = data.taxInclusiveUnitPrice; form.futureTickets = data.futureTickets; temFutureTickets.value = data.futureTickets; } }; const load = async (id, purchaseLedgerId, productModelId) => { const { code, data } = await getProductRecordById({ id: id, purchaseLedgerId: purchaseLedgerId, productModelId: productModelId, }); if (code === 200) { form.id = data.id; form.purchaseContractNumber = data.purchaseContractNumber; form.salesContractNo = data.salesContractNo; form.createdAt = data.createdAt; form.invoiceNumber = data.invoiceNumber; form.ticketsNum = data.ticketsNum; form.ticketsAmount = data.ticketsAmount.toFixed(2); form.taxInclusiveUnitPrice = data.taxInclusiveUnitPrice; form.futureTickets = data.futureTickets; temFutureTickets.value = data.futureTickets; } }; const inputTicketsNum = (val) => { // ç¡®ä¿å«ç¨åä»·åå¨ä¸ä¸ä¸ºé¶ if (!form.taxInclusiveUnitPrice || Number(form.taxInclusiveUnitPrice) === 0) { proxy.$modal.msgWarning("å«ç¨åä»·ä¸è½ä¸ºé¶ææªå®ä¹"); return; } if (Number(form.ticketsNum) > Number(temFutureTickets.value)) { proxy.$modal.msgWarning("å¼ç¥¨æ°ä¸å¾å¤§äºæªå¼ç¥¨æ°"); form.ticketsNum = temFutureTickets.value } // ç¡®ä¿æææ°å¼é½è½¬æ¢ä¸ºæ°åç±»åè¿è¡è®¡ç® const ticketsAmount = Number(form.ticketsNum) * Number(form.taxInclusiveUnitPrice); const futureTickets = Number(temFutureTickets.value) - Number(form.ticketsNum); form.futureTickets = Number(futureTickets.toFixed(2)); form.ticketsAmount = Number(ticketsAmount.toFixed(2)); }; const inputTicketsAmount = (val) => { // ç¡®ä¿å«ç¨åä»·åå¨ä¸ä¸ä¸ºé¶ if (!form.taxInclusiveUnitPrice || Number(form.taxInclusiveUnitPrice) === 0) { proxy.$modal.msgWarning("å«ç¨åä»·ä¸è½ä¸ºé¶ææªå®ä¹"); return; } if (Number(val) > Number(form.futureTickets*form.taxInclusiveUnitPrice)) { proxy.$modal.msgWarning("æ¬æ¬¡æ¥ç¥¨éé¢ä¸å¾å¤§äºæ»éé¢"); form.ticketsAmount = (form.futureTickets*form.taxInclusiveUnitPrice).toFixed(2) const ticketsNum = Number(form.ticketsAmount) / Number(form.taxInclusiveUnitPrice); form.ticketsNum = Number(ticketsNum.toFixed(2)) return; } // ç¡®ä¿æææ°å¼é½è½¬æ¢ä¸ºæ°åç±»åè¿è¡è®¡ç® const ticketsNum = Number(val) / Number(form.taxInclusiveUnitPrice); form.ticketsNum = Number(ticketsNum.toFixed(2)); }; const inputTicketsNum = val => { // ç¡®ä¿å«ç¨åä»·åå¨ä¸ä¸ä¸ºé¶ if (!form.taxInclusiveUnitPrice || Number(form.taxInclusiveUnitPrice) === 0) { proxy.$modal.msgWarning("å«ç¨åä»·ä¸è½ä¸ºé¶ææªå®ä¹"); return; } if (Number(form.ticketsNum) > Number(temFutureTickets.value)) { proxy.$modal.msgWarning("å¼ç¥¨æ°ä¸å¾å¤§äºæªå¼ç¥¨æ°"); form.ticketsNum = temFutureTickets.value; } defineExpose({ load, form, resetForm, }); // ç¡®ä¿æææ°å¼é½è½¬æ¢ä¸ºæ°åç±»åè¿è¡è®¡ç® const ticketsAmount = Number(form.ticketsNum) * Number(form.taxInclusiveUnitPrice); const futureTickets = Number(temFutureTickets.value) - Number(form.ticketsNum); form.futureTickets = Number(futureTickets.toFixed(2)); form.ticketsAmount = Number(ticketsAmount.toFixed(2)); }; const inputTicketsAmount = val => { // ç¡®ä¿å«ç¨åä»·åå¨ä¸ä¸ä¸ºé¶ if (!form.taxInclusiveUnitPrice || Number(form.taxInclusiveUnitPrice) === 0) { proxy.$modal.msgWarning("å«ç¨åä»·ä¸è½ä¸ºé¶ææªå®ä¹"); return; } if (Number(val) > Number(form.futureTickets * form.taxInclusiveUnitPrice)) { proxy.$modal.msgWarning("æ¬æ¬¡æ¥ç¥¨éé¢ä¸å¾å¤§äºæ»éé¢"); form.ticketsAmount = ( form.futureTickets * form.taxInclusiveUnitPrice ).toFixed(2); const ticketsNum = Number(form.ticketsAmount) / Number(form.taxInclusiveUnitPrice); form.ticketsNum = Number(ticketsNum.toFixed(2)); return; } // ç¡®ä¿æææ°å¼é½è½¬æ¢ä¸ºæ°åç±»åè¿è¡è®¡ç® const ticketsNum = Number(val) / Number(form.taxInclusiveUnitPrice); form.ticketsNum = Number(ticketsNum.toFixed(2)); }; defineExpose({ load, form, resetForm, }); </script> <style lang="scss" scoped></style> src/views/procurementManagement/procurementInvoiceLedger/Modal/EditModal.vue
@@ -1,62 +1,66 @@ <template> <el-dialog :title="modalOptions.title" v-model="visible" @close="close"> <el-dialog :title="modalOptions.title" v-model="visible" @close="close"> <EditForm ref="editFormRef" /> <template #footer> <el-button type="primary" :loading="loading" @click="sendForm"> {{ modalOptions.confirmText }} </el-button> <el-button type="primary" :loading="loading" @click="sendForm"> {{ modalOptions.confirmText }} </el-button> <el-button @click="closeModal">{{ modalOptions.cancelText }}</el-button> </template> </el-dialog> </template> <script setup> import { useModal } from "@/hooks/useModal"; import EditForm from "../Form/EditForm.vue"; import { updateRegistration } from "@/api/procurementManagement/procurementInvoiceLedger"; import { ElMessage } from "element-plus"; import { useModal } from "@/hooks/useModal"; import EditForm from "../Form/EditForm.vue"; import { updateRegistration } from "@/api/procurementManagement/procurementInvoiceLedger"; import { ElMessage } from "element-plus"; defineOptions({ name: "æ¥ç¥¨å°è´¦ç¼è¾", }); const emits = defineEmits(["success"]); defineOptions({ name: "æ¥ç¥¨å°è´¦ç¼è¾", }); const emits = defineEmits(["success"]); const saleLedgerProjectId = ref('') const editFormRef = ref(); const { id, visible, loading, openModal, modalOptions, handleConfirm, closeModal, } = useModal({ title: "æ¥ç¥¨å°è´¦" }); const saleLedgerProjectId = ref(""); const editFormRef = ref(); const { id, visible, loading, openModal, modalOptions, handleConfirm, closeModal, } = useModal({ title: "æ¥ç¥¨å°è´¦" }); const open = async (row) => { openModal(row.id); saleLedgerProjectId.value = row.saleLedgerProjectId; await nextTick(); editFormRef.value.load(row.id); }; const open = async row => { openModal(row.id); saleLedgerProjectId.value = row.saleLedgerProjectId; await nextTick(); editFormRef.value.load(row.id, row.purchaseLedgerId, row.productModelId); }; const close = () => { editFormRef.value.resetForm(); closeModal(); }; const close = () => { editFormRef.value.resetForm(); closeModal(); }; const sendForm = async () => { const form = editFormRef.value.form; form.saleLedgerProjectId = saleLedgerProjectId.value; const { code } = await updateRegistration(form); if (code === 200) { emits("success"); ElMessage({ message: "æä½æå", type: "success" }); close(); } }; const sendForm = async () => { const form = editFormRef.value.form; form.saleLedgerProjectId = saleLedgerProjectId.value; const { code } = await updateRegistration(form); if (code === 200) { emits("success"); ElMessage({ message: "æä½æå", type: "success" }); close(); } }; defineExpose({ open, }); defineExpose({ open, }); </script> src/views/procurementManagement/procurementLedger/index.vue
@@ -53,8 +53,7 @@ <div style="display: flex;justify-content: flex-end;margin-bottom: 20px;"> <el-button type="primary" @click="openForm('add')">æ°å¢å°è´¦</el-button> <el-button type="success" @click="openScanAddDialog">æ«ç æ°å¢</el-button> <el-button type="primary" plain @click="handleImport">å¯¼å ¥</el-button> <el-button @click="handleOut">导åº</el-button> <el-button type="danger" plain @@ -69,8 +68,7 @@ show-summary :summary-method="summarizeMainTable" @expand-change="expandChange" height="calc(100vh - 19em)" :row-class-name="tableRowClassName"> height="calc(100vh - 21.5em)"> <el-table-column align="center" type="selection" width="55" /> @@ -112,36 +110,28 @@ width="60" /> <el-table-column label="éè´ååå·" prop="purchaseContractNumber" width="200" width="160" show-overflow-tooltip /> <el-table-column label="éå®ååå·" prop="salesContractNo" width="160" show-overflow-tooltip /> <el-table-column label="ä¾åºååç§°" prop="supplierName" width="160" show-overflow-tooltip /> <el-table-column label="订åç¶æ" width="100" align="center"> <template #default="scope"> <el-tag v-if="scope.row.isInvalid" type="danger" size="small">失æ</el-tag> <el-tag v-else type="success" size="small">æ£å¸¸</el-tag> </template> </el-table-column> <el-table-column label="项ç®åç§°" prop="projectName" width="420" width="320" show-overflow-tooltip /> <el-table-column label="审æ¹ç¶æ" prop="approvalStatus" width="200" width="100" show-overflow-tooltip> <template #default="scope"> <el-tag size="small"> <el-tag :type="getApprovalStatusType(scope.row.approvalStatus)" size="small"> {{ approvalStatusText[scope.row.approvalStatus] || 'æªç¥ç¶æ' }} </el-tag> </template> @@ -169,17 +159,14 @@ show-overflow-tooltip /> <el-table-column fixed="right" label="æä½" width="180" width="120" align="center"> <template #default="scope"> <el-button link type="primary" size="small" @click="openForm('edit', scope.row)">ç¼è¾</el-button> <el-button link type="success" size="small" @click="showQRCode(scope.row)">çæäºç»´ç </el-button> @click="openForm('edit', scope.row)" :disabled="scope.row.approvalStatus !== 1 && scope.row.approvalStatus !== 4">ç¼è¾</el-button> <el-button link type="primary" size="small" @@ -194,10 +181,13 @@ :limit="page.size" @pagination="paginationChange" /> </div> <el-dialog v-model="dialogFormVisible" <FormDialog v-model="dialogFormVisible" :title="operationType === 'add' ? 'æ°å¢éè´å°è´¦é¡µé¢' : 'ç¼è¾éè´å°è´¦é¡µé¢'" width="70%" @close="closeDia"> :width="'70%'" :operation-type="operationType" @close="closeDia" @confirm="submitForm" @cancel="closeDia"> <el-form :model="form" label-width="140px" label-position="top" @@ -275,24 +265,12 @@ </el-row> <el-row :gutter="30"> <el-col :span="12"> <el-form-item label="审æ¹äººï¼" prop="approverId"> <el-select v-model="form.approverId" placeholder="è¯·éæ©å®¡æ¹äºº" clearable> <el-option v-for="item in userList" :key="item.userId" :label="item.nickName" :value="item.userId" /> </el-select> </el-form-item> <el-form-item label="å½å ¥äººï¼" prop="recorderId" v-show="false"> prop="recorderId"> <el-select v-model="form.recorderId" placeholder="è¯·éæ©" clearable disabled> filterable> <el-option v-for="item in userList" :key="item.userId" :label="item.nickName" @@ -313,6 +291,50 @@ </el-form-item> </el-col> </el-row> <el-row :gutter="30"> <el-col :span="24"> <el-form-item> <template #label> <div style="display: flex; align-items: center; justify-content: space-between; width: 100%;"> <span>审æ¹äººéæ©ï¼</span> <el-button type="primary" size="small" @click="addApproverNode" icon="Plus">æ°å¢èç¹</el-button> </div> </template> <div class="approver-nodes-container"> <div v-for="(node, index) in approverNodes" :key="node.id" class="approver-node-item" > <div class="approver-node-header"> <span class="approver-node-label">审æ¹èç¹ {{ index + 1 }}</span> <el-button v-if="approverNodes.length > 1" type="danger" size="small" text @click="removeApproverNode(index)" icon="Delete" >å é¤</el-button> </div> <el-select v-model="node.userId" placeholder="è¯·éæ©å®¡æ¹äºº" filterable style="width: 100%;" > <el-option v-for="user in userList" :key="user.userId" :label="user.nickName" :value="user.userId" /> </el-select> </div> </div> </el-form-item> </el-col> </el-row> <el-row> <el-form-item label="产åä¿¡æ¯ï¼" prop="entryDate"> @@ -323,29 +345,42 @@ @click="deleteProduct">å é¤</el-button> </el-form-item> <div class="select-button-group" style="width: 220px; margin: 20px 0;" style="width: 500px; margin: 20px 0;" v-if="operationType === 'add'"> <el-select filterable allow-create :reserve-keyword="true" :default-first-option="false" clearable v-model="templateName" :input-value="filterInputValue" @filter-change="onTemplateFilterChange" @change="onTemplateChange" style="width: 180px; border-right: none; border-radius: 4px 0 0 4px;" placeholder="è¯·éæ©" @focus="getTemplateList" style="width: 500px;" placeholder="è¯·éæ©æ¨¡çæè è¾å ¥æ°ç模çåç§°åéæ©" class="no-arrow-select"> <el-option v-for="item in templateList" :key="item.value" :key="item.id || item.value" :label="item.templateName" :value="item.templateName"></el-option> :value="item.templateName"> <div style="display: flex; justify-content: space-between; align-items: center;"> <span>{{ item.templateName }}</span> <el-icon v-if="item.id" class="delete-icon" @click.stop="handleDeleteTemplate(item)" style="cursor: pointer; color: #f56c6c; font-size: 14px; margin-left: 8px;"> <Delete /> </el-icon> </div> </el-option> </el-select> <!-- æé®ï¼ä¸ Select é«åº¦å¹é ï¼å»æå·¦ä¾§è¾¹æ¡ï¼æ ç¼è¡æ¥ --> <el-button size="small" style="height: 32px; border-radius: 0 4px 4px 0; margin-left: -1px;" style="height: 32px;margin-left: 8px;" @click="handleButtonClick" :disabled="!templateName || templateName.trim() === '' || isTemplateNameDuplicate"> :disabled="!templateName || templateName.trim() === '' || (!currentTemplateId && isTemplateNameDuplicate)"> ä¿å </el-button> </div> @@ -450,18 +485,49 @@ </el-col> </el-row> </el-form> <template #footer> <div class="dialog-footer"> <el-button type="primary" @click="submitForm">确认</el-button> <el-button @click="closeDia">åæ¶</el-button> </FormDialog> <!-- å¯¼å ¥å¼¹çª --> <FormDialog v-model="importUpload.open" :title="importUpload.title" :width="'600px'" @close="importUpload.open = false" @confirm="submitImportFile" @cancel="importUpload.open = false" > <el-upload ref="importUploadRef" :limit="1" accept=".xlsx,.xls" :action="importUpload.url" :headers="importUpload.headers" :before-upload="importUpload.beforeUpload" :on-success="importUpload.onSuccess" :on-error="importUpload.onError" :on-progress="importUpload.onProgress" :on-change="importUpload.onChange" :auto-upload="false" drag > <i class="el-icon-upload"></i> <div class="el-upload__text"> å°æä»¶æå°æ¤å¤ï¼æ<em>ç¹å»ä¸ä¼ </em> </div> </template> </el-dialog> <el-dialog v-model="productFormVisible" <template #tip> <div class="el-upload__tip"> ä» æ¯æ xls/xlsxï¼å¤§å°ä¸è¶ è¿ 10MBã <el-button link type="primary" @click="downloadTemplate">ä¸è½½å¯¼å ¥æ¨¡æ¿</el-button> </div> </template> </el-upload> </FormDialog> <FormDialog v-model="productFormVisible" :title="productOperationType === 'add' ? 'æ°å¢äº§å' : 'ç¼è¾äº§å'" width="40%" @close="closeProductDia"> :width="'40%'" :operation-type="productOperationType" @close="closeProductDia" @confirm="submitProduct" @cancel="closeProductDia"> <el-form :model="productForm" label-width="140px" label-position="top" @@ -617,228 +683,12 @@ </el-col> </el-row> </el-form> <template #footer> <div class="dialog-footer"> <el-button type="primary" @click="submitProduct">确认</el-button> <el-button @click="closeProductDia">åæ¶</el-button> </div> </template> </el-dialog> <!-- äºç»´ç æ¾ç¤ºå¯¹è¯æ¡ --> <el-dialog v-model="qrCodeDialogVisible" title="éè´ååå·äºç»´ç " width="400px" center> <div style="text-align: center;"> <img :src="qrCodeUrl" alt="äºç»´ç " style="width:200px;height:200px;" /> <div style="margin: 20px;"> <el-button type="primary" @click="downloadQRCode">ä¸è½½äºç»´ç å¾ç</el-button> </div> </div> </el-dialog> <!-- æ«ç æ°å¢å¯¹è¯æ¡ --> <el-dialog v-model="scanAddDialogVisible" title="æ«ç æ°å¢éè´å°è´¦" width="70%" @close="closeScanAddDialog"> <el-form :model="scanAddForm" label-width="140px" label-position="top" :rules="scanAddRules" ref="scanAddFormRef"> <el-row :gutter="20"> <el-col :span="24"> <el-form-item label="æ«ç å 容ï¼"> <el-input v-model="scanAddForm.scanContent" type="textarea" :rows="3" placeholder="è¯·æ«æäºç»´ç ææå¨è¾å ¥éè´ååä¿¡æ¯" @input="parseScanContent" /> </el-form-item> </el-col> </el-row> <el-row :gutter="20"> <el-col :span="12"> <el-form-item label="éè´ååå·ï¼" prop="purchaseContractNumber"> <el-input v-model="scanAddForm.purchaseContractNumber" placeholder="请è¾å ¥" clearable /> </el-form-item> </el-col> <el-col :span="12"> <el-form-item label="ä¾åºååç§°ï¼" prop="supplierName"> <el-input v-model="scanAddForm.supplierName" placeholder="请è¾å ¥" clearable /> </el-form-item> </el-col> </el-row> <el-row :gutter="20"> <el-col :span="12"> <el-form-item label="项ç®åç§°ï¼" prop="projectName"> <el-input v-model="scanAddForm.projectName" placeholder="请è¾å ¥" clearable /> </el-form-item> </el-col> <el-col :span="12"> <el-form-item label="ååéé¢(å )ï¼" prop="contractAmount"> <el-input-number v-model="scanAddForm.contractAmount" :precision="2" :step="0.1" clearable style="width: 100%" placeholder="请è¾å ¥" /> </el-form-item> </el-col> </el-row> <el-row :gutter="20"> <el-col :span="12"> <el-form-item label="仿¬¾æ¹å¼ï¼"> <el-input v-model="scanAddForm.paymentMethod" placeholder="请è¾å ¥" clearable /> </el-form-item> </el-col> <el-col :span="12"> <el-form-item label="å½å ¥äººï¼"> <el-input v-model="scanAddForm.recorderName" disabled /> </el-form-item> </el-col> </el-row> <el-row :gutter="20"> <el-col :span="24"> <el-form-item label="夿³¨ï¼"> <el-input v-model="scanAddForm.remark" type="textarea" :rows="2" placeholder="请è¾å ¥å¤æ³¨ä¿¡æ¯" clearable /> </el-form-item> </el-col> </el-row> </el-form> <template #footer> <div class="dialog-footer"> <el-button type="primary" @click="submitScanAdd">确认æ°å¢</el-button> <el-button @click="closeScanAddDialog">åæ¶</el-button> </div> </template> </el-dialog> <!-- æ«ç ç»è®°å¯¹è¯æ¡ --> <el-dialog v-model="scanDialogVisible" title="æ«ç ç»è®°" width="60%" @close="closeScanDialog"> <el-form :model="scanForm" label-width="120px" label-position="left" :rules="scanRules" ref="scanFormRef"> <el-row :gutter="20"> <el-col :span="12"> <el-form-item label="éè´ååå·ï¼"> <el-input v-model="scanForm.purchaseContractNumber" disabled /> </el-form-item> </el-col> <el-col :span="12"> <el-form-item label="ä¾åºååç§°ï¼"> <el-input v-model="scanForm.supplierName" disabled /> </el-form-item> </el-col> </el-row> <el-row :gutter="20"> <el-col :span="12"> <el-form-item label="项ç®åç§°ï¼"> <el-input v-model="scanForm.projectName" disabled /> </el-form-item> </el-col> <el-col :span="12"> <el-form-item label="æ«ç æ¶é´ï¼"> <el-input v-model="scanForm.scanTime" disabled /> </el-form-item> </el-col> </el-row> <el-row :gutter="20"> <el-col :span="12"> <el-form-item label="æ«ç 人ï¼"> <el-input v-model="scanForm.scannerName" disabled /> </el-form-item> </el-col> <el-col :span="12"> <el-form-item label="æ«ç ç¶æï¼"> <el-tag :type="scanForm.scanStatus === 'å·²æ«ç ' ? 'success' : 'warning'"> {{ scanForm.scanStatus }} </el-tag> </el-form-item> </el-col> </el-row> <el-row :gutter="20"> <el-col :span="24"> <el-form-item label="æ«ç 夿³¨ï¼"> <el-input v-model="scanForm.scanRemark" type="textarea" :rows="3" placeholder="请è¾å ¥æ«ç 夿³¨ä¿¡æ¯" /> </el-form-item> </el-col> </el-row> <el-row :gutter="20"> <el-col :span="24"> <el-form-item label="æ«ç è®°å½ï¼"> <el-table :data="scanRecords" border style="width: 100%"> <el-table-column label="åºå·" type="index" width="60" align="center" /> <el-table-column label="æ«ç æ¶é´" prop="scanTime" width="180" /> <el-table-column label="æ«ç 人" prop="scannerName" width="120" /> <el-table-column label="æ«ç ç¶æ" prop="scanStatus" width="100"> <template #default="scope"> <el-tag :type="scope.row.scanStatus === 'å·²æ«ç ' ? 'success' : 'warning'"> {{ scope.row.scanStatus }} </el-tag> </template> </el-table-column> <el-table-column label="夿³¨" prop="scanRemark" /> </el-table> </el-form-item> </el-col> </el-row> </el-form> <template #footer> <div class="dialog-footer"> <el-button type="primary" @click="submitScan">确认æ«ç </el-button> <el-button @click="closeScanDialog">åæ¶</el-button> </div> </template> </el-dialog> <FileList ref="fileListRef" /> </FormDialog> <FileListDialog ref="fileListRef" v-model="fileListDialogVisible" title="éä»¶å表" /> </div> </template> @@ -853,10 +703,11 @@ getCurrentInstance, nextTick, } from "vue"; import { Search } from "@element-plus/icons-vue"; import { Search, Delete } from "@element-plus/icons-vue"; import { ElMessageBox, ElMessage } from "element-plus"; import { userListNoPage } from "@/api/system/user.js"; import FileList from "./fileList.vue"; import FormDialog from '@/components/Dialog/FormDialog.vue'; import FileListDialog from '@/components/Dialog/FileListDialog.vue'; import { getSalesLedgerWithProducts, addOrUpdateSalesLedgerProduct, @@ -867,6 +718,7 @@ import { addOrEditPurchase, addPurchaseTemplate, updatePurchaseTemplate, createPurchaseNo, delPurchase, getSalesNo, @@ -875,9 +727,9 @@ getPurchaseById, getOptions, getPurchaseTemplateList, delPurchaseTemplate, } from "@/api/procurementManagement/procurementLedger.js"; import useFormData from "@/hooks/useFormData.js"; import QRCode from "qrcode"; const { proxy } = getCurrentInstance(); const tableData = ref([]); @@ -902,21 +754,41 @@ const userStore = useUserStore(); // äºç»´ç ç¸å ³åé const qrCodeDialogVisible = ref(false); const qrCodeUrl = ref(""); // 审æ¹äººèç¹ï¼ä»¿éå®å°è´¦å货审æ¹äººï¼ const approverNodes = ref([{ id: 1, userId: null }]); let nextApproverId = 2; const addApproverNode = () => { approverNodes.value.push({ id: nextApproverId++, userId: null }); }; const removeApproverNode = (index) => { approverNodes.value.splice(index, 1); }; // 订å审æ¹ç¶ææ¾ç¤ºææ¬ const approvalStatusText = { 0: "审æ¹ä¸", 1: "审æ¹éè¿", 2: "审æ¹å¤±è´¥", 1: "å¾ å®¡æ ¸", 2: "审æ¹ä¸", 3: "审æ¹éè¿", 4: "审æ¹å¤±è´¥", }; // è·å审æ¹ç¶ææ ç¾ç±»å const getApprovalStatusType = (status) => { const typeMap = { 1: "info", // å¾ å®¡æ ¸ - ç°è² 2: "warning", // 审æ¹ä¸ - æ©è² 3: "success", // 审æ¹éè¿ - ç»¿è² 4: "danger", // 审æ¹å¤±è´¥ - çº¢è² }; return typeMap[status] || ""; }; const templateName = ref(""); const filterInputValue = ref(""); const templateList = ref([]); const isTemplateNameDuplicate = ref(false); // æ 记模æ¿åç§°æ¯å¦éå¤ // å½åéä¸ç模æ¿IDï¼ç¨äºåºåæ°å¢æ¨¡æ¿è¿æ¯æ´æ°æ¨¡æ¿ï¼ const currentTemplateId = ref(null); // æ£æ¥æ¨¡æ¿åç§°æ¯å¦éå¤ const checkTemplateNameDuplicate = name => { @@ -970,22 +842,28 @@ ); if (matchedTemplate?.id) { // 妿æ¾å°æ¨¡æ¿ï¼å è½½æ¨¡æ¿æ°æ® form.value = { ...form.value, ...matchedTemplate, }; productData.value = matchedTemplate.productData || []; // çææ°çéè´ååå· try { const res = await createPurchaseNo(); if (res?.data) { form.value.purchaseContractNumber = res.data; } } catch (error) { console.error("çæéè´ååå·å¤±è´¥:", error); // è®°å½å½åéä¸ç模æ¿IDï¼åç»ä¿åæ¶è¿è¡æ´æ°æä½ currentTemplateId.value = matchedTemplate.id; // éä¸å·²ææ¨¡æ¿æ¶ï¼ä¸åºè§ä¸ºâ模æ¿åç§°éå¤å¯¼è´ä¸å¯ä¿åâ isTemplateNameDuplicate.value = false; // 妿æ¾å°æ¨¡æ¿ï¼åªèµå¼ä¾åºåã项ç®åç§°ã仿¬¾æ¹å¼å产åä¿¡æ¯ if (matchedTemplate.supplierId) { form.value.supplierId = matchedTemplate.supplierId; } if (matchedTemplate.supplierName) { form.value.supplierName = matchedTemplate.supplierName; } if (matchedTemplate.projectName) { form.value.projectName = matchedTemplate.projectName; } if (matchedTemplate.paymentMethod) { form.value.paymentMethod = matchedTemplate.paymentMethod; } // æ¨¡æ¿æ°æ®ä¸ç产ååæ®µæ¯ productListï¼éè¦è½¬æ¢ä¸º productData productData.value = matchedTemplate.productList || matchedTemplate.productData || []; } else { // æªå¹é å°å·²ææ¨¡æ¿ï¼è§ä¸ºæ°æ¨¡æ¿ currentTemplateId.value = null; // å¦ææ²¡ææ¾å°æ¨¡æ¿ï¼é置表åï¼ä¿æå½å表åç¶æï¼ const currentFormData = { ...form.value }; const currentProductData = [...productData.value]; @@ -1108,6 +986,71 @@ headers: { Authorization: "Bearer " + getToken() }, }); // å¯¼å ¥ç¸å ³ const importUploadRef = ref(null); const importUpload = reactive({ title: "å¯¼å ¥éè´å°è´¦", open: false, url: import.meta.env.VITE_APP_BASE_API + "/purchase/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) => { // noop }, onProgress: (event, file, fileList) => { // noop }, onSuccess: (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: () => { importUpload.isUploading = false; proxy.$modal.msgError("å¯¼å ¥å¤±è´¥ï¼è¯·éè¯"); }, }); const handleImport = () => { importUpload.title = "å¯¼å ¥éè´å°è´¦"; importUpload.open = true; importUpload.isUploading = false; if (importUploadRef.value) { importUploadRef.value.clearFiles?.(); } }; // ä¸è½½å¯¼å ¥æ¨¡æ¿ï¼å¦å端路å¾ä¸åï¼å¯å¨æ¤å¤è°æ´ï¼ const downloadTemplate = () => { proxy.download("/purchase/ledger/exportTemplate", {}, "éè´å°è´¦å¯¼å ¥æ¨¡æ¿.xlsx"); }; const submitImportFile = () => { importUpload.isUploading = true; proxy.$refs["importUploadRef"]?.submit?.(); }; const changeDaterange = value => { if (value) { searchForm.entryDateStart = dayjs(value[0]).format("YYYY-MM-DD"); @@ -1140,14 +1083,17 @@ return; } // æ£æ¥æ¨¡æ¿åç§°æ¯å¦éå¤ const isDuplicate = checkTemplateNameDuplicate(templateName.value); if (isDuplicate) { ElMessage({ message: "模æ¿åç§°å·²åå¨ï¼è¯·æ´æ¢æ¨¡æ¿åç§°", type: "warning", }); return; // 妿æ¯âæ°å¢æ¨¡æ¿âï¼æ²¡æéä¸å·²ææ¨¡æ¿ï¼ï¼æéè¦åéåæ ¡éªï¼ // è¥æ¯éä¸å·²ææ¨¡æ¿åä¿®æ¹ï¼åå 许使ç¨ååç§°ï¼è§ä¸ºæ´æ°ï¼ if (!currentTemplateId.value) { const isDuplicate = checkTemplateNameDuplicate(templateName.value); if (isDuplicate) { ElMessage({ message: "模æ¿åç§°å·²åå¨ï¼è¯·æ´æ¢æ¨¡æ¿åç§°", type: "warning", }); return; } } // æ£æ¥ä¾åºåæ¯å¦éæ© @@ -1160,29 +1106,47 @@ } // æ£æ¥æ¯å¦æäº§åæ°æ® // if (!productData.value || productData.value.length === 0) { // ElMessage({ // message: 'è¯·å æ·»å 产åä¿¡æ¯', // type: 'warning', // }); // return; // } if (!productData.value || productData.value.length === 0) { ElMessage({ message: 'è¯·å æ·»å 产åä¿¡æ¯', type: 'warning', }); return; } try { // è·å审æ¹äººIDå符串 const approveUserIds = approverNodes.value .filter(node => node.userId) .map(node => node.userId) .join(","); let params = { productData: proxy.HaveJson(productData.value), supplierId: form.value.supplierId, paymentMethod: form.value.paymentMethod, recorderId: form.value.recorderId, approverId: form.value.approverId, projectName: form.value.projectName, approveUserIds: approveUserIds, templateName: templateName.value.trim(), }; console.log(params); let res = await addPurchaseTemplate(params); console.log("template params ===>", params, "currentTemplateId:", currentTemplateId.value); // 妿 currentTemplateId æå¼ï¼è¯´æå½åæ¯âç¼è¾å·²ææ¨¡æ¿â â è°ç¨æ´æ°æ¥å£ // å¦åä¸ºâæ°å»ºæ¨¡æ¿â â è°ç¨æ°å¢æ¥å£ let res; if (currentTemplateId.value) { res = await updatePurchaseTemplate({ id: currentTemplateId.value, ...params, }); } else { res = await addPurchaseTemplate(params); } if (res && res.code === 200) { ElMessage({ message: "模æ¿ä¿åæå", message: currentTemplateId.value ? "æ¨¡æ¿æ´æ°æå" : "模æ¿ä¿åæå", type: "success", }); // ä¿åæååéæ°è·å模æ¿å表 @@ -1191,6 +1155,8 @@ templateName.value = ""; filterInputValue.value = ""; isTemplateNameDuplicate.value = false; // ä¿å/æ´æ°å®æåï¼éç½®å½å模æ¿ID currentTemplateId.value = null; } else { ElMessage({ message: res?.msg || "模æ¿ä¿å失败", @@ -1238,7 +1204,6 @@ // tableData.value = res.data.records; tableData.value = res.data.records.map(record => ({ ...record, isInvalid: record.isWhite === 1, })); // åå§ååæ°æ®æ°ç» tableData.value.forEach(item => { @@ -1297,6 +1262,14 @@ }; // æå¼å¼¹æ¡ const openForm = async (type, row) => { // ç¼è¾æ¶æ£æ¥å®¡æ ¸ç¶æï¼åªæå¾ å®¡æ ¸(1)å审æ¹å¤±è´¥(4)æè½ç¼è¾ if (type === "edit" && row) { if (row.approvalStatus !== 1 && row.approvalStatus !== 4) { proxy.$modal.msgWarning("åªæå¾ å®¡æ ¸å审æ¹å¤±è´¥ç¶æçè®°å½æè½ç¼è¾"); return; } } await getTemplateList(); operationType.value = type; form.value = {}; @@ -1305,6 +1278,9 @@ templateName.value = ""; filterInputValue.value = ""; isTemplateNameDuplicate.value = false; // é置审æ¹äººèç¹ï¼é»è®¤ä¸ä¸ªç©ºèç¹ï¼ approverNodes.value = [{ id: 1, userId: null }]; nextApproverId = 2; try { // å¹¶è¡å è½½åºç¡æ°æ® const [userRes, salesRes, supplierRes] = await Promise.all([ @@ -1343,6 +1319,15 @@ form.value = { ...purchaseRes }; productData.value = purchaseRes.productData || []; fileList.value = purchaseRes.salesLedgerFiles || []; // 妿ç¼è¾æ¶æå®¡æ¹äººï¼è§£æå®¡æ¹äººIDå符串并设置å°èç¹ä¸ if (purchaseRes.approveUserIds) { const approverIds = purchaseRes.approveUserIds.split(","); approverNodes.value = approverIds.map((id, index) => ({ id: index + 1, userId: Number(id) })); nextApproverId = approverIds.length + 1; } } catch (error) { console.error("å è½½éè´å°è´¦æ°æ®å¤±è´¥:", error); proxy.$modal.msgError("å è½½æ°æ®å¤±è´¥"); @@ -1411,8 +1396,24 @@ const submitForm = () => { proxy.$refs["formRef"].validate(valid => { if (valid) { // 审æ¹äººå¿ å¡«æ ¡éªï¼ææèç¹é½è¦éäººï¼ const hasEmptyApprover = approverNodes.value.some(node => !node.userId); if (hasEmptyApprover) { proxy.$modal.msgError("请为ææå®¡æ¹èç¹éæ©å®¡æ¹äººï¼"); return; } const approveUserIds = approverNodes.value.map(node => node.userId).join(","); if (productData.value.length > 0) { form.value.productData = proxy.HaveJson(productData.value); // æ°å¢æ¶ï¼éè¦ä»æ¯ä¸ªäº§å对象ä¸å é¤ id åæ®µ let processedProductData = productData.value; if (operationType.value === "add") { processedProductData = productData.value.map(product => { const { id, ...rest } = product; return rest; }); } form.value.productData = proxy.HaveJson(processedProductData); } else { proxy.$modal.msgWarning("请添å 产åä¿¡æ¯"); return; @@ -1423,13 +1424,20 @@ } form.value.tempFileIds = tempFileIds; form.value.type = 2; form.value.approveUserIds = approveUserIds; // 妿salesLedgerId为空ï¼åä¸ä¼ ésalesContractNo if (!form.value.salesLedgerId) { form.value.salesContractNo = ""; } addOrEditPurchase(form.value).then(res => { // æ°å¢æ¶ä¸ä¼ éid const submitData = { ...form.value }; if (operationType.value === "add") { delete submitData.id; } addOrEditPurchase(submitData).then(res => { proxy.$modal.msgSuccess("æäº¤æå"); closeDia(); getList(); @@ -1440,35 +1448,102 @@ // å ³éå¼¹æ¡ const closeDia = () => { proxy.resetForm("formRef"); // é置审æ¹äººèç¹ï¼é»è®¤ä¸ä¸ªç©ºèç¹ï¼ approverNodes.value = [{ id: 1, userId: null }]; nextApproverId = 2; dialogFormVisible.value = false; }; // æå¼äº§åå¼¹æ¡ const openProductForm = (type, row, index) => { const openProductForm = async (type, row, index) => { productOperationType.value = type; productOperationIndex.value = index; productForm.value = {}; proxy.resetForm("productFormRef"); if (type === "edit") { productForm.value = { ...row }; } productFormVisible.value = true; getProductOptions(); // å è·å产åé项ï¼ç¡®ä¿æ°æ®å è½½å®æ await getProductOptions(); // çå¾ DOM æ´æ° await nextTick(); if (type === "edit") { // å¤å¶è¡æ°æ® productForm.value = { ...row }; // 妿æ¯ä»æ¨¡æ¿å è½½çæ°æ®ï¼å¯è½æ²¡æ productId å productModelId // éè¦æ ¹æ® productCategory å specificationModel æ¥æ¥æ¾å¯¹åºç ID if (!productForm.value.productId && productForm.value.productCategory) { // æ ¹æ® productCategory æ¥æ¾ productId const findProductIdByCategory = (nodes, categoryName) => { for (let i = 0; i < nodes.length; i++) { if (nodes[i].label === categoryName) { return nodes[i].value; } if (nodes[i].children && nodes[i].children.length > 0) { const found = findProductIdByCategory(nodes[i].children, categoryName); if (found) return found; } } return null; }; const productId = findProductIdByCategory(productOptions.value, productForm.value.productCategory); if (productId) { productForm.value.productId = productId; // è·ååå·å表并çå¾ å®æ const modelRes = await modelList({ id: productId }); modelOptions.value = modelRes; // çå¾ DOM æ´æ° await nextTick(); // æ ¹æ® specificationModel æ¥æ¾ productModelId if (productForm.value.specificationModel && modelOptions.value.length > 0) { const modelItem = modelOptions.value.find( item => item.model === productForm.value.specificationModel ); if (modelItem) { productForm.value.productModelId = modelItem.id; // è®¾ç½®è§æ ¼åå·ååä½ getProductModel(modelItem.id); } } } } else if (productForm.value.productId) { // 妿æ productIdï¼æ£å¸¸å è½½åå·å表 await getModels(productForm.value.productId); // çå¾ DOM æ´æ° await nextTick(); if (productForm.value.productModelId) { getProductModel(productForm.value.productModelId); } } // æååçå¾ ä¸æ¬¡ DOM æ´æ°ï¼ç¡®ä¿æææ°æ®é½å·²è®¾ç½® await nextTick(); } }; const getProductOptions = () => { productTreeList().then(res => { return productTreeList().then(res => { productOptions.value = convertIdToValue(res); return res; }); }; const getModels = value => { if (value) { productForm.value.productCategory = findNodeById(productOptions.value, value) || ""; modelList({ id: value }).then(res => { return modelList({ id: value }).then(res => { modelOptions.value = res; return res; }); } else { productForm.value.productCategory = ""; modelOptions.value = []; return Promise.resolve([]); } }; const getProductModel = value => { @@ -1773,205 +1848,11 @@ }; const fileListRef = ref(null); const fileListDialogVisible = ref(false); const downLoadFile = row => { fileListRef.value.open(row.salesLedgerFiles); }; // æ¾ç¤ºäºç»´ç const showQRCode = async row => { try { // æå»ºäºç»´ç å 容ï¼åªå å«éè´ååå·ï¼çº¯ææ¬ï¼ const qrContent = row.purchaseContractNumber || ""; // æ£æ¥å 容æ¯å¦ä¸ºç©º if (!qrContent || qrContent.trim() === "") { proxy.$modal.msgWarning("è¯¥è¡æ²¡æéè´ååå·ï¼æ æ³çæäºç»´ç "); return; } qrCodeUrl.value = await QRCode.toDataURL(qrContent, { width: 200, margin: 2, color: { dark: "#000000", light: "#FFFFFF", }, }); qrCodeDialogVisible.value = true; } catch (error) { console.error("çæäºç»´ç 失败:", error); proxy.$modal.msgError("çæäºç»´ç 失败ï¼" + error.message); if (fileListRef.value) { fileListRef.value.open(row.salesLedgerFiles); } }; // ä¸è½½äºç»´ç const downloadQRCode = () => { if (!qrCodeUrl.value) { proxy.$modal.msgWarning("äºç»´ç æªçæ"); return; } const a = document.createElement("a"); a.href = qrCodeUrl.value; a.download = `éè´ååå·äºç»´ç _${new Date().getTime()}.png`; document.body.appendChild(a); a.click(); document.body.removeChild(a); proxy.$modal.msgSuccess("ä¸è½½æå"); }; // æ«ç æ°å¢å¯¹è¯æ¡ç¸å ³åé const scanAddDialogVisible = ref(false); const scanAddForm = reactive({ scanContent: "", purchaseContractNumber: "", supplierName: "", projectName: "", contractAmount: "", paymentMethod: "", recorderName: "", scanRemark: "", }); const scanAddRules = { purchaseContractNumber: [ { required: true, message: "请è¾å ¥éè´ååå·", trigger: "blur" }, ], supplierName: [ { required: true, message: "请è¾å ¥ä¾åºååç§°", trigger: "blur" }, ], projectName: [{ required: true, message: "请è¾å ¥é¡¹ç®åç§°", trigger: "blur" }], }; // æ«ç ç»è®°å¯¹è¯æ¡ç¸å ³åé const scanDialogVisible = ref(false); const scanForm = reactive({ purchaseContractNumber: "", supplierName: "", projectName: "", scanTime: "", scannerName: "", scanStatus: "æªæ«ç ", scanRemark: "", }); const scanRules = { scanRemark: [{ required: true, message: "请è¾å ¥æ«ç 夿³¨", trigger: "blur" }], }; const scanRecords = ref([]); // æå¼æ«ç æ°å¢å¯¹è¯æ¡ const openScanAddDialog = () => { scanAddForm.scanContent = ""; scanAddForm.purchaseContractNumber = ""; scanAddForm.supplierName = ""; scanAddForm.projectName = ""; scanAddForm.contractAmount = ""; scanAddForm.paymentMethod = ""; scanAddForm.recorderName = userStore.nickName; scanAddForm.scanRemark = ""; scanAddDialogVisible.value = true; }; // è§£ææ«ç å å®¹ï¼æ¨¡æè§£æäºç»´ç æ°æ®ï¼ const parseScanContent = content => { if (!content) return; // 模æè§£æäºç»´ç å 容ï¼è¿éå¯ä»¥æ ¹æ®å®é éæ±è°æ´è§£æé»è¾ // å设æ«ç å å®¹æ ¼å¼ä¸ºï¼ååå·|ä¾åºå|éé¢|仿¬¾æ¹å¼ const parts = content.split("|"); if (parts.length >= 2) { scanAddForm.purchaseContractNumber = parts[0] || ""; scanAddForm.supplierName = parts[1] || ""; scanAddForm.contractAmount = parts[2] || ""; scanAddForm.paymentMethod = parts[3] || ""; scanAddForm.projectName = parts[4] || ""; // scanAddForm.contractAmount = parts[3] || ""; // scanAddForm.paymentMethod = parts[4] || ""; } }; // å ³éæ«ç æ°å¢å¯¹è¯æ¡ const closeScanAddDialog = () => { scanAddDialogVisible.value = false; proxy.resetForm("scanAddFormRef"); }; // æäº¤æ«ç æ°å¢ const submitScanAdd = () => { proxy.$refs["scanAddFormRef"].validate(valid => { if (valid) { // æå»ºæ°å¢æ°æ® const newData = { purchaseContractNumber: scanAddForm.purchaseContractNumber, supplierName: scanAddForm.supplierName, projectName: scanAddForm.projectName, contractAmount: scanAddForm.contractAmount, paymentMethod: scanAddForm.paymentMethod, recorderName: scanAddForm.recorderName, entryDate: getCurrentDate(), remark: scanAddForm.scanRemark, type: 2, }; // æ¨¡ææ°å¢æå proxy.$modal.msgSuccess("æ«ç æ°å¢æåï¼"); closeScanAddDialog(); // å¯ä»¥éæ©æ¯å¦å·æ°å表 // getList(); } }); }; // æå¼æ«ç ç»è®°å¯¹è¯æ¡ const openScanDialog = row => { scanForm.purchaseContractNumber = row.purchaseContractNumber; scanForm.supplierName = row.supplierName; scanForm.projectName = row.projectName; scanForm.scanTime = getCurrentDateTime(); scanForm.scannerName = userStore.nickName; scanForm.scanStatus = "æªæ«ç "; scanForm.scanRemark = ""; scanRecords.value = []; scanDialogVisible.value = true; }; // å ³éæ«ç ç»è®°å¯¹è¯æ¡ const closeScanDialog = () => { scanDialogVisible.value = false; proxy.resetForm("scanFormRef"); }; // æäº¤æ«ç ç»è®° const submitScan = () => { proxy.$refs["scanFormRef"].validate(valid => { if (valid) { // æ·»å æ«ç è®°å½ scanRecords.value.push({ ...scanForm, id: Date.now(), // 模æID scanTime: getCurrentDateTime(), }); scanForm.scanStatus = "å·²æ«ç "; scanForm.scanRemark = scanForm.scanRemark || "æ "; proxy.$modal.msgSuccess("æ«ç ç»è®°æåï¼"); closeScanDialog(); } }); }; // è·åå½åæ¥ææ¶é´ function getCurrentDateTime() { const now = new Date(); const year = now.getFullYear(); const month = String(now.getMonth() + 1).padStart(2, "0"); const day = String(now.getDate()).padStart(2, "0"); const hours = String(now.getHours()).padStart(2, "0"); const minutes = String(now.getMinutes()).padStart(2, "0"); const seconds = String(now.getSeconds()).padStart(2, "0"); return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`; } // æ·»å è¡ç±»åæ¹æ³ const tableRowClassName = ({ row }) => { return row.isInvalid ? "invalid-row" : ""; }; // è·å模æ¿ä¿¡æ¯ @@ -1979,6 +1860,54 @@ let res = await getPurchaseTemplateList(); if (res && res.code === 200 && Array.isArray(res.data)) { templateList.value = res.data; } }; // å 餿¨¡æ¿ const handleDeleteTemplate = async (item) => { if (!item.id) { proxy.$modal.msgWarning("æ æ³å é¤è¯¥æ¨¡æ¿"); return; } try { await ElMessageBox.confirm( `ç¡®å®è¦å 餿¨¡æ¿"${item.templateName}"åï¼`, "å é¤ç¡®è®¤", { confirmButtonText: "ç¡®å®", cancelButtonText: "åæ¶", type: "warning", } ); const res = await delPurchaseTemplate([item.id]); if (res && res.code === 200) { ElMessage({ message: "å 餿å", type: "success", }); // 妿å é¤çæ¯å½åéä¸ç模æ¿ï¼æ¸ ç©ºéæ© if (templateName.value === item.templateName) { templateName.value = ""; filterInputValue.value = ""; } // éæ°è·å模æ¿å表 await getTemplateList(); } else { ElMessage({ message: res?.msg || "å é¤å¤±è´¥", type: "error", }); } } catch (error) { if (error !== "cancel") { console.error("å 餿¨¡æ¿å¤±è´¥:", error); ElMessage({ message: "å é¤å¤±è´¥ï¼è¯·ç¨åéè¯", type: "error", }); } } }; @@ -2004,4 +1933,64 @@ display: flex; align-items: center; } // 审æ¹äººèç¹å®¹å¨æ ·å¼ .approver-nodes-container { display: flex; flex-wrap: wrap; gap: 16px; padding: 16px; background-color: #f8f9fa; border-radius: 4px; border: 1px solid #e4e7ed; } .approver-node-item { flex: 0 0 calc(33.333% - 12px); min-width: 200px; padding: 12px; background-color: #fff; border-radius: 4px; border: 1px solid #dcdfe6; transition: all 0.3s; &:hover { border-color: #409eff; box-shadow: 0 2px 8px rgba(64, 158, 255, 0.1); } } .approver-node-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px; } .approver-node-label { font-size: 13px; font-weight: 500; color: #606266; } @media (max-width: 1200px) { .approver-node-item { flex: 0 0 calc(50% - 8px); } } @media (max-width: 768px) { .approver-node-item { flex: 0 0 100%; } } // å é¤å¾æ æ ·å¼ .delete-icon { transition: all 0.3s; &:hover { color: #f56c6c !important; transform: scale(1.2); } } </style> src/views/procurementManagement/procurementReport/index.vue
@@ -103,12 +103,10 @@ { label: '产å大类', prop: 'productCategory', width: 150, }, { label: 'è§æ ¼åå·', prop: 'specificationModel', width: 180 }, { label: 'éè´æ°é', @@ -121,7 +119,6 @@ { label: 'éè´éé¢', prop: 'purchaseAmount', width: 140, formatData: (val) => { return val ? `Â¥${parseFloat(val).toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}` : 'Â¥0.00' } @@ -142,7 +139,6 @@ { label: 'ä¾åºååç§°', prop: 'supplierName', width: 200 }, { label: 'å½å ¥æ¥æ', src/views/productionManagement/productStructure/Detail/index.vue
@@ -2,133 +2,85 @@ <div class="app-container"> <PageHeader content="产åç»æè¯¦æ "> <template #right-button> <el-button v-if="dataValue.isEdit && !isOrderPage" type="primary" @click="addItem">æ·»å <el-button v-if="!dataValue.isEdit && !isOrderPage" type="primary" @click="dataValue.isEdit = true">ç¼è¾ </el-button> <el-button v-if="!dataValue.isEdit && !isOrderPage" type="primary" @click="dataValue.isEdit = true">ç¼è¾ <el-button v-if="dataValue.isEdit && !isOrderPage" type="primary" @click="cancelEdit">åæ¶ </el-button> <el-button v-if="dataValue.isEdit && !isOrderPage" type="primary" @click="cancelEdit">åæ¶ </el-button> <el-button v-if="!isOrderPage" type="primary" :loading="dataValue.loading" @click="submit" :disabled="!dataValue.isEdit">确认 <el-button v-if="!isOrderPage" type="primary" :loading="dataValue.loading" @click="submit" :disabled="!dataValue.isEdit">确认 </el-button> </template> </PageHeader> <el-table :data="tableData" border :preserve-expanded-content="false" :default-expand-all="true" style="width: 100%" > <el-table :data="tableData" border :preserve-expanded-content="false" :default-expand-all="true" style="width: 100%"> <el-table-column type="expand"> <template #default="props"> <el-form ref="form" :model="dataValue"> <el-table :data="dataValue.dataList" style="width: 100%"> <el-table-column prop="productName" label="产å"/> <el-table-column prop="model" label="è§æ ¼"> <el-form ref="form" :model="dataValue"> <el-table :data="dataValue.dataList" row-key="tempId" default-expand-all :tree-props="{ children: 'children', hasChildren: 'hasChildren' }" style="width: 100%"> <el-table-column prop="productName" label="产å" /> <el-table-column prop="model" label="è§æ ¼"> <template #default="{ row, $index }"> <el-form-item v-if="dataValue.isEdit" :prop="`dataList.${$index}.model`" :rules="[{ required: true, message: 'è¯·éæ©è§æ ¼', trigger: ['blur','change'] }]" style="margin: 0"> <el-select v-model="row.model" placeholder="è¯·éæ©è§æ ¼" clearable :disabled="!dataValue.isEdit" style="width: 100%" @visible-change="(v) => { if (v) openDialog($index) }"> <el-option v-if="row.model" :label="row.model" :value="row.model" /> :rules="[{ required: true, message: 'è¯·éæ©è§æ ¼', trigger: ['blur', 'change'] }]" style="margin: 0"> <el-select v-model="row.model" placeholder="è¯·éæ©è§æ ¼" clearable :disabled="!dataValue.isEdit || dataValue.dataList.some(item => (item as any).tempId === row.tempId)" style="width: 100%" @visible-change="(v) => { if (v) openDialog(row.tempId) }"> <el-option v-if="row.model" :label="row.model" :value="row.model" /> </el-select> </el-form-item> </template> </el-table-column> <el-table-column prop="processId" label="æ¶èå·¥åº"> <el-table-column prop="processName" label="æ¶èå·¥åº"> <template #default="{ row, $index }"> <el-form-item :prop="`dataList.${$index}.processId`" :rules="[{ required: true, message: 'è¯·éæ©æ¶èå·¥åº', trigger: 'change' }]" style="margin: 0"> <el-select v-model="row.processId" placeholder="è¯·éæ©" filterable clearable style="width: 100%" :disabled="!dataValue.isEdit"> <el-option v-for="item in dataValue.processOptions" :key="item.id" :label="item.name" :value="item.id" /> <el-form-item v-if="dataValue.isEdit" :rules="dataValue.dataList.some(item => (item as any).tempId === row.tempId) ? [] : [{ required: true, message: 'è¯·éæ©æ¶èå·¥åº', trigger: 'change' }]" style="margin: 0"> <el-select v-model="row.processId" placeholder="è¯·éæ©" filterable clearable style="width: 100%" :disabled="!dataValue.isEdit || dataValue.dataList.some(item => (item as any).tempId === row.tempId)"> <el-option v-for="item in dataValue.processOptions" :key="item.id" :label="item.name" :value="item.id" /> </el-select> </el-form-item> </template> </el-table-column> <el-table-column prop="unitQuantity" label="åä½äº§åºæéæ°é"> <el-table-column prop="unitQuantity" label="åä½äº§åºæéæ°é"> <template #default="{ row, $index }"> <el-form-item :prop="`dataList.${$index}.unitQuantity`" :rules="[{ required: true, message: '请è¾å ¥åä½äº§åºæéæ°é', trigger: ['blur','change'] }]" style="margin: 0"> <el-input-number v-model="row.unitQuantity" :min="0" :precision="2" :step="1" controls-position="right" style="width: 100%" :disabled="!dataValue.isEdit" /> <el-form-item v-if="dataValue.isEdit" :rules="[{ required: true, message: '请è¾å ¥åä½äº§åºæéæ°é', trigger: ['blur', 'change'] }]" style="margin: 0"> <el-input-number v-model="row.unitQuantity" :min="0" :precision="2" :step="1" controls-position="right" style="width: 100%" :disabled="!dataValue.isEdit || dataValue.dataList.some(item => (item as any).tempId === row.tempId)" /> </el-form-item> </template> </el-table-column> <el-table-column v-if="isOrderPage" prop="demandedQuantity" label="éæ±æ»é"> <el-table-column v-if="isOrderPage" prop="demandedQuantity" label="éæ±æ»é"> <template #default="{ row, $index }"> <el-form-item :prop="`dataList.${$index}.demandedQuantity`" :rules="[{ required: true, message: '请è¾å ¥éæ±æ»é', trigger: ['blur','change'] }]" style="margin: 0"> <el-input-number v-model="row.demandedQuantity" :min="0" :precision="2" :step="1" controls-position="right" style="width: 100%" :disabled="!dataValue.isEdit" /> <el-form-item v-if="dataValue.isEdit" :rules="[{ required: true, message: '请è¾å ¥éæ±æ»é', trigger: ['blur', 'change'] }]" style="margin: 0"> <el-input-number v-model="row.demandedQuantity" :min="0" :precision="2" :step="1" controls-position="right" style="width: 100%" :disabled="!dataValue.isEdit || dataValue.dataList.some(item => (item as any).tempId === row.tempId)" /> </el-form-item> </template> </el-table-column> <el-table-column prop="unit" label="åä½"> <el-table-column prop="unit" label="åä½"> <template #default="{ row, $index }"> <el-form-item :prop="`dataList.${$index}.unit`" :rules="[{ required: true, message: '请è¾å ¥åä½', trigger: ['blur','change'] }]" style="margin: 0"> <el-input v-model="row.unit" placeholder="请è¾å ¥åä½" clearable :disabled="!dataValue.isEdit" /> <el-form-item v-if="dataValue.isEdit" :rules="[{ required: true, message: '请è¾å ¥åä½', trigger: ['blur', 'change'] }]" style="margin: 0"> <el-input v-model="row.unit" placeholder="请è¾å ¥åä½" clearable :disabled="!dataValue.isEdit || dataValue.dataList.some(item => (item as any).tempId === row.tempId)" /> </el-form-item> </template> </el-table-column> <el-table-column label="æä½" fixed="right" width="100"> <el-table-column label="æä½" fixed="right" width="200"> <template #default="{ row, $index }"> <el-button v-if="dataValue.isEdit" type="danger" text @click="dataValue.dataList.splice($index, 1)">å é¤ <el-button v-if="dataValue.isEdit && !dataValue.dataList.some(item => (item as any).tempId === row.tempId)" type="danger" text @click="removeItem(row.tempId)">å é¤ </el-button> <el-button v-if="dataValue.isEdit" type="primary" text @click="addItem2(row.tempId)">æ·»å </el-button> </template> </el-table-column> @@ -140,10 +92,8 @@ <el-table-column label="产ååç§°" prop="productName" /> <el-table-column label="è§æ ¼åå·" prop="model" /> </el-table> <product-select-dialog v-if="dataValue.showProductDialog" v-model:model-value="dataValue.showProductDialog" @confirm="handleProduct" /> <product-select-dialog v-if="dataValue.showProductDialog" v-model:model-value="dataValue.showProductDialog" @confirm="handleProduct" /> </div> </template> @@ -160,36 +110,41 @@ import { listProcessBom } from "@/api/productionManagement/productionOrder.js"; import { list } from "@/api/productionManagement/productionProcess"; import { ElMessage } from "element-plus"; import {useRoute, useRouter} from "vue-router"; import { useRoute, useRouter } from "vue-router"; defineComponent({ name: "StructureEdit", }); const ProductSelectDialog = defineAsyncComponent( () => import("@/views/basicData/product/ProductSelectDialog.vue") () => import("@/views/basicData/product/ProductSelectDialog.vue") ); const emit = defineEmits(["update:router"]); const form = ref(); const route = useRoute() const router = useRouter() const route = useRoute(); const router = useRouter(); const routeId = computed({ get() { return route.query.id; }, set(val) { emit('update:router', val) } emit("update:router", val); }, }); // ä»è·¯ç±åæ°è·å产åä¿¡æ¯ const routeBomNo = computed(() => route.query.bomNo || ''); const routeProductName = computed(() => route.query.productName || ''); const routeProductModelName = computed(() => route.query.productModelName || ''); const routeBomNo = computed(() => route.query.bomNo || ""); const routeProductName = computed(() => route.query.productName || ""); const routeProductModelName = computed( () => route.query.productModelName || "" ); const routeOrderId = computed(() => route.query.orderId); const pageType = computed(() => route.query.type); const isOrderPage = computed(() => pageType.value === 'order' && routeOrderId.value); const isOrderPage = computed( () => pageType.value === "order" && routeOrderId.value ); const dataValue = reactive({ dataList: [], @@ -197,6 +152,7 @@ processOptions: [], showProductDialog: false, currentRowIndex: null, currentRowName: null, loading: false, isEdit: false, }); @@ -206,11 +162,12 @@ productName: "", model: "", bomNo: "", } ]) }, ]); const openDialog = index => { dataValue.currentRowIndex = index; const openDialog = (tempId: any) => { console.log(tempId, "tempId"); dataValue.currentRowName = tempId; dataValue.showProductDialog = true; }; @@ -218,83 +175,275 @@ if (isOrderPage.value) { // è®¢åæ åµï¼ä½¿ç¨è®¢åç产åç»ææ¥å£ const { data } = await listProcessBom({ orderId: routeOrderId.value }); dataValue.dataList = data || []; dataValue.dataList = (data as any) || []; } else { // éè®¢åæ åµï¼ä½¿ç¨åæ¥çæ¥å£ const { data } = await queryList(routeId.value); dataValue.dataList = data || []; dataValue.dataList = (data as any) || []; // 为ææé¡¹åå ¶å项设置name屿§ const setNameRecursively = (items: any[]) => { items.forEach((item: any) => { item.tempId = item.id; item.processName = dataValue.processOptions.find(option => option.id === item.processId) ?.name || ""; if (item.children && item.children.length > 0) { setNameRecursively(item.children); } }); }; setNameRecursively(dataValue.dataList); console.log(dataValue.dataList, "dataValue.dataList"); } }; const fetchProcessOptions = async () => { const { data } = await list(routeId.value); dataValue.processOptions = data; const { data } = await list(); dataValue.processOptions = data as any; }; const handleProduct = row => { const handleProduct = (row: any) => { if (row?.length > 1) { ElMessage.error("åªè½éæ©ä¸ä¸ªäº§å"); } dataValue.dataList[dataValue.currentRowIndex].productName = row[0].productName; dataValue.dataList[dataValue.currentRowIndex].model = row[0].model; dataValue.dataList[dataValue.currentRowIndex].productModelId = row[0].id; dataValue.dataList[dataValue.currentRowIndex].unit = row[0].unit || ""; const productData = row[0]; // æå¤å±ç»ä»¶ä¸ï¼ä¸å½å产åç¸åç产ååªè½æä¸ä¸ª const isTopLevel = dataValue.dataList.some(item => (item as any).tempId === dataValue.currentRowName); if (isTopLevel) { if (productData.productName === tableData[0].productName && productData.model === tableData[0].model) { // æ¥æ¾æ¯å¦å·²ç»æå ¶ä»é¡¶å±è¡å·²ç»æ¯è¿ä¸ªäº§å const hasOther = dataValue.dataList.some(item => (item as any).tempId !== dataValue.currentRowName && (item as any).productName === tableData[0].productName && (item as any).model === tableData[0].model ); if (hasOther) { ElMessage.warning("æå¤å±åå½å产å䏿 ·çä¸çº§åªè½æä¸ä¸ª"); return; } } } // dataValue.dataList[dataValue.currentRowIndex].productName = // row[0].productName; // dataValue.dataList[dataValue.currentRowIndex].model = row[0].model; // dataValue.dataList[dataValue.currentRowIndex].productModelId = row[0].id; // dataValue.dataList[dataValue.currentRowIndex].unit = row[0].unit || ""; dataValue.dataList.map(item => { if (item.tempId === dataValue.currentRowName) { item.productName = productData.productName; item.model = productData.model; item.productModelId = productData.id; item.unit = productData.unit || ""; return; } childItem(item, dataValue.currentRowName, productData); }); dataValue.showProductDialog = false; }; const childItem = (item: any, tempId: any, productData: any) => { if (item.tempId === tempId) { item.productName = productData.productName; item.model = productData.model; item.productModelId = productData.id; item.unit = productData.unit || ""; return true; } if (item.children && item.children.length > 0) { for (let child of item.children) { if (childItem(child, tempId, productData)) { return true; } } } return false; }; // é彿 ¡éªææå±çº§çè¡¨åæ°æ® const validateAll = () => { let isValid = true; // æ ¡éªå½æ° const validateItem = (item: any, isTopLevel = false) => { // æ ¡éªå½å项çå¿ å¡«åæ®µ if (!item.model) { ElMessage.error("è¯·éæ©è§æ ¼"); isValid = false; return; } if (!isTopLevel && !item.processId) { ElMessage.error("è¯·éæ©æ¶èå·¥åº"); isValid = false; return; } if (!item.unitQuantity) { ElMessage.error("请è¾å ¥åä½äº§åºæéæ°é"); isValid = false; return; } if (isOrderPage.value && !item.demandedQuantity) { ElMessage.error("请è¾å ¥éæ±æ»é"); isValid = false; return; } if (!item.unit) { ElMessage.error("请è¾å ¥åä½"); isValid = false; return; } // é彿 ¡éªå项 if (item.children && item.children.length > 0) { item.children.forEach(child => { validateItem(child, false); }); } }; // éåææé¡¶å±é¡¹ dataValue.dataList.forEach(item => { validateItem(item, true); }); return isValid; }; const submit = () => { form.value .validate(valid => { dataValue.loading = true; if (valid) { add({ bomId: routeId.value, productStructureList: dataValue.dataList || [], }).then(res => { router.push({ path: '/productionManagement/productionManagement/productStructure/index', }) ElMessage.success("ä¿åæå"); dataValue.loading = false; }); } dataValue.loading = true; // å è¿è¡è¡¨åæ ¡éª const valid = validateAll(); console.log(dataValue.dataList, "dataValue.dataList"); if (valid) { add({ bomId: routeId.value, children: dataValue.dataList || [], }) .then(res => { router.push({ path: "/productionManagement/productionManagement/productStructure/index", }); ElMessage.success("ä¿åæå"); dataValue.loading = false; }) .finally(() => { .catch(() => { dataValue.loading = false; }); } else { dataValue.loading = false; } }; const addItem = () => { dataValue.dataList.push({ productName: "", productId: "", model: undefined, productModelId: undefined, processId: "", unitQuantity: 0, demandedQuantity: 0, unit: "", const removeItem = (tempId: string) => { // å å°è¯ä»é¡¶å±å é¤ const topIndex = dataValue.dataList.findIndex(item => item.tempId === tempId); if (topIndex !== -1) { dataValue.dataList.splice(topIndex, 1); return; } // éå½å é¤å项 const delchildItem = (items: any[], tempId: any) => { for (let i = 0; i < items.length; i++) { const item = items[i]; if (item.tempId === tempId) { items.splice(i, 1); return true; } if (item.children && item.children.length > 0) { if (delchildItem(item.children, tempId)) { return true; } } } return false; }; dataValue.dataList.forEach(item => { if (item.children && item.children.length > 0) { delchildItem(item.children, tempId); } }); }; const addItem2 = tempId => { dataValue.dataList.map(item => { if (item.tempId === tempId) { if (!item.children) { item.children = []; } item.children.push({ parentId: item.id || "", parentTempId: item.tempId || "", productName: "", productId: "", model: undefined, productModelId: undefined, processId: "", processName: "", unitQuantity: 0, demandedQuantity: 0, unit: "", children: [], tempId: new Date().getTime(), }); return; } addchildItem(item, tempId); }); }; const addchildItem = (item: any, tempId: any) => { if (item.tempId === tempId) { console.log(item, "item"); if (!item.children) { item.children = []; } item.children.push({ parentId: item.id || "", parentTempId: item.tempId || "", productName: "", productId: "", model: undefined, productModelId: undefined, processId: "", unitQuantity: 0, demandedQuantity: 0, children: [], unit: "", tempId: new Date().getTime(), }); return true; } if (item.children && item.children.length > 0) { for (let child of item.children) { if (addchildItem(child, tempId)) { return true; } } } return false; }; const cancelEdit = () => { dataValue.isEdit = false; dataValue.dataList = dataValue.dataList.filter(item => item.id !== undefined); // dataValue.dataList = dataValue.dataList.filter(item => item.id !== undefined); fetchData(); }; onMounted(() => { onMounted(async () => { // ä»è·¯ç±åæ°åæ¾æ°æ® tableData[0].productName = routeProductName.value; tableData[0].model = routeProductModelName.value; tableData[0].bomNo = routeBomNo.value; tableData[0].productName = routeProductName.value as string; tableData[0].model = routeProductModelName.value as string; tableData[0].bomNo = routeBomNo.value as string; // è®¢åæ åµä¸ç¦ç¨ç¼è¾ if (isOrderPage.value) { dataValue.isEdit = false; } fetchData(); fetchProcessOptions(); // å å 载工åºé项ï¼åå è½½æ°æ®ï¼ç¡®ä¿el-selectè½å¤æ£ç¡®åæ¾ await fetchProcessOptions(); await fetchData(); }); </script> src/views/productionManagement/productStructure/index.vue
@@ -1,42 +1,26 @@ <template> <div class="app-container"> <div style="text-align: right; margin-bottom: 10px;"> <el-button type="info" plain icon="Upload" @click="handleImport" v-hasPermi="['product:bom:import']">å¯¼å ¥</el-button> <el-button type="warning" plain icon="Download" @click="handleExport" :disabled="selectedRows.length !== 1" v-hasPermi="['product:bom:export']">导åº</el-button> <el-button type="primary" @click="handleAdd">æ°å¢</el-button> <el-button type="danger" plain @click="handleBatchDelete" :disabled="selectedRows.length === 0">å é¤</el-button> </div> <PIMTable rowKey="id" :column="tableColumn" :tableData="tableData" :page="page" :isSelection="true" @selection-change="handleSelectionChange" :tableLoading="tableLoading" @pagination="pagination" > <template #detail="{row}"> <el-button type="primary" text @click="showDetail(row)">{{ row.bomNo }} <PIMTable rowKey="id" :column="tableColumn" :tableData="tableData" :page="page" :isSelection="true" @selection-change="handleSelectionChange" :tableLoading="tableLoading" @pagination="pagination"> <template #detail="{ row }"> <el-button type="primary" text @click="showDetail(row)">{{ row.bomNo }} </el-button> </template> </PIMTable> <StructureEdit v-if="showEdit" v-model:show-model="showEdit" :record="currentRow"/> <StructureEdit v-if="showEdit" v-model:show-model="showEdit" :record="currentRow" /> <!-- æ°å¢/ç¼è¾å¼¹çª --> <el-dialog v-model="dialogVisible" :title="operationType === 'add' ? 'æ°å¢BOM' : 'ç¼è¾BOM'" width="600px" @close="closeDialog" > <el-form ref="formRef" :model="form" :rules="rules" label-width="120px" > <el-dialog v-model="dialogVisible" :title="operationType === 'add' ? 'æ°å¢BOM' : 'ç¼è¾BOM'" width="600px" @close="closeDialog"> <el-form ref="formRef" :model="form" :rules="rules" label-width="120px"> <el-form-item label="产ååç§°" prop="productModelId"> <el-button type="primary" @click="showProductSelectDialog = true"> {{ form.productName || 'éæ©äº§å' }} @@ -46,13 +30,7 @@ <el-input v-model="form.version" placeholder="请è¾å ¥çæ¬å·" clearable /> </el-form-item> <el-form-item label="夿³¨" prop="remark"> <el-input v-model="form.remark" type="textarea" :rows="3" placeholder="请è¾å ¥å¤æ³¨" clearable /> <el-input v-model="form.remark" type="textarea" :rows="3" placeholder="请è¾å ¥å¤æ³¨" clearable /> </el-form-item> </el-form> <template #footer> @@ -60,22 +38,26 @@ <el-button type="primary" @click="handleSubmit">ç¡®å®</el-button> </template> </el-dialog> <!-- 产åéæ©å¼¹çª --> <ProductSelectDialog v-model="showProductSelectDialog" @confirm="handleProductSelect" single /> <ProductSelectDialog v-model="showProductSelectDialog" @confirm="handleProductSelect" single /> <!-- BOMå¯¼å ¥å¯¹è¯æ¡ --> <ImportDialog ref="uploadRef" v-model="upload.open" :title="upload.title" :action="upload.url" :headers="upload.headers" :disabled="upload.isUploading" :on-progress="handleFileUploadProgress" :on-success="handleFileSuccess" :show-download-template="true" @confirm="submitFileForm" @download-template="handleDownloadTemplate" @close="handleImportClose" /> </div> </template> <script setup> import { ref, reactive, toRefs, onMounted, getCurrentInstance, defineAsyncComponent } from "vue"; import { listPage, add, update, batchDelete } from "@/api/productionManagement/productBom.js"; import { getToken } from "@/utils/auth"; import { listPage, add, update, batchDelete, exportBom, downloadTemplate } from "@/api/productionManagement/productBom.js"; import { useRouter } from 'vue-router' import { ElMessageBox } from 'element-plus' import ProductSelectDialog from "@/views/basicData/product/ProductSelectDialog.vue"; import ImportDialog from "@/components/Dialog/ImportDialog.vue"; const router = useRouter() const { proxy } = getCurrentInstance() @@ -92,7 +74,7 @@ { label: "产ååç§°", prop: "productName", minWidth: 160 }, { @@ -145,6 +127,20 @@ const operationType = ref('add'); // add | edit const formRef = ref(null); const showProductSelectDialog = ref(false); // BOMå¯¼å ¥åæ° const upload = reactive({ // æ¯å¦æ¾ç¤ºå¼¹åºå±ï¼BOMå¯¼å ¥ï¼ open: false, // å¼¹åºå±æ é¢ï¼BOMå¯¼å ¥ï¼ title: "", // æ¯å¦ç¦ç¨ä¸ä¼ isUploading: false, // 设置ä¸ä¼ ç请æ±å¤´é¨ headers: { Authorization: "Bearer " + getToken() }, // ä¸ä¼ çå°å url: import.meta.env.VITE_APP_BASE_API + "/productBom/uploadBom" }); const page = reactive({ current: 1, @@ -246,7 +242,7 @@ proxy.$modal.msgError('å é¤å¤±è´¥'); }); }) .catch(() => {}); .catch(() => { }); }; // æ¹éå é¤ @@ -271,7 +267,7 @@ proxy.$modal.msgError('å é¤å¤±è´¥'); }); }) .catch(() => {}); .catch(() => { }); }; // 产åéæ© @@ -321,6 +317,103 @@ formRef.value?.resetFields(); }; // å¯¼å ¥æé®æä½ const handleImport = () => { upload.title = "BOMå¯¼å ¥"; upload.open = true; }; // å ³éå¯¼å ¥å¯¹è¯æ¡æ¶æ¸ 餿件 const handleImportClose = () => { proxy.$refs["uploadRef"].clearFiles(); }; // æä»¶ä¸ä¼ ä¸å¤ç const handleFileUploadProgress = (event, file, fileList) => { upload.isUploading = true; }; // æä»¶ä¸ä¼ æåå¤ç const handleFileSuccess = (response, file, fileList) => { upload.open = false; upload.isUploading = false; proxy.$refs["uploadRef"].clearFiles(); if (response.code === 200) { proxy.$modal.msgSuccess(response.msg || "å¯¼å ¥æå"); getList(); } else { proxy.$alert("<div style='overflow: auto;overflow-x: hidden;max-height: 70vh;padding: 10px 20px 0;'>" + response.msg + "</div>", "å¯¼å ¥ç»æ", { dangerouslyUseHTMLString: true }); } }; // æäº¤ä¸ä¼ æä»¶ const submitFileForm = () => { proxy.$refs["uploadRef"].submit(); }; // å¯¼åºæé®æä½ const handleExport = () => { if (selectedRows.value.length !== 1) { proxy.$modal.msgWarning("è¯·éæ©ä¸æ¡æ°æ®è¿è¡å¯¼åº"); return; } const bomId = selectedRows.value[0].id; const fileName = `BOM_${selectedRows.value[0].bomNo || bomId}.xlsx`; exportBom(bomId).then(res => { // è¿åçæ°æ®æ¯å¦ä¸ºç©º if (!res) { proxy.$modal.msgError("导åºå¤±è´¥ï¼è¿åæ°æ®ä¸ºç©º"); return; } const blob = new Blob([res], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' }); const downloadElement = document.createElement('a'); const href = window.URL.createObjectURL(blob); downloadElement.style.display = 'none'; downloadElement.href = href; downloadElement.download = fileName; document.body.appendChild(downloadElement); downloadElement.click(); document.body.removeChild(downloadElement); window.URL.revokeObjectURL(href); proxy.$modal.msgSuccess("å¯¼åºæå"); }).catch(err => { console.error("导åºå¼å¸¸ï¼", err); proxy.$modal.msgError("ç³»ç»å¼å¸¸ï¼å¯¼åºå¤±è´¥"); }); }; // ä¸è½½æ¨¡æ¿ const handleDownloadTemplate = async () => { const res = await downloadTemplate(); // è¿åçæ°æ®æ¯å¦ä¸ºç©º if (!res) { proxy.$modal.msgError("ä¸è½½å¤±è´¥ï¼è¿åæ°æ®ä¸ºç©º"); return; } const blob = new Blob([res], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' }); const downloadElement = document.createElement('a'); const href = window.URL.createObjectURL(blob); downloadElement.href = href; downloadElement.download = "BOM模æ¿.xlsx"; document.body.appendChild(downloadElement); downloadElement.click(); document.body.removeChild(downloadElement); window.URL.revokeObjectURL(href); proxy.$modal.msgSuccess("ä¸è½½æå"); }; // æ¥ç详æ const showDetail = (row) => { router.push({ src/views/salesManagement/deliveryLedger/index.vue
@@ -3,11 +3,15 @@ <div class="search_form"> <el-form :model="searchForm" :inline="true"> <el-form-item label="éå®è®¢åå·ï¼"> <el-input v-model="searchForm.salesContractNo" placeholder="请è¾å ¥" clearable prefix-icon="Search" <el-input v-model="searchForm.salesContractNo" placeholder="请è¾å ¥" clearable prefix-icon="Search" style="width: 200px" @change="handleQuery" /> </el-form-item> <el-form-item label="车çå·ï¼"> <el-input v-model="searchForm.shippingCarNumber" placeholder="请è¾å ¥" clearable prefix-icon="Search" <el-input v-model="searchForm.shippingCarNumber" placeholder="请è¾å ¥" clearable prefix-icon="Search" style="width: 200px" @change="handleQuery" /> </el-form-item> <el-form-item label="å¿«éåå·ï¼"> <el-input v-model="searchForm.expressNumber" placeholder="请è¾å ¥" clearable prefix-icon="Search" style="width: 200px" @change="handleQuery" /> </el-form-item> <el-form-item> @@ -24,78 +28,160 @@ </div> </div> <el-table :data="tableData" border v-loading="tableLoading" @selection-change="handleSelectionChange" :row-key="(row) => row.id" style="width: 100%" height="calc(100vh - 18.5em)"> :row-key="(row) => row.id" style="width: 100%" height="calc(100vh - 21.5em)"> <el-table-column align="center" type="selection" width="55" /> <el-table-column align="center" label="åºå·" type="index" width="60" /> <el-table-column label="éå®è®¢å" prop="salesContractNo" show-overflow-tooltip /> <el-table-column label="å货订åå·" prop="shippingNo" show-overflow-tooltip /> <el-table-column label="客æ·åç§°" prop="customerName" show-overflow-tooltip /> <el-table-column label="åè´§æ¶é´" prop="shippingDate" show-overflow-tooltip /> <el-table-column label="å货车çå·" prop="shippingCarNumber" show-overflow-tooltip /> <el-table-column label="å¿«éå ¬å¸" prop="expressCompany" show-overflow-tooltip /> <el-table-column label="å¿«éåå·" prop="expressNumber" show-overflow-tooltip /> <el-table-column label="å®¡æ ¸ç¶æ" prop="status" align="center" width="120"> <template #default="scope"> <el-tag :type="getApprovalStatusType(scope.row.status)"> {{ getApprovalStatusText(scope.row.status) }} </el-tag> </template> </el-table-column> <el-table-column fixed="right" label="æä½" width="150" align="center"> <template #default="scope"> <el-button link type="primary" size="small" @click="openForm('edit', scope.row)">ç¼è¾</el-button> <el-button link type="danger" size="small" @click="handleDeleteSingle(scope.row)">å é¤</el-button> <el-button link type="primary" size="small" :disabled="!isApproved(scope.row.status)" @click="openForm('edit', scope.row)">ç¼è¾</el-button> <el-button link type="danger" size="small" :disabled="!isApproved(scope.row.status)" @click="handleDeleteSingle(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="'50%'" :operation-type="operationType" @close="closeDia" @confirm="submitForm" @cancel="closeDia"> <el-dialog v-model="dialogFormVisible" :title="operationType === 'add' ? 'æ°å¢åè´§å°è´¦' : 'ç¼è¾åè´§å°è´¦'" width="40%" @close="closeDia"> <el-form :model="form" label-width="120px" label-position="top" :rules="rules" ref="formRef"> <el-row :gutter="30"> <el-col :span="24"> <el-form-item label="éå®è®¢åï¼" prop="salesContractNo"> <el-select v-model="form.salesContractNo" placeholder="è¯·éæ©" clearable filterable @change="handleSalesOrderChange" style="width: 100%" :disabled="operationType === 'edit'"> <el-option v-for="item in salesOrderOptions" :key="item.salesContractNo" :label="item.salesContractNo" :value="item.salesContractNo"> {{ item.salesContractNo + ' - ' + item.customerName }} </el-option> <el-form-item label="åè´§ç±»åï¼" prop="type"> <el-select v-model="form.type" placeholder="è¯·éæ©åè´§ç±»å" style="width: 100%" @change="handleShippingTypeChange" > <el-option label="货车" value="货车" /> <el-option label="å¿«é" value="å¿«é" /> </el-select> </el-form-item> </el-col> </el-row> <el-row :gutter="30"> <el-col :span="24"> <el-form-item label="客æ·åç§°ï¼" prop="customerName"> <el-input v-model="form.customerName" placeholder="请è¾å ¥" clearable :disabled="operationType === 'edit'" /> <el-form-item label="åè´§æ¥æï¼" prop="shippingDate"> <el-date-picker style="width: 100%" v-model="form.shippingDate" value-format="YYYY-MM-DD" format="YYYY-MM-DD" type="date" placeholder="è¯·éæ©åè´§æ¥æ" clearable /> </el-form-item> </el-col> </el-row> <el-row :gutter="30"> <el-col :span="24"> <el-form-item label="åè´§æ¶é´ï¼" prop="shippingDate"> <el-date-picker style="width: 100%" v-model="form.shippingDate" value-format="YYYY-MM-DD" format="YYYY-MM-DD" type="date" placeholder="è¯·éæ©" clearable /> </el-form-item> </el-col> </el-row> <el-row :gutter="30"> <el-col :span="24"> <el-col :span="24" v-if="form.type === '货车'"> <el-form-item label="å货车çå·ï¼" prop="shippingCarNumber"> <el-input v-model="form.shippingCarNumber" placeholder="请è¾å ¥" clearable /> <el-input v-model="form.shippingCarNumber" placeholder="请è¾å ¥å货车çå·" clearable /> </el-form-item> </el-col> <el-col :span="24" v-else> <el-form-item label="å¿«éå ¬å¸ï¼" prop="expressCompany"> <el-input v-model="form.expressCompany" placeholder="请è¾å ¥å¿«éå ¬å¸" clearable /> </el-form-item> </el-col> </el-row> <el-row :gutter="30" v-if="form.type === 'å¿«é'"> <el-col :span="24"> <el-form-item label="å¿«éåå·ï¼" prop="expressNumber"> <el-input v-model="form.expressNumber" placeholder="请è¾å ¥å¿«éåå·" clearable /> </el-form-item> </el-col> </el-row> <el-row :gutter="30"> <el-col :span="24"> <el-form-item label="åè´§å¾çï¼"> <el-upload v-model:file-list="deliveryFileList" :action="upload.url" multiple ref="deliveryFileUpload" auto-upload :headers="upload.headers" :data="{ type: 9 }" :before-upload="handleDeliveryBeforeUpload" :on-error="handleDeliveryUploadError" :on-success="handleDeliveryUploadSuccess" :on-remove="handleDeliveryRemove" list-type="picture-card" :limit="9" accept="image/png,image/jpeg,image/jpg" > <el-icon class="avatar-uploader-icon"><Plus /></el-icon> <template #tip> <div class="el-upload__tip"> æ¯æ jpgãjpegãpng æ ¼å¼ï¼æå¤ä¸ä¼ 9 å¼ ï¼åå¼ å¤§å°ä¸è¶ è¿ 10MB </div> </template> </el-upload> </el-form-item> </el-col> </el-row> </el-form> </FormDialog> <template #footer> <div class="dialog-footer"> <el-button type="primary" @click="submitForm">确认</el-button> <el-button @click="closeDia">åæ¶</el-button> </div> </template> </el-dialog> </div> </template> <script setup> import pagination from "@/components/PIMTable/Pagination.vue"; import FormDialog from '@/components/Dialog/FormDialog.vue'; import { onMounted, ref, reactive, toRefs, getCurrentInstance } from "vue"; import { ElMessageBox } from "element-plus"; import { Plus } from "@element-plus/icons-vue"; import { getToken } from "@/utils/auth"; import { getCurrentDate } from "@/utils/index.js"; import { deliveryLedgerListPage, addOrUpdateDeliveryLedger, delDeliveryLedger, } from "@/api/salesManagement/deliveryLedger.js"; import { delLedgerFile } from "@/api/salesManagement/salesLedger.js"; const { proxy } = getCurrentInstance(); @@ -108,6 +194,16 @@ size: 100, }); const total = ref(0); const deliveryFileList = ref([]); const javaApi = proxy.javaApi; // ä¸ä¼ é ç½® const upload = reactive({ // ä¸ä¼ çå°å url: import.meta.env.VITE_APP_BASE_API + "/file/upload", // 设置ä¸ä¼ ç请æ±å¤´é¨ headers: { Authorization: "Bearer " + getToken() }, }); // ç¨æ·ä¿¡æ¯è¡¨åå¼¹æ¡æ°æ® const operationType = ref(""); @@ -116,19 +212,31 @@ searchForm: { salesContractNo: "", // éå®è®¢åå· shippingCarNumber: "", // 车çå· expressNumber: "", // å¿«éåå· }, form: { id: null, salesContractNo: "", customerName: "", type: "货车", // 货车, å¿«é shippingDate: "", shippingCarNumber: "", expressCompany: "", expressNumber: "", // å¿«éåå· }, rules: { salesContractNo: [{ required: true, message: "è¯·éæ©éå®è®¢å", trigger: "change" }], customerName: [{ required: true, message: "请è¾å ¥å®¢æ·åç§°", trigger: "blur" }], type: [ { required: true, message: "è¯·éæ©åè´§ç±»å", trigger: "change" } ], shippingDate: [{ required: true, message: "è¯·éæ©åè´§æ¶é´", trigger: "change" }], shippingCarNumber: [{ required: true, message: "请è¾å ¥å货车çå·", trigger: "blur" }], shippingCarNumber: [ { validator: (_, value, callback) => validateShippingCarNumber(value, callback), trigger: "blur" } ], expressCompany: [ { validator: (_, value, callback) => validateExpressCompany(value, callback), trigger: "blur" } ], }, }); const { form, rules } = toRefs(data); @@ -176,23 +284,92 @@ // æå¼å¼¹æ¡ const openForm = async (type, row) => { // ç¼è¾æ¶æ£æ¥å®¡æ ¸ç¶æ if (type === 'edit' && row && !isApproved(row.status)) { proxy.$modal.msgWarning("åªè½ç¼è¾å®¡æ ¸éè¿çæ°æ®"); return; } operationType.value = type; const baseUrl = import.meta.env.VITE_APP_BASE_API; if (type === 'edit' && row) { form.value = { id: row.id ?? null, salesContractNo: row.salesContractNo ?? "", customerName: row.customerName ?? "", type: row.type || "货车", shippingDate: row.shippingDate || getCurrentDate(), shippingCarNumber: row.shippingCarNumber ?? "", expressCompany: row.expressCompany ?? "", expressNumber: row.expressNumber ?? "", }; // 妿æå¾çï¼å° commonFileList 转æ¢ä¸ºæä»¶åè¡¨æ ¼å¼ if (row.commonFileList && Array.isArray(row.commonFileList) && row.commonFileList.length > 0) { deliveryFileList.value = row.commonFileList.map((file, index) => { // å¤ç URLï¼å° Windows è·¯å¾è½¬æ¢ä¸ºå¯è®¿é®ç URL let fileUrl = file.url || ''; console.log('åå§ URL:', fileUrl); // 妿 URL æ¯ Windows è·¯å¾æ ¼å¼ï¼å å«åææ ï¼ï¼éè¦è½¬æ¢ if (fileUrl && fileUrl.indexOf('\\') > -1) { // æ¥æ¾ uploads å ³é®åçä½ç½®ï¼ä»é£éå¼å§æåç¸å¯¹è·¯å¾ const uploadsIndex = fileUrl.toLowerCase().indexOf('uploads'); if (uploadsIndex > -1) { // ä» uploads å¼å§æåè·¯å¾ï¼å¹¶å°åææ æ¿æ¢ä¸ºæ£ææ const relativePath = fileUrl.substring(uploadsIndex).replace(/\\/g, '/'); fileUrl = '/' + relativePath; console.log('转æ¢åçç¸å¯¹è·¯å¾:', fileUrl); } else { // å¦ææ²¡ææ¾å° uploadsï¼æåæåä¸ä¸ªç®å½åæä»¶å const parts = fileUrl.split('\\'); const fileName = parts[parts.length - 1]; fileUrl = '/uploads/' + fileName; console.log('æªæ¾å° uploadsï¼ä½¿ç¨æä»¶å:', fileUrl); } } // ç¡®ä¿ææé http å¼å¤´ç URL 齿¼æ¥ baseUrl if (fileUrl && !fileUrl.startsWith('http')) { // ç¡®ä¿è·¯å¾ä»¥ / å¼å¤´ if (!fileUrl.startsWith('/')) { fileUrl = '/' + fileUrl; } // æ¼æ¥ baseUrl fileUrl = javaApi + fileUrl; console.log('æç»æ¼æ¥ç URL:', fileUrl); } return { uid: file.id || Date.now() + index, name: file.name || `image_${index + 1}.jpg`, url: fileUrl, status: 'success', response: { code: 200, data: { tempId: file.id, url: fileUrl } }, tempId: file.id // ä¿åæä»¶IDï¼ç¨äºæäº¤æ¶ä½¿ç¨ }; }); } else { deliveryFileList.value = []; } } else { form.value = { id: null, salesContractNo: "", customerName: "", type: "货车", shippingDate: getCurrentDate(), shippingCarNumber: "", expressCompany: "", expressNumber: "", }; deliveryFileList.value = []; } dialogFormVisible.value = true; @@ -202,10 +379,18 @@ const submitForm = () => { proxy.$refs["formRef"].validate((valid) => { if (valid) { let tempFileIds = []; if (deliveryFileList.value !== null && deliveryFileList.value.length > 0) { tempFileIds = deliveryFileList.value.map((item) => item.tempId); } const payload = { id: form.value.id, type: form.value.type, shippingDate: form.value.shippingDate, shippingCarNumber: form.value.shippingCarNumber, shippingCarNumber: form.value.type === "货车" ? form.value.shippingCarNumber : "", expressCompany: form.value.type === "å¿«é" ? form.value.expressCompany : "", expressNumber: form.value.type === "å¿«é" ? form.value.expressNumber : "", tempFileIds: tempFileIds, }; addOrUpdateDeliveryLedger(payload).then((res) => { proxy.$modal.msgSuccess("æä½æå"); @@ -219,6 +404,7 @@ // å ³éå¼¹æ¡ const closeDia = () => { proxy.resetForm("formRef"); deliveryFileList.value = []; // æ¸ ç©ºæä»¶å表 dialogFormVisible.value = false; }; @@ -239,13 +425,19 @@ // æ¹éå é¤ const handleDelete = () => { let ids = []; if (selectedRows.value.length > 0) { ids = selectedRows.value.map((item) => item.id); } else { if (selectedRows.value.length === 0) { proxy.$modal.msgWarning("è¯·éæ©æ°æ®"); return; } // æ£æ¥éä¸çè¡æ¯å¦é½æ¯"å®¡æ ¸éè¿"ç¶æ const notApprovedRows = selectedRows.value.filter(row => !isApproved(row.status)); if (notApprovedRows.length > 0) { proxy.$modal.msgWarning("åªè½å é¤å®¡æ ¸éè¿çæ°æ®"); return; } const ids = selectedRows.value.map((item) => item.id); ElMessageBox.confirm("éä¸çå 容å°è¢«å é¤ï¼æ¯å¦ç¡®è®¤å é¤ï¼", "å é¤", { confirmButtonText: "确认", cancelButtonText: "åæ¶", @@ -264,6 +456,12 @@ // å个å é¤ const handleDeleteSingle = (row) => { // æ£æ¥æ¯å¦ä¸º"å®¡æ ¸éè¿"ç¶æ if (!isApproved(row.deliveryLedger)) { proxy.$modal.msgWarning("åªè½å é¤å®¡æ ¸éè¿çæ°æ®"); return; } ElMessageBox.confirm("æ¤æä½å°å é¤è¯¥è®°å½ï¼æ¯å¦ç¡®è®¤ï¼", "å é¤", { confirmButtonText: "确认", cancelButtonText: "åæ¶", @@ -280,6 +478,163 @@ }); }; // åè´§ç±»åæ ¡éªï¼è´§è½¦æ¶è¦æ±è½¦çï¼å¿«éæ¶è¦æ±å¿«éå ¬å¸ const validateShippingCarNumber = (value, callback) => { if (form.value.type === "货车") { if (!value) return callback(new Error("请è¾å ¥å货车çå·")); } callback(); }; const validateExpressCompany = (value, callback) => { if (form.value.type === "å¿«é") { if (!value) return callback(new Error("请è¾å ¥å¿«éå ¬å¸")); } callback(); }; // åè´§å¾çä¸ä¼ åæ ¡æ£ function handleDeliveryBeforeUpload(file) { // æ ¡æ£æä»¶ç±»å const isImage = file.type === 'image/png' || file.type === 'image/jpeg' || file.type === 'image/jpg'; if (!isImage) { proxy.$modal.msgError("åªè½ä¸ä¼ jpgãjpegãpng æ ¼å¼çå¾ç!"); return false; } // æ ¡æ£æä»¶å¤§å° const isLt10M = file.size / 1024 / 1024 < 10; if (!isLt10M) { proxy.$modal.msgError("ä¸ä¼ å¾ç大å°ä¸è½è¶ è¿ 10MB!"); return false; } proxy.$modal.loading("æ£å¨ä¸ä¼ å¾çï¼è¯·ç¨å..."); return true; } // åè´§å¾çä¸ä¼ 失败 function handleDeliveryUploadError(err) { proxy.$modal.msgError("ä¸ä¼ å¾ç失败"); proxy.$modal.closeLoading(); } // åè´§å¾çä¸ä¼ æååè° function handleDeliveryUploadSuccess(res, file, uploadFiles) { proxy.$modal.closeLoading(); if (res.code === 200) { file.tempId = res.data.tempId; proxy.$modal.msgSuccess("ä¸ä¼ æå"); } else { proxy.$modal.msgError(res.msg); proxy.$refs.deliveryFileUpload.handleRemove(file); } } // ç§»é¤åè´§å¾ç function handleDeliveryRemove(file) { console.log('file--', file) // 妿æ¯ç¼è¾æ¨¡å¼ä¸æä»¶æ idï¼éè¦è°ç¨æ¥å£å é¤ if (operationType.value === "edit") { let ids = []; ids.push(file.uid); delLedgerFile(ids).then((res) => { proxy.$modal.msgSuccess("å 餿å"); // 仿件å表ä¸ç§»é¤ const index = deliveryFileList.value.findIndex(item => item.uid === file.uid); if (index > -1) { deliveryFileList.value.splice(index, 1); } }).catch(() => { proxy.$modal.msgError("å é¤å¤±è´¥"); }); } else { // æ°å¢æ¨¡å¼ææ²¡æ id çæä»¶ï¼ç´æ¥ä»å表ä¸ç§»é¤ const index = deliveryFileList.value.findIndex(item => item.uid === file.uid); if (index > -1) { deliveryFileList.value.splice(index, 1); } } } // åè´§ç±»ååæ¢æ¶æ¸ 空对åºå段 const handleShippingTypeChange = (val) => { if (val === "货车") { form.value.expressCompany = ""; form.value.expressNumber = ""; } else { form.value.shippingCarNumber = ""; } }; // è·åå®¡æ ¸ç¶æææ¬ const getApprovalStatusText = (status) => { if (status === null || status === undefined || status === '') { return 'å¾ å®¡æ ¸'; } // å¦ææ¯æ°å if (typeof status === 'number') { const statusMap = { 0: 'å¾ å®¡æ ¸', 1: 'å®¡æ ¸ä¸', 2: 'å®¡æ ¸æç»', 3: 'å®¡æ ¸éè¿' }; return statusMap[status] || 'å¾ å®¡æ ¸'; } // 妿æ¯å符串ï¼ç´æ¥è¿åææ å° const statusStr = String(status).trim(); const statusTextMap = { 'å¾ å®¡æ ¸': 'å¾ å®¡æ ¸', 'å®¡æ ¸ä¸': 'å®¡æ ¸ä¸', 'å®¡æ ¸æç»': 'å®¡æ ¸æç»', 'å®¡æ ¸éè¿': 'å®¡æ ¸éè¿', '0': 'å¾ å®¡æ ¸', '1': 'å®¡æ ¸ä¸', '2': 'å®¡æ ¸æç»', '3': 'å®¡æ ¸éè¿' }; return statusTextMap[statusStr] || statusStr || 'å¾ å®¡æ ¸'; }; // è·åå®¡æ ¸ç¶ææ ç¾ç±»åï¼é¢è²ï¼ const getApprovalStatusType = (status) => { if (status === null || status === undefined || status === '') { return 'info'; } // å¦ææ¯æ°å if (typeof status === 'number') { const typeMap = { 0: 'info', // å¾ å®¡æ ¸ - ç°è² 1: 'warning', // å®¡æ ¸ä¸ - é»è² 2: 'danger', // å®¡æ ¸æç» - çº¢è² 3: 'success' // å®¡æ ¸éè¿ - ç»¿è² }; return typeMap[status] || 'info'; } // 妿æ¯å符串 const statusStr = String(status).trim(); const typeTextMap = { 'å¾ å®¡æ ¸': 'info', 'å®¡æ ¸ä¸': 'warning', 'å®¡æ ¸æç»': 'danger', 'å®¡æ ¸éè¿': 'success', '0': 'info', '1': 'warning', '2': 'danger', '3': 'success' }; return typeTextMap[statusStr] || 'info'; }; // æ£æ¥å®¡æ ¸ç¶ææ¯å¦ä¸º"å®¡æ ¸éè¿" const isApproved = (status) => { if (status === null || status === undefined || status === '') { return false; } // å¦ææ¯æ°åï¼3 è¡¨ç¤ºå®¡æ ¸éè¿ if (typeof status === 'number') { return status === 3; } // 妿æ¯å符串 const statusStr = String(status).trim(); return statusStr === 'å®¡æ ¸éè¿' || statusStr === '3'; }; onMounted(() => { getList(); }); @@ -295,5 +650,12 @@ justify-content: space-between; margin-bottom: 10px; } // éèå¾çä¸ä¼ ç»ä»¶çé¢è§æé®ï¼æ¾å¤§éï¼ :deep(.el-upload-list--picture-card .el-upload-list__item-actions) { .el-upload-list__item-preview { display: none; } } </style> src/views/salesManagement/invoiceLedger/index.vue
@@ -32,7 +32,6 @@ <el-table-column align="center" label="åºå·" type="index" width="60" /> <el-table-column label="éå®ååå·" prop="salesContractNo" show-overflow-tooltip width="180" /> <el-table-column label="客æ·åç§°" prop="customerName" show-overflow-tooltip width="240" /> <!-- <el-table-column label="项ç®" prop="projectName" width="320" />--> <el-table-column label="产å大类" prop="productCategory" width="200" /> <el-table-column label="è§æ ¼åå·" prop="specificationModel" width="160" show-overflow-tooltip /> <el-table-column label="å票å·" prop="invoiceNo" width="200" show-overflow-tooltip /> @@ -42,17 +41,6 @@ <el-table-column label="å½å ¥äºº" prop="invoicePerson" show-overflow-tooltip /> <el-table-column label="å½å ¥æ¥æ" prop="createTime" show-overflow-tooltip :formatter="formatDate" width="180" /> <el-table-column label="å¼ç¥¨æ¥æ" prop="invoiceDate" show-overflow-tooltip width="120" /> <!-- <el-table-column label="å票" prop="invoiceFileName" width="120" align="center" show-overflow-tooltip fixed="right"> <template #default="scope"> <el-button v-if="scope.row.invoiceFileName" text bg type="primary" @click="handleFile(scope.row.commonFiles)"> æ¥çéä»¶ </el-button> <el-button v-else link type="primary" @click="handleDownload(scope.row)"> ä¸ä¼ </el-button> </template> </el-table-column> --> <el-table-column fixed="right" label="æä½" width="150" align="center"> <template #default="scope"> <el-button link type="primary" size="small" @click="openForm(scope.row)">ç¼è¾</el-button> @@ -439,12 +427,6 @@ } onMounted(() => { // 设置å¼ç¥¨æ¥æèå´é»è®¤å¼ä¸ºå½å¤© const today = dayjs().format('YYYY-MM-DD'); searchForm.invoiceDate = [today, today]; // 设置èå´æ¥æå段çèµ·å§åç»ææ¶é´ searchForm.invoiceDateStart = today; searchForm.invoiceDateEnd = today; getList(); }); </script> src/views/salesManagement/salesLedger/index.vue
@@ -34,6 +34,7 @@ <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> @@ -42,23 +43,27 @@ <el-table :data="tableData" border v-loading="tableLoading" @selection-change="handleSelectionChange" :expand-row-keys="expandedRowKeys" :row-key="(row) => row.id" show-summary style="width: 100%" :summary-method="summarizeMainTable" @expand-change="expandChange" height="calc(100vh - 18.5em)"> <el-table-column align="center" type="selection" width="55" /> <el-table-column type="expand"> <el-table-column align="center" type="selection" width="55" fixed="left"/> <el-table-column type="expand" width="60" fixed="left"> <template #default="props"> <el-table :data="props.row.children" border show-summary :summary-method="summarizeChildrenTable"> <el-table-column align="center" label="åºå·" type="index" width="60" /> <el-table-column align="center" label="åºå·" type="index"/> <el-table-column label="产å大类" prop="productCategory" /> <el-table-column label="è§æ ¼åå·" prop="specificationModel" /> <el-table-column label="åä½" prop="unit" /> <el-table-column label="产åç¶æ" width="100px" align="center"> <el-table-column label="产åç¶æ" width="100px" align="center"> <template #default="scope"> <el-tag v-if="scope.row.approveStatus === 0" type="info">æªåºåº</el-tag> <el-tag v-if="scope.row.approveStatus === 1" type="success">å·²åºåº</el-tag> <el-tag v-if="scope.row.approveStatus === 2" type="warning">å®¡æ ¸ä¸</el-tag> <el-tag v-if="scope.row.approveStatus === 3" type="success">å®¡æ ¸æå</el-tag> <el-tag v-if="scope.row.approveStatus === 4" type="danger">å®¡æ ¸å¤±è´¥</el-tag> <el-tag v-if="scope.row.approveStatus === 1" type="success">å è¶³</el-tag> <el-tag v-else type="danger">ä¸è¶³</el-tag> </template> </el-table-column> <el-table-column label="åè´§ç¶æ" prop="shippingStatus" width="140" align="center" show-overflow-tooltip /> <el-table-column label="å¿«éå ¬å¸" prop="expressCompany" show-overflow-tooltip /> <el-table-column label="å¿«éåå·" prop="expressNumber" show-overflow-tooltip /> <el-table-column label="å货车ç" minWidth="100px" align="center"> <template #default="scope"> <div> @@ -67,11 +72,14 @@ </div> </template> </el-table-column> <el-table-column label="åè´§æ¥æ" minWidth="100px" align="center"> <el-table-column label="åè´§æ¥æ" minWidth="100px" align="center"> <template #default="scope"> <div> <div v-if="scope.row.shippingDate">{{ scope.row.shippingDate }}</div> <el-tag v-else type="info">æªåè´§</el-tag> <el-tag v-else type="info">-</el-tag> </div> </template> </el-table-column> @@ -83,7 +91,14 @@ <!--æä½--> <el-table-column Width="60px" label="æä½" align="center"> <template #default="scope"> <el-button :disabled="scope.row.approveStatus!==2 || scope.row.approveStatus!==5" link type="primary" size="small" @click="openDeliveryForm(scope.row)">åè´§</el-button> <el-button link type="primary" size="small" :disabled="scope.row.approveStatus !== 1 || !!scope.row.shippingDate || !!scope.row.shippingCarNumber" @click="openDeliveryForm(scope.row)"> åè´§ </el-button> </template> </el-table-column> </el-table> @@ -113,8 +128,8 @@ <pagination v-show="total > 0" :total="total" layout="total, sizes, prev, pager, next, jumper" :page="page.current" :limit="page.size" @pagination="paginationChange" /> </div> <el-dialog v-model="dialogFormVisible" :title="operationType === 'add' ? 'æ°å¢éå®å°è´¦é¡µé¢' : 'ç¼è¾éå®å°è´¦é¡µé¢'" width="70%" @close="closeDia"> <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"> @@ -161,113 +176,167 @@ format="YYYY-MM-DD" type="date" placeholder="è¯·éæ©" clearable :disabled="operationType === 'view'" /> </el-form-item> </el-col> </el-row> <el-row :gutter="30"> </el-row> <el-row :gutter="30"> <el-col :span="12"> <el-form-item label="å½å ¥äººï¼" prop="entryPerson"> <el-select v-model="form.entryPerson" placeholder="è¯·éæ©" clearable @change="changs" disabled> <el-select v-model="form.entryPerson" filterable default-first-option :reserve-keyword="false" placeholder="è¯·éæ©" clearable @change="changs"> <el-option v-for="item in userList" :key="item.userId" :label="item.nickName" :value="item.userId" /> </el-select> </el-form-item> </el-col> <el-col :span="12"> <el-form-item label="å½å ¥æ¥æï¼" prop="entryDate"> <el-date-picker style="width: 100%" v-model="form.entryDate" value-format="YYYY-MM-DD" format="YYYY-MM-DD" type="date" placeholder="è¯·éæ©" clearable /> </el-form-item> </el-col> </el-row> <el-row :gutter="30"> <el-col :span="12"> <el-form-item label="仿¬¾æ¹å¼"> <el-input v-model="form.paymentMethod" placeholder="请è¾å ¥" clearable :disabled="operationType === 'view'" /> </el-form-item> </el-col> </el-row> <el-row> <el-form-item label="产åä¿¡æ¯ï¼" prop="entryDate"> <el-button v-if="operationType !== 'view'" type="primary" @click="openProductForm('add')">æ·»å </el-button> <el-button v-if="operationType !== 'view'" plain type="danger" @click="deleteProduct" >å é¤</el-button> </el-form-item> </el-row> <el-table :data="productData" border @selection-change="productSelected" show-summary :summary-method="summarizeMainTable"> <el-table-column align="center" type="selection" width="55" v-if="operationType !== 'view'" /> <el-table-column align="center" label="åºå·" type="index" width="60" /> <el-table-column label="产å大类" prop="productCategory" /> <el-table-column label="è§æ ¼åå·" prop="specificationModel" /> <el-table-column label="åä½" prop="unit" /> <el-table-column label="æ°é" prop="quantity" /> <el-table-column label="ç¨ç(%)" prop="taxRate" /> <el-table-column label="å«ç¨åä»·(å )" prop="taxInclusiveUnitPrice" :formatter="formattedNumber" /> <el-table-column label="å«ç¨æ»ä»·(å )" prop="taxInclusiveTotalPrice" :formatter="formattedNumber" /> <el-table-column label="ä¸å«ç¨æ»ä»·(å )" prop="taxExclusiveTotalPrice" :formatter="formattedNumber" /> <el-table-column fixed="right" label="æä½" min-width="60" align="center" v-if="operationType !== 'view'"> <template #default="scope"> <el-button link type="primary" size="small" @click="openProductForm('edit', scope.row,scope.$index)">ç¼è¾</el-button> </template> </el-table-column> </el-table> <el-row :gutter="30"> <el-col :span="24"> <el-form-item label="夿³¨Â·ï¼" prop="remark"> <el-input v-model="form.remark" placeholder="请è¾å ¥" clearable type="textarea" :rows="2" :disabled="operationType === 'view'" /> </el-form-item> </el-col> </el-row> <el-row :gutter="30"> <el-col :span="24"> <el-form-item label="éä»¶ææï¼" prop="remark"> <el-upload v-model:file-list="fileList" :action="upload.url" multiple ref="fileUpload" auto-upload :headers="upload.headers" :before-upload="handleBeforeUpload" :on-error="handleUploadError" :on-success="handleUploadSuccess" :on-remove="handleRemove"> <el-button type="primary" v-if="operationType !== 'view'">ä¸ä¼ </el-button> <template #tip v-if="operationType !== 'view'"> <div class="el-upload__tip"> æä»¶æ ¼å¼æ¯æ docï¼docxï¼xlsï¼xlsxï¼pptï¼pptxï¼pdfï¼txtï¼xmlï¼jpgï¼jpegï¼pngï¼gifï¼bmpï¼rarï¼zipï¼7z </div> </template> </el-upload> </el-form-item> </el-col> </el-row> </el-form> <template #footer> <div class="dialog-footer"> <el-button type="primary" @click="submitForm">确认</el-button> <el-button @click="closeDia">åæ¶</el-button> </div> </template> </el-dialog> <el-dialog v-model="productFormVisible" :title="productOperationType === 'add' ? 'æ°å¢äº§å' : 'ç¼è¾äº§å'" width="40%" @close="closeProductDia"> <el-form :model="productForm" label-width="140px" label-position="top" :rules="productRules" ref="productFormRef"> <el-row :gutter="30"> <el-col :span="24"> <el-form-item label="产å大类ï¼" prop="productCategory"> <!-- <el-select v-model="productForm.productCategory" placeholder="è¯·éæ©" clearable> <el-option v-for="item in userList" :key="item.nickName" :label="item.nickName" :value="item.nickName"/> </el-select> --> <el-tree-select v-model="productForm.productCategory" placeholder="è¯·éæ©" clearable check-strictly @change="getModels" :data="productOptions" :render-after-expand="false" style="width: 100%" /> </el-form-item> </el-col> </el-row> <el-row :gutter="30"> <el-col :span="24"> <el-form-item label="è§æ ¼åå·ï¼" prop="productModelId"> <el-select v-model="productForm.productModelId" placeholder="è¯·éæ©" clearable @change="getProductModel"> <el-option v-for="item in modelOptions" :key="item.id" :label="item.model" :value="item.id" /> </el-select> </el-form-item> </el-col> </el-row> <el-row :gutter="30"> <el-col :span="12"> <el-form-item label="åä½ï¼" prop="unit"> <el-input v-model="productForm.unit" placeholder="请è¾å ¥" clearable /> </el-form-item> </el-col> <el-col :span="12"> <el-form-item label="å½å ¥æ¥æï¼" prop="entryDate"> <el-date-picker style="width: 100%" v-model="form.entryDate" value-format="YYYY-MM-DD" format="YYYY-MM-DD" type="date" placeholder="è¯·éæ©" clearable /> </el-form-item> </el-col> </el-row> <el-row> <el-form-item label="产åä¿¡æ¯ï¼" prop="entryDate"> <el-button v-if="operationType !== 'view'" type="primary" @click="openProductForm('add')">æ·»å </el-button> <el-button v-if="operationType !== 'view'" plain type="danger" @click="deleteProduct" >å é¤</el-button> </el-form-item> </el-row> <el-table :data="productData" border @selection-change="productSelected" show-summary :summary-method="summarizeMainTable"> <el-table-column align="center" type="selection" width="55" v-if="operationType !== 'view'" /> <el-table-column align="center" label="åºå·" type="index" width="60" /> <el-table-column label="产å大类" prop="productCategory" /> <el-table-column label="è§æ ¼åå·" prop="specificationModel" /> <el-table-column label="åä½" prop="unit" /> <el-table-column label="æ°é" prop="quantity" /> <el-table-column label="ç¨ç(%)" prop="taxRate" /> <el-table-column label="å«ç¨åä»·(å )" prop="taxInclusiveUnitPrice" :formatter="formattedNumber" /> <el-table-column label="å«ç¨æ»ä»·(å )" prop="taxInclusiveTotalPrice" :formatter="formattedNumber" /> <el-table-column label="ä¸å«ç¨æ»ä»·(å )" prop="taxExclusiveTotalPrice" :formatter="formattedNumber" /> <el-table-column fixed="right" label="æä½" min-width="60" align="center" v-if="operationType !== 'view'"> <template #default="scope"> <el-button link type="primary" size="small" @click="openProductForm('edit', scope.row,scope.$index)">ç¼è¾</el-button> </template> </el-table-column> </el-table> <el-row :gutter="30"> <el-col :span="24"> <el-form-item label="夿³¨Â·ï¼" prop="remark"> <el-input v-model="form.remark" placeholder="请è¾å ¥" clearable type="textarea" :rows="2" :disabled="operationType === 'view'" /> </el-form-item> </el-col> </el-row> <el-row :gutter="30"> <el-col :span="24"> <el-form-item label="éä»¶ææï¼" prop="remark"> <el-upload v-model:file-list="fileList" :action="upload.url" multiple ref="fileUpload" auto-upload :headers="upload.headers" :before-upload="handleBeforeUpload" :on-error="handleUploadError" :on-success="handleUploadSuccess" :on-remove="handleRemove"> <el-button type="primary" v-if="operationType !== 'view'">ä¸ä¼ </el-button> <template #tip v-if="operationType !== 'view'"> <div class="el-upload__tip"> æä»¶æ ¼å¼æ¯æ docï¼docxï¼xlsï¼xlsxï¼pptï¼pptxï¼pdfï¼txtï¼xmlï¼jpgï¼jpegï¼pngï¼gifï¼bmpï¼rarï¼zipï¼7z </div> </template> </el-upload> </el-form-item> </el-col> </el-row> </el-form> </FormDialog> <!-- 仿¥ä»·åå¯¼å ¥ï¼ä» 审æ¹éè¿ï¼ --> <el-dialog v-model="quotationDialogVisible" title="鿩审æ¹éè¿çé宿¥ä»·å" width="80%" :close-on-click-modal="false" > <div style="margin-bottom: 12px; display:flex; gap: 12px; align-items:center;"> <el-input v-model="quotationSearchForm.quotationNo" placeholder="请è¾å ¥æ¥ä»·åå·" clearable style="max-width: 260px;" @change="fetchQuotationList" /> <el-input v-model="quotationSearchForm.customer" placeholder="请è¾å ¥å®¢æ·åç§°" clearable style="max-width: 260px;" @change="fetchQuotationList" /> <el-button type="primary" @click="fetchQuotationList">æç´¢</el-button> <el-button @click="resetQuotationSearch">éç½®</el-button> </div> <el-table :data="quotationList" border stripe v-loading="quotationLoading" height="420px" > <el-table-column align="center" label="åºå·" type="index" width="60" /> <el-table-column prop="quotationNo" label="æ¥ä»·åå·" width="180" show-overflow-tooltip /> <el-table-column prop="customer" label="客æ·åç§°" min-width="220" show-overflow-tooltip /> <el-table-column prop="salesperson" label="ä¸å¡å" width="120" show-overflow-tooltip /> <el-table-column prop="quotationDate" label="æ¥ä»·æ¥æ" width="140" /> <el-table-column prop="status" label="审æ¹ç¶æ" width="120" align="center" /> <el-table-column prop="totalAmount" label="æ¥ä»·éé¢(å )" width="160" align="right"> <template #default="scope"> {{ Number(scope.row.totalAmount ?? 0).toFixed(2) }} </template> </el-table-column> <el-table-column fixed="right" label="æä½" width="120" align="center"> <template #default="scope"> <el-button type="primary" link @click="applyQuotation(scope.row)">éæ©</el-button> </template> </el-table-column> </el-table> <template #footer> <el-button @click="quotationDialogVisible = false">å ³é</el-button> </template> </el-dialog> <FormDialog v-model="productFormVisible" :title="productOperationType === 'add' ? 'æ°å¢äº§å' : 'ç¼è¾äº§å'" :width="'40%'" :operation-type="productOperationType" @close="closeProductDia" @confirm="submitProduct" @cancel="closeProductDia"> <el-form :model="productForm" label-width="140px" label-position="top" :rules="productRules" ref="productFormRef"> <el-row :gutter="30"> <el-col :span="24"> <el-form-item label="产å大类ï¼" prop="productCategory"> <!-- <el-select v-model="productForm.productCategory" placeholder="è¯·éæ©" clearable> <el-option v-for="item in userList" :key="item.nickName" :label="item.nickName" :value="item.nickName"/> </el-select> --> <el-tree-select v-model="productForm.productCategory" placeholder="è¯·éæ©" clearable check-strictly @change="getModels" :data="productOptions" :render-after-expand="false" style="width: 100%" /> </el-form-item> </el-col> </el-row> <el-row :gutter="30"> <el-col :span="24"> <el-form-item label="è§æ ¼åå·ï¼" prop="productModelId"> <el-select v-model="productForm.productModelId" placeholder="è¯·éæ©" clearable @change="getProductModel" filterable> <el-option v-for="item in modelOptions" :key="item.id" :label="item.model" :value="item.id" /> </el-select> </el-form-item> </el-col> </el-row> <el-row :gutter="30"> <el-col :span="12"> <el-form-item label="åä½ï¼" prop="unit"> <el-input v-model="productForm.unit" placeholder="请è¾å ¥" clearable /> </el-form-item> </el-col> <el-col :span="12"> <el-form-item label="ç¨ç(%)ï¼" prop="taxRate"> <el-select v-model="productForm.taxRate" placeholder="è¯·éæ©" clearable @change="calculateFromTaxRate"> @@ -277,15 +346,15 @@ </el-select> </el-form-item> </el-col> </el-row> <el-row :gutter="30"> <el-col :span="12"> <el-form-item label="å«ç¨åä»·(å )ï¼" prop="taxInclusiveUnitPrice"> <el-input-number :step="0.01" :min="0" v-model="productForm.taxInclusiveUnitPrice" style="width: 100%" </el-row> <el-row :gutter="30"> <el-col :span="12"> <el-form-item label="å«ç¨åä»·(å )ï¼" prop="taxInclusiveUnitPrice"> <el-input-number :step="0.01" :min="0" v-model="productForm.taxInclusiveUnitPrice" style="width: 100%" :precision="2" placeholder="请è¾å ¥" clearable @change="calculateFromUnitPrice" /> </el-form-item> </el-col> </el-form-item> </el-col> <el-col :span="12"> <el-form-item label="æ°éï¼" prop="quantity"> <el-input-number :step="0.1" :min="0" v-model="productForm.quantity" placeholder="请è¾å ¥" clearable @@ -293,37 +362,72 @@ @change="calculateFromQuantity" style="width: 100%" /> </el-form-item> </el-col> </el-row> <el-row :gutter="30"> <el-col :span="12"> <el-form-item label="å«ç¨æ»ä»·(å )ï¼" prop="taxInclusiveTotalPrice"> <el-input v-model="productForm.taxInclusiveTotalPrice" placeholder="请è¾å ¥" clearable @change="calculateFromTotalPrice" /> </el-form-item> </el-col> <el-col :span="12"> <el-form-item label="ä¸å«ç¨æ»ä»·(å )ï¼" prop="taxExclusiveTotalPrice"> <el-input v-model="productForm.taxExclusiveTotalPrice" placeholder="请è¾å ¥" clearable @change="calculateFromExclusiveTotalPrice" /> </el-form-item> </el-col> </el-row> <el-row :gutter="30"> <el-col :span="12"> <el-form-item label="å票类åï¼" prop="invoiceType"> <el-select v-model="productForm.invoiceType" placeholder="è¯·éæ©" clearable> <el-option label="墿®ç¥¨" value="墿®ç¥¨" /> <el-option label="å¢ä¸ç¥¨" value="å¢ä¸ç¥¨" /> </el-select> </el-form-item> </el-col> </el-row> </el-form> <template #footer> <div class="dialog-footer"> <el-button type="primary" @click="submitProduct">确认</el-button> <el-button @click="closeProductDia">åæ¶</el-button> </div> </template> </el-dialog> </el-row> <el-row :gutter="30"> <el-col :span="12"> <el-form-item label="å«ç¨æ»ä»·(å )ï¼" prop="taxInclusiveTotalPrice"> <el-input v-model="productForm.taxInclusiveTotalPrice" placeholder="请è¾å ¥" clearable @change="calculateFromTotalPrice" /> </el-form-item> </el-col> <el-col :span="12"> <el-form-item label="ä¸å«ç¨æ»ä»·(å )ï¼" prop="taxExclusiveTotalPrice"> <el-input v-model="productForm.taxExclusiveTotalPrice" placeholder="请è¾å ¥" clearable @change="calculateFromExclusiveTotalPrice" /> </el-form-item> </el-col> </el-row> <el-row :gutter="30"> <el-col :span="12"> <el-form-item label="å票类åï¼" prop="invoiceType"> <el-select v-model="productForm.invoiceType" placeholder="è¯·éæ©" clearable> <el-option label="墿®ç¥¨" value="墿®ç¥¨" /> <el-option label="å¢ä¸ç¥¨" value="å¢ä¸ç¥¨" /> </el-select> </el-form-item> </el-col> </el-row> </el-form> </FormDialog> <!-- å¯¼å ¥å¼¹çª --> <FormDialog v-model="importUpload.open" :title="importUpload.title" :width="'600px'" @close="importUpload.open = false" @confirm="submitImportFile" @cancel="importUpload.open = false" > <el-upload ref="importUploadRef" :limit="1" accept=".xlsx,.xls" :action="importUpload.url" :headers="importUpload.headers" :before-upload="importUpload.beforeUpload" :on-success="importUpload.onSuccess" :on-error="importUpload.onError" :on-progress="importUpload.onProgress" :on-change="importUpload.onChange" :auto-upload="false" drag > <i class="el-icon-upload"></i> <div class="el-upload__text"> å°æä»¶æå°æ¤å¤ï¼æ<em>ç¹å»ä¸ä¼ </em> </div> <template #tip> <div class="el-upload__tip"> ä» æ¯æ xls/xlsxï¼å¤§å°ä¸è¶ è¿ 10MBã <el-button link type="primary" @click="downloadTemplate">ä¸è½½å¯¼å ¥æ¨¡æ¿</el-button> </div> </template> </el-upload> </FormDialog> <!-- éä»¶åè¡¨å¼¹çª --> <FileListDialog ref="fileListRef" v-model="fileListDialogVisible" title="éä»¶å表" /> <!-- æå°é¢è§å¼¹çª --> <el-dialog v-model="printPreviewVisible" @@ -358,12 +462,15 @@ <span class="value">{{ formatDate(item.createTime) }}</span> </div> <div> <span class="label">客æ·åç§°ï¼</span> <span class="value">{{ item.customerName || 'å¼ ç±æ' }}</span> <span class="label">å货车çå·ï¼</span> <span class="value">{{ item.shippingCarNumber }}</span> </div> </div> <div class="info-row"> <div> <span class="label">客æ·åç§°ï¼</span> <span class="value">{{ item.customerName || 'å¼ ç±æ' }}</span> </div> <span class="label">åå·ï¼</span> <span class="value">{{ item.salesContractNo }}</span> </div> @@ -442,42 +549,66 @@ <el-dialog v-model="deliveryFormVisible" title="åè´§ä¿¡æ¯" width="40%" width="40%" @close="closeDeliveryDia" > <el-form :model="deliveryForm" label-width="120px" label-position="top" :rules="deliveryRules" ref="deliveryFormRef"> <el-row :gutter="30"> <el-col :span="24"> <el-form-item label="åè´§æ¥æï¼" prop="shippingDate"> <el-date-picker <el-form-item label="åè´§ç±»åï¼" prop="type"> <el-select v-model="deliveryForm.type" placeholder="è¯·éæ©åè´§ç±»å" style="width: 100%" v-model="deliveryForm.shippingDate" value-format="YYYY-MM-DD" format="YYYY-MM-DD" type="date" placeholder="è¯·éæ©åè´§æ¥æ" clearable /> > <el-option label="货车" value="货车" /> <el-option label="å¿«é" value="å¿«é" /> </el-select> </el-form-item> </el-col> </el-row> <el-row :gutter="30"> <el-col :span="24"> <el-form-item label="å货车çå·ï¼" prop="shippingCarNumber"> <el-input v-model="deliveryForm.shippingCarNumber" placeholder="请è¾å ¥å货车çå·" clearable /> </el-form-item> </el-col> </el-row> <el-row :gutter="30"> <!-- 审æ¹äººéæ©ï¼ä»¿åå审æ¹éç审æ¹äººèç¹éæ©ï¼ --> <el-row> <el-col :span="24"> <el-form-item label="审æ¹äººï¼" prop="approverId"> <el-select v-model="deliveryForm.approverId" placeholder="è¯·éæ©å®¡æ¹äºº" clearable :disabled="operationType === 'view'"> <el-option v-for="item in userList" :key="item.userId" :label="item.nickName" :value="item.userId" /> </el-select> <el-form-item> <template #label> <span>审æ¹äººéæ©ï¼</span> <el-button type="primary" @click="addApproverNode" style="margin-left: 8px;">æ°å¢èç¹</el-button> </template> <div style="display: flex; align-items: flex-end; flex-wrap: wrap;"> <div v-for="(node, index) in approverNodes" :key="node.id" style="margin-right: 20px; text-align: center; margin-bottom: 10px;" > <div> <span>审æ¹äºº</span> â </div> <el-select v-model="node.userId" placeholder="éæ©äººå" filterable style="width: 140px; margin-bottom: 8px;" > <el-option v-for="user in userList" :key="user.userId" :label="user.nickName" :value="user.userId" /> </el-select> <div> <el-button type="danger" size="small" @click="removeApproverNode(index)" v-if="approverNodes.length > 1" >å é¤</el-button> </div> </div> </div> </el-form-item> </el-col> </el-row> @@ -489,45 +620,7 @@ </div> </template> </el-dialog> <FileListDialog ref="fileListRef" v-model="fileListDialogVisible" /> <!-- å¯¼å ¥å¯¹è¯æ¡ --> <el-dialog :title="importUpload.title" v-model="importUpload.open" width="400px" append-to-body > <el-upload ref="importUploadRef" :limit="1" accept=".xlsx, .xls" :headers="importUpload.headers" :action="importUpload.url" :disabled="importUpload.isUploading" :before-upload="importUpload.beforeUpload" :on-progress="importUpload.onProgress" :on-success="importUpload.onSuccess" :on-error="importUpload.onError" :on-change="importUpload.onChange" :auto-upload="false" drag > <el-icon class="el-icon--upload"><UploadFilled /></el-icon> <div class="el-upload__text">å°æä»¶æå°æ¤å¤ï¼æ<em>ç¹å»ä¸ä¼ </em></div> <template #tip> <div class="el-upload__tip text-center"> <span>ä» å è®¸å¯¼å ¥xlsãxlsxæ ¼å¼æä»¶ã</span> </div> </template> </el-upload> <template #footer> <div class="dialog-footer"> <el-button type="primary" @click="submitImportFile" :loading="importUpload.isUploading">ç¡® å®</el-button> <el-button @click="importUpload.open = false">å æ¶</el-button> </div> </template> </el-dialog> </div> </div> </template> <script setup> @@ -536,24 +629,27 @@ import {onMounted, ref, getCurrentInstance} from "vue"; import { addShippingInfo } from "@/api/salesManagement/deliveryLedger.js"; import { ElMessageBox, ElMessage } from "element-plus"; import { UploadFilled } from "@element-plus/icons-vue"; import { UploadFilled, Download } from "@element-plus/icons-vue"; import useUserStore from "@/store/modules/user"; import { userListNoPage } from "@/api/system/user.js"; import FileList from "./fileList.vue"; import FileListDialog from '@/components/Dialog/FileListDialog.vue'; import FormDialog from '@/components/Dialog/FormDialog.vue'; import { getQuotationList } from "@/api/salesManagement/salesQuotation.js"; import { ledgerListPage, productList, customerList, addOrUpdateSalesLedger, getSalesLedgerWithProducts, delLedger, addOrUpdateSalesLedgerProduct, delProduct, delLedgerFile, getProductInventory, ledgerListPage, productList, customerList, addOrUpdateSalesLedger, getSalesLedgerWithProducts, delLedger, addOrUpdateSalesLedgerProduct, delProduct, delLedgerFile, getProductInventory, } from "@/api/salesManagement/salesLedger.js"; import { modelList, productTreeList } from "@/api/basicData/product.js"; import useFormData from "@/hooks/useFormData.js"; import dayjs from "dayjs"; import { getCurrentDate } from "@/utils/index.js"; const userStore = useUserStore(); const { proxy } = getCurrentInstance(); @@ -567,8 +663,8 @@ const modelOptions = ref([]); const tableLoading = ref(false); const page = reactive({ current: 1, size: 100, current: 1, size: 100, }); const total = ref(0); const fileList = ref([]); @@ -577,39 +673,30 @@ const operationType = ref(""); const dialogFormVisible = ref(false); const data = reactive({ searchForm: { customerName: "", // 客æ·åç§° customerContractNo: "", // 客æ·ååç¼å· salesContractNo: "", // éå®ååç¼å· projectName: "", // 项ç®åç§° entryDate: null, // å½å ¥æ¥æ entryDateStart: undefined, entryDateEnd: undefined, }, form: { salesContractNo: "", salesman: "", customerContractNo: "", customerId: "", projectName: "", entryPerson: "", entryDate: "", maintenanceTime: "", productData: [], executionDate: "", paymentMethod: "", }, rules: { salesman: [{ required: true, message: "è¯·éæ©", trigger: "change" }], customerContractNo: [ { required: true, message: "请è¾å ¥", trigger: "blur" }, ], customerId: [{ required: true, message: "è¯·éæ©", trigger: "change" }], projectName: [{ required: true, message: "请è¾å ¥", trigger: "blur" }], entryPerson: [{ required: true, message: "è¯·éæ©", trigger: "change" }], entryDate: [{ required: true, message: "è¯·éæ©", trigger: "change" }], executionDate: [{ required: true, message: "è¯·éæ©", trigger: "change" }], }, searchForm: { customerName: "", // 客æ·åç§° salesContractNo: "", // éå®ååç¼å· entryDate: null, // å½å ¥æ¥æ entryDateStart: undefined, entryDateEnd: undefined, }, form: { salesContractNo: "", salesman: "", customerId: "", entryPerson: "", entryDate: "", maintenanceTime: "", productData: [], executionDate: "", }, rules: { salesman: [{ required: true, message: "è¯·éæ©", trigger: "change" }], customerId: [{ required: true, message: "è¯·éæ©", trigger: "change" }], entryPerson: [{ required: true, message: "è¯·éæ©", trigger: "change" }], entryDate: [{ required: true, message: "è¯·éæ©", trigger: "change" }], executionDate: [{ required: true, message: "è¯·éæ©", trigger: "change" }], }, }); const { form, rules } = toRefs(data); const { form: searchForm } = useFormData(data.searchForm); @@ -618,514 +705,646 @@ const productOperationType = ref(""); const currentId = ref(""); const productFormData = reactive({ productForm: { productCategory: "", specificationModel: "", unit: "", quantity: "", taxInclusiveUnitPrice: "", taxRate: "", taxInclusiveTotalPrice: "", taxExclusiveTotalPrice: "", invoiceType: "", }, productRules: { productCategory: [{ required: true, message: "è¯·éæ©", trigger: "change" }], productForm: { productCategory: "", specificationModel: "", unit: "", quantity: "", taxInclusiveUnitPrice: "", taxRate: "", taxInclusiveTotalPrice: "", taxExclusiveTotalPrice: "", invoiceType: "", }, productRules: { productCategory: [{ required: true, message: "è¯·éæ©", trigger: "change" }], productModelId: [{ required: true, message: "è¯·éæ©", trigger: "change" }], specificationModel: [ { required: true, message: "è¯·éæ©", trigger: "change" }, ], unit: [{ required: true, message: "请è¾å ¥", trigger: "blur" }], quantity: [{ required: true, message: "请è¾å ¥", trigger: "blur" }], taxInclusiveUnitPrice: [ { required: true, message: "请è¾å ¥", trigger: "blur" }, ], taxRate: [{ required: true, message: "è¯·éæ©", trigger: "change" }], taxInclusiveTotalPrice: [ { required: true, message: "请è¾å ¥", trigger: "blur" }, ], taxExclusiveTotalPrice: [ { required: true, message: "请è¾å ¥", trigger: "blur" }, ], invoiceType: [{ required: true, message: "è¯·éæ©", trigger: "change" }], }, specificationModel: [ { required: true, message: "è¯·éæ©", trigger: "change" }, ], unit: [{ required: true, message: "请è¾å ¥", trigger: "blur" }], quantity: [{ required: true, message: "请è¾å ¥", trigger: "blur" }], taxInclusiveUnitPrice: [ { required: true, message: "请è¾å ¥", trigger: "blur" }, ], taxRate: [{ required: true, message: "è¯·éæ©", trigger: "change" }], taxInclusiveTotalPrice: [ { required: true, message: "请è¾å ¥", trigger: "blur" }, ], taxExclusiveTotalPrice: [ { required: true, message: "请è¾å ¥", trigger: "blur" }, ], invoiceType: [{ required: true, message: "è¯·éæ©", trigger: "change" }], }, }); const { productForm, productRules } = toRefs(productFormData); // 鲿¢å¾ªç¯è®¡ç®çæ å¿ const isCalculating = ref(false); const upload = reactive({ // ä¸ä¼ çå°å url: import.meta.env.VITE_APP_BASE_API + "/file/upload", // 设置ä¸ä¼ ç请æ±å¤´é¨ headers: { Authorization: "Bearer " + getToken() }, // ä¸ä¼ çå°å url: import.meta.env.VITE_APP_BASE_API + "/file/upload", // 设置ä¸ä¼ ç请æ±å¤´é¨ headers: { Authorization: "Bearer " + getToken() }, }); // æå°ç¸å ³ const printPreviewVisible = ref(false); const printData = ref([]); // æ¥ä»·åå¯¼å ¥ç¸å ³ const quotationDialogVisible = ref(false); const quotationLoading = ref(false); const quotationList = ref([]); const quotationSearchForm = reactive({ quotationNo: "", customer: "", }); const selectedQuotation = ref(null); // åè´§ç¸å ³ const deliveryFormVisible = ref(false); const currentDeliveryRow = ref(null); const deliveryFormData = reactive({ deliveryForm: { shippingDate: "", shippingCarNumber: "", type: "货车", // 货车, å¿«é }, deliveryRules: { shippingDate: [ { required: true, message: "è¯·éæ©åè´§æ¥æ", trigger: "change" } ], shippingCarNumber: [ { required: true, message: "请è¾å ¥å货车çå·", trigger: "blur" } ], approverId:[ { required: true,message: "", } type: [ { required: true, message: "è¯·éæ©åè´§ç±»å", trigger: "change" } ] }, }); const { deliveryForm, deliveryRules } = toRefs(deliveryFormData); // å货审æ¹äººèç¹ï¼ä»¿ååå®¡æ¹ infoFormDia.vueï¼ const approverNodes = ref([{ id: 1, userId: null }]); let nextApproverId = 2; const addApproverNode = () => { approverNodes.value.push({ id: nextApproverId++, userId: null }); }; const removeApproverNode = (index) => { approverNodes.value.splice(index, 1); }; // å¯¼å ¥ç¸å ³ 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("å¯¼å ¥å¤±è´¥ï¼è¯·éè¯"); }, 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 changeDaterange = (value) => { if (value) { searchForm.entryDateStart = dayjs(value[0]).format("YYYY-MM-DD"); searchForm.entryDateEnd = dayjs(value[1]).format("YYYY-MM-DD"); } else { searchForm.entryDateStart = undefined; searchForm.entryDateEnd = undefined; } handleQuery(); if (value) { searchForm.entryDateStart = dayjs(value[0]).format("YYYY-MM-DD"); searchForm.entryDateEnd = dayjs(value[1]).format("YYYY-MM-DD"); } else { searchForm.entryDateStart = undefined; searchForm.entryDateEnd = undefined; } handleQuery(); }; // æ¥è¯¢å表 /** æç´¢æé®æä½ */ const handleQuery = () => { page.current = 1; // åªæå¨ç¹å»æç´¢æé®æ¶æé置页ç å°ç¬¬ä¸é¡µ // é¿å 表ååæ®µchangeäºä»¶å¹²æ°å页 if (arguments.length === 0) { page.current = 1; } expandedRowKeys.value = []; getList(); getList(); }; const paginationChange = (obj) => { page.current = obj.page; page.size = obj.limit; getList(); page.current = obj.page; page.size = obj.limit; getList(); }; const getList =async () => { let userLists = await userListNoPage(); userList.value = userLists.data; tableLoading.value = true; const { entryDate, ...rest } = searchForm; ledgerListPage({ ...rest, ...page }) .then((res) => { tableLoading.value = false; tableData.value = res.records; tableData.value.map((item) => { item.children = []; }); total.value = res.total; }) .catch(() => { tableLoading.value = false; }); const getList = () => { tableLoading.value = true; const { entryDate, ...rest } = searchForm; // å°èå´æ¥æåæ®µä¼ éç»å端 const params = { ...rest, ...page }; // ç§»é¤å½å ¥æ¥æçé»è®¤å¼è®¾ç½®ï¼åªä¿çèå´æ¥æå段 delete params.entryDate; return ledgerListPage(params) .then((res) => { tableLoading.value = false; tableData.value = res.records; tableData.value.map((item) => { item.children = []; }); total.value = res.total; return res; }) .catch(() => { tableLoading.value = false; }); }; // è·å产å大类treeæ°æ® const getProductOptions = () => { productTreeList().then((res) => { productOptions.value = convertIdToValue(res); }); // è¿å Promiseï¼ä¾¿äºå¨ç¼è¾äº§åæ¶çå¾ å è½½å®æ return productTreeList().then((res) => { productOptions.value = convertIdToValue(res); return productOptions.value; }); }; const formattedNumber = (row, column, cellValue) => { return parseFloat(cellValue).toFixed(2); return parseFloat(cellValue).toFixed(2); }; // è·åtreeåæ°æ® const getModels = (value) => { productForm.value.productCategory = findNodeById(productOptions.value, value); modelList({ id: value }).then((res) => { modelOptions.value = res; }); productForm.value.productCategory = findNodeById(productOptions.value, value); modelList({ id: value }).then((res) => { modelOptions.value = res; }); }; const getProductModel = (value) => { const index = modelOptions.value.findIndex((item) => item.id === value); if (index !== -1) { productForm.value.specificationModel = modelOptions.value[index].model; productForm.value.unit = modelOptions.value[index].unit; } else { productForm.value.specificationModel = null; productForm.value.unit = null; } const index = modelOptions.value.findIndex((item) => item.id === value); if (index !== -1) { productForm.value.specificationModel = modelOptions.value[index].model; productForm.value.unit = modelOptions.value[index].unit; } else { productForm.value.specificationModel = null; productForm.value.unit = null; } }; 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 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 }; function convertIdToValue(data) { if (!data || !Array.isArray(data)) return []; return data.map((item) => { const { id, children, ...rest } = item; const newItem = { ...rest, value: id, // å° id æ¹ä¸º value }; if (children && children.length > 0) { newItem.children = convertIdToValue(children); } return newItem; }); return data.map((item) => { const { id, children, ...rest } = item; const newItem = { ...rest, value: id, // å° id æ¹ä¸º value }; if (children && children.length > 0) { newItem.children = convertIdToValue(children); } return newItem; }); } // æ ¹æ®åç§°åæ¥äº§å大类 idï¼ä¾¿äºä» ååç§°æ¶çåæ¾ function findNodeIdByLabel(nodes, label) { if (!label) return null; for (let i = 0; i < nodes.length; i++) { const node = nodes[i]; if (node.label === label) return node.value; if (node.children && node.children.length > 0) { const found = findNodeIdByLabel(node.children, label); if (found !== null && found !== undefined) return found; } } return null; } // è¡¨æ ¼éæ©æ°æ® const handleSelectionChange = (selection) => { // è¿æ»¤æåæ°æ® selectedRows.value = selection.filter((item) => item.children !== undefined); console.log("selection", selectedRows.value); // è¿æ»¤æåæ°æ® selectedRows.value = selection.filter((item) => item.children !== undefined); console.log("selection", selectedRows.value); }; const productSelected = (selectedRows) => { productSelectedRows.value = selectedRows; productSelectedRows.value = selectedRows; }; const expandedRowKeys = ref([]); // å±å¼è¡ï¼å§ç»åªå±å¼ä¸è¡ï¼ const expandChange = (row) => { const rowKey = row.id; const isExpanded = expandedRowKeys.value.includes(rowKey); if (isExpanded) { // å½åè¡å·²å±å¼ -> æ¶èµ· expandedRowKeys.value = []; return; } // å±å¼å½åè¡åï¼å æ¶èµ·å ¶å®è¡ expandedRowKeys.value = []; try { productList({ salesLedgerId: row.id, type: 1 }).then((res) => { const index = tableData.value.findIndex((item) => item.id === row.id); if (index > -1) { tableData.value[index].children = res.data; } // åªä¿çå½åè¿ä¸è¡å¤äºå±å¼ç¶æ expandedRowKeys.value = [rowKey]; }); } catch (error) { console.log(error); } // å±å¼è¡ const expandChange = (row, expandedRows) => { if (expandedRows.length > 0) { expandedRowKeys.value = []; try { productList({ salesLedgerId: row.id, type: 1 }).then((res) => { const index = tableData.value.findIndex((item) => item.id === row.id); if (index > -1) { tableData.value[index].children = res.data; } expandedRowKeys.value.push(row.id); }); } catch (error) { console.log(error); } } else { expandedRowKeys.value = []; } }; // 主表åè®¡æ¹æ³ const summarizeMainTable = (param) => { return proxy.summarizeTable(param, [ "contractAmount", "taxInclusiveTotalPrice", "taxExclusiveTotalPrice", ]); return proxy.summarizeTable(param, [ "contractAmount", "taxInclusiveTotalPrice", "taxExclusiveTotalPrice", ]); }; // å表åè®¡æ¹æ³ const summarizeChildrenTable = (param) => { return proxy.summarizeTable(param, [ "taxInclusiveUnitPrice", "taxInclusiveTotalPrice", "taxExclusiveTotalPrice", ]); return proxy.summarizeTable(param, [ "taxInclusiveUnitPrice", "taxInclusiveTotalPrice", "taxExclusiveTotalPrice", ]); }; // æå¼å¼¹æ¡ const openForm = async (type, row) => { operationType.value = type; form.value = {}; productData.value = []; customerList().then((res) => { customerOption.value = res; }); form.value.entryPerson = userStore.id; if (type !== "add") { currentId.value = row.id; getSalesLedgerWithProducts({ id: row.id, type: 1 }).then((res) => { form.value = { ...res }; form.value.entryPerson = Number(res.entryPerson); productData.value = form.value.productData; fileList.value = form.value.salesLedgerFiles; }); } // let userAll = await userStore.getInfo() // userList.value.forEach(element => { // if(userAll.user.nickName === element.nickName && userAll.user.userName === element.userName) { // form.value.entryPerson = userAll.user.userId // 设置é»è®¤ä¸å¡å为å½åç¨æ· // } // }); form.value.entryDate = getCurrentDate(); // 设置é»è®¤å½å ¥æ¥æä¸ºå½åæ¥æ dialogFormVisible.value = true; operationType.value = type; form.value = {}; productData.value = []; selectedQuotation.value = null; let userLists = await userListNoPage(); userList.value = userLists.data; customerList().then((res) => { customerOption.value = res; }); form.value.entryPerson = userStore.id; if (type === "add") { // æ°å¢æ¶è®¾ç½®å½å ¥æ¥æä¸ºå½å¤© form.value.entryDate = getCurrentDate(); // ç¾è®¢æ¥æé»è®¤ä¸ºå½å¤© form.value.executionDate = getCurrentDate(); } else { currentId.value = row.id; getSalesLedgerWithProducts({ id: row.id, type: 1 }).then((res) => { form.value = { ...res }; form.value.entryPerson = Number(res.entryPerson); productData.value = form.value.productData; fileList.value = form.value.salesLedgerFiles; }); } // let userAll = await userStore.getInfo() // userList.value.forEach(element => { // if(userAll.user.nickName === element.nickName && userAll.user.userName === element.userName) { // form.value.entryPerson = userAll.user.userId // 设置é»è®¤ä¸å¡å为å½åç¨æ· // } // }); form.value.entryDate = getCurrentDate(); // 设置é»è®¤å½å ¥æ¥æä¸ºå½åæ¥æ dialogFormVisible.value = true; }; // æå¼æ¥ä»·å鿩弹çªï¼ä» 审æ¹éè¿ï¼ const openQuotationDialog = async () => { if (operationType.value === "view") return; quotationDialogVisible.value = true; // å ç¡®ä¿å®¢æ·å表已å è½½ï¼ä¾¿äºåç»åå¡« customerId if (!customerOption.value || customerOption.value.length === 0) { try { const res = await customerList(); customerOption.value = res; } catch (e) { // ignoreï¼å è®¸ç¨æ·åç»æå¨éæ©å®¢æ· } } await fetchQuotationList(); }; 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 resetQuotationSearch = async () => { quotationSearchForm.quotationNo = ""; quotationSearchForm.customer = ""; await fetchQuotationList(); }; // é䏿¥ä»·åååå¡«å°å°è´¦è¡¨å const applyQuotation = (row) => { if (!row) return; selectedQuotation.value = row; // ä¸å¡å form.value.salesman = row.salesperson || ""; // 客æ·åç§° -> customerId const customer = (customerOption.value || []).find((c) => c.customerName === row.customer); if (customer?.id) { form.value.customerId = customer.id; } else { // 妿æ¾ä¸å°ï¼ä¿çåå¼ï¼å è®¸ç¨æ·æå¨éæ©/䏿æå·²æè¾å ¥ï¼ form.value.customerId = form.value.customerId || ""; } // 产åä¿¡æ¯æ å°ï¼æ¥ä»· products -> å°è´¦ productData const products = Array.isArray(row.products) ? row.products : []; productData.value = products.map((p) => { const quantity = Number(p.quantity ?? 0) || 0; const unitPrice = Number(p.unitPrice ?? 0) || 0; const taxRate = "13"; // é»è®¤ 13%ï¼ä¾¿äºç´æ¥æäº¤ï¼å¦éå¯å¨äº§åä¸èªè¡ä¿®æ¹ï¼ const taxInclusiveTotalPrice = (unitPrice * quantity).toFixed(2); const taxExclusiveTotalPrice = proxy.calculateTaxExclusiveTotalPrice(taxInclusiveTotalPrice, taxRate); return { // å°è´¦å段 productCategory: p.product || p.productName || "", specificationModel: p.specification || "", unit: p.unit || "", quantity: quantity, taxRate: taxRate, taxInclusiveUnitPrice: unitPrice.toFixed(2), taxInclusiveTotalPrice: taxInclusiveTotalPrice, taxExclusiveTotalPrice: taxExclusiveTotalPrice, invoiceType: "墿®ç¥¨", }; }); quotationDialogVisible.value = false; }; function changs(val) { console.log(val); console.log(val); } // ä¸ä¼ åæ ¡æ£ function handleBeforeUpload(file) { // æ ¡æ£æä»¶å¤§å° // if (file.size > 1024 * 1024 * 10) { // proxy.$modal.msgError("ä¸ä¼ æä»¶å¤§å°ä¸è½è¶ è¿10MB!"); // return false; // } proxy.$modal.loading("æ£å¨ä¸ä¼ æä»¶ï¼è¯·ç¨å..."); return true; // æ ¡æ£æä»¶å¤§å° // if (file.size > 1024 * 1024 * 10) { // proxy.$modal.msgError("ä¸ä¼ æä»¶å¤§å°ä¸è½è¶ è¿10MB!"); // return false; // } proxy.$modal.loading("æ£å¨ä¸ä¼ æä»¶ï¼è¯·ç¨å..."); return true; } // ä¸ä¼ 失败 function handleUploadError(err) { proxy.$modal.msgError("ä¸ä¼ æä»¶å¤±è´¥"); proxy.$modal.closeLoading(); proxy.$modal.msgError("ä¸ä¼ æä»¶å¤±è´¥"); proxy.$modal.closeLoading(); } // ä¸ä¼ æååè° function handleUploadSuccess(res, file, uploadFiles) { proxy.$modal.closeLoading(); if (res.code === 200) { file.tempId = res.data.tempId; proxy.$modal.msgSuccess("ä¸ä¼ æå"); } else { proxy.$modal.msgError(res.msg); proxy.$refs.fileUpload.handleRemove(file); } proxy.$modal.closeLoading(); if (res.code === 200) { file.tempId = res.data.tempId; proxy.$modal.msgSuccess("ä¸ä¼ æå"); } else { proxy.$modal.msgError(res.msg); proxy.$refs.fileUpload.handleRemove(file); } } // ç§»é¤æä»¶ function handleRemove(file) { if (operationType.value === "edit") { let ids = []; ids.push(file.id); delLedgerFile(ids).then((res) => { proxy.$modal.msgSuccess("å 餿å"); }); } if (operationType.value === "edit") { let ids = []; ids.push(file.id); delLedgerFile(ids).then((res) => { proxy.$modal.msgSuccess("å 餿å"); }); } } // æäº¤è¡¨å const submitForm = () => { proxy.$refs["formRef"].validate((valid) => { if (valid) { proxy.$refs["formRef"].validate((valid) => { if (valid) { console.log('productData.value--', productData.value) if (productData.value !== null && productData.value.length > 0) { form.value.productData = proxy.HaveJson(productData.value); } else { proxy.$modal.msgWarning("请添å 产åä¿¡æ¯"); return; } let tempFileIds = []; if (fileList.value !== null && fileList.value.length > 0) { tempFileIds = fileList.value.map((item) => item.tempId); } form.value.tempFileIds = tempFileIds; form.value.type = 1; addOrUpdateSalesLedger(form.value).then((res) => { proxy.$modal.msgSuccess("æäº¤æå"); closeDia(); getList(); }); } }); if (productData.value !== null && productData.value.length > 0) { form.value.productData = proxy.HaveJson(productData.value); } else { proxy.$modal.msgWarning("请添å 产åä¿¡æ¯"); return; } let tempFileIds = []; if (fileList.value !== null && fileList.value.length > 0) { tempFileIds = fileList.value.map((item) => item.tempId); } form.value.tempFileIds = tempFileIds; form.value.type = 1; addOrUpdateSalesLedger(form.value).then((res) => { proxy.$modal.msgSuccess("æäº¤æå"); closeDia(); getList(); }); } }); }; // å ³éå¼¹æ¡ const closeDia = () => { proxy.resetForm("formRef"); dialogFormVisible.value = false; proxy.resetForm("formRef"); dialogFormVisible.value = false; }; const productIndex = ref(0); // æå¼äº§åå¼¹æ¡ const openProductForm =async (type, row,index) => { productOperationType.value = type; productForm.value = {}; proxy.resetForm("productFormRef"); // æ°å¢ãç¼è¾é½éå å è½½äº§åæ ï¼å¦å el-tree-select æ æ°æ® try { await getProductOptions(); } catch (e) { console.error("å è½½äº§åæ å¤±è´¥", e); } if (type === "edit") { productForm.value = { ...row }; productIndex.value = index; } productFormVisible.value = true; getProductOptions(); const openProductForm = async (type, row, index) => { productOperationType.value = type; productForm.value = {}; proxy.resetForm("productFormRef"); if (type === "edit") { productForm.value = { ...row }; productIndex.value = index; // ç¼è¾æ¶æ ¹æ®äº§å大类åç§°åæ¥ tree èç¹ idï¼å¹¶å è½½è§æ ¼åå·å表 try { const options = productOptions.value && productOptions.value.length > 0 ? productOptions.value : await getProductOptions(); const categoryId = findNodeIdByLabel(options, productForm.value.productCategory); if (categoryId) { const models = await modelList({ id: categoryId }); modelOptions.value = models || []; // æ ¹æ®å½åè§æ ¼åå·åç§°åæ¥å¹¶è®¾ç½® productModelIdï¼ä¾¿äºä¸ææ¡æ¾ç¤ºå·²éå¼ const currentModel = (modelOptions.value || []).find( (m) => m.model === productForm.value.specificationModel ); if (currentModel) { productForm.value.productModelId = currentModel.id; } } } catch (e) { // å 载失败æ¶ä¿æå¯ç¼è¾ï¼ä¸ä¸æå¼¹çª console.error("å 载产åè§æ ¼åå·å¤±è´¥", e); } } productFormVisible.value = true; }; // æäº¤äº§å表å const submitProduct = () => { proxy.$refs["productFormRef"].validate((valid) => { if (valid) { if (operationType.value === "edit") { submitProductEdit(); } else { if(productOperationType.value === "add"){ productData.value.push({ ...productForm.value }); }else{ productData.value[productIndex.value] = { ...productForm.value } } closeProductDia(); } } }); proxy.$refs["productFormRef"].validate((valid) => { if (valid) { if (operationType.value === "edit") { submitProductEdit(); } else { if(productOperationType.value === "add"){ productData.value.push({ ...productForm.value }); }else{ productData.value[productIndex.value] = { ...productForm.value } } closeProductDia(); } } }); }; const submitProductEdit = () => { productForm.value.salesLedgerId = currentId.value; productForm.value.type = 1 addOrUpdateSalesLedgerProduct(productForm.value).then((res) => { proxy.$modal.msgSuccess("æäº¤æå"); closeProductDia(); getSalesLedgerWithProducts({ id: currentId.value, type: 1 }).then((res) => { productData.value = res.productData; }); }); productForm.value.salesLedgerId = currentId.value; productForm.value.type = 1 addOrUpdateSalesLedgerProduct(productForm.value).then((res) => { proxy.$modal.msgSuccess("æäº¤æå"); closeProductDia(); getSalesLedgerWithProducts({ id: currentId.value, type: 1 }).then((res) => { productData.value = res.productData; }); }); }; // å é¤äº§å const deleteProduct = () => { if (productSelectedRows.value.length === 0) { proxy.$modal.msgWarning("è¯·éæ©æ°æ®"); return; } if (operationType.value === "add") { productSelectedRows.value.forEach((selectedRow) => { const index = productData.value.findIndex( (product) => product.id === selectedRow.id ); if (index !== -1) { productData.value.splice(index, 1); } }); } else { let ids = []; if (productSelectedRows.value.length > 0) { ids = productSelectedRows.value.map((item) => item.id); } ElMessageBox.confirm("éä¸çå 容å°è¢«å é¤ï¼æ¯å¦ç¡®è®¤å é¤ï¼", "导åº", { confirmButtonText: "确认", cancelButtonText: "åæ¶", type: "warning", }) .then(() => { delProduct(ids).then((res) => { proxy.$modal.msgSuccess("å 餿å"); closeProductDia(); getSalesLedgerWithProducts({ id: currentId.value, type: 1 }).then( (res) => { productData.value = res.productData; } ); }); }) .catch(() => { proxy.$modal.msg("已忶"); }); } if (productSelectedRows.value.length === 0) { proxy.$modal.msgWarning("è¯·éæ©æ°æ®"); return; } if (operationType.value === "add") { productSelectedRows.value.forEach((selectedRow) => { const index = productData.value.findIndex( (product) => product.id === selectedRow.id ); if (index !== -1) { productData.value.splice(index, 1); } }); } else { let ids = []; if (productSelectedRows.value.length > 0) { ids = productSelectedRows.value.map((item) => item.id); } ElMessageBox.confirm("éä¸çå 容å°è¢«å é¤ï¼æ¯å¦ç¡®è®¤å é¤ï¼", "导åº", { confirmButtonText: "确认", cancelButtonText: "åæ¶", type: "warning", }) .then(() => { delProduct(ids).then((res) => { proxy.$modal.msgSuccess("å 餿å"); closeProductDia(); getSalesLedgerWithProducts({ id: currentId.value, type: 1 }).then( (res) => { productData.value = res.productData; } ); }); }) .catch(() => { proxy.$modal.msg("已忶"); }); } }; // å ³é产åå¼¹æ¡ const closeProductDia = () => { proxy.resetForm("productFormRef"); productFormVisible.value = false; proxy.resetForm("productFormRef"); productFormVisible.value = false; }; // å¯¼å ¥ const handleImport = () => { importUpload.title = "å¯¼å ¥éå®å°è´¦"; importUpload.open = true; if (importUploadRef.value) { importUploadRef.value.clearFiles(); } importUpload.title = "å¯¼å ¥éå®å°è´¦"; importUpload.open = true; if (importUploadRef.value) { importUploadRef.value.clearFiles(); } }; // ä¸è½½å¯¼å ¥æ¨¡æ¿ const downloadTemplate = () => { proxy.download("/sales/ledger/exportTemplate", {}, "éå®å°è´¦å¯¼å ¥æ¨¡æ¿.xlsx"); }; // æäº¤å¯¼å ¥æä»¶ const submitImportFile = () => { importUpload.isUploading = true; proxy.$refs["importUploadRef"].submit(); importUpload.isUploading = true; proxy.$refs["importUploadRef"].submit(); }; // å¯¼åº const handleOut = () => { ElMessageBox.confirm("éä¸çå 容å°è¢«å¯¼åºï¼æ¯å¦ç¡®è®¤å¯¼åºï¼", "导åº", { confirmButtonText: "确认", cancelButtonText: "åæ¶", type: "warning", }) .then(() => { proxy.download("/sales/ledger/export", {}, "éå®å°è´¦.xlsx"); }) .catch(() => { proxy.$modal.msg("已忶"); }); ElMessageBox.confirm("éä¸çå 容å°è¢«å¯¼åºï¼æ¯å¦ç¡®è®¤å¯¼åºï¼", "导åº", { confirmButtonText: "确认", cancelButtonText: "åæ¶", type: "warning", }) .then(() => { proxy.download("/sales/ledger/export", {}, "éå®å°è´¦.xlsx"); }) .catch(() => { proxy.$modal.msg("已忶"); }); }; // å é¤ 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(() => { delLedger(ids).then((res) => { proxy.$modal.msgSuccess("å 餿å"); getList(); }); }) .catch(() => { proxy.$modal.msg("已忶"); }); 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(() => { delLedger(ids).then((res) => { proxy.$modal.msgSuccess("å 餿å"); getList(); }); }) .catch(() => { proxy.$modal.msg("已忶"); }); }; // æå°åè½ @@ -1359,8 +1578,8 @@ </tr> </thead> <tbody> ${item.products && item.products.length > 0 ? item.products.map(product => ` ${item.products && item.products.length > 0 ? item.products.map(product => ` <tr> <td>${product.productCategory || ''}</td> <td>${product.specificationModel || ''}</td> @@ -1369,9 +1588,9 @@ <td>${product.quantity || '0'}</td> <td>${product.taxInclusiveTotalPrice || '0'}</td> </tr> `).join('') : '<tr><td colspan="6" style="text-align: center; color: #999;">ææ äº§åæ°æ®</td></tr>' } `).join('') : '<tr><td colspan="6" style="text-align: center; color: #999;">ææ äº§åæ°æ®</td></tr>' } </tbody> <tfoot> <tr> @@ -1454,100 +1673,91 @@ const seconds = String(date.getSeconds()).padStart(2, "0"); return `${year}/${month}/${day} ${hours}:${minutes}:${seconds}`; }; // è·åå½åæ¥æå¹¶æ ¼å¼å为 YYYY-MM-DD function getCurrentDate() { const today = new Date(); const year = today.getFullYear(); const month = String(today.getMonth() + 1).padStart(2, "0"); // æä»½ä»0å¼å§ const day = String(today.getDate()).padStart(2, "0"); return `${year}-${month}-${day}`; } // 计ç®äº§åæ»æ°é const getTotalQuantity = (products) => { if (!products || products.length === 0) return '0'; const total = products.reduce((sum, product) => { return sum + (parseFloat(product.quantity) || 0); }, 0); return total.toFixed(2); if (!products || products.length === 0) return '0'; const total = products.reduce((sum, product) => { return sum + (parseFloat(product.quantity) || 0); }, 0); return total.toFixed(2); }; // 计ç®äº§åæ»éé¢ const getTotalAmount = (products) => { if (!products || products.length === 0) return '0'; const total = products.reduce((sum, product) => { return sum + (parseFloat(product.taxInclusiveTotalPrice) || 0); }, 0); return total.toFixed(2); if (!products || products.length === 0) return '0'; const total = products.reduce((sum, product) => { return sum + (parseFloat(product.taxInclusiveTotalPrice) || 0); }, 0); return total.toFixed(2); }; // ç¨äºæå°ç计ç®å½æ° const getTotalQuantityForPrint = (products) => { if (!products || products.length === 0) return '0'; const total = products.reduce((sum, product) => { return sum + (parseFloat(product.quantity) || 0); }, 0); return total.toFixed(2); if (!products || products.length === 0) return '0'; const total = products.reduce((sum, product) => { return sum + (parseFloat(product.quantity) || 0); }, 0); return total.toFixed(2); }; const getTotalAmountForPrint = (products) => { if (!products || products.length === 0) return '0'; const total = products.reduce((sum, product) => { return sum + (parseFloat(product.taxInclusiveTotalPrice) || 0); }, 0); return total.toFixed(2); if (!products || products.length === 0) return '0'; const total = products.reduce((sum, product) => { return sum + (parseFloat(product.taxInclusiveTotalPrice) || 0); }, 0); return total.toFixed(2); }; const mathNum = () => { console.log("productForm.value", productForm.value); if (!productForm.value.taxInclusiveUnitPrice) { return; } if (!productForm.value.quantity) { return; } // å«ç¨æ»ä»·è®¡ç® productForm.value.taxInclusiveTotalPrice = proxy.calculateTaxIncludeTotalPrice( productForm.value.taxInclusiveUnitPrice, productForm.value.quantity ); if (productForm.value.taxRate) { // ä¸å«ç¨æ»ä»·è®¡ç® productForm.value.taxExclusiveTotalPrice = proxy.calculateTaxExclusiveTotalPrice( productForm.value.taxInclusiveTotalPrice, productForm.value.taxRate ); } console.log("productForm.value", productForm.value); if (!productForm.value.taxInclusiveUnitPrice) { return; } if (!productForm.value.quantity) { return; } // å«ç¨æ»ä»·è®¡ç® productForm.value.taxInclusiveTotalPrice = proxy.calculateTaxIncludeTotalPrice( productForm.value.taxInclusiveUnitPrice, productForm.value.quantity ); if (productForm.value.taxRate) { // ä¸å«ç¨æ»ä»·è®¡ç® productForm.value.taxExclusiveTotalPrice = proxy.calculateTaxExclusiveTotalPrice( productForm.value.taxInclusiveTotalPrice, productForm.value.taxRate ); } }; // æ ¹æ®å«ç¨æ»ä»·è®¡ç®å«ç¨åä»·åæ°é const calculateFromTotalPrice = () => { if (isCalculating.value) return; const totalPrice = parseFloat(productForm.value.taxInclusiveTotalPrice); const quantity = parseFloat(productForm.value.quantity); if (!totalPrice || !quantity || quantity <= 0) { return; } isCalculating.value = true; // 计ç®å«ç¨åä»· = å«ç¨æ»ä»· / æ°é productForm.value.taxInclusiveUnitPrice = (totalPrice / quantity).toFixed(2); // 妿æç¨çï¼è®¡ç®ä¸å«ç¨æ»ä»· if (productForm.value.taxRate) { productForm.value.taxExclusiveTotalPrice = proxy.calculateTaxExclusiveTotalPrice( totalPrice, productForm.value.taxRate ); } isCalculating.value = false; if (isCalculating.value) return; const totalPrice = parseFloat(productForm.value.taxInclusiveTotalPrice); const quantity = parseFloat(productForm.value.quantity); if (!totalPrice || !quantity || quantity <= 0) { return; } isCalculating.value = true; // 计ç®å«ç¨åä»· = å«ç¨æ»ä»· / æ°é productForm.value.taxInclusiveUnitPrice = (totalPrice / quantity).toFixed(2); // 妿æç¨çï¼è®¡ç®ä¸å«ç¨æ»ä»· if (productForm.value.taxRate) { productForm.value.taxExclusiveTotalPrice = proxy.calculateTaxExclusiveTotalPrice( totalPrice, productForm.value.taxRate ); } isCalculating.value = false; }; // æ ¹æ®ä¸å«ç¨æ»ä»·è®¡ç®å«ç¨åä»·åæ°é @@ -1556,27 +1766,27 @@ proxy.$modal.msgWarning("请å éæ©ç¨ç"); return; } if (isCalculating.value) return; const exclusiveTotalPrice = parseFloat(productForm.value.taxExclusiveTotalPrice); const quantity = parseFloat(productForm.value.quantity); const taxRate = parseFloat(productForm.value.taxRate); if (!exclusiveTotalPrice || !quantity || quantity <= 0 || !taxRate) { return; } isCalculating.value = true; // å 计ç®å«ç¨æ»ä»· = ä¸å«ç¨æ»ä»· / (1 - ç¨ç/100) const taxRateDecimal = taxRate / 100; const inclusiveTotalPrice = exclusiveTotalPrice / (1 - taxRateDecimal); productForm.value.taxInclusiveTotalPrice = inclusiveTotalPrice.toFixed(2); // 计ç®å«ç¨åä»· = å«ç¨æ»ä»· / æ°é productForm.value.taxInclusiveUnitPrice = (inclusiveTotalPrice / quantity).toFixed(2); isCalculating.value = false; if (isCalculating.value) return; const exclusiveTotalPrice = parseFloat(productForm.value.taxExclusiveTotalPrice); const quantity = parseFloat(productForm.value.quantity); const taxRate = parseFloat(productForm.value.taxRate); if (!exclusiveTotalPrice || !quantity || quantity <= 0 || !taxRate) { return; } isCalculating.value = true; // å 计ç®å«ç¨æ»ä»· = ä¸å«ç¨æ»ä»· / (1 - ç¨ç/100) const taxRateDecimal = taxRate / 100; const inclusiveTotalPrice = exclusiveTotalPrice / (1 - taxRateDecimal); productForm.value.taxInclusiveTotalPrice = inclusiveTotalPrice.toFixed(2); // 计ç®å«ç¨åä»· = å«ç¨æ»ä»· / æ°é productForm.value.taxInclusiveUnitPrice = (inclusiveTotalPrice / quantity).toFixed(2); isCalculating.value = false; }; // æ ¹æ®æ°éååè®¡ç®æ»ä»· @@ -1585,30 +1795,30 @@ proxy.$modal.msgWarning("请å éæ©ç¨ç"); return; } if (isCalculating.value) return; const quantity = parseFloat(productForm.value.quantity); const unitPrice = parseFloat(productForm.value.taxInclusiveUnitPrice); if (!quantity || quantity <= 0 || !unitPrice) { return; } isCalculating.value = true; // 计ç®å«ç¨æ»ä»· productForm.value.taxInclusiveTotalPrice = (unitPrice * quantity).toFixed(2); // 妿æç¨çï¼è®¡ç®ä¸å«ç¨æ»ä»· if (productForm.value.taxRate) { productForm.value.taxExclusiveTotalPrice = proxy.calculateTaxExclusiveTotalPrice( productForm.value.taxInclusiveTotalPrice, productForm.value.taxRate ); } isCalculating.value = false; if (isCalculating.value) return; const quantity = parseFloat(productForm.value.quantity); const unitPrice = parseFloat(productForm.value.taxInclusiveUnitPrice); if (!quantity || quantity <= 0 || !unitPrice) { return; } isCalculating.value = true; // 计ç®å«ç¨æ»ä»· productForm.value.taxInclusiveTotalPrice = (unitPrice * quantity).toFixed(2); // 妿æç¨çï¼è®¡ç®ä¸å«ç¨æ»ä»· if (productForm.value.taxRate) { productForm.value.taxExclusiveTotalPrice = proxy.calculateTaxExclusiveTotalPrice( productForm.value.taxInclusiveTotalPrice, productForm.value.taxRate ); } isCalculating.value = false; }; // æ ¹æ®å«ç¨åä»·ååè®¡ç®æ»ä»· @@ -1617,30 +1827,30 @@ proxy.$modal.msgWarning("请å éæ©ç¨ç"); return; } if (isCalculating.value) return; const quantity = parseFloat(productForm.value.quantity); const unitPrice = parseFloat(productForm.value.taxInclusiveUnitPrice); if (!quantity || quantity <= 0 || !unitPrice) { return; } isCalculating.value = true; // 计ç®å«ç¨æ»ä»· productForm.value.taxInclusiveTotalPrice = (unitPrice * quantity).toFixed(2); // 妿æç¨çï¼è®¡ç®ä¸å«ç¨æ»ä»· if (productForm.value.taxRate) { productForm.value.taxExclusiveTotalPrice = proxy.calculateTaxExclusiveTotalPrice( productForm.value.taxInclusiveTotalPrice, productForm.value.taxRate ); } isCalculating.value = false; if (isCalculating.value) return; const quantity = parseFloat(productForm.value.quantity); const unitPrice = parseFloat(productForm.value.taxInclusiveUnitPrice); if (!quantity || quantity <= 0 || !unitPrice) { return; } isCalculating.value = true; // 计ç®å«ç¨æ»ä»· productForm.value.taxInclusiveTotalPrice = (unitPrice * quantity).toFixed(2); // 妿æç¨çï¼è®¡ç®ä¸å«ç¨æ»ä»· if (productForm.value.taxRate) { productForm.value.taxExclusiveTotalPrice = proxy.calculateTaxExclusiveTotalPrice( productForm.value.taxInclusiveTotalPrice, productForm.value.taxRate ); } isCalculating.value = false; }; // æ ¹æ®ç¨çåå计ç®ä¸å«ç¨æ»ä»· @@ -1649,25 +1859,25 @@ proxy.$modal.msgWarning("请å éæ©ç¨ç"); return; } if (isCalculating.value) return; const inclusiveTotalPrice = parseFloat(productForm.value.taxInclusiveTotalPrice); const taxRate = parseFloat(productForm.value.taxRate); if (!inclusiveTotalPrice || !taxRate) { return; } isCalculating.value = true; // 计ç®ä¸å«ç¨æ»ä»· productForm.value.taxExclusiveTotalPrice = proxy.calculateTaxExclusiveTotalPrice( inclusiveTotalPrice, taxRate ); isCalculating.value = false; if (isCalculating.value) return; const inclusiveTotalPrice = parseFloat(productForm.value.taxInclusiveTotalPrice); const taxRate = parseFloat(productForm.value.taxRate); if (!inclusiveTotalPrice || !taxRate) { return; } isCalculating.value = true; // 计ç®ä¸å«ç¨æ»ä»· productForm.value.taxExclusiveTotalPrice = proxy.calculateTaxExclusiveTotalPrice( inclusiveTotalPrice, taxRate ); isCalculating.value = false; }; /** * ä¸è½½æä»¶ @@ -1675,42 +1885,79 @@ * @param row ä¸è½½æä»¶çç¸å ³ä¿¡æ¯å¯¹è±¡ */ const fileListRef = ref(null) const fileListDialogVisible = ref(false) const downLoadFile = (row) => { getSalesLedgerWithProducts({ id: row.id, type: 1 }).then((res) => { fileListRef.value.open(res.salesLedgerFiles) }); getSalesLedgerWithProducts({ id: row.id, type: 1 }).then((res) => { if (fileListRef.value) { fileListRef.value.open(res.salesLedgerFiles) } }); } // æå¼åè´§å¼¹æ¡ const openDeliveryForm = (row) => { currentDeliveryRow.value = row; // æ ¡éªï¼åªæäº§åç¶æä¸ºå è¶³ä¸æªåè´§æ¶æè½åè´§ if (row.approveStatus !== 1) { proxy.$modal.msgWarning("产åç¶æä¸è¶³ï¼æ æ³åè´§"); return; } if (row.shippingDate || row.shippingCarNumber) { proxy.$modal.msgWarning("该产åå·²åè´§ï¼æ æ³éå¤åè´§"); return; } currentDeliveryRow.value = row; deliveryForm.value = { shippingDate: "", // ç§»é¤é»è®¤å¼è®¾ç½® shippingCarNumber: "", type: "货车", }; deliveryFormVisible.value = true; // é置审æ¹äººèç¹ï¼é»è®¤ä¸ä¸ªç©ºèç¹ï¼ approverNodes.value = [{ id: 1, userId: null }]; nextApproverId = 2; deliveryFormVisible.value = true; }; // æäº¤å货表å const submitDelivery = () => { proxy.$refs["deliveryFormRef"].validate((valid) => { if (valid) { // 审æ¹äººå¿ å¡«æ ¡éªï¼ææèç¹é½è¦éäººï¼ const hasEmptyApprover = approverNodes.value.some(node => !node.userId); if (hasEmptyApprover) { proxy.$modal.msgError("请为ææå®¡æ¹èç¹éæ©å®¡æ¹äººï¼"); return; } const approveUserIds = approverNodes.value.map(node => node.userId).join(","); // ä¿åå½åå±å¼çè¡IDï¼ä»¥ä¾¿åè´§åéæ°å è½½åè¡¨æ ¼æ°æ® const currentExpandedKeys = [...expandedRowKeys.value]; const salesLedgerId = currentDeliveryRow.value.salesLedgerId; addShippingInfo({ approverId:deliveryForm.value.approverId, salesLedgerId: currentDeliveryRow.value.salesLedgerId, salesLedgerId: salesLedgerId, salesLedgerProductId: currentDeliveryRow.value.id, shippingDate: deliveryForm.value.shippingDate, shippingCarNumber: deliveryForm.value.shippingCarNumber, type: deliveryForm.value.type, approveUserIds, }) .then(() => { proxy.$modal.msgSuccess("åè´§æå"); closeDeliveryDia(); getList(); expandedRowKeys.value = []; // å·æ°ä¸»è¡¨æ°æ® getList().then(() => { // 妿ä¹åæå±å¼çè¡ï¼éæ°å è½½è¿äºè¡çåè¡¨æ ¼æ°æ® if (currentExpandedKeys.length > 0) { // ä½¿ç¨ Promise.all å¹¶è¡å è½½ææå±å¼è¡çåè¡¨æ ¼æ°æ® const loadPromises = currentExpandedKeys.map(ledgerId => { return productList({ salesLedgerId: ledgerId, type: 1 }).then((res) => { const index = tableData.value.findIndex((item) => item.id === ledgerId); if (index > -1) { tableData.value[index].children = res.data; } }); }); Promise.all(loadPromises).then(() => { // æ¢å¤å±å¼ç¶æ expandedRowKeys.value = currentExpandedKeys; }); } }); }) .catch(() => { proxy.$modal.msgError("å货失败ï¼è¯·éè¯"); }); } }); }; @@ -1721,25 +1968,33 @@ deliveryFormVisible.value = false; currentDeliveryRow.value = null; }; const currentFactoryName = ref(""); const getCurrentFactoryName = async () => { let res = await userStore.getInfo(); currentFactoryName.value = res.user.currentFactoryName; }; onMounted(() => { getList(); userListNoPage().then(res => { userList.value = res.data; }) getCurrentFactoryName(); }); </script> <style scoped lang="scss"> .ml-10 { margin-left: 10px; margin-left: 10px; } .table_list { margin-top: unset; margin-top: unset; } .actions { display: flex; justify-content: space-between; margin-bottom: 10px; display: flex; justify-content: space-between; margin-bottom: 10px; } .print-preview-dialog { .el-dialog__body {