| | |
| | | <el-row :gutter="20" class="search-row"> |
| | | <el-col :span="8"> |
| | | <el-input |
| | | v-model="searchForm.quotationNo" |
| | | placeholder="请è¾å
¥æ¥ä»·åå·" |
| | | clearable |
| | | @keyup.enter="handleSearch" |
| | | v-model="searchForm.quotationNo" |
| | | placeholder="请è¾å
¥æ¥ä»·åå·" |
| | | clearable |
| | | @keyup.enter="handleSearch" |
| | | > |
| | | <template #prefix> |
| | | <el-icon><Search /></el-icon> |
| | | <el-icon> |
| | | <Search/> |
| | | </el-icon> |
| | | </template> |
| | | </el-input> |
| | | </el-col> |
| | | <el-col :span="8"> |
| | | <el-select v-model="searchForm.customer" placeholder="è¯·éæ©å®¢æ·" clearable> |
| | | <el-option v-for="item in customerOption" :key="item.id" :label="item.customerName" :value="item.customerName"> |
| | | {{ |
| | | item.customerName + "ââ" + item.taxpayerIdentificationNumber |
| | | }} |
| | | </el-option> |
| | | <el-option v-for="item in customerOption" :key="item.id" :label="item.customerName" |
| | | :value="item.customerName"> |
| | | {{ |
| | | item.customerName + "ââ" + item.taxpayerIdentificationNumber |
| | | }} |
| | | </el-option> |
| | | </el-select> |
| | | </el-col> |
| | | <!-- <el-col :span="6">--> |
| | | <!-- <el-select v-model="searchForm.status" placeholder="è¯·éæ©æ¥ä»·ç¶æ" clearable>--> |
| | | <!-- <el-option label="è稿" value="è稿"></el-option>--> |
| | | <!-- <el-option label="å·²åé" value="å·²åé"></el-option>--> |
| | | <!-- <el-option label="客æ·ç¡®è®¤" value="客æ·ç¡®è®¤"></el-option>--> |
| | | <!-- <el-option label="å·²è¿æ" value="å·²è¿æ"></el-option>--> |
| | | <!-- </el-select>--> |
| | | <!-- </el-col>--> |
| | | <!-- <el-col :span="6">--> |
| | | <!-- <el-select v-model="searchForm.status" placeholder="è¯·éæ©æ¥ä»·ç¶æ" clearable>--> |
| | | <!-- <el-option label="è稿" value="è稿"></el-option>--> |
| | | <!-- <el-option label="å·²åé" value="å·²åé"></el-option>--> |
| | | <!-- <el-option label="客æ·ç¡®è®¤" value="客æ·ç¡®è®¤"></el-option>--> |
| | | <!-- <el-option label="å·²è¿æ" value="å·²è¿æ"></el-option>--> |
| | | <!-- </el-select>--> |
| | | <!-- </el-col>--> |
| | | <el-col :span="8"> |
| | | <el-button type="primary" @click="handleSearch">æç´¢</el-button> |
| | | <el-button @click="resetSearch">éç½®</el-button> |
| | |
| | | |
| | | <!-- æ¥ä»·å表 --> |
| | | <el-table |
| | | :data="filteredList" |
| | | style="width: 100%" |
| | | v-loading="loading" |
| | | border |
| | | stripe |
| | | height="calc(100vh - 22em)" |
| | | :data="filteredList" |
| | | style="width: 100%" |
| | | v-loading="loading" |
| | | border |
| | | stripe |
| | | height="calc(100vh - 22em)" |
| | | > |
| | | <el-table-column align="center" label="åºå·" type="index" width="60" /> |
| | | <el-table-column prop="quotationNo" label="æ¥ä»·åå·" /> |
| | | <el-table-column prop="customer" label="客æ·åç§°" /> |
| | | <el-table-column prop="salesperson" label="ä¸å¡å" width="100" /> |
| | | <el-table-column prop="quotationDate" label="æ¥ä»·æ¥æ" width="120" /> |
| | | <el-table-column prop="validDate" label="æææè³" width="120" /> |
| | | <el-table-column align="center" label="åºå·" type="index" width="60"/> |
| | | <el-table-column prop="quotationNo" label="æ¥ä»·åå·"/> |
| | | <el-table-column prop="customer" label="客æ·åç§°"/> |
| | | <el-table-column prop="salesperson" label="ä¸å¡å" width="100"/> |
| | | <el-table-column prop="quotationDate" label="æ¥ä»·æ¥æ" width="120"/> |
| | | <el-table-column prop="validDate" label="æææè³" width="120"/> |
| | | <el-table-column prop="status" label="审æ¹ç¶æ" width="120" align="center"> |
| | | <template #default="{ row }"> |
| | | <el-tag :type="getStatusType(row.status)" disable-transitions> |
| | |
| | | ¥{{ scope.row.totalAmount.toFixed(2) }} |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="æä½" width="200" fixed="right" align="center"> |
| | | <el-table-column label="æä½" width="250" fixed="right" align="center"> |
| | | <template #default="scope"> |
| | | <el-button link type="primary" @click="handleEdit(scope.row)" :disabled="!['å¾
审æ¹','æç»'].includes(scope.row.status)">ç¼è¾</el-button> |
| | | <el-button link type="primary" @click="handleEdit(scope.row)" |
| | | :disabled="!['å¾
审æ¹','æç»'].includes(scope.row.status)">ç¼è¾ |
| | | </el-button> |
| | | <el-button link type="primary" @click="handleView(scope.row)" style="color: #67C23A">æ¥ç</el-button> |
| | | <el-button link type="danger" @click="handleDelete(scope.row)">å é¤</el-button> |
| | | <el-button link type="primary" @click="handleImportDetail(scope.row)">导å
¥è¯¦æ
</el-button> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | |
| | | <!-- å页 --> |
| | | <pagination |
| | | :total="pagination.total" |
| | | layout="total, sizes, prev, pager, next, jumper" |
| | | :page="pagination.currentPage" |
| | | :limit="pagination.pageSize" |
| | | @pagination="handleCurrentChange" |
| | | :total="pagination.total" |
| | | layout="total, sizes, prev, pager, next, jumper" |
| | | :page="pagination.currentPage" |
| | | :limit="pagination.pageSize" |
| | | @pagination="handleCurrentChange" |
| | | /> |
| | | </el-card> |
| | | |
| | | <!-- æ°å¢/ç¼è¾å¯¹è¯æ¡ --> |
| | | <FormDialog v-model="dialogVisible" :title="dialogTitle" width="85%" :close-on-click-modal="false" @close="dialogVisible = false" @confirm="handleSubmit" @cancel="dialogVisible = false"> |
| | | <FormDialog v-model="dialogVisible" :title="dialogTitle" width="85%" :close-on-click-modal="false" |
| | | @close="dialogVisible = false" @confirm="handleSubmit" @cancel="dialogVisible = false"> |
| | | <div class="quotation-form-container"> |
| | | <el-form :model="form" :rules="rules" ref="formRef" label-width="120px" class="quotation-form"> |
| | | <!-- åºæ¬ä¿¡æ¯ --> |
| | | <el-card class="form-card" shadow="hover"> |
| | | <template #header> |
| | | <div class="card-header-wrapper"> |
| | | <el-icon class="card-icon"><Document /></el-icon> |
| | | <span class="card-title">åºæ¬ä¿¡æ¯</span> |
| | | </div> |
| | | </template> |
| | | <div class="form-content"> |
| | | <el-row :gutter="24"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="客æ·åç§°" prop="customer"> |
| | | <el-select v-model="form.customer" placeholder="è¯·éæ©å®¢æ·" style="width: 100%" @change="handleCustomerChange" clearable> |
| | | <el-option v-for="item in customerOption" :key="item.id" :label="item.customerName" :value="item.customerName"> |
| | | {{ |
| | | item.customerName + "ââ" + item.taxpayerIdentificationNumber |
| | | }} |
| | | </el-option> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="ä¸å¡å" prop="salesperson"> |
| | | <el-select v-model="form.salesperson" placeholder="è¯·éæ©ä¸å¡å" style="width: 100%" clearable> |
| | | <el-option v-for="item in userList" :key="item.nickName" :label="item.nickName" |
| | | :value="item.nickName" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="24"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="æ¥ä»·æ¥æ" prop="quotationDate"> |
| | | <el-date-picker |
| | | v-model="form.quotationDate" |
| | | type="date" |
| | | placeholder="éæ©æ¥ä»·æ¥æ" |
| | | style="width: 100%" |
| | | format="YYYY-MM-DD" |
| | | value-format="YYYY-MM-DD" |
| | | clearable |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="æææè³" prop="validDate"> |
| | | <el-date-picker |
| | | v-model="form.validDate" |
| | | type="date" |
| | | placeholder="éæ©æææ" |
| | | style="width: 100%" |
| | | format="YYYY-MM-DD" |
| | | value-format="YYYY-MM-DD" |
| | | clearable |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="24"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="仿¬¾æ¹å¼" prop="paymentMethod"> |
| | | <el-input v-model="form.paymentMethod" placeholder="请è¾å
¥ä»æ¬¾æ¹å¼" clearable /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | </div> |
| | | </el-card> |
| | | |
| | | <!-- 审æ¹äººä¿¡æ¯ --> |
| | | <el-card class="form-card" shadow="hover"> |
| | | <template #header> |
| | | <div class="card-header-wrapper"> |
| | | <el-icon class="card-icon"><UserFilled /></el-icon> |
| | | <span class="card-title">审æ¹äººéæ©</span> |
| | | <el-button type="primary" size="small" @click="addApproverNode" class="header-btn"> |
| | | <el-icon><Plus /></el-icon> |
| | | æ°å¢èç¹ |
| | | </el-button> |
| | | </div> |
| | | </template> |
| | | <div class="form-content"> |
| | | <el-row> |
| | | <el-col :span="24"> |
| | | <el-form-item> |
| | | <div class="approver-nodes-container"> |
| | | <div |
| | | v-for="(node, index) in approverNodes" |
| | | :key="node.id" |
| | | class="approver-node-item" |
| | | > |
| | | <div class="approver-node-label"> |
| | | <span class="node-step">{{ index + 1 }}</span> |
| | | <span class="node-text">审æ¹äºº</span> |
| | | <el-icon class="arrow-icon"><ArrowRight /></el-icon> |
| | | </div> |
| | | <el-select |
| | | v-model="node.userId" |
| | | placeholder="éæ©äººå" |
| | | class="approver-select" |
| | | <!-- åºæ¬ä¿¡æ¯ --> |
| | | <el-card class="form-card" shadow="hover"> |
| | | <template #header> |
| | | <div class="card-header-wrapper"> |
| | | <el-icon class="card-icon"> |
| | | <Document/> |
| | | </el-icon> |
| | | <span class="card-title">åºæ¬ä¿¡æ¯</span> |
| | | </div> |
| | | </template> |
| | | <div class="form-content"> |
| | | <el-row :gutter="24"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="客æ·åç§°" prop="customer"> |
| | | <el-select v-model="form.customer" placeholder="è¯·éæ©å®¢æ·" style="width: 100%" |
| | | @change="handleCustomerChange" clearable> |
| | | <el-option v-for="item in customerOption" :key="item.id" :label="item.customerName" |
| | | :value="item.customerName"> |
| | | {{ |
| | | item.customerName + "ââ" + item.taxpayerIdentificationNumber |
| | | }} |
| | | </el-option> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="ä¸å¡å" prop="salesperson"> |
| | | <el-select v-model="form.salesperson" placeholder="è¯·éæ©ä¸å¡å" style="width: 100%" clearable> |
| | | <el-option v-for="item in userList" :key="item.nickName" :label="item.nickName" |
| | | :value="item.nickName"/> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="24"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="æ¥ä»·æ¥æ" prop="quotationDate"> |
| | | <el-date-picker |
| | | v-model="form.quotationDate" |
| | | type="date" |
| | | placeholder="éæ©æ¥ä»·æ¥æ" |
| | | style="width: 100%" |
| | | format="YYYY-MM-DD" |
| | | value-format="YYYY-MM-DD" |
| | | clearable |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="æææè³" prop="validDate"> |
| | | <el-date-picker |
| | | v-model="form.validDate" |
| | | type="date" |
| | | placeholder="éæ©æææ" |
| | | style="width: 100%" |
| | | format="YYYY-MM-DD" |
| | | value-format="YYYY-MM-DD" |
| | | clearable |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="24"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="仿¬¾æ¹å¼" prop="paymentMethod"> |
| | | <el-input v-model="form.paymentMethod" placeholder="请è¾å
¥ä»æ¬¾æ¹å¼" clearable/> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | </div> |
| | | </el-card> |
| | | |
| | | <!-- 审æ¹äººä¿¡æ¯ --> |
| | | <el-card class="form-card" shadow="hover"> |
| | | <template #header> |
| | | <div class="card-header-wrapper"> |
| | | <el-icon class="card-icon"> |
| | | <UserFilled/> |
| | | </el-icon> |
| | | <span class="card-title">审æ¹äººéæ©</span> |
| | | <el-button type="primary" size="small" @click="addApproverNode" class="header-btn"> |
| | | <el-icon> |
| | | <Plus/> |
| | | </el-icon> |
| | | æ°å¢èç¹ |
| | | </el-button> |
| | | </div> |
| | | </template> |
| | | <div class="form-content"> |
| | | <el-row> |
| | | <el-col :span="24"> |
| | | <el-form-item> |
| | | <div class="approver-nodes-container"> |
| | | <div |
| | | v-for="(node, index) in approverNodes" |
| | | :key="node.id" |
| | | class="approver-node-item" |
| | | > |
| | | <div class="approver-node-label"> |
| | | <span class="node-step">{{ index + 1 }}</span> |
| | | <span class="node-text">审æ¹äºº</span> |
| | | <el-icon class="arrow-icon"> |
| | | <ArrowRight/> |
| | | </el-icon> |
| | | </div> |
| | | <el-select |
| | | v-model="node.userId" |
| | | placeholder="éæ©äººå" |
| | | class="approver-select" |
| | | clearable |
| | | > |
| | | <el-option |
| | | v-for="user in userList" |
| | | :key="user.userId" |
| | | :label="user.nickName" |
| | | :value="user.userId" |
| | | /> |
| | | </el-select> |
| | | <el-button |
| | | type="danger" |
| | | size="small" |
| | | :icon="Delete" |
| | | @click="removeApproverNode(index)" |
| | | v-if="approverNodes.length > 1" |
| | | class="remove-btn" |
| | | >å é¤ |
| | | </el-button> |
| | | </div> |
| | | </div> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | </div> |
| | | </el-card> |
| | | |
| | | <!-- 产åä¿¡æ¯ --> |
| | | <el-card class="form-card" shadow="hover"> |
| | | <template #header> |
| | | <div class="card-header-wrapper"> |
| | | <el-icon class="card-icon"> |
| | | <Box/> |
| | | </el-icon> |
| | | <span class="card-title">产åä¿¡æ¯</span> |
| | | <el-button type="primary" size="small" @click="addProduct" class="header-btn"> |
| | | <el-icon> |
| | | <Plus/> |
| | | </el-icon> |
| | | æ·»å 产å |
| | | </el-button> |
| | | </div> |
| | | </template> |
| | | <div class="form-content"> |
| | | <el-table :data="form.products" border style="width: 100%" class="product-table" |
| | | v-if="form.products.length > 0"> |
| | | <el-table-column prop="product" label="产ååç§°" width="200"> |
| | | <template #default="scope"> |
| | | <el-form-item :prop="`products.${scope.$index}.productId`" class="product-table-form-item"> |
| | | <el-tree-select |
| | | v-model="scope.row.productId" |
| | | placeholder="è¯·éæ©" |
| | | clearable |
| | | check-strictly |
| | | @change="getModels($event, scope.row)" |
| | | :data="productOptions" |
| | | :render-after-expand="false" |
| | | style="width: 100%" |
| | | /> |
| | | </el-form-item> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="specification" label="è§æ ¼åå·" width="200"> |
| | | <template #default="scope"> |
| | | <el-form-item :prop="`products.${scope.$index}.specificationId`" class="product-table-form-item"> |
| | | <el-select |
| | | v-model="scope.row.specificationId" |
| | | placeholder="è¯·éæ©" |
| | | clearable |
| | | @change="getProductModel($event, scope.row)" |
| | | style="width: 100%" |
| | | > |
| | | <el-option |
| | | v-for="user in userList" |
| | | :key="user.userId" |
| | | :label="user.nickName" |
| | | :value="user.userId" |
| | | v-for="item in scope.row.modelOptions || []" |
| | | :key="item.id" |
| | | :label="item.model" |
| | | :value="item.id" |
| | | /> |
| | | </el-select> |
| | | <el-button |
| | | type="danger" |
| | | size="small" |
| | | :icon="Delete" |
| | | @click="removeApproverNode(index)" |
| | | v-if="approverNodes.length > 1" |
| | | class="remove-btn" |
| | | >å é¤</el-button> |
| | | </div> |
| | | </div> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | </div> |
| | | </el-card> |
| | | |
| | | <!-- 产åä¿¡æ¯ --> |
| | | <el-card class="form-card" shadow="hover"> |
| | | <template #header> |
| | | <div class="card-header-wrapper"> |
| | | <el-icon class="card-icon"><Box /></el-icon> |
| | | <span class="card-title">产åä¿¡æ¯</span> |
| | | <el-button type="primary" size="small" @click="addProduct" class="header-btn"> |
| | | <el-icon><Plus /></el-icon> |
| | | æ·»å 产å |
| | | </el-button> |
| | | </el-form-item> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="unit" label="åä½"> |
| | | <template #default="scope"> |
| | | <el-form-item :prop="`products.${scope.$index}.unit`" class="product-table-form-item"> |
| | | <el-input v-model="scope.row.unit" placeholder="åä½" clearable/> |
| | | </el-form-item> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="unitPrice" label="åä»·"> |
| | | <template #default="scope"> |
| | | <el-form-item :prop="`products.${scope.$index}.unitPrice`" class="product-table-form-item"> |
| | | <el-input-number v-model="scope.row.unitPrice" :min="0" :precision="2" style="width: 100%"/> |
| | | </el-form-item> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="æä½" width="80" align="center"> |
| | | <template #default="scope"> |
| | | <el-button link type="danger" @click="removeProduct(scope.$index)">å é¤</el-button> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | <el-empty v-else description="ææ äº§åï¼è¯·ç¹å»æ·»å 产å" :image-size="80"/> |
| | | </div> |
| | | </template> |
| | | <div class="form-content"> |
| | | <el-table :data="form.products" border style="width: 100%" class="product-table" v-if="form.products.length > 0"> |
| | | <el-table-column prop="product" label="产ååç§°" width="200"> |
| | | <template #default="scope"> |
| | | <el-form-item :prop="`products.${scope.$index}.productId`" class="product-table-form-item"> |
| | | <el-tree-select |
| | | v-model="scope.row.productId" |
| | | placeholder="è¯·éæ©" |
| | | clearable |
| | | check-strictly |
| | | @change="getModels($event, scope.row)" |
| | | :data="productOptions" |
| | | :render-after-expand="false" |
| | | style="width: 100%" |
| | | /> |
| | | </el-form-item> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="specification" label="è§æ ¼åå·" width="200"> |
| | | <template #default="scope"> |
| | | <el-form-item :prop="`products.${scope.$index}.specificationId`" class="product-table-form-item"> |
| | | <el-select |
| | | v-model="scope.row.specificationId" |
| | | placeholder="è¯·éæ©" |
| | | clearable |
| | | @change="getProductModel($event, scope.row)" |
| | | style="width: 100%" |
| | | > |
| | | <el-option |
| | | v-for="item in scope.row.modelOptions || []" |
| | | :key="item.id" |
| | | :label="item.model" |
| | | :value="item.id" |
| | | /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="unit" label="åä½"> |
| | | <template #default="scope"> |
| | | <el-form-item :prop="`products.${scope.$index}.unit`" class="product-table-form-item"> |
| | | <el-input v-model="scope.row.unit" placeholder="åä½" clearable/> |
| | | </el-form-item> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="unitPrice" label="åä»·"> |
| | | <template #default="scope"> |
| | | <el-form-item :prop="`products.${scope.$index}.unitPrice`" class="product-table-form-item"> |
| | | <el-input-number v-model="scope.row.unitPrice" :min="0" :precision="2" style="width: 100%" /> |
| | | </el-form-item> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="æä½" width="80" align="center"> |
| | | <template #default="scope"> |
| | | <el-button link type="danger" @click="removeProduct(scope.$index)">å é¤</el-button> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | <el-empty v-else description="ææ äº§åï¼è¯·ç¹å»æ·»å 产å" :image-size="80" /> |
| | | </div> |
| | | </el-card> |
| | | </el-card> |
| | | |
| | | <!-- 夿³¨ä¿¡æ¯ --> |
| | | <el-card class="form-card" shadow="hover"> |
| | | <template #header> |
| | | <div class="card-header-wrapper"> |
| | | <el-icon class="card-icon"><EditPen /></el-icon> |
| | | <span class="card-title">夿³¨ä¿¡æ¯</span> |
| | | <!-- 夿³¨ä¿¡æ¯ --> |
| | | <el-card class="form-card" shadow="hover"> |
| | | <template #header> |
| | | <div class="card-header-wrapper"> |
| | | <el-icon class="card-icon"> |
| | | <EditPen/> |
| | | </el-icon> |
| | | <span class="card-title">夿³¨ä¿¡æ¯</span> |
| | | </div> |
| | | </template> |
| | | <div class="form-content"> |
| | | <el-form-item label="夿³¨" prop="remark"> |
| | | <el-input |
| | | type="textarea" |
| | | v-model="form.remark" |
| | | placeholder="请è¾å
¥å¤æ³¨ä¿¡æ¯ï¼éå¡«ï¼" |
| | | :rows="4" |
| | | maxlength="500" |
| | | show-word-limit |
| | | ></el-input> |
| | | </el-form-item> |
| | | </div> |
| | | </template> |
| | | <div class="form-content"> |
| | | <el-form-item label="夿³¨" prop="remark"> |
| | | <el-input |
| | | type="textarea" |
| | | v-model="form.remark" |
| | | placeholder="请è¾å
¥å¤æ³¨ä¿¡æ¯ï¼éå¡«ï¼" |
| | | :rows="4" |
| | | maxlength="500" |
| | | show-word-limit |
| | | ></el-input> |
| | | </el-form-item> |
| | | </div> |
| | | </el-card> |
| | | </el-form> |
| | | </el-card> |
| | | </el-form> |
| | | </div> |
| | | </FormDialog> |
| | | |
| | | <FormDialog v-model="importDialogVisible" title="导å
¥æ¥ä»·å" width="85%" :close-on-click-modal="false" @close="importDialogVisible = false" @confirm="handleImportSubmit" @cancel="importDialogVisible = false"> |
| | | <FormDialog v-model="importDialogVisible" title="导å
¥æ¥ä»·å" width="85%" :close-on-click-modal="false" |
| | | @close="importDialogVisible = false" @confirm="handleImportSubmit" |
| | | @cancel="importDialogVisible = false"> |
| | | <!-- 审æ¹äººä¿¡æ¯ --> |
| | | <el-card class="form-card" shadow="hover"> |
| | | <template #header> |
| | | <div class="card-header-wrapper"> |
| | | <el-icon class="card-icon"><UserFilled /></el-icon> |
| | | <span class="card-title">审æ¹äººéæ©</span> |
| | | <el-button type="primary" size="small" @click="addImportApproverNode" class="header-btn"> |
| | | <el-icon><Plus /></el-icon> |
| | | æ°å¢èç¹ |
| | | </el-button> |
| | | </div> |
| | | </template> |
| | | <div class="form-content"> |
| | | <el-row> |
| | | <el-col :span="24"> |
| | | <el-form-item> |
| | | <div class="approver-nodes-container"> |
| | | <div |
| | | <el-card class="form-card" shadow="hover"> |
| | | <template #header> |
| | | <div class="card-header-wrapper"> |
| | | <el-icon class="card-icon"> |
| | | <UserFilled/> |
| | | </el-icon> |
| | | <span class="card-title">审æ¹äººéæ©</span> |
| | | <el-button type="primary" size="small" @click="addImportApproverNode" class="header-btn"> |
| | | <el-icon> |
| | | <Plus/> |
| | | </el-icon> |
| | | æ°å¢èç¹ |
| | | </el-button> |
| | | </div> |
| | | </template> |
| | | <div class="form-content"> |
| | | <el-row> |
| | | <el-col :span="24"> |
| | | <el-form-item> |
| | | <div class="approver-nodes-container"> |
| | | <div |
| | | v-for="(node, index) in importApproverNodes" |
| | | :key="node.id" |
| | | class="approver-node-item" |
| | | > |
| | | <div class="approver-node-label"> |
| | | <span class="node-step">{{ index + 1 }}</span> |
| | | <span class="node-text">审æ¹äºº</span> |
| | | <el-icon class="arrow-icon"><ArrowRight /></el-icon> |
| | | </div> |
| | | <el-select |
| | | > |
| | | <div class="approver-node-label"> |
| | | <span class="node-step">{{ index + 1 }}</span> |
| | | <span class="node-text">审æ¹äºº</span> |
| | | <el-icon class="arrow-icon"> |
| | | <ArrowRight/> |
| | | </el-icon> |
| | | </div> |
| | | <el-select |
| | | v-model="node.userId" |
| | | placeholder="éæ©äººå" |
| | | class="approver-select" |
| | | clearable |
| | | > |
| | | <el-option |
| | | > |
| | | <el-option |
| | | v-for="user in userList" |
| | | :key="user.userId" |
| | | :label="user.nickName" |
| | | :value="user.userId" |
| | | /> |
| | | </el-select> |
| | | <el-button |
| | | /> |
| | | </el-select> |
| | | <el-button |
| | | type="danger" |
| | | size="small" |
| | | :icon="Delete" |
| | | @click="removeImportApproverNode(index)" |
| | | v-if="importApproverNodes.length > 1" |
| | | class="remove-btn" |
| | | >å é¤</el-button> |
| | | </div> |
| | | >å é¤ |
| | | </el-button> |
| | | </div> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | </div> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | </div> |
| | | </el-card> |
| | | <el-card class="form-card" shadow="hover"> |
| | | <template #header> |
| | | <div class="card-header-wrapper"> |
| | | <el-icon class="card-icon"> |
| | | <Paperclip/> |
| | | </el-icon> |
| | | <span class="card-title">éä»¶ææ</span> |
| | | </div> |
| | | </el-card> |
| | | <el-card class="form-card" shadow="hover"> |
| | | <template #header> |
| | | <div class="card-header-wrapper"> |
| | | <el-icon class="card-icon"><Paperclip /></el-icon> |
| | | <span class="card-title">éä»¶ææ</span> |
| | | </div> |
| | | </template> |
| | | <div class="form-content"> |
| | | <el-form-item label="éä»¶ææ" prop="files"> |
| | | <el-upload |
| | | v-model:file-list="importFileList" |
| | | :limit="1" |
| | | ref="fileUpload" |
| | | :auto-upload="false" |
| | | :on-change="handleFileChange" |
| | | :on-exceed="handleExceed" |
| | | :on-remove="handleRemove" |
| | | :on-preview="handlePreview" |
| | | :show-file-list="true" |
| | | > |
| | | <el-button type="primary">ä¸ä¼ </el-button> |
| | | <template #file="{ file }"> |
| | | <div style="display:flex; align-items:center; gap: 10px; width: 100%;"> |
| | | </template> |
| | | <div class="form-content"> |
| | | <el-button type="primary" @click="downloadImportTemplate" style="margin-bottom: 20px">ä¸è½½æ¨¡æ¿æä»¶</el-button> |
| | | <el-form-item label="éä»¶ææ" prop="files"> |
| | | <el-upload |
| | | v-model:file-list="importFileList" |
| | | :limit="1" |
| | | ref="fileUpload" |
| | | :auto-upload="false" |
| | | :on-change="handleFileChange" |
| | | :on-exceed="handleExceed" |
| | | :on-remove="handleRemove" |
| | | :on-preview="handlePreview" |
| | | :show-file-list="true" |
| | | > |
| | | <el-button type="primary">ä¸ä¼ </el-button> |
| | | <template #file="{ file }"> |
| | | <div style="display:flex; align-items:center; gap: 10px; width: 100%;"> |
| | | <span style="flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;"> |
| | | {{ file.name }} |
| | | </span> |
| | | <!-- <div style="display:flex; align-items:center; gap: 6px;">--> |
| | | <!-- <el-button link type="success" :icon="Download" @click="handleDownload(file)" />--> |
| | | <!-- <el-button link type="primary" :icon="View" @click="handlePreview(file)" />--> |
| | | <!-- <el-button link type="danger" :icon="Delete" @click="triggerRemoveFile(file)" />--> |
| | | <!-- </div>--> |
| | | </div> |
| | | </template> |
| | | <template #tip> |
| | | <div class="el-upload__tip"> |
| | | æ¯æææ¡£ï¼xls, xlsxï¼æ ¼å¼ |
| | | </div> |
| | | </template> |
| | | </el-upload> |
| | | </el-form-item> |
| | | </div> |
| | | </el-card> |
| | | <div style="display:flex; align-items:center; gap: 6px;"> |
| | | <!-- <el-button link type="success" :icon="Download" @click="handleDownload(file)" />--> |
| | | <!-- <el-button link type="primary" :icon="View" @click="handlePreview(file)" />--> |
| | | <!-- <el-button link type="danger" :icon="Delete" @click="triggerRemoveFile(file)" />--> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | <template #tip> |
| | | <div class="el-upload__tip"> |
| | | æ¯æææ¡£ï¼xls, xlsxï¼æ ¼å¼ |
| | | </div> |
| | | </template> |
| | | </el-upload> |
| | | </el-form-item> |
| | | </div> |
| | | </el-card> |
| | | </FormDialog> |
| | | |
| | | <!-- æ¥ç详æ
å¯¹è¯æ¡ --> |
| | |
| | | <el-descriptions-item label="æ¥ä»·æ¥æ">{{ currentQuotation.quotationDate }}</el-descriptions-item> |
| | | <el-descriptions-item label="æææè³">{{ currentQuotation.validDate }}</el-descriptions-item> |
| | | <el-descriptions-item label="仿¬¾æ¹å¼">{{ currentQuotation.paymentMethod }}</el-descriptions-item> |
| | | <!-- <el-descriptions-item label="æ¥ä»·ç¶æ">--> |
| | | <!-- <el-tag :type="getStatusType(currentQuotation.status)">{{ currentQuotation.status }}</el-tag>--> |
| | | <!-- </el-descriptions-item>--> |
| | | <!-- <el-descriptions-item label="æ¥ä»·ç¶æ">--> |
| | | <!-- <el-tag :type="getStatusType(currentQuotation.status)">{{ currentQuotation.status }}</el-tag>--> |
| | | <!-- </el-descriptions-item>--> |
| | | <el-descriptions-item label="æ¥ä»·æ»é¢" :span="2"> |
| | | <span style="font-size: 18px; color: #e6a23c; font-weight: bold;">Â¥{{ currentQuotation.totalAmount?.toFixed(2) }}</span> |
| | | <span style="font-size: 18px; color: #e6a23c; font-weight: bold;">Â¥{{ |
| | | currentQuotation.totalAmount?.toFixed(2) |
| | | }}</span> |
| | | </el-descriptions-item> |
| | | </el-descriptions> |
| | | |
| | | |
| | | <div style="margin: 20px 0;"> |
| | | <h4>产åæç»</h4> |
| | | <el-table :data="currentQuotation.products" border style="width: 100%"> |
| | | <el-table-column prop="product" label="产ååç§°" /> |
| | | <el-table-column prop="specification" label="è§æ ¼åå·" /> |
| | | <el-table-column prop="unit" label="åä½" /> |
| | | <el-table-column prop="product" label="产ååç§°"/> |
| | | <el-table-column prop="specification" label="è§æ ¼åå·"/> |
| | | <el-table-column prop="unit" label="åä½"/> |
| | | <el-table-column prop="unitPrice" label="åä»·"> |
| | | <template #default="scope"> |
| | | ¥{{ scope.row.unitPrice.toFixed(2) }} |
| | |
| | | <p>{{ currentQuotation.remark }}</p> |
| | | </div> |
| | | </el-dialog> |
| | | <ImportQuotationDetail v-if="showDetail" v-model:showModal="showDetail" :quotationId="currentQuotation.id" /> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, reactive, computed, onMounted, markRaw, shallowRef, getCurrentInstance } from 'vue' |
| | | import { ElMessage, ElMessageBox } from 'element-plus' |
| | | import { Search, Document, UserFilled, Box, EditPen, Plus, ArrowRight, Delete, Download, View } from '@element-plus/icons-vue' |
| | | import {ref, reactive, computed, onMounted, markRaw, shallowRef, getCurrentInstance} from 'vue' |
| | | import {ElMessage, ElMessageBox} from 'element-plus' |
| | | import { |
| | | Search, |
| | | Document, |
| | | UserFilled, |
| | | Box, |
| | | EditPen, |
| | | Plus, |
| | | ArrowRight, |
| | | Delete, |
| | | } from '@element-plus/icons-vue' |
| | | import Pagination from '@/components/PIMTable/Pagination.vue' |
| | | import FormDialog from '@/components/Dialog/FormDialog.vue' |
| | | import {getQuotationList,addQuotation,updateQuotation,deleteQuotation, importQuotation} from '@/api/salesManagement/salesQuotation.js' |
| | | import ImportQuotationDetail from '@/views/salesManagement/salesQuotation/ImportQuotationDetail.vue' |
| | | import { |
| | | getQuotationList, |
| | | addQuotation, |
| | | updateQuotation, |
| | | deleteQuotation, |
| | | importQuotation |
| | | } from '@/api/salesManagement/salesQuotation.js' |
| | | import {userListNoPage} from "@/api/system/user.js"; |
| | | import {customerList} from "@/api/salesManagement/salesLedger.js"; |
| | | import {modelList, productTreeList} from "@/api/basicData/product.js"; |
| | | |
| | | const { proxy } = getCurrentInstance(); |
| | | const {proxy} = getCurrentInstance(); |
| | | |
| | | // ååºå¼æ°æ® |
| | | const loading = ref(false) |
| | |
| | | const viewDialogVisible = ref(false) |
| | | const importFileList = ref([]) |
| | | const importApproverNodes = ref([ |
| | | { id: 1, userId: null } |
| | | {id: 1, userId: null} |
| | | ]) |
| | | let nextImportApproverId = 2 |
| | | |
| | |
| | | }) |
| | | |
| | | const baseRules = { |
| | | customer: [{ required: true, message: 'è¯·éæ©å®¢æ·', trigger: 'change' }], |
| | | salesperson: [{ required: true, message: 'è¯·éæ©ä¸å¡å', trigger: 'change' }], |
| | | quotationDate: [{ required: true, message: 'è¯·éæ©æ¥ä»·æ¥æ', trigger: 'change' }], |
| | | validDate: [{ required: true, message: 'è¯·éæ©æææ', trigger: 'change' }], |
| | | paymentMethod: [{ required: true, message: '请è¾å
¥ä»æ¬¾æ¹å¼', trigger: 'blur' }] |
| | | customer: [{required: true, message: 'è¯·éæ©å®¢æ·', trigger: 'change'}], |
| | | salesperson: [{required: true, message: 'è¯·éæ©ä¸å¡å', trigger: 'change'}], |
| | | quotationDate: [{required: true, message: 'è¯·éæ©æ¥ä»·æ¥æ', trigger: 'change'}], |
| | | validDate: [{required: true, message: 'è¯·éæ©æææ', trigger: 'change'}], |
| | | paymentMethod: [{required: true, message: '请è¾å
¥ä»æ¬¾æ¹å¼', trigger: 'blur'}] |
| | | } |
| | | |
| | | const productRowRules = { |
| | | productId: [{ required: true, message: 'è¯·éæ©äº§ååç§°', trigger: 'change' }], |
| | | specificationId: [{ required: true, message: 'è¯·éæ©è§æ ¼åå·', trigger: 'change' }], |
| | | unit: [{ required: true, message: '请填ååä½', trigger: 'blur' }], |
| | | unitPrice: [{ required: true, message: '请填ååä»·', trigger: 'change' }] |
| | | productId: [{required: true, message: 'è¯·éæ©äº§ååç§°', trigger: 'change'}], |
| | | specificationId: [{required: true, message: 'è¯·éæ©è§æ ¼åå·', trigger: 'change'}], |
| | | unit: [{required: true, message: '请填ååä½', trigger: 'blur'}], |
| | | unitPrice: [{required: true, message: '请填ååä»·', trigger: 'change'}] |
| | | } |
| | | const rules = computed(() => { |
| | | const r = { ...baseRules } |
| | | const r = {...baseRules} |
| | | ;(form.products || []).forEach((_, i) => { |
| | | r[`products.${i}.productId`] = productRowRules.productId |
| | | r[`products.${i}.specificationId`] = productRowRules.specificationId |
| | |
| | | |
| | | // 审æ¹äººèç¹ç¸å
³ |
| | | const approverNodes = ref([ |
| | | { id: 1, userId: null } |
| | | {id: 1, userId: null} |
| | | ]) |
| | | let nextApproverId = 2 |
| | | |
| | | const isEdit = ref(false) |
| | | const showDetail = ref(false) |
| | | const editId = ref(null) |
| | | const currentQuotation = ref({}) |
| | | const formRef = ref() |
| | | |
| | | // æ·»å 审æ¹äººèç¹ |
| | | function addApproverNode() { |
| | | approverNodes.value.push({ id: nextApproverId++, userId: null }) |
| | | approverNodes.value.push({id: nextApproverId++, userId: null}) |
| | | } |
| | | |
| | | // å é¤å®¡æ¹äººèç¹ |
| | |
| | | |
| | | // 导å
¥å¼¹çªå®¡æ¹äººèç¹ç¸å
³ |
| | | function addImportApproverNode() { |
| | | importApproverNodes.value.push({ id: nextImportApproverId++, userId: null }) |
| | | importApproverNodes.value.push({id: nextImportApproverId++, userId: null}) |
| | | } |
| | | |
| | | function removeImportApproverNode(index) { |
| | |
| | | |
| | | const formData = new FormData() |
| | | formData.append('file', rawFile) |
| | | |
| | | |
| | | // å®¡æ ¸äºº IDsï¼ä»¥éå·åå² |
| | | const approveUserIds = importApproverNodes.value.map(node => node.userId).join(',') |
| | | formData.append('approveUserIdsJson', approveUserIds) |
| | |
| | | importFileList.value = list |
| | | }; |
| | | |
| | | // å¤çæä»¶ç§»é¤ |
| | | // å¤çæä»¶ç§»é¤ |
| | | function triggerRemoveFile(file) { |
| | | fileUpload.value?.handleRemove?.(file) || proxy.$refs.fileUpload?.handleRemove?.(file); |
| | | } |
| | |
| | | importFileList.value = [] |
| | | |
| | | // â
æ¸
空â导å
¥ç¨âç审æ¹äºº |
| | | importApproverNodes.value = [{ id: 1, userId: null }] |
| | | importApproverNodes.value = [{id: 1, userId: null}] |
| | | nextImportApproverId = 2 |
| | | |
| | | let userLists = await userListNoPage(); |
| | |
| | | isEdit.value = false |
| | | resetForm() |
| | | // é置审æ¹äººèç¹ |
| | | approverNodes.value = [{ id: 1, userId: null }] |
| | | approverNodes.value = [{id: 1, userId: null}] |
| | | nextApproverId = 2 |
| | | dialogVisible.value = true |
| | | let userLists = await userListNoPage(); |
| | | // åªå¤å¶éè¦çåæ®µï¼é¿å
å°ç»ä»¶å¼ç¨æ¾å
¥ååºå¼å¯¹è±¡ |
| | | userList.value = (userLists.data || []).map(item => ({ |
| | | let userLists = await userListNoPage(); |
| | | // åªå¤å¶éè¦çåæ®µï¼é¿å
å°ç»ä»¶å¼ç¨æ¾å
¥ååºå¼å¯¹è±¡ |
| | | userList.value = (userLists.data || []).map(item => ({ |
| | | userId: item.userId, |
| | | nickName: item.nickName || '', |
| | | userName: item.userName || '' |
| | | })); |
| | | getProductOptions(); |
| | | customerList().then((res) => { |
| | | // åªå¤å¶éè¦çåæ®µï¼é¿å
å°ç»ä»¶å¼ç¨æ¾å
¥ååºå¼å¯¹è±¡ |
| | | customerOption.value = (Array.isArray(res) ? res : []).map(item => ({ |
| | | getProductOptions(); |
| | | customerList().then((res) => { |
| | | // åªå¤å¶éè¦çåæ®µï¼é¿å
å°ç»ä»¶å¼ç¨æ¾å
¥ååºå¼å¯¹è±¡ |
| | | customerOption.value = (Array.isArray(res) ? res : []).map(item => ({ |
| | | id: item.id, |
| | | customerName: item.customerName || '', |
| | | taxpayerIdentificationNumber: item.taxpayerIdentificationNumber || '' |
| | | })) |
| | | }); |
| | | }); |
| | | } |
| | | const getProductOptions = () => { |
| | | // è¿å Promiseï¼ä¾¿äºç¼è¾æ¶ await ç¡®ä¿è½åæ¾ |
| | | return productTreeList().then((res) => { |
| | | productOptions.value = convertIdToValue(res); |
| | | return productOptions.value |
| | | }); |
| | | // è¿å Promiseï¼ä¾¿äºç¼è¾æ¶ await ç¡®ä¿è½åæ¾ |
| | | return productTreeList().then((res) => { |
| | | productOptions.value = convertIdToValue(res); |
| | | return productOptions.value |
| | | }); |
| | | }; |
| | | |
| | | function convertIdToValue(data) { |
| | | 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; |
| | | 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 getModels = (value, row) => { |
| | | if (!row) return; |
| | | // 妿æ¸
ç©ºéæ©ï¼åæ¸
空ç¸å
³å段 |
| | | if (!value) { |
| | | row.productId = ''; |
| | | row.product = ''; |
| | | row.modelOptions = []; |
| | | row.specificationId = ''; |
| | | row.specification = ''; |
| | | row.unit = ''; |
| | | return; |
| | | } |
| | | // æ´æ° productIdï¼v-model å·²ç»èªå¨æ´æ°ï¼è¿éç¡®ä¿ä¸è´æ§ï¼ |
| | | row.productId = value; |
| | | // æ¾å°å¯¹åºç label å¹¶èµå¼ç» row.product |
| | | const label = findNodeById(productOptions.value, value); |
| | | if (label) { |
| | | row.product = label; |
| | | } |
| | | // è·åè§æ ¼åå·å表ï¼è®¾ç½®å°å½åè¡ç modelOptions |
| | | modelList({ id: value }).then((res) => { |
| | | row.modelOptions = res || []; |
| | | }); |
| | | if (!row) return; |
| | | // 妿æ¸
ç©ºéæ©ï¼åæ¸
空ç¸å
³å段 |
| | | if (!value) { |
| | | row.productId = ''; |
| | | row.product = ''; |
| | | row.modelOptions = []; |
| | | row.specificationId = ''; |
| | | row.specification = ''; |
| | | row.unit = ''; |
| | | return; |
| | | } |
| | | // æ´æ° productIdï¼v-model å·²ç»èªå¨æ´æ°ï¼è¿éç¡®ä¿ä¸è´æ§ï¼ |
| | | row.productId = value; |
| | | // æ¾å°å¯¹åºç label å¹¶èµå¼ç» row.product |
| | | const label = findNodeById(productOptions.value, value); |
| | | if (label) { |
| | | row.product = label; |
| | | } |
| | | // è·åè§æ ¼åå·å表ï¼è®¾ç½®å°å½åè¡ç modelOptions |
| | | modelList({id: value}).then((res) => { |
| | | row.modelOptions = res || []; |
| | | }); |
| | | }; |
| | | const getProductModel = (value, row) => { |
| | | if (!row) return; |
| | | // 妿æ¸
ç©ºéæ©ï¼åæ¸
空ç¸å
³å段 |
| | | if (!value) { |
| | | row.specificationId = ''; |
| | | row.specification = ''; |
| | | row.unit = ''; |
| | | return; |
| | | } |
| | | // æ´æ° specificationIdï¼v-model å·²ç»èªå¨æ´æ°ï¼è¿éç¡®ä¿ä¸è´æ§ï¼ |
| | | row.specificationId = value; |
| | | const modelOptions = row.modelOptions || []; |
| | | const index = modelOptions.findIndex((item) => item.id === value); |
| | | if (index !== -1) { |
| | | row.specification = modelOptions[index].model; |
| | | row.unit = modelOptions[index].unit; |
| | | } else { |
| | | row.specification = ''; |
| | | row.unit = ''; |
| | | } |
| | | if (!row) return; |
| | | // 妿æ¸
ç©ºéæ©ï¼åæ¸
空ç¸å
³å段 |
| | | if (!value) { |
| | | row.specificationId = ''; |
| | | row.specification = ''; |
| | | row.unit = ''; |
| | | return; |
| | | } |
| | | // æ´æ° specificationIdï¼v-model å·²ç»èªå¨æ´æ°ï¼è¿éç¡®ä¿ä¸è´æ§ï¼ |
| | | row.specificationId = value; |
| | | const modelOptions = row.modelOptions || []; |
| | | const index = modelOptions.findIndex((item) => item.id === value); |
| | | if (index !== -1) { |
| | | row.specification = modelOptions[index].model; |
| | | row.unit = modelOptions[index].unit; |
| | | } else { |
| | | row.specification = ''; |
| | | row.unit = ''; |
| | | } |
| | | }; |
| | | const findNodeById = (nodes, productId) => { |
| | | for (let i = 0; i < nodes.length; i++) { |
| | | if (nodes[i].value === productId) { |
| | | return nodes[i].label; // æ¾å°èç¹ï¼è¿å label |
| | | } |
| | | if (nodes[i].children && nodes[i].children.length > 0) { |
| | | const foundLabel = findNodeById(nodes[i].children, productId); |
| | | if (foundLabel) { |
| | | return foundLabel; // å¨åèç¹ä¸æ¾å°ï¼è¿å label |
| | | } |
| | | } |
| | | } |
| | | return null; // æ²¡ææ¾å°èç¹ï¼è¿ånull |
| | | for (let i = 0; i < nodes.length; i++) { |
| | | if (nodes[i].value === productId) { |
| | | return nodes[i].label; // æ¾å°èç¹ï¼è¿å label |
| | | } |
| | | if (nodes[i].children && nodes[i].children.length > 0) { |
| | | const foundLabel = findNodeById(nodes[i].children, productId); |
| | | if (foundLabel) { |
| | | return foundLabel; // å¨åèç¹ä¸æ¾å°ï¼è¿å label |
| | | } |
| | | } |
| | | } |
| | | return null; // æ²¡ææ¾å°èç¹ï¼è¿ånull |
| | | }; |
| | | const handleView = (row) => { |
| | | // åªå¤å¶éè¦çåæ®µï¼é¿å
å°ç»ä»¶å¼ç¨æ¾å
¥ååºå¼å¯¹è±¡ |
| | |
| | | const productName = product.product || product.productName || '' |
| | | // ä¼å
ç¨ productIdï¼å¦æåªæåç§°ï¼å°è¯åæ¥ id 以便æ éæ©å¨åæ¾ |
| | | const resolvedProductId = product.productId |
| | | ? Number(product.productId) |
| | | : findNodeIdByLabel(productOptions.value, productName) || '' |
| | | |
| | | ? Number(product.productId) |
| | | : findNodeIdByLabel(productOptions.value, productName) || '' |
| | | |
| | | // 妿æäº§åIDï¼å 载对åºçè§æ ¼åå·å表 |
| | | let modelOptions = []; |
| | | let resolvedSpecificationId = product.specificationId || ''; |
| | | |
| | | |
| | | if (resolvedProductId) { |
| | | try { |
| | | const res = await modelList({ id: resolvedProductId }); |
| | | const res = await modelList({id: resolvedProductId}); |
| | | modelOptions = res || []; |
| | | |
| | | |
| | | // 妿è¿åçæ°æ®æ²¡æ specificationIdï¼ä½æ specification åç§°ï¼æ ¹æ®åç§°æ¥æ¾ ID |
| | | if (!resolvedSpecificationId && product.specification) { |
| | | const foundModel = modelOptions.find(item => item.model === product.specification); |
| | |
| | | console.error('å è½½è§æ ¼åå·å¤±è´¥:', error); |
| | | } |
| | | } |
| | | |
| | | |
| | | return { |
| | | productId: resolvedProductId, |
| | | product: productName, |
| | |
| | | form.discountRate = row.discountRate || 0 |
| | | form.discountAmount = row.discountAmount || 0 |
| | | form.totalAmount = row.totalAmount || 0 |
| | | |
| | | |
| | | // 忾审æ¹äºº |
| | | if (row.approveUserIds) { |
| | | const userIds = row.approveUserIds.split(',') |
| | |
| | | })) |
| | | nextApproverId = userIds.length + 1 |
| | | } else { |
| | | approverNodes.value = [{ id: 1, userId: null }] |
| | | approverNodes.value = [{id: 1, userId: null}] |
| | | nextApproverId = 2 |
| | | } |
| | | |
| | | |
| | | // å è½½ç¨æ·å表 |
| | | let userLists = await userListNoPage(); |
| | | userList.value = (userLists.data || []).map(item => ({ |
| | |
| | | nickName: item.nickName || '', |
| | | userName: item.userName || '' |
| | | })); |
| | | |
| | | |
| | | dialogVisible.value = true |
| | | } |
| | | |
| | |
| | | }).then(() => { |
| | | const index = quotationList.value.findIndex(item => item.id === row.id) |
| | | if (index > -1) { |
| | | deleteQuotation(row.id).then(res=>{ |
| | | deleteQuotation(row.id).then(res => { |
| | | // console.log(res) |
| | | if(res.code===200){ |
| | | if (res.code === 200) { |
| | | ElMessage.success('å 餿å') |
| | | handleSearch() |
| | | } |
| | |
| | | ElMessage.error('请为ææå®¡æ¹èç¹éæ©å®¡æ¹äººï¼') |
| | | return |
| | | } |
| | | |
| | | |
| | | // æ¶éææèç¹ç审æ¹äººid |
| | | form.approveUserIds = approverNodes.value.map(node => node.userId).join(',') |
| | | |
| | | |
| | | // è®¡ç®ææäº§åçåä»·æ»å |
| | | form.totalAmount = form.products.reduce((sum, product) => { |
| | | const price = Number(product.unitPrice) || 0 |
| | | return sum + price |
| | | }, 0) |
| | | |
| | | |
| | | if (isEdit.value) { |
| | | // ç¼è¾ |
| | | const index = quotationList.value.findIndex(item => item.id === editId.value) |
| | | if (index > -1) { |
| | | updateQuotation(form).then(res=>{ |
| | | updateQuotation(form).then(res => { |
| | | // console.log(res) |
| | | if(res.code===200){ |
| | | if (res.code === 200) { |
| | | ElMessage.success('ç¼è¾æå') |
| | | dialogVisible.value = false |
| | | handleSearch() |
| | |
| | | } |
| | | } else { |
| | | // æ°å¢ |
| | | addQuotation(form).then(res=>{ |
| | | if(res.code===200){ |
| | | addQuotation(form).then(res => { |
| | | if (res.code === 200) { |
| | | ElMessage.success('æ°å¢æå') |
| | | dialogVisible.value = false |
| | | handleSearch() |
| | | } |
| | | }) |
| | | } |
| | | |
| | | |
| | | } |
| | | }) |
| | | } |
| | | |
| | | const downloadImportTemplate = () => { |
| | | proxy.download("/sales/quotation/downloadTemplate", {}, "æ¥ä»·å导å
¥æ¨¡æ¿.xlsx"); |
| | | } |
| | | |
| | | const handleCurrentChange = (val) => { |
| | |
| | | // å页ååæ¶éæ°æ¥è¯¢å表 |
| | | handleSearch() |
| | | } |
| | | const handleSearch = ()=>{ |
| | | const handleSearch = () => { |
| | | const params = { |
| | | // å端å页忰ï¼current / size |
| | | current: pagination.currentPage, |
| | | size: pagination.pageSize, |
| | | ...searchForm |
| | | } |
| | | getQuotationList(params).then(res=>{ |
| | | getQuotationList(params).then(res => { |
| | | // console.log(res) |
| | | if(res.code===200){ |
| | | if (res.code === 200) { |
| | | // åªå¤å¶éè¦çåæ®µï¼é¿å
å°ç»ä»¶å¼ç¨æå
¶ä»å¯¹è±¡æ¾å
¥ååºå¼å¯¹è±¡ |
| | | quotationList.value = (res.data.records || []).map(item => ({ |
| | | id: item.id, |
| | |
| | | pagination.total = res.data.total |
| | | } |
| | | }) |
| | | customerList().then((res) => { |
| | | // åªå¤å¶éè¦çåæ®µï¼é¿å
å°ç»ä»¶å¼ç¨æ¾å
¥ååºå¼å¯¹è±¡ |
| | | customerOption.value = (Array.isArray(res) ? res : []).map(item => ({ |
| | | customerList().then((res) => { |
| | | // åªå¤å¶éè¦çåæ®µï¼é¿å
å°ç»ä»¶å¼ç¨æ¾å
¥ååºå¼å¯¹è±¡ |
| | | customerOption.value = (Array.isArray(res) ? res : []).map(item => ({ |
| | | id: item.id, |
| | | customerName: item.customerName || '', |
| | | taxpayerIdentificationNumber: item.taxpayerIdentificationNumber || '' |
| | | })) |
| | | }); |
| | | }); |
| | | } |
| | | |
| | | onMounted(()=>{ |
| | | const handleImportDetail = (row) => { |
| | | showDetail.value = true |
| | | currentQuotation.value = row |
| | | } |
| | | |
| | | |
| | | onMounted(() => { |
| | | handleSearch() |
| | | }) |
| | | </script> |
| | |
| | | padding: 10px 0; |
| | | max-height: calc(100vh - 200px); |
| | | overflow-y: auto; |
| | | |
| | | |
| | | &::-webkit-scrollbar { |
| | | width: 6px; |
| | | height: 6px; |
| | | } |
| | | |
| | | |
| | | &::-webkit-scrollbar-thumb { |
| | | background: #c1c1c1; |
| | | border-radius: 3px; |
| | | |
| | | |
| | | &:hover { |
| | | background: #a8a8a8; |
| | | } |
| | |
| | | margin-bottom: 24px; |
| | | border-radius: 8px; |
| | | transition: all 0.3s ease; |
| | | |
| | | |
| | | &:hover { |
| | | box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08) !important; |
| | | } |
| | | |
| | | |
| | | :deep(.el-card__header) { |
| | | padding: 16px 20px; |
| | | background: linear-gradient(135deg, #f5f7fa 0%, #ffffff 100%); |
| | | border-bottom: 1px solid #ebeef5; |
| | | } |
| | | |
| | | |
| | | :deep(.el-card__body) { |
| | | padding: 20px; |
| | | } |
| | |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 8px; |
| | | |
| | | |
| | | .card-icon { |
| | | font-size: 18px; |
| | | color: #409eff; |
| | | } |
| | | |
| | | |
| | | .card-title { |
| | | font-weight: 600; |
| | | font-size: 16px; |
| | | color: #303133; |
| | | flex: 1; |
| | | } |
| | | |
| | | |
| | | .header-btn { |
| | | margin-left: auto; |
| | | } |
| | |
| | | |
| | | .product-table-form-item { |
| | | margin-bottom: 0; |
| | | |
| | | :deep(.el-form-item__content) { |
| | | margin-left: 0 !important; |
| | | } |
| | | |
| | | :deep(.el-form-item__label) { |
| | | width: auto; |
| | | min-width: auto; |
| | |
| | | border: 1px solid #e4e7ed; |
| | | transition: all 0.3s ease; |
| | | min-width: 180px; |
| | | |
| | | |
| | | &:hover { |
| | | border-color: #409eff; |
| | | background: #f0f7ff; |
| | |
| | | gap: 8px; |
| | | font-size: 14px; |
| | | color: #606266; |
| | | |
| | | |
| | | .node-step { |
| | | display: inline-flex; |
| | | align-items: center; |
| | |
| | | font-size: 12px; |
| | | font-weight: 600; |
| | | } |
| | | |
| | | |
| | | .node-text { |
| | | font-weight: 500; |
| | | } |
| | | |
| | | |
| | | .arrow-icon { |
| | | color: #909399; |
| | | font-size: 14px; |
| | |
| | | .product-table { |
| | | :deep(.el-table__header) { |
| | | background-color: #f5f7fa; |
| | | |
| | | |
| | | th { |
| | | background-color: #f5f7fa !important; |
| | | color: #606266; |
| | | font-weight: 600; |
| | | } |
| | | } |
| | | |
| | | |
| | | :deep(.el-table__row) { |
| | | &:hover { |
| | | background-color: #f5f7fa; |
| | | } |
| | | } |
| | | |
| | | |
| | | :deep(.el-table__cell) { |
| | | padding: 12px 0; |
| | | } |
| | |
| | | .approver-nodes-container { |
| | | gap: 16px; |
| | | } |
| | | |
| | | |
| | | .approver-node-item { |
| | | min-width: 160px; |
| | | } |