| 2026-03-28 | ZN | ![]() |
| 2026-03-28 | ZN | ![]() |
| 2026-03-28 | yuan | ![]() |
| 2026-03-28 | yuan | ![]() |
| 2026-03-28 | yuan | ![]() |
| 2026-03-28 | gongchunyi | ![]() |
| 2026-03-28 | gongchunyi | ![]() |
| 2026-03-28 | yuan | ![]() |
| 2026-03-28 | zhangwencui | ![]() |
| 2026-03-28 | chenhj | ![]() |
| 2026-03-28 | chenhj | ![]() |
| 2026-03-28 | yyb | ![]() |
| 2026-03-28 | yyb | ![]() |
| 2026-03-28 | ZN | ![]() |
| 2026-03-28 | ZN | ![]() |
| 2026-03-28 | ZN | ![]() |
| 2026-03-28 | ZN | ![]() |
| 2026-03-28 | zhangwencui | ![]() |
| 2026-03-28 | yyb | ![]() |
| src/api/basicData/enterpriseInfo.js | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/api/financialManagement/fixedAssets.js | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/views/basicData/customerFile/index.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/views/basicData/enterpriseInfo/index.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/views/basicData/supplierManage/filesDia.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/views/financialManagement/fixedAssets/index.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/views/inventoryManagement/receiptManagement/index.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/views/procurementManagement/advancedPriceManagement/index.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/views/procurementManagement/procurementLedger/index.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/views/salesManagement/salesLedger/index.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 |
src/api/basicData/enterpriseInfo.js
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,47 @@ // ä¼ä¸é¨æ·é¡µé¢æ¥å£ import request from '@/utils/request' // è·åä¼ä¸ä¿¡æ¯ export function getEnterpriseInfo() { return request({ url: '/system/enterpriseInfo/getInfo', method: 'get' }) } // ä¿åä¼ä¸ä¿¡æ¯ export function saveEnterpriseInfo(data) { return request({ url: '/system/enterpriseInfo/save', method: 'post', data: data }) } // ä¸ä¼ Logo export function uploadLogo(file) { const formData = new FormData() formData.append('file', file) return request({ url: '/system/enterpriseInfo/uploadLogo', method: 'post', data: formData, headers: { 'Content-Type': 'multipart/form-data' } }) } // ä¸ä¼ äºç»´ç export function uploadQrCode(file) { const formData = new FormData() formData.append('file', file) return request({ url: '/system/enterpriseInfo/uploadQrCode', method: 'post', data: formData, headers: { 'Content-Type': 'multipart/form-data' } }) } src/api/financialManagement/fixedAssets.js
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,43 @@ import request from "@/utils/request"; // å页æ¥è¯¢åºå®èµäº§å表 export const listPage = (params) => { return request({ url: "/enterpriseFixedAssets/listPage", method: "get", params, }); }; // æ°å¢åºå®èµäº§ export function add(data) { return request({ url: "/enterpriseFixedAssets/add", method: "post", data: data, }); } // æ´æ°åºå®èµäº§ export function update(data) { return request({ url: "/enterpriseFixedAssets/update", method: "post", data: data, }); } // å é¤åºå®èµäº§ export const delFixedAssets = (query) => { return request({ url: `/enterpriseFixedAssets/delete`, method: "delete", data: query, }); }; // 导åºåºå®èµäº§ export const exportFixedAssets = (query) => { return request({ url: "/financial/fixedAssets/export", method: "post", data: query, responseType: "blob", }); }; src/views/basicData/customerFile/index.vue
@@ -3,244 +3,291 @@ <div class="search_form"> <div> <span class="search_title">客æ·åç§°ï¼</span> <el-input v-model="searchForm.customerName" style="width: 240px;margin-right: 10px" placeholder="请è¾å ¥" @change="handleQuery" clearable :prefix-icon="Search" /> <span class="search_title">客æ·åç±»ï¼</span> <el-select v-model="searchForm.customerType" placeholder="è¯·éæ©" style="width: 240px" clearable @change="handleQuery" > <el-option label="é¶å®å®¢æ·" value="é¶å®å®¢æ·" /> <el-option label="è¿éå客æ·" value="è¿éå客æ·" /> <el-input v-model="searchForm.customerName" style="width: 240px;margin-right: 10px" placeholder="请è¾å ¥" @change="handleQuery" clearable :prefix-icon="Search" /> <span class="search_title">客æ·åç±»ï¼</span> <el-select v-model="searchForm.customerType" placeholder="è¯·éæ©" style="width: 240px" clearable @change="handleQuery"> <el-option label="é¶å®å®¢æ·" value="é¶å®å®¢æ·" /> <el-option label="ç»éå客æ·" value="ç»éå客æ·" /> </el-select> <el-button type="primary" @click="handleQuery" style="margin-left: 10px" >æç´¢</el-button > <el-button type="primary" @click="handleQuery" style="margin-left: 10px">æç´¢</el-button> </div> <div> <el-button type="primary" @click="openForm('add')">æ°å¢å®¢æ·</el-button> <el-button type="primary" @click="openForm('add')">æ°å¢å®¢æ·</el-button> <el-button @click="handleOut">导åº</el-button> <el-button type="info" plain icon="Upload" @click="handleImport" >å¯¼å ¥</el-button > <el-button type="danger" plain @click="handleDelete">å é¤</el-button> <el-button type="info" plain icon="Upload" @click="handleImport">å¯¼å ¥</el-button> <el-button type="danger" plain @click="handleDelete">å é¤</el-button> </div> </div> <div class="table_list"> <PIMTable rowKey="id" :column="tableColumn" :tableData="tableData" :page="page" :isSelection="true" @selection-change="handleSelectionChange" :tableLoading="tableLoading" @pagination="pagination" ></PIMTable> <PIMTable rowKey="id" :column="tableColumn" :tableData="tableData" :page="page" :isSelection="true" @selection-change="handleSelectionChange" :tableLoading="tableLoading" @pagination="pagination"></PIMTable> </div> <el-dialog v-model="dialogFormVisible" :title="operationType === 'add' ? 'æ°å¢å®¢æ·ä¿¡æ¯' : 'ç¼è¾å®¢æ·ä¿¡æ¯'" width="70%" @close="closeDia" > <el-form :model="form" label-width="140px" label-position="top" :rules="rules" ref="formRef" > <el-dialog v-model="dialogFormVisible" :title="operationType === 'add' ? 'æ°å¢å®¢æ·ä¿¡æ¯' : 'ç¼è¾å®¢æ·ä¿¡æ¯'" width="70%" @close="closeDia"> <el-form :model="form" label-width="140px" label-position="top" :rules="rules" ref="formRef"> <el-row :gutter="30"> <el-col :span="12"> <el-form-item label="客æ·åç§°ï¼" prop="customerName"> <el-input v-model="form.customerName" placeholder="请è¾å ¥" clearable /> <el-form-item label="客æ·åç§°ï¼" prop="customerName"> <el-input v-model="form.customerName" placeholder="请è¾å ¥" clearable /> </el-form-item> </el-col> <el-col :span="12"> <el-form-item label="纳ç¨äººè¯å«å·ï¼" prop="taxpayerIdentificationNumber" > <el-input v-model="form.taxpayerIdentificationNumber" placeholder="请è¾å ¥" clearable /> <el-form-item label="纳ç¨äººè¯å«å·ï¼" prop="taxpayerIdentificationNumber"> <el-input v-model="form.taxpayerIdentificationNumber" placeholder="请è¾å ¥" clearable /> </el-form-item> </el-col> </el-row> <el-row :gutter="30"> <el-col :span="12"> <el-form-item label="å ¬å¸å°åï¼" prop="companyAddress"> <el-input v-model="form.companyAddress" placeholder="请è¾å ¥" clearable /> <el-form-item label="å ¬å¸å°åï¼" prop="companyAddress"> <el-input v-model="form.companyAddress" placeholder="请è¾å ¥" clearable /> </el-form-item> </el-col> <el-col :span="12"> <el-form-item label="å ¬å¸çµè¯ï¼" prop="companyPhone"> <el-input v-model="form.companyPhone" placeholder="请è¾å ¥" clearable /> <el-form-item label="å ¬å¸çµè¯ï¼" prop="companyPhone"> <el-input v-model="form.companyPhone" placeholder="请è¾å ¥" clearable /> </el-form-item> </el-col> </el-row> <el-row :gutter="30"> <el-col :span="12"> <el-form-item label="é¶è¡åºæ¬æ·ï¼" prop="basicBankAccount"> <el-input v-model="form.basicBankAccount" placeholder="请è¾å ¥" clearable /> <el-form-item label="é¶è¡åºæ¬æ·ï¼" prop="basicBankAccount"> <el-input v-model="form.basicBankAccount" placeholder="请è¾å ¥" clearable /> </el-form-item> </el-col> <el-col :span="12"> <el-form-item label="é¶è¡è´¦å·ï¼" prop="bankAccount"> <el-input v-model="form.bankAccount" placeholder="请è¾å ¥" clearable /> <el-form-item label="é¶è¡è´¦å·ï¼" prop="bankAccount"> <el-input v-model="form.bankAccount" placeholder="请è¾å ¥" clearable /> </el-form-item> </el-col> </el-row> <el-row :gutter="30"> <el-col :span="12"> <el-form-item label="弿·è¡å·ï¼" prop="bankCode"> <el-input v-model="form.bankCode" placeholder="请è¾å ¥" clearable /> <el-form-item label="弿·è¡å·ï¼" prop="bankCode"> <el-input v-model="form.bankCode" placeholder="请è¾å ¥" clearable /> </el-form-item> </el-col> <el-col :span="12"> <el-form-item label="客æ·åç±»ï¼" prop="customerType"> <el-select v-model="form.customerType" placeholder="è¯·éæ©" clearable> <el-option label="é¶å®å®¢æ·" value="é¶å®å®¢æ·" /> <el-option label="è¿éå客æ·" value="è¿éå客æ·" /> <el-form-item label="客æ·åç±»ï¼" prop="customerType"> <el-select v-model="form.customerType" placeholder="è¯·éæ©" clearable> <el-option label="é¶å®å®¢æ·" value="é¶å®å®¢æ·" /> <el-option label="ç»éå客æ·" value="ç»éå客æ·" /> </el-select> </el-form-item> </el-col> </el-row> <el-row :gutter="30" v-for="(contact, index) in formYYs.contactList" :key="index"> <el-col :span="12"> <el-form-item label="è系人ï¼" prop="contactPerson"> <el-input v-model="contact.contactPerson" placeholder="请è¾å ¥" clearable /> </el-form-item> </el-col> <el-col :span="12"> <el-form-item label="èç³»çµè¯ï¼" prop="contactPhone"> <div style="display: flex; align-items: center;width: 100%;"> <el-input v-model="contact.contactPhone" placeholder="请è¾å ¥" clearable /> <el-button @click="removeContact(index)" type="danger" circle style="margin-left: 5px;"> <el-icon><Close /></el-icon> </el-button> </div> </el-form-item> </el-col> </el-row> <el-button @click="addNewContact" style="margin-bottom: 10px;">+ æ°å¢è系人</el-button> <el-row :gutter="30" v-for="(contact, index) in formYYs.contactList" :key="index"> <el-col :span="12"> <el-form-item label="è系人ï¼" prop="contactPerson"> <el-input v-model="contact.contactPerson" placeholder="请è¾å ¥" clearable /> </el-form-item> </el-col> <el-col :span="12"> <el-form-item label="èç³»çµè¯ï¼" prop="contactPhone"> <div style="display: flex; align-items: center;width: 100%;"> <el-input v-model="contact.contactPhone" placeholder="请è¾å ¥" clearable /> <el-button @click="removeContact(index)" type="danger" circle style="margin-left: 5px;"> <el-icon> <Close /> </el-icon> </el-button> </div> </el-form-item> </el-col> </el-row> <el-button @click="addNewContact" style="margin-bottom: 10px;">+ æ°å¢è系人</el-button> <el-row :gutter="30"> <el-col :span="12"> <el-form-item label="ç»´æ¤äººï¼" prop="maintainer"> <el-select v-model="form.maintainer" placeholder="è¯·éæ©" clearable disabled > <el-option v-for="item in userList" :key="item.nickName" :label="item.nickName" :value="item.nickName" /> <el-form-item label="ç»´æ¤äººï¼" prop="maintainer"> <el-select v-model="form.maintainer" placeholder="è¯·éæ©" clearable disabled> <el-option v-for="item in userList" :key="item.nickName" :label="item.nickName" :value="item.nickName" /> </el-select> </el-form-item> </el-col> <el-col :span="12"> <el-form-item label="ç»´æ¤æ¶é´ï¼" prop="maintenanceTime"> <el-date-picker style="width: 100%" v-model="form.maintenanceTime" value-format="YYYY-MM-DD" format="YYYY-MM-DD" type="date" placeholder="è¯·éæ©" clearable /> <el-form-item label="ç»´æ¤æ¶é´ï¼" prop="maintenanceTime"> <el-date-picker style="width: 100%" v-model="form.maintenanceTime" value-format="YYYY-MM-DD" format="YYYY-MM-DD" type="date" placeholder="è¯·éæ©" clearable /> </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 type="primary" @click="submitForm">确认</el-button> <el-button @click="closeDia">åæ¶</el-button> </div> </template> </el-dialog> <!-- ç¨æ·å¯¼å ¥å¯¹è¯æ¡ --> <el-dialog :title="upload.title" v-model="upload.open" width="400px" append-to-body > <el-upload ref="uploadRef" :limit="1" accept=".xlsx, .xls" :headers="upload.headers" :action="upload.url + '?updateSupport=' + upload.updateSupport" :disabled="upload.isUploading" :before-upload="upload.beforeUpload" :on-progress="upload.onProgress" :on-success="upload.onSuccess" :on-error="upload.onError" :on-change="upload.onChange" :auto-upload="false" drag > <el-dialog :title="upload.title" v-model="upload.open" width="400px" append-to-body> <el-upload ref="uploadRef" :limit="1" accept=".xlsx, .xls" :headers="upload.headers" :action="upload.url + '?updateSupport=' + upload.updateSupport" :disabled="upload.isUploading" :before-upload="upload.beforeUpload" :on-progress="upload.onProgress" :on-success="upload.onSuccess" :on-error="upload.onError" :on-change="upload.onChange" :auto-upload="false" drag> <el-icon class="el-icon--upload"><upload-filled /></el-icon> <div class="el-upload__text">å°æä»¶æå°æ¤å¤ï¼æ<em>ç¹å»ä¸ä¼ </em></div> <template #tip> <div class="el-upload__tip text-center"> <span>ä» å è®¸å¯¼å ¥xlsãxlsxæ ¼å¼æä»¶ã</span> <el-link type="primary" :underline="false" style="font-size: 12px; vertical-align: baseline" @click="importTemplate" >ä¸è½½æ¨¡æ¿</el-link > <el-link type="primary" :underline="false" style="font-size: 12px; vertical-align: baseline" @click="importTemplate">ä¸è½½æ¨¡æ¿</el-link> </div> </template> </el-upload> <template #footer> <div class="dialog-footer"> <el-button type="primary" @click="submitFileForm">ç¡® å®</el-button> <el-button type="primary" @click="submitFileForm">ç¡® å®</el-button> <el-button @click="upload.open = false">å æ¶</el-button> </div> </template> </el-dialog> <!-- 客æ·ç»åå¼¹çª --> <el-dialog v-model="profileDialogVisible" title="客æ·ç»å" width="70%" @close="closeProfileDialog"> <div class="profile-content"> <div class="profile-info"> <h3 class="profile-title">è´ä¹°ä¹ æ¯</h3> <el-descriptions :column="2" border> <el-descriptions-item label="客æ·åç§°">{{ currentCustomer.customerName || '-' }}</el-descriptions-item> <el-descriptions-item label="è´ä¹°æ¬¡æ°">{{ purchaseHabits.purchaseCount || 0 }}</el-descriptions-item> <el-descriptions-item label="å¹³åéé¢"><span style="color:#ff6f00">{{ purchaseHabits.averageAmount || 0 }}</span> å </el-descriptions-item> <el-descriptions-item label="æè¿è´ä¹°æ¶é´">{{ purchaseHabits.latestPurchaseTime || '-' }}</el-descriptions-item> <el-descriptions-item label="常è´å"> <el-tag v-for="item in purchaseHabits.topProducts" style="margin-right: 10px;" :key="item">{{ item }}</el-tag> </el-descriptions-item> </el-descriptions> </div> <div class="profile-form"> <h3 class="profile-title">å®¢æ·æ ç¾è®¾ç½®</h3> <el-form :model="profileForm" label-width="120px"> <el-form-item label="客æ·ç级"> <el-radio-group v-model="profileForm.isVip"> <el-radio :label="true">VIP客æ·</el-radio> <el-radio :label="false">æ®é客æ·</el-radio> </el-radio-group> </el-form-item> <el-form-item label="ä»·æ ¼ææ"> <el-checkbox v-model="profileForm.isPriceSensitive">ä»·æ ¼ææ</el-checkbox> </el-form-item> <el-form-item label="客æ·å好"> <el-input v-model="profileForm.preferences" type="textarea" :rows="4" placeholder="请è¾å ¥å®¢æ·å好ï¼å¦ï¼å欢é«å质产åãæ³¨éå®åæå¡ç" /> </el-form-item> </el-form> </div> </div> <template #footer> <div class="dialog-footer"> <el-button type="primary" @click="saveProfile">ä¿å</el-button> <el-button @click="closeProfileDialog">åæ¶</el-button> </div> </template> </el-dialog> @@ -248,384 +295,546 @@ </template> <script setup> import {onMounted, ref} from "vue"; import { Search } from "@element-plus/icons-vue"; import { addCustomer, delCustomer, getCustomer, listCustomer, updateCustomer, } from "@/api/basicData/customerFile.js"; import { ElMessageBox } from "element-plus"; import { userListNoPage } from "@/api/system/user.js"; import useUserStore from "@/store/modules/user"; import { getToken } from "@/utils/auth.js"; const { proxy } = getCurrentInstance(); const userStore = useUserStore(); import { onMounted, ref } from "vue"; import { Search } from "@element-plus/icons-vue"; import { addCustomer, delCustomer, getCustomer, listCustomer, updateCustomer, } from "@/api/basicData/customerFile.js"; import { ElMessageBox } from "element-plus"; import { userListNoPage } from "@/api/system/user.js"; import useUserStore from "@/store/modules/user"; import { getToken } from "@/utils/auth.js"; const { proxy } = getCurrentInstance(); const userStore = useUserStore(); const tableColumn = ref([ { label: "客æ·åç±»", prop: "customerType", width: 120, }, { label: "客æ·åç§°", prop: "customerName", width: 220, }, { label: "纳ç¨äººè¯å«ç ", prop: "taxpayerIdentificationNumber", width: 220, }, { label: "å°ååèç³»æ¹å¼", prop: "addressPhone", width: 250, }, { label: "è系人", prop: "contactPerson", }, { label: "èç³»çµè¯", prop: "contactPhone", width:150 }, { label: "é¶è¡åºæ¬æ·", prop: "basicBankAccount", width: 220, }, { label: "é¶è¡è´¦å·", prop: "bankAccount", width: 220, }, { label: "弿·è¡å·", prop: "bankCode", width:220 }, { label: "ç»´æ¤äºº", prop: "maintainer", }, { label: "ç»´æ¤æ¶é´", prop: "maintenanceTime", width: 100, }, { dataType: "action", label: "æä½", align: "center", fixed: 'right', operation: [ { name: "ç¼è¾", type: "text", clickFun: (row) => { openForm("edit", row); const tableColumn = ref([ { label: "客æ·åç±»", prop: "customerType", width: 120, }, { label: "客æ·åç§°", prop: "customerName", width: 220, }, { label: "纳ç¨äººè¯å«ç ", prop: "taxpayerIdentificationNumber", width: 220, }, { label: "å°ååèç³»æ¹å¼", prop: "addressPhone", width: 250, }, { label: "è系人", prop: "contactPerson", }, { label: "èç³»çµè¯", prop: "contactPhone", width: 150, }, { label: "é¶è¡åºæ¬æ·", prop: "basicBankAccount", width: 220, }, { label: "é¶è¡è´¦å·", prop: "bankAccount", width: 220, }, { label: "弿·è¡å·", prop: "bankCode", width: 220, }, { label: "ç»´æ¤äºº", prop: "maintainer", }, { label: "ç»´æ¤æ¶é´", prop: "maintenanceTime", width: 100, }, { dataType: "action", label: "æä½", align: "center", width: 160, fixed: "right", operation: [ { name: "ç¼è¾", type: "text", clickFun: row => { openForm("edit", row); }, }, { name: "客æ·ç»å", type: "text", color: "rgb(229 187 21)", showHide: row => { return row.customerType == "ç»éå客æ·"; }, clickFun: row => { openCustomerProfile(row); }, }, ], }, ]); const tableData = ref([]); const selectedRows = ref([]); const userList = ref([]); const tableLoading = ref(false); const page = reactive({ current: 1, size: 100, total: 0, }); const total = ref(0); // ç¨æ·ä¿¡æ¯è¡¨åå¼¹æ¡æ°æ® const operationType = ref(""); const dialogFormVisible = ref(false); const formYYs = ref({ // å ¶ä»å段... contactList: [ { contactPerson: "", contactPhone: "", }, ], }, ]); const tableData = ref([]); const selectedRows = ref([]); const userList = ref([]); const tableLoading = ref(false); const page = reactive({ current: 1, size: 100, total: 0, }); const total = ref(0); // ç¨æ·ä¿¡æ¯è¡¨åå¼¹æ¡æ°æ® const operationType = ref(""); const dialogFormVisible = ref(false); const formYYs = ref({ // å ¶ä»å段... contactList: [ { }); const data = reactive({ searchForm: { customerName: "", customerType: "", }, form: { customerName: "", taxpayerIdentificationNumber: "", companyAddress: "", companyPhone: "", contactPerson: "", contactPhone: "" } ] }); const data = reactive({ searchForm: { customerName: "", customerType: "", }, form: { customerName: "", taxpayerIdentificationNumber: "", companyAddress: "", companyPhone: "", contactPerson: "", contactPhone: "", maintainer: "", maintenanceTime: "", basicBankAccount: "", bankAccount: "", bankCode: "", customerType: "", }, rules: { customerName: [{ required: true, message: "请è¾å ¥", trigger: "blur" }], taxpayerIdentificationNumber: [ { required: true, message: "请è¾å ¥", trigger: "blur" }, ], companyAddress: [{ required: true, message: "请è¾å ¥", trigger: "blur" }], companyPhone: [{ required: true, message: "请è¾å ¥", trigger: "blur" }], // contactPerson: [{ required: true, message: "请è¾å ¥", trigger: "blur" }], // contactPhone: [{ required: true, message: "请è¾å ¥", trigger: "blur" }], maintainer: [{ required: false, message: "è¯·éæ©", trigger: "change" }], maintenanceTime: [ { required: false, message: "è¯·éæ©", trigger: "change" }, ], basicBankAccount: [{ required: true, message: "请è¾å ¥", trigger: "blur" }], bankAccount: [{ required: true, message: "请è¾å ¥", trigger: "blur" }], bankCode: [{ required: true, message: "请è¾å ¥", trigger: "blur" }], customerType: [{ required: true, message: "è¯·éæ©", trigger: "change" }], }, }); const upload = reactive({ // æ¯å¦æ¾ç¤ºå¼¹åºå±ï¼å®¢æ·å¯¼å ¥ï¼ open: false, // å¼¹åºå±æ é¢ï¼å®¢æ·å¯¼å ¥ï¼ title: "", // æ¯å¦ç¦ç¨ä¸ä¼ isUploading: false, // 设置ä¸ä¼ ç请æ±å¤´é¨ headers: { Authorization: "Bearer " + getToken() }, // ä¸ä¼ çå°å url: import.meta.env.VITE_APP_BASE_API + "/basic/customer/importData", // æä»¶ä¸ä¼ åçåè° beforeUpload: (file) => { console.log('æä»¶å³å°ä¸ä¼ ', file); // å¯ä»¥å¨æ¤å¤åæä»¶ç±»åæå¤§å°æ ¡éª const isValid = file.type === 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' || file.name.endsWith('.xlsx') || file.name.endsWith('.xls'); if (!isValid) { proxy.$modal.msgError("åªè½ä¸ä¼ Excel æä»¶"); } return isValid; }, // æä»¶ç¶ææ¹åæ¶çåè° onChange: (file, fileList) => { console.log('æä»¶ç¶ææ¹å', file, fileList); }, // æä»¶ä¸ä¼ æåæ¶çåè° onSuccess: (response, file, fileList) => { console.log('ä¸ä¼ æå', response, file, fileList); upload.isUploading = false; if(response.code === 200){ proxy.$modal.msgSuccess("æä»¶ä¸ä¼ æå"); upload.open = false; proxy.$refs["uploadRef"].clearFiles(); getList(); }else if(response.code === 500){ proxy.$modal.msgError(response.msg); }else{ proxy.$modal.msgWarning(response.msg); } }, // æä»¶ä¸ä¼ 失败æ¶çåè° onError: (error, file, fileList) => { console.error('ä¸ä¼ 失败', error, file, fileList); upload.isUploading = false; proxy.$modal.msgError("æä»¶ä¸ä¼ 失败"); }, // æä»¶ä¸ä¼ è¿åº¦åè° onProgress: (event, file, fileList) => { console.log('ä¸ä¼ ä¸...', event.percent); } }); const { searchForm, form, rules } = toRefs(data); const addNewContact = () => { formYYs.value.contactList.push({ contactPerson: "", contactPhone: "" contactPhone: "", maintainer: "", maintenanceTime: "", basicBankAccount: "", bankAccount: "", bankCode: "", customerType: "", }, rules: { customerName: [{ required: true, message: "请è¾å ¥", trigger: "blur" }], taxpayerIdentificationNumber: [ { required: true, message: "请è¾å ¥", trigger: "blur" }, ], companyAddress: [{ required: true, message: "请è¾å ¥", trigger: "blur" }], companyPhone: [{ required: true, message: "请è¾å ¥", trigger: "blur" }], // contactPerson: [{ required: true, message: "请è¾å ¥", trigger: "blur" }], // contactPhone: [{ required: true, message: "请è¾å ¥", trigger: "blur" }], maintainer: [{ required: false, message: "è¯·éæ©", trigger: "change" }], maintenanceTime: [ { required: false, message: "è¯·éæ©", trigger: "change" }, ], basicBankAccount: [{ required: true, message: "请è¾å ¥", trigger: "blur" }], bankAccount: [{ required: true, message: "请è¾å ¥", trigger: "blur" }], bankCode: [{ required: true, message: "请è¾å ¥", trigger: "blur" }], customerType: [{ required: true, message: "è¯·éæ©", trigger: "change" }], }, }); }; const removeContact = (index) => { if (formYYs.value.contactList.length > 1) { formYYs.value.contactList.splice(index, 1); } }; // æ¥è¯¢å表 /** æç´¢æé®æä½ */ const handleQuery = () => { page.current = 1; getList(); }; const pagination = (obj) => { page.current = obj.page; page.size = obj.limit; getList(); }; const getList = () => { tableLoading.value = true; listCustomer({ ...searchForm.value, ...page }).then((res) => { tableLoading.value = false; tableData.value = res.records; page.total = res.total; }); }; // è¡¨æ ¼éæ©æ°æ® const handleSelectionChange = (selection) => { selectedRows.value = selection; }; /** æäº¤ä¸ä¼ æä»¶ */ function submitFileForm() { upload.isUploading = true; proxy.$refs["uploadRef"].submit(); } /** å¯¼å ¥æé®æä½ */ function handleImport() { upload.title = "客æ·å¯¼å ¥"; upload.open = true; } /** ä¸è½½æ¨¡æ¿ */ function importTemplate() { proxy.download("/basic/customer/downloadTemplate", {}, "客æ·å¯¼å ¥æ¨¡æ¿.xlsx"); } // æå¼å¼¹æ¡ const openForm = (type, row) => { operationType.value = type; form.value = {}; form.value.maintainer = userStore.nickName; formYYs.value.contactList = [ { contactPerson: "", contactPhone: "" } ]; form.value.maintenanceTime = getCurrentDate(); userListNoPage().then((res) => { userList.value = res.data; }); if (type === "edit") { getCustomer(row.id).then((res) => { form.value = { ...res.data }; formYYs.value.contactList = res.data.contactPerson.split(",").map((item, index) => { return { contactPerson: item, contactPhone: res.data.contactPhone.split(",")[index] } }); }); } dialogFormVisible.value = true; }; // æäº¤è¡¨å const submitForm = () => { proxy.$refs["formRef"].validate((valid) => { if (valid) { if (operationType.value === "edit") { submitEdit(); } else { submitAdd(); const upload = reactive({ // æ¯å¦æ¾ç¤ºå¼¹åºå±ï¼å®¢æ·å¯¼å ¥ï¼ open: false, // å¼¹åºå±æ é¢ï¼å®¢æ·å¯¼å ¥ï¼ title: "", // æ¯å¦ç¦ç¨ä¸ä¼ isUploading: false, // 设置ä¸ä¼ ç请æ±å¤´é¨ headers: { Authorization: "Bearer " + getToken() }, // ä¸ä¼ çå°å url: import.meta.env.VITE_APP_BASE_API + "/basic/customer/importData", // æä»¶ä¸ä¼ åçåè° beforeUpload: file => { console.log("æä»¶å³å°ä¸ä¼ ", file); // å¯ä»¥å¨æ¤å¤åæä»¶ç±»åæå¤§å°æ ¡éª const isValid = file.type === "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" || file.name.endsWith(".xlsx") || file.name.endsWith(".xls"); if (!isValid) { proxy.$modal.msgError("åªè½ä¸ä¼ Excel æä»¶"); } } return isValid; }, // æä»¶ç¶ææ¹åæ¶çåè° onChange: (file, fileList) => { console.log("æä»¶ç¶ææ¹å", file, fileList); }, // æä»¶ä¸ä¼ æåæ¶çåè° onSuccess: (response, file, fileList) => { console.log("ä¸ä¼ æå", response, file, fileList); upload.isUploading = false; if (response.code === 200) { proxy.$modal.msgSuccess("æä»¶ä¸ä¼ æå"); upload.open = false; proxy.$refs["uploadRef"].clearFiles(); getList(); } else if (response.code === 500) { proxy.$modal.msgError(response.msg); } else { proxy.$modal.msgWarning(response.msg); } }, // æä»¶ä¸ä¼ 失败æ¶çåè° onError: (error, file, fileList) => { console.error("ä¸ä¼ 失败", error, file, fileList); upload.isUploading = false; proxy.$modal.msgError("æä»¶ä¸ä¼ 失败"); }, // æä»¶ä¸ä¼ è¿åº¦åè° onProgress: (event, file, fileList) => { console.log("ä¸ä¼ ä¸...", event.percent); }, }); }; // æäº¤æ°å¢ const submitAdd = () => { if(formYYs.value.contactList.length < 1){ return proxy.$modal.msgWarning("请è³å°æ·»å ä¸ä¸ªè系人"); } form.value.contactPerson = formYYs.value.contactList.map(item => item.contactPerson).join(","); form.value.contactPhone = formYYs.value.contactList.map(item => item.contactPhone).join(","); addCustomer(form.value).then((res) => { proxy.$modal.msgSuccess("æäº¤æå"); closeDia(); getList(); }); }; // æäº¤ä¿®æ¹ const submitEdit = () => { form.value.contactPerson = formYYs.value.contactList.map(item => item.contactPerson).join(","); form.value.contactPhone = formYYs.value.contactList.map(item => item.contactPhone).join(","); updateCustomer(form.value).then((res) => { proxy.$modal.msgSuccess("æäº¤æå"); closeDia(); getList(); }); }; // å ³éå¼¹æ¡ const closeDia = () => { proxy.resetForm("formRef"); dialogFormVisible.value = false; }; // å¯¼åº const handleOut = () => { ElMessageBox.confirm("éä¸çå 容å°è¢«å¯¼åºï¼æ¯å¦ç¡®è®¤å¯¼åºï¼", "导åº", { confirmButtonText: "确认", cancelButtonText: "åæ¶", type: "warning", }) .then(() => { proxy.download("/basic/customer/export", {}, "å®¢æ·æ¡£æ¡.xlsx"); }) .catch(() => { proxy.$modal.msg("已忶"); const { searchForm, form, rules } = toRefs(data); const addNewContact = () => { formYYs.value.contactList.push({ contactPerson: "", contactPhone: "", }); }; // å é¤ const handleDelete = () => { let ids = []; if (selectedRows.value.length > 0) { // æ£æ¥æ¯å¦æä»äººç»´æ¤çæ°æ® const unauthorizedData = selectedRows.value.filter(item => item.maintainer !== userStore.nickName); if (unauthorizedData.length > 0) { proxy.$modal.msgWarning("ä¸å¯å é¤ä»äººç»´æ¤çæ°æ®"); }; const removeContact = index => { if (formYYs.value.contactList.length > 1) { formYYs.value.contactList.splice(index, 1); } }; // æ¥è¯¢å表 /** æç´¢æé®æä½ */ const handleQuery = () => { page.current = 1; getList(); }; const pagination = obj => { page.current = obj.page; page.size = obj.limit; getList(); }; const getList = () => { tableLoading.value = true; listCustomer({ ...searchForm.value, ...page }).then(res => { tableLoading.value = false; tableData.value = res.records; page.total = res.total; }); }; // è¡¨æ ¼éæ©æ°æ® const handleSelectionChange = selection => { selectedRows.value = selection; }; /** æäº¤ä¸ä¼ æä»¶ */ function submitFileForm() { upload.isUploading = true; proxy.$refs["uploadRef"].submit(); } /** å¯¼å ¥æé®æä½ */ function handleImport() { upload.title = "客æ·å¯¼å ¥"; upload.open = true; } /** ä¸è½½æ¨¡æ¿ */ function importTemplate() { proxy.download("/basic/customer/downloadTemplate", {}, "客æ·å¯¼å ¥æ¨¡æ¿.xlsx"); } // æå¼å¼¹æ¡ const openForm = (type, row) => { operationType.value = type; form.value = {}; form.value.maintainer = userStore.nickName; formYYs.value.contactList = [ { contactPerson: "", contactPhone: "", }, ]; form.value.maintenanceTime = getCurrentDate(); userListNoPage().then(res => { userList.value = res.data; }); if (type === "edit") { getCustomer(row.id).then(res => { form.value = { ...res.data }; formYYs.value.contactList = res.data.contactPerson .split(",") .map((item, index) => { return { contactPerson: item, contactPhone: res.data.contactPhone.split(",")[index], }; }); }); } dialogFormVisible.value = true; }; // æäº¤è¡¨å const submitForm = () => { proxy.$refs["formRef"].validate(valid => { if (valid) { if (operationType.value === "edit") { submitEdit(); } else { submitAdd(); } } }); }; // æäº¤æ°å¢ const submitAdd = () => { if (formYYs.value.contactList.length < 1) { return proxy.$modal.msgWarning("请è³å°æ·»å ä¸ä¸ªè系人"); } form.value.contactPerson = formYYs.value.contactList .map(item => item.contactPerson) .join(","); form.value.contactPhone = formYYs.value.contactList .map(item => item.contactPhone) .join(","); addCustomer(form.value).then(res => { proxy.$modal.msgSuccess("æäº¤æå"); closeDia(); getList(); }); }; // æäº¤ä¿®æ¹ const submitEdit = () => { form.value.contactPerson = formYYs.value.contactList .map(item => item.contactPerson) .join(","); form.value.contactPhone = formYYs.value.contactList .map(item => item.contactPhone) .join(","); updateCustomer(form.value).then(res => { proxy.$modal.msgSuccess("æäº¤æå"); closeDia(); getList(); }); }; // å ³éå¼¹æ¡ const closeDia = () => { proxy.resetForm("formRef"); dialogFormVisible.value = false; }; // å¯¼åº const handleOut = () => { ElMessageBox.confirm("éä¸çå 容å°è¢«å¯¼åºï¼æ¯å¦ç¡®è®¤å¯¼åºï¼", "导åº", { confirmButtonText: "确认", cancelButtonText: "åæ¶", type: "warning", }) .then(() => { proxy.download("/basic/customer/export", {}, "å®¢æ·æ¡£æ¡.xlsx"); }) .catch(() => { proxy.$modal.msg("已忶"); }); }; // å é¤ const handleDelete = () => { let ids = []; if (selectedRows.value.length > 0) { // æ£æ¥æ¯å¦æä»äººç»´æ¤çæ°æ® const unauthorizedData = selectedRows.value.filter( item => item.maintainer !== userStore.nickName ); if (unauthorizedData.length > 0) { proxy.$modal.msgWarning("ä¸å¯å é¤ä»äººç»´æ¤çæ°æ®"); return; } ids = selectedRows.value.map(item => item.id); } else { proxy.$modal.msgWarning("è¯·éæ©æ°æ®"); return; } ids = selectedRows.value.map((item) => item.id); } else { proxy.$modal.msgWarning("è¯·éæ©æ°æ®"); return; } ElMessageBox.confirm("éä¸çå 容å°è¢«å é¤ï¼æ¯å¦ç¡®è®¤å é¤ï¼", "å é¤æç¤º", { confirmButtonText: "确认", cancelButtonText: "åæ¶", type: "warning", }) .then(() => { tableLoading.value = true; delCustomer(ids) .then((res) => { proxy.$modal.msgSuccess("å 餿å"); getList(); }) .finally(() => { tableLoading.value = false; }); ElMessageBox.confirm("éä¸çå 容å°è¢«å é¤ï¼æ¯å¦ç¡®è®¤å é¤ï¼", "å é¤æç¤º", { confirmButtonText: "确认", cancelButtonText: "åæ¶", type: "warning", }) .catch(() => { proxy.$modal.msg("已忶"); .then(() => { tableLoading.value = true; delCustomer(ids) .then(res => { proxy.$modal.msgSuccess("å 餿å"); getList(); }) .finally(() => { tableLoading.value = false; }); }) .catch(() => { proxy.$modal.msg("已忶"); }); }; // è·åå½åæ¥æå¹¶æ ¼å¼å为 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 profileDialogVisible = ref(false); const currentCustomer = ref({}); const purchaseHabits = ref({ purchaseCount: 0, averageAmount: 1240, latestPurchaseTime: "", topProducts: [], }); const profileForm = ref({ isVip: false, isPriceSensitive: false, preferences: "", }); // æå¼å®¢æ·ç»åå¼¹çª const openCustomerProfile = row => { currentCustomer.value = { ...row }; purchaseHabits.value = { purchaseCount: row.purchaseCount || 0, averageAmount: row.averageAmount || 0, latestPurchaseTime: row.latestPurchaseTime || "-", topProducts: row.topProducts || [], }; profileForm.value = { isVip: row.isVip, isPriceSensitive: row.isPriceSensitive, preferences: row.preferences || "", }; profileDialogVisible.value = true; }; // å ³é客æ·ç»åå¼¹çª const closeProfileDialog = () => { profileDialogVisible.value = false; currentCustomer.value = {}; }; // ä¿å客æ·ç»å const saveProfile = () => { const updateData = { id: currentCustomer.value.id, preferences: profileForm.value.preferences, isPriceSensitive: profileForm.value.isPriceSensitive, isVip: profileForm.value.isVip, }; updateCustomer(updateData).then(res => { proxy.$modal.msgSuccess("客æ·ç»åä¿åæå"); closeProfileDialog(); getList(); }); }; }; // è·åå½åæ¥æå¹¶æ ¼å¼å为 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}`; } onMounted(() => { getList(); }); onMounted(() => { getList(); }); </script> <style scoped lang="scss"></style> <style scoped lang="scss"> .profile-content { .profile-title { font-size: 16px; font-weight: 600; color: #303133; margin: 20px 0 15px 0; padding-bottom: 10px; border-bottom: 2px solid #409eff; position: relative; &::before { content: ""; position: absolute; left: 0; bottom: -2px; width: 60px; height: 2px; background-color: #409eff; } } .profile-info { margin-bottom: 30px; :deep(.el-descriptions) { border-radius: 8px; overflow: hidden; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08); } :deep(.el-descriptions__label) { background-color: #f5f7fa; font-weight: 500; color: #606266; } :deep(.el-descriptions__content) { color: #303133; font-weight: 500; } } .profile-form { background-color: #f9f9f9; padding: 20px; border-radius: 8px; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08); :deep(.el-form-item__label) { font-weight: 500; color: #606266; } :deep(.el-radio-group) { display: flex; gap: 20px; } :deep(.el-radio) { margin-right: 0; } :deep(.el-checkbox) { font-weight: normal; } :deep(.el-textarea__inner) { border-radius: 6px; border: 1px solid #dcdfe6; transition: all 0.3s ease; &:focus { border-color: #409eff; box-shadow: 0 0 0 2px rgba(64, 158, 255, 0.1); } } } } </style> src/views/basicData/enterpriseInfo/index.vue
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,664 @@ <template> <div class="app-container"> <!-- 页颿 颿 --> <div class="page-header"> <h2>ä¼ä¸é¨æ·</h2> <div class="header-actions"> <el-button @click="handlePreview" :icon="View" type="info" plain>é¢è§</el-button> <el-button @click="toggleEdit" :icon="isEdit ? 'Close' : 'Edit'" :type="isEdit ? 'default' : 'primary'"> {{ isEdit ? 'åæ¶ç¼è¾' : 'ç¼è¾' }} </el-button> </div> </div> <!-- ä¼ä¸ä¿¡æ¯å¡ç --> <div class="enterprise-info-card" v-loading="loading"> <!-- åºæ¬ä¿¡æ¯åºå --> <div class="info-section"> <div class="section-header"> <h3>åºæ¬ä¿¡æ¯</h3> </div> <el-descriptions :column="2" border class="info-descriptions"> <el-descriptions-item label="å ¬å¸åç§°"> <el-input v-if="isEdit" v-model="form.companyName" placeholder="请è¾å ¥å ¬å¸åç§°" clearable /> <span v-else class="content-text">{{ form.companyName || '-' }}</span> </el-descriptions-item> <el-descriptions-item label="è系人"> <el-input v-if="isEdit" v-model="form.contactPerson" placeholder="请è¾å ¥è系人" clearable /> <span v-else class="content-text">{{ form.contactPerson || '-' }}</span> </el-descriptions-item> <el-descriptions-item label="èç³»çµè¯"> <el-input v-if="isEdit" v-model="form.contactPhone" placeholder="请è¾å ¥èç³»çµè¯" clearable /> <span v-else class="content-text">{{ form.contactPhone || '-' }}</span> </el-descriptions-item> <el-descriptions-item label="å ¬å¸å°å"> <el-input v-if="isEdit" v-model="form.companyAddress" placeholder="请è¾å ¥å ¬å¸å°å" clearable /> <span v-else class="content-text">{{ form.companyAddress || '-' }}</span> </el-descriptions-item> <el-descriptions-item label="å ¬å¸ç½ç«"> <el-input v-if="isEdit" v-model="form.website" placeholder="请è¾å ¥å ¬å¸ç½ç«" clearable /> <a v-else-if="form.website" :href="form.website" target="_blank" class="link-text">{{ form.website }}</a> <span v-else class="content-text">-</span> </el-descriptions-item> </el-descriptions> </div> <!-- Logoåäºç»´ç åºå --> <div class="info-section"> <div class="section-header"> <h3>ä¼ä¸æ è¯</h3> </div> <div class="logo-qr-container"> <!-- å ¬å¸Logo --> <div class="upload-item"> <span class="upload-label">å ¬å¸Logo</span> <div class="upload-wrapper"> <el-upload v-if="isEdit" class="logo-uploader" :show-file-list="false" :before-upload="(file) => beforeLogoUpload(file, 'companyLogo')" action="#"> <img v-if="form.companyLogo" :src="form.companyLogo" class="uploaded-image" alt="Image Preview" /> <div v-else class="upload-placeholder"> <el-icon class="upload-icon"><Plus /></el-icon> <span class="upload-text">ä¸ä¼ Logo</span> </div> </el-upload> <img v-else-if="form.companyLogo" :src="form.companyLogo" class="display-image" alt="Image Preview" /> <div v-else class="empty-placeholder"> <el-icon :size="40"><Picture /></el-icon> <span>ææ Logo</span> </div> </div> </div> <!-- äºç»´ç --> <div class="upload-item"> <span class="upload-label">äºç»´ç </span> <div class="upload-wrapper"> <el-upload v-if="isEdit" class="qr-uploader" :show-file-list="false" :before-upload="(file) => beforeLogoUpload(file, 'qrCode')" action="#"> <img v-if="form.qrCode" :src="form.qrCode" class="uploaded-image" alt="Image Preview" /> <div v-else class="upload-placeholder"> <el-icon class="upload-icon"><Plus /></el-icon> <span class="upload-text">ä¸ä¼ äºç»´ç </span> </div> </el-upload> <img v-else-if="form.qrCode" :src="form.qrCode" class="display-image" alt="Image Preview" /> <div v-else class="empty-placeholder"> <el-icon :size="40"><Picture /></el-icon> <span>ææ äºç»´ç </span> </div> </div> </div> </div> </div> <!-- å ¬å¸ç®ä» --> <div class="info-section"> <div class="section-header"> <h3>å ¬å¸ç®ä»</h3> </div> <div class="content-editor"> <el-input v-if="isEdit" v-model="form.companyIntro" type="textarea" :rows="6" maxlength="2000" show-word-limit placeholder="请è¾å ¥å ¬å¸ç®ä»..." /> <div v-else class="content-display" v-html="form.companyIntro || '<span class=\'empty-text\'>ææ å ¬å¸ç®ä»</span>'"></div> </div> </div> <!-- 产åä»ç» --> <div class="info-section"> <div class="section-header"> <h3>产åä»ç»</h3> </div> <div class="content-editor"> <el-input v-if="isEdit" v-model="form.productIntro" type="textarea" :rows="6" maxlength="2000" show-word-limit placeholder="请è¾å ¥äº§åä»ç»..." /> <div v-else class="content-display" v-html="form.productIntro || '<span class=\'empty-text\'>ææ äº§åä»ç»</span>'"></div> </div> </div> <!-- 设å¤ä»ç» --> <div class="info-section"> <div class="section-header"> <h3>设å¤ä»ç»</h3> </div> <div class="content-editor"> <el-input v-if="isEdit" v-model="form.equipmentIntro" type="textarea" :rows="6" maxlength="2000" show-word-limit placeholder="请è¾å ¥è®¾å¤ä»ç»..." /> <div v-else class="content-display" v-html="form.equipmentIntro || '<span class=\'empty-text\'>ææ è®¾å¤ä»ç»</span>'"></div> </div> </div> <!-- æä½æé® --> <div v-if="isEdit" class="form-actions"> <el-button type="primary" @click="handleSave" :loading="saving" size="large">ä¿å</el-button> <el-button @click="handleCancel" size="large">åæ¶</el-button> </div> </div> <!-- é¢è§å¼¹çª --> <el-dialog v-model="previewVisible" title="ä¼ä¸ä¿¡æ¯é¢è§" width="70%" :destroy-on-close="true"> <div class="preview-content"> <div class="preview-header"> <img v-if="form.companyLogo" :src="form.companyLogo" class="preview-logo" alt="Image Preview" /> <div class="preview-title"> <h1>{{ form.companyName || 'å ¬å¸åç§°' }}</h1> <p v-if="form.website">{{ form.website }}</p> </div> </div> <el-divider /> <div class="preview-section"> <h4>èç³»æ¹å¼</h4> <p>è系人ï¼{{ form.contactPerson || '-' }}</p> <p>èç³»çµè¯ï¼{{ form.contactPhone || '-' }}</p> <p>å ¬å¸å°åï¼{{ form.companyAddress || '-' }}</p> </div> <div class="preview-section"> <h4>å ¬å¸ç®ä»</h4> <div v-html="form.companyIntro || 'ææ ç®ä»'"></div> </div> <div class="preview-section"> <h4>产åä»ç»</h4> <div v-html="form.productIntro || 'ææ ä»ç»'"></div> </div> <div class="preview-section"> <h4>设å¤ä»ç»</h4> <div v-html="form.equipmentIntro || 'ææ ä»ç»'"></div> </div> <div v-if="form.qrCode" class="preview-section preview-qr"> <h4>æ«ç å ³æ³¨</h4> <img :src="form.qrCode" class="qr-image" alt="Image Preview" /> </div> </div> </el-dialog> </div> </template> <script setup> import { ref, reactive, onMounted } from 'vue' import { ElMessage } from 'element-plus' import { Plus, Picture, View } from '@element-plus/icons-vue' import { getEnterpriseInfo, saveEnterpriseInfo, uploadLogo, uploadQrCode } from '@/api/basicData/enterpriseInfo.js' const { proxy } = getCurrentInstance() const loading = ref(false) const saving = ref(false) const isEdit = ref(false) const previewVisible = ref(false) const uploadType = ref('') const form = reactive({ id: null, companyName: '', companyLogo: '', companyIntro: '', productIntro: '', equipmentIntro: '', contactPerson: '', contactPhone: '', companyAddress: '', website: '', qrCode: '' }) // æ·±æ·è´åå§æ°æ®ï¼ç¨äºåæ¶æ¶æ¢å¤ let originalForm = {} // è·åä¼ä¸ä¿¡æ¯ const fetchInfo = () => { loading.value = true getEnterpriseInfo().then(res => { if (res.code === 200 && res.data) { // å°å¾çè·¯å¾æ¼æ¥ä¸ºå®æ´å°å const base = window.location.protocol + '//' + window.location.host if (res.data.companyLogo) { res.data.companyLogo = base + res.data.companyLogo } if (res.data.qrCode) { res.data.qrCode = base + res.data.qrCode } Object.assign(form, res.data) originalForm = JSON.parse(JSON.stringify(res.data)) } }).finally(() => { loading.value = false }) } // 忢ç¼è¾æ¨¡å¼ const toggleEdit = () => { if (isEdit.value) { // åæ¶ç¼è¾ï¼æ¢å¤åå§æ°æ® Object.assign(form, originalForm) isEdit.value = false } else { // è¿å ¥ç¼è¾æ¨¡å¼ originalForm = JSON.parse(JSON.stringify(form)) isEdit.value = true } } // é¢è§ const handlePreview = () => { previewVisible.value = true } // æä»¶ä¸ä¼ åæ ¡éª const beforeLogoUpload = (file, type) => { const isImage = file.type.startsWith('image/') const isLt2M = file.size / 1024 / 1024 < 2 if (!isImage) { ElMessage.error('åªè½ä¸ä¼ å¾çæä»¶ï¼') return false } if (!isLt2M) { ElMessage.error('å¾ç大å°ä¸è½è¶ è¿ 2MBï¼') return false } uploadType.value = type uploadFileReq(file) return false } // ä¸ä¼ æä»¶è¯·æ± const uploadFileReq = (file) => { const uploadFn = uploadType.value === 'companyLogo' ? uploadLogo : uploadQrCode uploadFn(file).then(res => { if (res.code === 200) { const path = res.data.tempPath if (uploadType.value === 'companyLogo') { form.companyLogo = path } else { form.qrCode = path } ElMessage.success('ä¸ä¼ æå') } else { ElMessage.error(res.msg || 'ä¸ä¼ 失败') } }).catch(() => { ElMessage.error('ä¸ä¼ 失败') }) } // ä¿å const handleSave = () => { saving.value = true saveEnterpriseInfo(form).then(res => { if (res.code === 200) { ElMessage.success('ä¿åæå') isEdit.value = false fetchInfo() } else { ElMessage.error(res.msg || 'ä¿å失败') } }).finally(() => { saving.value = false }) } // åæ¶ const handleCancel = () => { Object.assign(form, originalForm) isEdit.value = false } onMounted(() => { fetchInfo() }) </script> <style scoped lang="scss"> .page-header { display: flex; justify-content: space-between; align-items: center; padding: 16px 24px; background: #fff; border-radius: 8px; margin-bottom: 16px; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06); h2 { margin: 0; font-size: 20px; font-weight: 600; color: #303133; } .header-actions { display: flex; gap: 10px; } } .enterprise-info-card { background: #fff; padding: 24px; border-radius: 8px; box-shadow: 0 2px 12px rgba(0, 0, 0, 0.06); } .info-section { margin-bottom: 32px; &:last-of-type { margin-bottom: 0; } .section-header { margin-bottom: 16px; h3 { margin: 0; font-size: 16px; font-weight: 600; color: #303133; padding-bottom: 10px; border-bottom: 2px solid #409eff; display: inline-block; position: relative; &::after { content: ''; position: absolute; left: 0; bottom: -2px; width: 60px; height: 2px; background-color: #409eff; } } } } .info-descriptions { :deep(.el-descriptions__label) { width: 120px; background-color: #f5f7fa; font-weight: 500; color: #606266; } :deep(.el-descriptions__content) { color: #303133; } } .content-text { color: #606266; } .link-text { color: #409eff; text-decoration: none; &:hover { text-decoration: underline; } } .logo-qr-container { display: flex; gap: 40px; } .upload-item { display: flex; flex-direction: column; align-items: center; .upload-label { font-size: 14px; color: #606266; margin-bottom: 12px; font-weight: 500; } .upload-wrapper { width: 140px; height: 140px; } } /* el-upload æ ¹èç¹æ¯å¤å± divï¼çæ£è§¦ååºå¨å å± .el-uploadï¼é让å å±éºæ»¡å¤å±èçº¿æ¡ */ .logo-uploader, .qr-uploader { display: flex; flex-direction: column; box-sizing: border-box; width: 140px; height: 140px; border: 1px dashed #d9d9d9; border-radius: 8px; cursor: pointer; transition: all 0.3s; overflow: hidden; :deep(.el-upload) { flex: 1; min-height: 0; width: 100%; display: flex !important; flex-direction: column; align-items: stretch; box-sizing: border-box; border: none; outline: none; } :deep(.uploaded-image), :deep(.upload-placeholder) { flex: 1 1 auto; min-height: 0; min-width: 0; width: 100%; } &:hover { border-color: #409eff; } :deep(.uploaded-image) { object-fit: contain; } :deep(.upload-placeholder) { display: flex; flex-direction: column; justify-content: center; align-items: center; background: #fafafa; color: #8c939d; .upload-icon { font-size: 32px; margin-bottom: 8px; } .upload-text { font-size: 12px; } } } .display-image { width: 140px; height: 140px; object-fit: contain; border-radius: 8px; border: 1px solid #e4e7ed; } .empty-placeholder { width: 140px; height: 140px; display: flex; flex-direction: column; justify-content: center; align-items: center; background: #fafafa; border-radius: 8px; border: 1px dashed #e4e7ed; color: #c0c4cc; span { margin-top: 8px; font-size: 12px; } } .content-editor { :deep(.el-textarea__inner) { border-radius: 6px; font-size: 14px; line-height: 1.8; } } .content-display { padding: 16px 20px; background: #fafafa; border-radius: 6px; border: 1px solid #ebeef5; line-height: 1.8; color: #606266; min-height: 120px; white-space: pre-wrap; :deep(.empty-text) { color: #c0c4cc; font-style: italic; } } .form-actions { display: flex; justify-content: center; gap: 16px; padding-top: 24px; margin-top: 16px; border-top: 1px solid #ebeef5; } /* é¢è§å¼¹çªæ ·å¼ */ .preview-content { .preview-header { display: flex; align-items: center; gap: 20px; padding: 20px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); border-radius: 8px; color: #fff; margin-bottom: 20px; .preview-logo { width: 80px; height: 80px; object-fit: contain; background: #fff; border-radius: 8px; padding: 8px; } .preview-title { h1 { margin: 0 0 8px 0; font-size: 28px; font-weight: 600; } p { margin: 0; opacity: 0.9; font-size: 14px; } } } .preview-section { margin-bottom: 24px; h4 { font-size: 16px; font-weight: 600; color: #303133; margin: 0 0 12px 0; padding-bottom: 8px; border-bottom: 1px solid #ebeef5; } p { margin: 8px 0; color: #606266; line-height: 1.6; } :deep(div) { line-height: 1.8; color: #606266; white-space: pre-wrap; } } .preview-qr { text-align: center; .qr-image { width: 150px; height: 150px; margin-top: 12px; border-radius: 8px; border: 1px solid #ebeef5; } } } :deep(.el-divider) { margin: 16px 0; } </style> src/views/basicData/supplierManage/filesDia.vue
@@ -28,18 +28,11 @@ :tableData="tableData" :tableLoading="tableLoading" :isSelection="true" :page="{ total, current: page.current, size: page.size }" @pagination="paginationSearch" @selection-change="handleSelectionChange" height="500" > </PIMTable> <pagination style="margin: 10px 0" v-show="total > 0" @pagination="paginationSearch" :total="total" :page="page.current" :limit="page.size" /> height="450" /> <template #footer> <div class="dialog-footer"> <el-button @click="closeDia">åæ¶</el-button> @@ -60,7 +53,6 @@ fileDel, fileListPage } from "@/api/basicData/supplierManageFile.js"; import Pagination from "@/components/PIMTable/Pagination.vue"; const { proxy } = getCurrentInstance() const emit = defineEmits(['close']) src/views/financialManagement/fixedAssets/index.vue
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,313 @@ <template> <div class="app-container"> <div class="search_form"> <div> <span class="search_title">èµäº§åç§°ï¼</span> <el-input v-model="searchForm.name" style="width: 240px" placeholder="请è¾å ¥" @change="handleQuery" clearable prefix-icon="Search" /> <el-button type="primary" @click="handleQuery" style="margin-left: 10px">æç´¢</el-button> </div> <div> <el-button type="primary" @click="openForm('add')">æ°å¢èµäº§</el-button> <el-button type="danger" plain @click="handleDelete">å é¤</el-button> </div> </div> <div class="table_list"> <el-table :data="tableData" border v-loading="tableLoading" @selection-change="handleSelectionChange" style="width: 100%" height="calc(100vh - 18.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="name" show-overflow-tooltip /> <el-table-column label="åå·" prop="model" show-overflow-tooltip /> <el-table-column label="ä»·æ ¼" prop="price" show-overflow-tooltip> <template #default="{ row }"> Â¥{{ row.price ? row.price.toFixed(2) : '0.00' }} </template> </el-table-column> <el-table-column label="ä½ç½®" prop="address" show-overflow-tooltip /> <el-table-column label="å建æ¶é´" prop="createTime" show-overflow-tooltip> <template #default="{ row }"> {{ row.createTime ? dayjs(row.createTime).format('YYYY-MM-DD HH:mm:ss') : '-' }} </template> </el-table-column> <el-table-column fixed="right" label="æä½" min-width="100" align="center"> <template #default="scope"> <el-button link type="primary" size="small" @click="openForm('edit', scope.row);">ç¼è¾</el-button> <el-button link type="danger" size="small" @click="handleDeleteSolo(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> <el-dialog v-model="dialogFormVisible" :title="operationType === 'add' ? 'æ°å¢åºå®èµäº§' : 'ç¼è¾åºå®èµäº§'" width="600px" @close="closeDia"> <el-form :model="form" label-width="100px" :rules="rules" ref="formRef"> <el-form-item label="èµäº§åç§°" prop="name"> <el-input v-model="form.name" placeholder="请è¾å ¥èµäº§åç§°" clearable /> </el-form-item> <el-form-item label="åå·" prop="model"> <el-input v-model="form.model" placeholder="请è¾å ¥åå·" clearable /> </el-form-item> <el-form-item label="ä»·æ ¼" prop="price"> <el-input-number v-model="form.price" :min="0" :precision="2" :step="0.01" style="width: 100%" placeholder="请è¾å ¥ä»·æ ¼" /> </el-form-item> <el-form-item label="ä½ç½®" prop="address"> <el-input v-model="form.address" type="textarea" :rows="3" placeholder="请è¾å ¥ä½ç½®" /> </el-form-item> </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> </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 useUserStore from "@/store/modules/user"; import { listPage, add, update, delFixedAssets, } from "@/api/financialManagement/fixedAssets.js"; import dayjs from "dayjs"; const userStore = useUserStore(); const { proxy } = getCurrentInstance(); const tableData = ref([]); const selectedRows = ref([]); const tableLoading = ref(false); const loading = ref(false); const page = reactive({ current: 1, size: 100, }); const total = ref(0); const operationType = ref(""); const dialogFormVisible = ref(false); const data = reactive({ searchForm: { name: "", }, form: { id: null, name: "", model: "", price: 0, address: "", }, rules: { name: [{ required: true, message: "请è¾å ¥èµäº§åç§°", trigger: "blur" }], model: [{ required: true, message: "请è¾å ¥åå·", trigger: "blur" }], price: [{ required: true, message: "请è¾å ¥ä»·æ ¼", trigger: "blur" }], address: [{ required: true, message: "请è¾å ¥ä½ç½®", trigger: "blur" }], }, }); const { searchForm, form, rules } = toRefs(data); const handleQuery = () => { page.current = 1; getList(); }; const paginationChange = obj => { page.current = obj.page; page.size = obj.limit; getList(); }; const getList = () => { tableLoading.value = true; listPage({ ...searchForm.value, ...page }) .then(res => { tableLoading.value = false; tableData.value = res.data.records; total.value = res.data.total; }) .catch(() => { tableLoading.value = false; }); }; const handleSelectionChange = selection => { selectedRows.value = selection; }; const openForm = async (type, row) => { operationType.value = type; dialogFormVisible.value = true; if (type === "add") { form.value = { id: null, name: "", model: "", price: 0, address: "", }; } else { form.value = { id: row.id, name: row.name, model: row.model, price: row.price, address: row.address, }; } }; const submitForm = async () => { try { await proxy.$refs.formRef.validate(); loading.value = true; if (operationType.value === "add") { await add(form.value); proxy.$modal.msgSuccess("æ°å¢èµäº§æå"); } else { await update(form.value); proxy.$modal.msgSuccess("ä¿®æ¹èµäº§æå"); } closeDia(); getList(); } catch (error) { console.error("æäº¤å¤±è´¥:", error); if (!error.errors) { proxy.$modal.msgError("æä½å¤±è´¥ï¼è¯·éè¯"); } } finally { loading.value = false; } }; const closeDia = () => { proxy.$refs.formRef.resetFields(); dialogFormVisible.value = false; }; const handleDeleteSolo = row => { ElMessageBox.confirm("该èµäº§å°è¢«å é¤ï¼æ¯å¦ç¡®è®¤å é¤ï¼", "å é¤æç¤º", { confirmButtonText: "确认", cancelButtonText: "åæ¶", type: "warning", }) .then(() => { const ids = [row.id]; delFixedAssets(ids) .then(res => { proxy.$modal.msgSuccess("å 餿å"); getList(); }) .catch(() => { proxy.$modal.msgError("å é¤å¤±è´¥"); }); }) .catch(() => { proxy.$modal.msg("已忶"); }); }; const handleDelete = () => { if (selectedRows.value.length === 0) { proxy.$modal.msgWarning("è¯·éæ©è¦å é¤çæ°æ®"); return; } ElMessageBox.confirm("éä¸çå 容å°è¢«å é¤ï¼æ¯å¦ç¡®è®¤å é¤ï¼", "å é¤æç¤º", { confirmButtonText: "确认", cancelButtonText: "åæ¶", type: "warning", }) .then(() => { const ids = selectedRows.value.map(item => item.id); delFixedAssets(ids) .then(res => { proxy.$modal.msgSuccess("å 餿å"); getList(); }) .catch(() => { proxy.$modal.msgError("å é¤å¤±è´¥"); }); }) .catch(() => { proxy.$modal.msg("已忶"); }); }; onMounted(() => { getList(); }); </script> <style scoped lang="scss"></style> src/views/inventoryManagement/receiptManagement/index.vue
@@ -76,6 +76,14 @@ prop="inboundNum" width="90" show-overflow-tooltip /> <el-table-column label="缺货æ°é" prop="outStockQuantity" width="100" show-overflow-tooltip /> <el-table-column label="缺货æ åµ" prop="shortageDescription" width="150" show-overflow-tooltip /> <el-table-column label="å«ç¨åä»·" prop="taxInclusiveUnitPrice" width="100" @@ -177,6 +185,25 @@ :min="0" style="width: 100%" v-model="scope.row.quantityStock" /> </template> </el-table-column> <el-table-column label="缺货æ°é" prop="outStockQuantity" width="150"> <template #default="scope"> <el-input-number :step="0.01" :min="0" style="width: 100%" v-model="scope.row.outStockQuantity" /> </template> </el-table-column> <el-table-column label="缺货æ åµ" prop="shortageDescription" width="150"> <template #default="scope"> <el-input v-model="scope.row.shortageDescription" placeholder="请è¾å ¥ç¼ºè´§æ åµ" clearable /> </template> </el-table-column> <el-table-column label="ç¨ç(%)" @@ -486,6 +513,8 @@ const stockInData = { id: selectedRows.value[0].recordId, quantityStock: Number(selectedRows.value[0].quantityStock), // ä½¿ç¨æ°æ ¼å¼å彿° outStockQuantity: selectedRows.value[0].outStockQuantity, shortageDescription: selectedRows.value[0].shortageDescription, }; await updateStockIn(stockInData); proxy.$modal.msgSuccess("ä¿®æ¹å ¥åºæå"); @@ -531,6 +560,8 @@ // id: product.salesLedgerProductId, inboundQuantity: Number(product.quantityStock), productModelId: product.productModelId, outStockQuantity: product.outStockQuantity, shortageDescription: product.shortageDescription, })), }; // è°ç¨API src/views/procurementManagement/advancedPriceManagement/index.vue
@@ -8,7 +8,7 @@ </el-form-item> <el-form-item label="ä¾åºåï¼"> <el-select v-model="searchForm.supplierId" placeholder="è¯·éæ©ä¾åºå" clearable style="width: 200px"> <el-option v-for="supplier in supplierList" :key="supplier.id" :label="supplier.name" :value="supplier.id" /> <el-option v-for="supplier in supplierList" :key="supplier.id" :label="supplier.supplierName" :value="supplier.id" /> </el-select> </el-form-item> <el-form-item> @@ -31,10 +31,10 @@ <el-icon><Plus /></el-icon> æ°å¢ä»·æ ¼ </el-button> <el-button type="success" @click="openBatchDiscountDialog"> <!-- <el-button type="success" @click="openBatchDiscountDialog"> <el-icon><Discount /></el-icon> æ¹éææ£ </el-button> </el-button> --> <el-button type="info" @click="exportData"> <el-icon><Download /></el-icon> å¯¼åºæ°æ® @@ -62,17 +62,16 @@ <div class="product-info"> <div class="product-name">{{ row.productName }}</div> <div class="product-spec">{{ row.specification }}</div> <div class="product-code">ç¼ç : {{ row.productCode }}</div> </div> </template> </el-table-column> <el-table-column label="ä¾åºå" prop="supplierName" width="150" /> <el-table-column label="åºç¡ä»·æ ¼" width="120" align="right"> <el-table-column label="ä¾åºå" prop="supplierName" width="200" /> <el-table-column label="åºç¡ä»·æ ¼(ä¸å«ç¨)" width="150" align="right"> <template #default="{ row }"> <span class="price-text">Â¥{{ row.basePrice }}</span> </template> </el-table-column> <el-table-column label="ææ£ä¿¡æ¯" width="150"> <el-table-column label="ææ£ä¿¡æ¯" width="120" align="center"> <template #default="{ row }"> <div v-if="row.discountType"> <el-tag :type="getDiscountTagType(row.discountType)" size="small"> @@ -83,9 +82,12 @@ <span v-else class="no-discount">æ ææ£</span> </template> </el-table-column> <el-table-column label="å®é ä»·æ ¼" width="120" align="right"> <!-- <el-table-column label="å®é ä»·æ ¼(ä¸å«ç¨)" prop="actuallyPrice" width="150" align="right"> --> <!-- </el-table-column> --> <el-table-column label="å®é ä»·æ ¼(ä¸å«ç¨)" width="150" align="right"> <template #default="{ row }"> <span class="final-price">Â¥{{ calculateFinalPrice(row) }}</span> <!-- <span class="final-price">Â¥{{ calculateFinalPrice(row) }}</span> --> <span class="final-price" >Â¥{{ row.actuallyPrice?row.actuallyPrice:row.basePrice }}</span> </template> </el-table-column> <el-table-column label="ä»·æ ¼æ§å¶" width="120"> @@ -103,13 +105,14 @@ <el-table-column label="ç¶æ" width="100" align="center"> <template #default="{ row }"> <el-tag :type="getStatusType(row.status)">{{ getStatusText(row.status) }}</el-tag> <div v-if="isPriceWarning(row)" class="warning-indicator"> <!-- <div v-if="isPriceWarning(row)" class="warning-indicator"> <el-icon color="#F56C6C"><Warning /></el-icon> </div> </div> --> </template> </el-table-column> <el-table-column label="çææ¶é´" prop="effectiveTime" width="180" /> <el-table-column label="æ´æ°æ¶é´" prop="updateTime" width="180" sortable /> <el-table-column label="çææ¶é´" prop="effectiveTime" width="100" align="center" /> <el-table-column label="æ´æ°æ¶é´" prop="updateTime" width="160" sortable align="center" /> <el-table-column label="夿³¨" width="200" prop="remark" align="center" show-overflow-tooltip /> <el-table-column label="æä½" width="250" align="center" fixed="right"> <template #default="{ row }"> <el-button type="primary" link @click="openDialog('edit', row)"> @@ -143,38 +146,65 @@ <el-form :model="formData" :rules="formRules" ref="formRef" label-width="120px"> <el-row :gutter="20"> <el-col :span="12"> <el-form-item label="åååç§°" prop="productName"> <el-select v-model="formData.productName" placeholder="è¯·éæ©åå" style="width: 100%" filterable> <el-option v-for="product in productList" :key="product.id" :label="product.name" :value="product.name" /> </el-select> <el-form-item label="åååç§°" prop="productId"> <el-tree-select v-model="formData.productId" placeholder="è¯·éæ©åå" clearable filterable check-strictly @change="getModels" :data="productOptions" :props="{ label: 'productName', value: 'id', children: 'children' }" node-key="id" :render-after-expand="false" style="width: 100%" /> </el-form-item> </el-col> <el-col :span="12"> <el-form-item label="ååç¼ç " prop="productCode"> <el-input v-model="formData.productCode" placeholder="请è¾å ¥ååç¼ç " /> </el-form-item> </el-col> </el-row> <el-row :gutter="20"> <el-col :span="12"> <el-form-item label="è§æ ¼åå·" prop="specification"> <el-input v-model="formData.specification" placeholder="请è¾å ¥è§æ ¼åå·" /> </el-form-item> </el-col> <el-col :span="12"> <el-form-item label="ä¾åºå" prop="supplierName"> <el-select v-model="formData.supplierName" placeholder="è¯·éæ©ä¾åºå" style="width: 100%"> <el-option v-for="supplier in supplierList" :key="supplier.id" :label="supplier.name" :value="supplier.name" /> <el-select v-model="formData.specification" 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="20"> <el-col :span="12"> <el-form-item label="ä¾åºå" prop="supplierId"> <el-select v-model="formData.supplierId" placeholder="è¯·éæ©ä¾åºå" clearable @change="handleSupplierChange" > <el-option v-for="item in supplierList" :key="item.id" :label="item.supplierName" :value="item.id" /> </el-select> </el-form-item> </el-col> <el-col :span="12"> <el-form-item label="åºç¡ä»·æ ¼" prop="basePrice"> <el-input-number v-model="formData.basePrice" :min="0" :precision="2" placeholder="请è¾å ¥åºç¡ä»·æ ¼" style="width: 100%" /> </el-form-item> </el-col> </el-row> <el-row :gutter="20"> <el-col :span="12"> <el-form-item label="åä½"> <el-input v-model="formData.unit" placeholder="请è¾å ¥åä½" /> @@ -185,7 +215,7 @@ <!-- ææ£è®¾ç½® --> <el-divider content-position="left">ææ£è®¾ç½®</el-divider> <el-row :gutter="20"> <el-col :span="8"> <el-col :span="12"> <el-form-item label="ææ£ç±»å"> <el-select v-model="formData.discountType" placeholder="è¯·éæ©ææ£ç±»å" style="width: 100%"> <el-option label="æ ææ£" value="" /> @@ -194,10 +224,11 @@ </el-select> </el-form-item> </el-col> <el-col :span="8"> <el-form-item label="ææ£å¼" v-if="formData.discountType && formData.discountType !== 'tiered'"> <el-col :span="12"> <el-form-item label="ææ£å¼"> <el-input-number v-model="formData.discountValue" :disabled="!(formData.discountType && formData.discountType !== 'tiered')" :min="0" :max="formData.discountType === 'percentage' ? 100 : undefined" :precision="2" @@ -206,10 +237,11 @@ /> </el-form-item> </el-col> <el-col :span="8"> <el-col :span="12"> <el-form-item label="ææ£æææ"> <el-date-picker v-model="formData.discountEndTime" :disabled="!(formData.discountType && formData.discountType !== 'tiered')" type="datetime" placeholder="éæ©ç»ææ¶é´" style="width: 100%" @@ -221,19 +253,14 @@ <!-- ä»·æ ¼æ§å¶ --> <el-divider content-position="left">ä»·æ ¼æ§å¶</el-divider> <el-row :gutter="20"> <el-col :span="8"> <el-col :span="12"> <el-form-item label="æä½ä»·æ ¼"> <el-input-number v-model="formData.minPrice" :min="0" :precision="2" placeholder="æä½ä»·æ ¼" style="width: 100%" /> </el-form-item> </el-col> <el-col :span="8"> <el-col :span="12"> <el-form-item label="æé«ä»·æ ¼"> <el-input-number v-model="formData.maxPrice" :min="0" :precision="2" placeholder="æé«ä»·æ ¼" style="width: 100%" /> </el-form-item> </el-col> <el-col :span="8"> <el-form-item label="é¢è¦éå¼(%)"> <el-input-number v-model="formData.warningThreshold" :min="0" :max="100" :precision="1" placeholder="é¢è¦éå¼" style="width: 100%" /> </el-form-item> </el-col> </el-row> @@ -334,12 +361,14 @@ <script setup> import {ref, reactive, computed, onMounted, getCurrentInstance} from 'vue' import { ElMessage, ElMessageBox } from 'element-plus' import { productTreeList, modelList } from "@/api/basicData/product.js"; import { Search, Refresh, Plus, Discount, Setting, Download, Delete, Edit, Warning } from '@element-plus/icons-vue' import { listPage, update, del, add } from '@/api/procurementManagement/advancedPriceManagement' import { getOptions, } from "@/api/procurementManagement/procurementLedger.js"; // ååºå¼æ°æ® const loading = ref(false) const submitLoading = ref(false) @@ -347,6 +376,8 @@ const batchDiscountVisible = ref(false) const priceControlVisible = ref(false) const dialogType = ref('add') const productOptions = ref([]); const modelOptions = ref([]); const selectedRows = ref([]) const formRef = ref() @@ -367,10 +398,12 @@ // è¡¨åæ°æ® const formData = reactive({ productId: null, productName: '', productCode: '', specification: '', supplierId: null, supplierName: '', specificationId: null, basePrice: 0, unit: '', discountType: '', @@ -405,24 +438,18 @@ // 表åéªè¯è§å const formRules = { productName: [{ required: true, message: 'è¯·éæ©åååç§°', trigger: 'change' }], productCode: [{ required: true, message: '请è¾å ¥ååç¼ç ', trigger: 'blur' }], supplierName: [{ required: true, message: 'è¯·éæ©ä¾åºå', trigger: 'change' }], productId: [{ required: true, message: 'è¯·éæ©åååç§°', trigger: 'change' }], supplierId: [{ required: true, message: 'è¯·éæ©ä¾åºå', trigger: 'change' }], basePrice: [{ required: true, message: '请è¾å ¥åºç¡ä»·æ ¼', trigger: 'blur' }], effectiveTime: [{ required: true, message: 'è¯·éæ©çææ¶é´', trigger: 'change' }], reason: [{ required: true, message: 'è¯·éæ©è°ä»·åå ', trigger: 'change' }] } const supplierList = ref([ { id: 1, name: 'ä¼è´¨äºéä¾åºå' }, { id: 2, name: 'é¢æè´¸æå ¬å¸' }, { id: 3, name: 'å»ºææ¹åå' } ]) getOptions().then((res) => { supplierList.value = res.data; }); const productList = ref([ { id: 1, name: 'é«å¼ºåº¦èºæ ' }, { id: 2, name: 'ä¸éé¢ç®¡' }, { id: 3, name: 'éåéåæ' } const supplierList = ref([ ]) @@ -434,7 +461,8 @@ } else if (row.discountType === 'fixed') { finalPrice = row.basePrice - row.discountValue } return Math.max(finalPrice, 0) let man = Math.max(finalPrice, 0) return man.toFixed(2) } const getDiscountTagType = (discountType) => { @@ -454,6 +482,39 @@ return textMap[discountType] || 'æªç¥' } const getProductOptions = () => { productTreeList().then((res) => { productOptions.value = res; }); }; const findNodeById = (data, id) => { for (const item of data) { if (item.id === id) return item; if (item.children) { const found = findNodeById(item.children, id); if (found) return found; } } return null; }; const getModels = (value) => { if (value) { const selectedProduct = findNodeById(productOptions.value, value); if (selectedProduct) { formData.productName = selectedProduct.productName; } modelList({ id: value }).then((res) => { modelOptions.value = res; }); formData.specification = ""; } else { formData.productName = ""; modelOptions.value = []; } }; const getStatusType = (status) => { const statusMap = { active: 'success', @@ -461,6 +522,27 @@ expired: 'info' } return statusMap[status] || 'info' } const getProductModel = (value) => { const index = modelOptions.value.findIndex((item) => item.id === value); if (index !== -1) { formData.specification = modelOptions.value[index].model; formData.specificationId = modelOptions.value[index].id; formData.unit = modelOptions.value[index].unit; } else { formData.specification = null; formData.specificationId = null; formData.unit = null; } }; const handleSupplierChange = (value) => { const supplier = supplierList.value.find(supplier => supplier.id === value) if (supplier) { formData.supplierId = supplier.id formData.supplierName = supplier.supplierName } } const getStatusText = (status) => { @@ -471,13 +553,6 @@ } return statusMap[status] || 'æªç¥' } const isPriceWarning = (row) => { if (!row.priceControl) return false const finalPrice = calculateFinalPrice(row) return finalPrice < row.priceControl.minPrice || finalPrice > row.priceControl.maxPrice } const handleSearch = () => { loading.value = true @@ -501,10 +576,14 @@ const openDialog = (type, row = {}) => { dialogType.value = type if (type === 'edit' && row.id) { // å¤å¶è¡æ°æ®å°è¡¨å Object.assign(formData, { ...row, minPrice: row.priceControl?.minPrice, maxPrice: row.priceControl?.maxPrice, // å ¼å®¹ä¸¤ç§æ°æ®ç»æï¼å¹³éºçåæ®µæåµå¥å¨ priceControl ä¸çåæ®µ minPrice: row.minPrice ?? row.priceControl?.minPrice, maxPrice: row.maxPrice ?? row.priceControl?.maxPrice, // ç¡®ä¿ææ£æææä¹è¢«èµå¼ (妿 row.discountEndTime åå¨çè¯) discountEndTime: row.discountEndTime || row.discountEndTime, tieredDiscount: row.tieredDiscount || [] }) } else { @@ -515,9 +594,10 @@ const resetFormData = () => { Object.assign(formData, { productId: null, productName: '', productCode: '', specification: '', supplierId: null, supplierName: '', basePrice: 0, unit: '', @@ -543,13 +623,13 @@ }) } const removeTieredRow = (index) => { formData.tieredDiscount.splice(index, 1) } const handleSubmit = async () => { if (!formRef.value) return formData.actuallyPrice = Number(calculateFinalPrice(formData)) if( formData.discountType === ''){ formData.discountEndTime = '2099-12-31 23:59:59' } try { await formRef.value.validate() submitLoading.value = true @@ -677,6 +757,7 @@ // çå½å¨æ onMounted(() => { handleSearch() getProductOptions() }) </script> src/views/procurementManagement/procurementLedger/index.vue
@@ -250,6 +250,7 @@ v-model="form.supplierId" placeholder="è¯·éæ©" clearable @change="handleSupplierChange" > <el-option v-for="item in supplierList" @@ -552,6 +553,11 @@ </el-select> </el-form-item> </el-col> <el-col :span="24"> <el-form-item label=" "> <el-button type="warning" :disabled="!(productForm.productId && productForm.productModelId && productForm.taxRate)" @click="showPriceReference" icon="Search">æ¥çåå²éè´ä»·æ ¼åè</el-button> </el-form-item> </el-col> </el-row> <el-row :gutter="30"> <el-col :span="12"> @@ -636,6 +642,83 @@ <el-button type="primary" @click="submitProduct">确认</el-button> <el-button @click="closeProductDia">åæ¶</el-button> </div> </template> </el-dialog> <!-- åå²éè´ä»·æ ¼åèå¼¹çª --> <el-dialog v-model="priceReferenceVisible" :title="`åå²éè´ä»·æ ¼åè ${productForm.supplierName}-${productForm.productName}-${productForm.productModel}`" width="1000px" append-to-body > <el-table :data="priceReferenceData" border v-loading="priceReferenceLoading" :default-sort="{ prop: 'updateTime', order: 'descending' }" > <!-- <el-table-column label="ååä¿¡æ¯" min-width="200"> <template #default="{ row }"> <div class="product-info"> <div class="product-name">{{ row.productName }}</div> <div class="product-spec">{{ row.specification }}</div> <div class="product-code">ç¼ç : {{ row.productCode }}</div> </div> </template> </el-table-column> --> <!-- <el-table-column label="ä¾åºå" prop="supplierName" width="200" /> --> <el-table-column label="åºç¡ä»·æ ¼(ä¸å«ç¨)" width="150" align="right"> <template #default="{ row }"> <span class="price-text">Â¥{{ row.basePrice }}</span> </template> </el-table-column> <el-table-column label="å®é ä»·æ ¼(ä¸å«ç¨)" prop="actuallyPrice" width="150" align="right"/> <el-table-column label="ææ£ä¿¡æ¯" width="120" align="center"> <template #default="{ row }"> <div v-if="row.discountType"> <el-tag :type="getDiscountTagType(row.discountType)" size="small"> {{ getDiscountText(row.discountType) }} </el-tag> <div class="discount-value">{{ row.discountValue }}{{ row.discountType === 'percentage' ? '%' : 'å ' }}</div> </div> <span v-else class="no-discount">æ ææ£</span> </template> </el-table-column> <el-table-column label="ç¶æ" width="100" align="center"> <template #default="{ row }"> <el-tag :type="getStatusType(row.status)">{{ getStatusText(row.status) }}</el-tag> <!-- <div v-if="isPriceWarning(row)" class="warning-indicator"> <el-icon color="#F56C6C"><Warning /></el-icon> </div> --> </template> </el-table-column> <el-table-column label="çææ¶é´" prop="effectiveTime" width="100" align="center" /> <el-table-column label="æ´æ°æ¶é´" prop="updateTime" width="160" sortable align="center" /> <el-table-column label="夿³¨" width="200" prop="remark" align="center" show-overflow-tooltip /> <el-table-column label="æä½" width="100" align="center" fixed="right"> <template #default="{ row }"> <el-button type="success" link @click="selectPriceReference(row)"> <el-icon><Check /></el-icon> å¼ç¨ </el-button> </template> </el-table-column> </el-table> <div class="pagination-container" style="margin-top: 20px; display: flex; justify-content: flex-end;"> <el-pagination v-model:current-page="priceReferencePagination.current" v-model:page-size="priceReferencePagination.size" :page-sizes="[10, 20, 50]" :total="priceReferenceTotal" layout="total, sizes, prev, pager, next" @size-change="handlePriceReferenceSizeChange" @current-change="handlePriceReferenceCurrentChange" /> </div> <template #footer> <el-button @click="priceReferenceVisible = false">å ³é</el-button> </template> </el-dialog> @@ -860,7 +943,7 @@ import { getToken } from "@/utils/auth"; import pagination from "@/components/PIMTable/Pagination.vue"; import { ref, onMounted, reactive, toRefs, getCurrentInstance, nextTick } from "vue"; import { Search } from "@element-plus/icons-vue"; import { Search, Check } from "@element-plus/icons-vue"; import { ElMessageBox } from "element-plus"; import { userListNoPage } from "@/api/system/user.js"; import { @@ -903,6 +986,7 @@ let nextApproverId = 2; import useUserStore from "@/store/modules/user"; import { modelList, productTreeList } from "@/api/basicData/product.js"; import { listPage as listAdvancedPrice } from "@/api/procurementManagement/advancedPriceManagement.js"; import dayjs from "dayjs"; const userStore = useUserStore(); @@ -975,6 +1059,9 @@ taxExclusiveTotalPrice: "", invoiceType: "", warnNum: "", supplierName: "", productName: "", productModel: "", }, productRules: { productId: [{ required: true, message: "è¯·éæ©", trigger: "change" }], @@ -996,6 +1083,139 @@ }, }); const { productForm, productRules } = toRefs(productFormData); // éè´ä»·æ ¼ç®¡çåèå¼¹çª const priceReferenceVisible = ref(false); const priceReferenceLoading = ref(false); const priceReferenceData = ref([]); const priceReferenceTotal = ref(0); const priceReferencePagination = reactive({ current: 1, size: 10 }); const calculateFinalPrice = (row) => { let finalPrice = row.basePrice; if (row.discountType === "percentage") { finalPrice = row.basePrice * (1 - row.discountValue / 100); } else if (row.discountType === "fixed") { finalPrice = row.basePrice - row.discountValue; } let man = Math.max(finalPrice, 0); return man.toFixed(2); }; const getDiscountTagType = (discountType) => { const typeMap = { percentage: "success", fixed: "warning", tiered: "info", }; return typeMap[discountType] || "info"; }; const getDiscountText = (discountType) => { const textMap = { percentage: "ç¾åæ¯", fixed: "åºå®éé¢", }; return textMap[discountType] || "æªç¥"; }; const getStatusType = (status) => { const statusMap = { active: "success", pending: "warning", expired: "info", }; return statusMap[status] || "info"; }; const getStatusText = (status) => { const statusMap = { active: "ææ", pending: "å¾ çæ", expired: "å·²è¿æ", }; return statusMap[status] || "æªç¥"; }; const showPriceReference = () => { if (form.value.supplierId) { const supplier = supplierList.value.find((item) => item.id === form.value.supplierId); if (supplier) { productForm.value.supplierName = supplier.supplierName; } } priceReferenceVisible.value = true; handlePriceReferenceSearch(); }; const handlePriceReferenceSearch = () => { priceReferenceLoading.value = true; // 模ææç´¢åæ°ï¼productId æ å°ä¸º productName æ IDï¼è¿éæ ¹æ® advancedPriceManagement ç API ç¡®å®åæ° // å设é«çº§ä»·æ ¼ç®¡çç listPage æ¥æ¶ productId const query = { productId: productForm.value.productId, supplierId: form.value.supplierId, current: priceReferencePagination.current, size: priceReferencePagination.size }; listAdvancedPrice(query).then(res => { console.log(res); priceReferenceData.value = res.data.records; priceReferenceTotal.value = res.data.total; priceReferenceLoading.value = false; }).catch(() => { priceReferenceLoading.value = false; }); }; const handlePriceReferenceSizeChange = (size) => { priceReferencePagination.size = size; handlePriceReferenceSearch(); }; const handlePriceReferenceCurrentChange = (page) => { priceReferencePagination.current = page; handlePriceReferenceSearch(); }; const selectPriceReference = (row) => { // 计ç®åå²ä»·æ ¼ï¼ä½ä¸ºä¸å«ç¨åä»·ï¼ï¼åºç¡ä»·æ ¼ - ææ£ let taxExclusivePrice = row.basePrice; if (row.discountType === "percentage") { taxExclusivePrice = row.basePrice * (1 - row.discountValue / 100); } else if (row.discountType === "fixed") { taxExclusivePrice = row.basePrice - row.discountValue; } taxExclusivePrice = Number(Math.max(taxExclusivePrice, 0)); // 设置æ°é为 1 if (!productForm.value.quantity) { productForm.value.quantity = 1; } // 妿已æç¨çï¼å计ç®å«ç¨åä»· if (productForm.value.taxRate) { const taxRate = Number(productForm.value.taxRate); productForm.value.taxInclusiveUnitPrice = ( taxExclusivePrice * (1 + taxRate / 100) ).toFixed(2); } else { // å¦ææ²¡æç¨çï¼ææ¶å°å«ç¨å价设为ä¸å«ç¨ä»·æ ¼ï¼æç¤ºç¨æ·éæ©ç¨ç productForm.value.taxInclusiveUnitPrice = taxExclusivePrice.toFixed(2); proxy.$modal.msgWarning("è¯·éæ©ç¨ç以å确计ç®å«ç¨ä»·æ ¼"); } // èªå¨è§¦å计ç®é»è¾ï¼è®¡ç®æ»ä»·çï¼ mathNum(); priceReferenceVisible.value = false; proxy.$modal.msgSuccess("å·²å¼ç¨åå²ä»·æ ¼ï¼ä¸å«ç¨ï¼"); }; const upload = reactive({ // ä¸ä¼ çå°å url: import.meta.env.VITE_APP_BASE_API + "/file/upload", @@ -1230,13 +1450,38 @@ }; // æå¼äº§åå¼¹æ¡ const openProductForm = (type, row, index) => { if(!form.value.supplierId){ proxy.$modal.msgWarning("è¯·éæ©ä¾åºå"); return; } productOperationType.value = type; productOperationIndex.value = index; productForm.value = {}; productForm.value = { supplierName: "", productName: "", productModel: "", }; proxy.resetForm("productFormRef"); if (type === "edit") { productForm.value = { ...row }; productForm.value = { ...row, productName: row.productCategory || "", productModel: row.specificationModel || "" }; // ç¼è¾æ¶ï¼æ ¹æ® productId é¢å è½½è§æ ¼åå·å表ï¼ç¡®ä¿ä¸ææ¡æ¾ç¤ºåç§°èé ID if (productForm.value.productId) { modelList({ id: productForm.value.productId }).then((res) => { modelOptions.value = res; }); } } // ç¡®ä¿ä¾åºååç§°å§ç»åæ¥ const supplier = supplierList.value.find((item) => item.id === form.value.supplierId); if (supplier) { productForm.value.supplierName = supplier.supplierName; } productFormVisible.value = true; getProductOptions(); }; @@ -1248,11 +1493,14 @@ const getModels = (value) => { if (value) { productForm.value.productCategory = findNodeById(productOptions.value, value) || ""; productForm.value.productName = productForm.value.productCategory; productForm.value.productModelId = ""; modelList({ id: value }).then((res) => { modelOptions.value = res; }); } else { productForm.value.productCategory = ""; productForm.value.productName = ""; modelOptions.value = []; } }; @@ -1260,12 +1508,24 @@ const index = modelOptions.value.findIndex((item) => item.id === value); if (index !== -1) { productForm.value.specificationModel = modelOptions.value[index].model; productForm.value.productModel = modelOptions.value[index].model; productForm.value.unit = modelOptions.value[index].unit; } else { productForm.value.specificationModel = null; productForm.value.productModel = null; productForm.value.unit = null; } }; const handleSupplierChange = (value) => { const supplier = supplierList.value.find((item) => item.id === value); if (supplier) { form.value.supplierName = supplier.supplierName; productForm.value.supplierName = supplier.supplierName; } else { form.value.supplierName = ""; productForm.value.supplierName = ""; } }; const findNodeById = (nodes, productId) => { for (let i = 0; i < nodes.length; i++) { if (nodes[i].value === productId) { src/views/salesManagement/salesLedger/index.vue
@@ -1669,7 +1669,7 @@ }; deliveryFormVisible.value = true; }).catch(err => { ElMessage.error(err.msg); // ElMessage.error(err); }); };