| | |
| | | <div class="app-container"> |
| | | <el-card class="box-card"> |
| | | <!-- 搜索区域 --> |
| | | <el-row :gutter="20" class="search-row"> |
| | | <el-row :gutter="20" |
| | | class="search-row"> |
| | | <el-col :span="8"> |
| | | <el-input |
| | | v-model="searchForm.quotationNo" |
| | | placeholder="请输入报价单号" |
| | | clearable |
| | | @keyup.enter="handleSearch" |
| | | > |
| | | <el-input 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"> |
| | | {{ |
| | | <el-select v-model="searchForm.customer" |
| | | placeholder="请选择客户" |
| | | filterable |
| | | 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> |
| | | </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 type="primary" |
| | | @click="handleSearch">搜索</el-button> |
| | | <el-button @click="resetSearch">重置</el-button> |
| | | <el-button style="float: right;" type="primary" @click="handleAdd"> |
| | | <el-button style="float: right;" |
| | | type="primary" |
| | | @click="handleAdd"> |
| | | 新增报价 |
| | | </el-button> |
| | | </el-col> |
| | | </el-row> |
| | | |
| | | <!-- 报价列表 --> |
| | | <el-table |
| | | :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="报价单号" width="150" /> |
| | | <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="totalAmount" label="报价金额" width="120"> |
| | | <el-table :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="报价单号" |
| | | width="150" /> |
| | | <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="totalAmount" |
| | | label="报价金额" |
| | | width="120"> |
| | | <template #default="scope"> |
| | | ¥{{ scope.row.totalAmount.toFixed(2) }} |
| | | </template> |
| | | </el-table-column> |
| | | <!-- <el-table-column prop="status" label="报价状态" width="100">--> |
| | | <!-- <template #default="scope">--> |
| | | <!-- <el-tag :type="getStatusType(scope.row.status)">--> |
| | | <!-- {{ scope.row.status }}--> |
| | | <!-- </el-tag>--> |
| | | <!-- </template>--> |
| | | <!-- </el-table-column>--> |
| | | <el-table-column label="操作" width="250" fixed="right" align="center"> |
| | | <!-- <el-table-column prop="status" label="报价状态" width="100">--> |
| | | <!-- <template #default="scope">--> |
| | | <!-- <el-tag :type="getStatusType(scope.row.status)">--> |
| | | <!-- {{ scope.row.status }}--> |
| | | <!-- </el-tag>--> |
| | | <!-- </template>--> |
| | | <!-- </el-table-column>--> |
| | | <el-table-column label="操作" |
| | | width="250" |
| | | fixed="right" |
| | | align="center"> |
| | | <template #default="scope"> |
| | | <el-button link type="primary" @click="handleView(scope.row)">查看</el-button> |
| | | <el-button link type="primary" @click="handleEdit(scope.row)" v-if="scope.row.status === '草稿'">编辑</el-button> |
| | | <el-button link type="danger" @click="handleDelete(scope.row)" v-if="scope.row.status === '草稿'">删除</el-button> |
| | | <el-button link |
| | | type="primary" |
| | | @click="handleView(scope.row)">查看</el-button> |
| | | <el-button link |
| | | type="primary" |
| | | @click="handleEdit(scope.row)" |
| | | v-if="scope.row.status === '草稿'">编辑</el-button> |
| | | <el-button link |
| | | type="danger" |
| | | @click="handleDelete(scope.row)" |
| | | v-if="scope.row.status === '草稿'">删除</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" |
| | | /> |
| | | <pagination :total="pagination.total" |
| | | layout="total, sizes, prev, pager, next, jumper" |
| | | :page="pagination.currentPage" |
| | | :limit="pagination.pageSize" |
| | | @pagination="handleCurrentChange" /> |
| | | </el-card> |
| | | |
| | | <!-- 新增/编辑对话框 --> |
| | | <el-dialog v-model="dialogVisible" :title="dialogTitle" width="1300px" :close-on-click-modal="false"> |
| | | <el-form :model="form" :rules="rules" ref="formRef" label-width="100px"> |
| | | <el-dialog v-model="dialogVisible" |
| | | :title="dialogTitle" |
| | | width="1300px" |
| | | :close-on-click-modal="false"> |
| | | <el-form :model="form" |
| | | :rules="rules" |
| | | ref="formRef" |
| | | label-width="100px"> |
| | | <!-- 基本信息 --> |
| | | <el-card class="form-card" shadow="never"> |
| | | <el-card class="form-card" |
| | | shadow="never"> |
| | | <template #header> |
| | | <span class="card-title">基本信息</span> |
| | | </template> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="客户名称" prop="customer"> |
| | | <el-select v-model="form.customer" placeholder="请选择客户" style="width: 100%" @change="handleCustomerChange"> |
| | | <el-option v-for="item in customerOption" :key="item.id" :label="item.customerName" :value="item.customerName"> |
| | | {{ |
| | | <el-form-item label="客户名称" |
| | | prop="customer"> |
| | | <el-select v-model="form.customer" |
| | | placeholder="请选择客户" |
| | | filterable |
| | | style="width: 100%" |
| | | @change="handleCustomerChange"> |
| | | <el-option v-for="item in customerOption" |
| | | :key="item.id" |
| | | :label="item.customerName" |
| | | :value="item.customerName"> |
| | | {{ |
| | | item.customerName + "——" + item.taxpayerIdentificationNumber |
| | | }} |
| | | </el-option> |
| | | </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%"> |
| | | <el-option v-for="item in userList" :key="item.nickName" :label="item.nickName" |
| | | :value="item.nickName" /> |
| | | <el-form-item label="业务员" |
| | | prop="salesperson"> |
| | | <el-select v-model="form.salesperson" |
| | | filterable |
| | | placeholder="请选择业务员" |
| | | style="width: 100%"> |
| | | <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="20"> |
| | | <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" |
| | | /> |
| | | <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" /> |
| | | </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" |
| | | /> |
| | | <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" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="交货期" prop="deliveryPeriod"> |
| | | <el-date-picker |
| | | v-model="form.deliveryPeriod" |
| | | type="date" |
| | | placeholder="选择交货期" |
| | | style="width: 100%" |
| | | format="YYYY-MM-DD" |
| | | value-format="YYYY-MM-DD" |
| | | /> |
| | | <el-form-item label="交货期" |
| | | prop="deliveryPeriod"> |
| | | <el-date-picker v-model="form.deliveryPeriod" |
| | | type="date" |
| | | placeholder="选择交货期" |
| | | style="width: 100%" |
| | | format="YYYY-MM-DD" |
| | | value-format="YYYY-MM-DD" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | </el-card> |
| | | |
| | | <!-- 产品信息 --> |
| | | <el-card class="form-card" shadow="never"> |
| | | <el-card class="form-card" |
| | | shadow="never"> |
| | | <template #header> |
| | | <div class="card-header"> |
| | | <span class="card-title">产品信息</span> |
| | | <el-button type="primary" size="small" @click="addProduct">添加产品</el-button> |
| | | <el-button type="primary" |
| | | size="small" |
| | | @click="addProduct">添加产品</el-button> |
| | | </div> |
| | | </template> |
| | | <el-table :data="form.products" border style="width: 100%"> |
| | | <el-table-column prop="product" label="产品名称" width="200"> |
| | | <el-table :data="form.products" |
| | | border |
| | | style="width: 100%"> |
| | | <el-table-column prop="product" |
| | | label="产品名称" |
| | | width="200"> |
| | | <template #default="scope"> |
| | | <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-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%" /> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="specification" label="规格型号" width="150"> |
| | | <el-table-column prop="specification" |
| | | label="规格型号" |
| | | width="150"> |
| | | <template #default="scope"> |
| | | <el-select |
| | | v-model="scope.row.specificationId" |
| | | placeholder="请选择" |
| | | clearable |
| | | @change="getProductModel($event, scope.row)" |
| | | > |
| | | <el-option |
| | | v-for="item in modelOptions" |
| | | :key="item.id" |
| | | :label="item.model" |
| | | :value="item.id" |
| | | /> |
| | | </el-select> |
| | | <el-select v-model="scope.row.specificationId" |
| | | filterable |
| | | placeholder="请选择" |
| | | clearable |
| | | @change="getProductModel($event, scope.row)"> |
| | | <el-option v-for="item in modelOptions" |
| | | :key="item.id" |
| | | :label="item.model" |
| | | :value="item.id" /> |
| | | </el-select> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="quantity" label="数量"> |
| | | <el-table-column prop="quantity" |
| | | label="数量"> |
| | | <template #default="scope"> |
| | | <el-input-number v-model="scope.row.quantity" :min="1" :precision="0" style="width: 100%" /> |
| | | <el-input-number v-model="scope.row.quantity" |
| | | :min="1" |
| | | :precision="0" |
| | | style="width: 100%" /> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="unit" label="单位"> |
| | | <el-table-column prop="unit" |
| | | label="单位"> |
| | | <template #default="scope"> |
| | | <el-input v-model="scope.row.unit" placeholder="单位" /> |
| | | <el-input v-model="scope.row.unit" |
| | | placeholder="单位" /> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="unitPrice" label="单价"> |
| | | <el-table-column prop="unitPrice" |
| | | label="单价"> |
| | | <template #default="scope"> |
| | | <el-input-number v-model="scope.row.unitPrice" :min="0" :precision="2" style="width: 100%" @change="calculateAmount(scope.row)" /> |
| | | <el-input-number v-model="scope.row.unitPrice" |
| | | :min="0" |
| | | :precision="2" |
| | | style="width: 100%" |
| | | @change="calculateAmount(scope.row)" /> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="amount" label="金额" width="120"> |
| | | <el-table-column prop="amount" |
| | | label="金额" |
| | | width="120"> |
| | | <template #default="scope"> |
| | | <span>¥{{ scope.row.amount.toFixed(2) }}</span> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="操作" width="80" align="center"> |
| | | <el-table-column label="操作" |
| | | width="80" |
| | | align="center"> |
| | | <template #default="scope"> |
| | | <el-button link type="danger" @click="removeProduct(scope.$index)">删除</el-button> |
| | | <el-button link |
| | | type="danger" |
| | | @click="removeProduct(scope.$index)">删除</el-button> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | </el-card> |
| | | |
| | | <!-- 费用信息 --> |
| | | <el-card class="form-card" shadow="never"> |
| | | <el-card class="form-card" |
| | | shadow="never"> |
| | | <template #header> |
| | | <span class="card-title">费用信息</span> |
| | | </template> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="8"> |
| | | <el-form-item label="产品小计"> |
| | | <el-input-number v-model="form.subtotal" :precision="2" :min="0" style="width: 100%" readonly /> |
| | | <el-input-number v-model="form.subtotal" |
| | | :precision="2" |
| | | :min="0" |
| | | style="width: 100%" |
| | | readonly /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="8"> |
| | | <el-form-item label="运费"> |
| | | <el-input-number v-model="form.freight" :precision="2" :min="0" style="width: 100%" @change="calculateTotal" /> |
| | | <el-input-number v-model="form.freight" |
| | | :precision="2" |
| | | :min="0" |
| | | style="width: 100%" |
| | | @change="calculateTotal" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="8"> |
| | | <el-form-item label="其他费用"> |
| | | <el-input-number v-model="form.otherFee" :precision="2" :min="0" style="width: 100%" @change="calculateTotal" /> |
| | | <el-input-number v-model="form.otherFee" |
| | | :precision="2" |
| | | :min="0" |
| | | style="width: 100%" |
| | | @change="calculateTotal" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="8"> |
| | | <el-form-item label="折扣率(%)"> |
| | | <el-input-number v-model="form.discountRate" :precision="2" :min="0" :max="100" style="width: 100%" @change="calculateTotal" /> |
| | | <el-input-number v-model="form.discountRate" |
| | | :precision="2" |
| | | :min="0" |
| | | :max="100" |
| | | style="width: 100%" |
| | | @change="calculateTotal" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="8"> |
| | | <el-form-item label="折扣金额"> |
| | | <el-input-number v-model="form.discountAmount" :precision="2" :min="0" style="width: 100%" readonly /> |
| | | <el-input-number v-model="form.discountAmount" |
| | | :precision="2" |
| | | :min="0" |
| | | style="width: 100%" |
| | | readonly /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="8"> |
| | | <el-form-item label="报价总额"> |
| | | <el-input-number v-model="form.totalAmount" :precision="2" :min="0" style="width: 100%" readonly /> |
| | | <el-input-number v-model="form.totalAmount" |
| | | :precision="2" |
| | | :min="0" |
| | | style="width: 100%" |
| | | readonly /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | </el-card> |
| | | |
| | | <!-- 备注信息 --> |
| | | <el-card class="form-card" shadow="never"> |
| | | <el-card class="form-card" |
| | | shadow="never"> |
| | | <template #header> |
| | | <span class="card-title">备注信息</span> |
| | | </template> |
| | | <el-form-item label="备注" prop="remark"> |
| | | <el-input type="textarea" v-model="form.remark" placeholder="请输入备注信息" rows="3"></el-input> |
| | | <el-form-item label="备注" |
| | | prop="remark"> |
| | | <el-input type="textarea" |
| | | v-model="form.remark" |
| | | placeholder="请输入备注信息" |
| | | rows="3"></el-input> |
| | | </el-form-item> |
| | | </el-card> |
| | | </el-form> |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button type="primary" |
| | | @click="handleSubmit">确 定</el-button> |
| | | <el-button @click="dialogVisible = false">取 消</el-button> |
| | | <el-button type="primary" @click="handleSubmit">确 定</el-button> |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | | |
| | | <!-- 查看详情对话框 --> |
| | | <el-dialog v-model="viewDialogVisible" title="报价详情" width="800px"> |
| | | <el-descriptions :column="2" border> |
| | | <el-dialog v-model="viewDialogVisible" |
| | | title="报价详情" |
| | | width="800px"> |
| | | <el-descriptions :column="2" |
| | | border> |
| | | <el-descriptions-item label="报价单号">{{ currentQuotation.quotationNo }}</el-descriptions-item> |
| | | <el-descriptions-item label="客户名称">{{ currentQuotation.customer }}</el-descriptions-item> |
| | | <el-descriptions-item label="业务员">{{ currentQuotation.salesperson }}</el-descriptions-item> |
| | | <el-descriptions-item label="报价日期">{{ currentQuotation.quotationDate }}</el-descriptions-item> |
| | | <el-descriptions-item label="有效期至">{{ currentQuotation.validDate }}</el-descriptions-item> |
| | | <el-descriptions-item label="交货期">{{ currentQuotation.deliveryPeriod }}</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"> |
| | | <!-- <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> |
| | | </el-descriptions-item> |
| | | </el-descriptions> |
| | | |
| | | <div style="margin-top: 20px;"> |
| | | <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="quantity" label="数量" /> |
| | | <el-table-column prop="unit" label="单位" /> |
| | | <el-table-column prop="unitPrice" label="单价"> |
| | | <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="quantity" |
| | | label="数量" /> |
| | | <el-table-column prop="unit" |
| | | label="单位" /> |
| | | <el-table-column prop="unitPrice" |
| | | label="单价"> |
| | | <template #default="scope"> |
| | | ¥{{ scope.row.unitPrice.toFixed(2) }} |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="amount" label="金额"> |
| | | <el-table-column prop="amount" |
| | | label="金额"> |
| | | <template #default="scope"> |
| | | ¥{{ scope.row.amount.toFixed(2) }} |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | </div> |
| | | |
| | | <div v-if="currentQuotation.remark" style="margin-top: 20px;"> |
| | | <div v-if="currentQuotation.remark" |
| | | style="margin-top: 20px;"> |
| | | <h4>备注</h4> |
| | | <p>{{ currentQuotation.remark }}</p> |
| | | </div> |
| | |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, reactive, computed, onMounted, markRaw, shallowRef } from 'vue' |
| | | import { ElMessage, ElMessageBox } from 'element-plus' |
| | | import { Search } from '@element-plus/icons-vue' |
| | | import Pagination from '@/components/PIMTable/Pagination.vue' |
| | | import {getQuotationList,addQuotation,updateQuotation,deleteQuotation} 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"; |
| | | import { ref, reactive, computed, onMounted, markRaw, shallowRef } from "vue"; |
| | | import { ElMessage, ElMessageBox } from "element-plus"; |
| | | import { Search } from "@element-plus/icons-vue"; |
| | | import Pagination from "@/components/PIMTable/Pagination.vue"; |
| | | import { |
| | | getQuotationList, |
| | | addQuotation, |
| | | updateQuotation, |
| | | deleteQuotation, |
| | | } 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 loading = ref(false) |
| | | const searchForm = reactive({ |
| | | quotationNo: '', |
| | | customer: '', |
| | | status: '' |
| | | }) |
| | | // 响应式数据 |
| | | const loading = ref(false); |
| | | const searchForm = reactive({ |
| | | quotationNo: "", |
| | | customer: "", |
| | | status: "", |
| | | }); |
| | | |
| | | const quotationList = ref([]) |
| | | const productOptions = ref([]); |
| | | const modelOptions = ref([]); |
| | | const pagination = reactive({ |
| | | total: 3, |
| | | currentPage: 1, |
| | | pageSize: 100 |
| | | }) |
| | | const quotationList = ref([]); |
| | | const productOptions = ref([]); |
| | | const modelOptions = ref([]); |
| | | const pagination = reactive({ |
| | | total: 3, |
| | | currentPage: 1, |
| | | pageSize: 100, |
| | | }); |
| | | |
| | | const dialogVisible = ref(false) |
| | | const viewDialogVisible = ref(false) |
| | | const dialogTitle = ref('新增报价') |
| | | const form = reactive({ |
| | | quotationNo: '', |
| | | customer: '', |
| | | salesperson: '', |
| | | quotationDate: '', |
| | | validDate: '', |
| | | paymentMethod: '', |
| | | deliveryPeriod: '', |
| | | status: '草稿', |
| | | remark: '', |
| | | products: [], |
| | | subtotal: 0, |
| | | freight: 0, |
| | | otherFee: 0, |
| | | discountRate: 0, |
| | | discountAmount: 0, |
| | | totalAmount: 0 |
| | | }) |
| | | const dialogVisible = ref(false); |
| | | const viewDialogVisible = ref(false); |
| | | const dialogTitle = ref("新增报价"); |
| | | const form = reactive({ |
| | | quotationNo: "", |
| | | customer: "", |
| | | salesperson: "", |
| | | quotationDate: "", |
| | | validDate: "", |
| | | paymentMethod: "", |
| | | deliveryPeriod: "", |
| | | status: "草稿", |
| | | remark: "", |
| | | products: [], |
| | | subtotal: 0, |
| | | freight: 0, |
| | | otherFee: 0, |
| | | discountRate: 0, |
| | | discountAmount: 0, |
| | | totalAmount: 0, |
| | | }); |
| | | |
| | | const rules = { |
| | | customer: [{ required: true, message: '请选择客户', trigger: 'change' }], |
| | | salesperson: [{ required: true, message: '请选择业务员', trigger: 'change' }], |
| | | quotationDate: [{ required: true, message: '请选择报价日期', trigger: 'change' }], |
| | | validDate: [{ required: true, message: '请选择有效期', trigger: 'change' }], |
| | | deliveryPeriod: [{ required: true, message: '请选择交货期', trigger: 'change' }] |
| | | } |
| | | const userList = ref([]); |
| | | const customerOption = ref([]); |
| | | const rules = { |
| | | customer: [{ required: true, message: "请选择客户", trigger: "change" }], |
| | | salesperson: [{ required: true, message: "请选择业务员", trigger: "change" }], |
| | | quotationDate: [ |
| | | { required: true, message: "请选择报价日期", trigger: "change" }, |
| | | ], |
| | | validDate: [{ required: true, message: "请选择有效期", trigger: "change" }], |
| | | deliveryPeriod: [ |
| | | { required: true, message: "请选择交货期", trigger: "change" }, |
| | | ], |
| | | }; |
| | | const userList = ref([]); |
| | | const customerOption = ref([]); |
| | | |
| | | const isEdit = ref(false) |
| | | const editId = ref(null) |
| | | const currentQuotation = ref({}) |
| | | const formRef = ref() |
| | | const isEdit = ref(false); |
| | | const editId = ref(null); |
| | | const currentQuotation = ref({}); |
| | | const formRef = ref(); |
| | | |
| | | // 计算属性 |
| | | const filteredList = computed(() => { |
| | | let list = quotationList.value |
| | | if (searchForm.quotationNo) { |
| | | list = list.filter(item => item.quotationNo.includes(searchForm.quotationNo)) |
| | | } |
| | | if (searchForm.customer) { |
| | | list = list.filter(item => item.customer === searchForm.customer) |
| | | } |
| | | if (searchForm.status) { |
| | | list = list.filter(item => item.status === searchForm.status) |
| | | } |
| | | return list |
| | | }) |
| | | |
| | | // 方法 |
| | | const getStatusType = (status) => { |
| | | const statusMap = { |
| | | '草稿': 'info', |
| | | '已发送': 'primary', |
| | | '客户确认': 'success', |
| | | '已过期': 'danger' |
| | | } |
| | | return statusMap[status] || 'info' |
| | | } |
| | | |
| | | const resetSearch = () => { |
| | | searchForm.quotationNo = '' |
| | | searchForm.customer = '' |
| | | searchForm.status = '' |
| | | } |
| | | |
| | | const handleAdd = async () => { |
| | | dialogTitle.value = '新增报价' |
| | | isEdit.value = false |
| | | resetForm() |
| | | dialogVisible.value = true |
| | | 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 => ({ |
| | | id: item.id, |
| | | customerName: item.customerName || '', |
| | | taxpayerIdentificationNumber: item.taxpayerIdentificationNumber || '' |
| | | })) |
| | | }); |
| | | } |
| | | const getProductOptions = () => { |
| | | productTreeList().then((res) => { |
| | | productOptions.value = convertIdToValue(res); |
| | | }); |
| | | }; |
| | | 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; |
| | | }); |
| | | } |
| | | const getModels = (value, row) => { |
| | | if (!row) return; |
| | | // 如果清空选择,则清空相关字段 |
| | | if (!value) { |
| | | row.productId = ''; |
| | | row.product = ''; |
| | | modelOptions.value = []; |
| | | 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; |
| | | } |
| | | // 获取规格型号列表 |
| | | modelList({ id: value }).then((res) => { |
| | | modelOptions.value = res || []; |
| | | }); |
| | | }; |
| | | const getProductModel = (value, row) => { |
| | | if (!row) return; |
| | | // 如果清空选择,则清空相关字段 |
| | | if (!value) { |
| | | row.specificationId = ''; |
| | | row.specification = ''; |
| | | row.unit = ''; |
| | | return; |
| | | } |
| | | // 更新 specificationId(v-model 已经自动更新,这里确保一致性) |
| | | row.specificationId = value; |
| | | const index = modelOptions.value.findIndex((item) => item.id === value); |
| | | if (index !== -1) { |
| | | row.specification = modelOptions.value[index].model; |
| | | row.unit = modelOptions.value[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 |
| | | }; |
| | | const handleView = (row) => { |
| | | // 只复制需要的字段,避免将组件引用放入响应式对象 |
| | | currentQuotation.value = { |
| | | quotationNo: row.quotationNo || '', |
| | | customer: row.customer || '', |
| | | salesperson: row.salesperson || '', |
| | | quotationDate: row.quotationDate || '', |
| | | validDate: row.validDate || '', |
| | | paymentMethod: row.paymentMethod || '', |
| | | deliveryPeriod: row.deliveryPeriod || '', |
| | | status: row.status || '', |
| | | remark: row.remark || '', |
| | | products: row.products ? row.products.map(product => ({ |
| | | productId: product.productId || '', |
| | | product: product.product || product.productName || '', |
| | | specificationId: product.specificationId || '', |
| | | specification: product.specification || '', |
| | | quantity: product.quantity || 0, |
| | | unit: product.unit || '', |
| | | unitPrice: product.unitPrice || 0, |
| | | amount: product.amount || 0 |
| | | })) : [], |
| | | totalAmount: row.totalAmount || 0 |
| | | } |
| | | viewDialogVisible.value = true |
| | | } |
| | | |
| | | const handleEdit = (row) => { |
| | | dialogTitle.value = '编辑报价' |
| | | isEdit.value = true |
| | | editId.value = row.id |
| | | // 只复制需要的字段,避免将组件引用放入响应式对象 |
| | | form.quotationNo = row.quotationNo || '' |
| | | form.customer = row.customer || '' |
| | | form.salesperson = row.salesperson || '' |
| | | form.quotationDate = row.quotationDate || '' |
| | | form.validDate = row.validDate || '' |
| | | form.paymentMethod = row.paymentMethod || '' |
| | | form.deliveryPeriod = row.deliveryPeriod || '' |
| | | form.status = row.status || '草稿' |
| | | form.remark = row.remark || '' |
| | | form.products = row.products ? row.products.map(product => ({ |
| | | productId: product.productId || '', |
| | | product: product.product || product.productName || '', |
| | | specificationId: product.specificationId || '', |
| | | specification: product.specification || '', |
| | | quantity: product.quantity || 0, |
| | | unit: product.unit || '', |
| | | unitPrice: product.unitPrice || 0, |
| | | amount: product.amount || 0 |
| | | })) : [] |
| | | form.subtotal = row.subtotal || 0 |
| | | form.freight = row.freight || 0 |
| | | form.otherFee = row.otherFee || 0 |
| | | form.discountRate = row.discountRate || 0 |
| | | form.discountAmount = row.discountAmount || 0 |
| | | form.totalAmount = row.totalAmount || 0 |
| | | dialogVisible.value = true |
| | | } |
| | | |
| | | |
| | | const handleDelete = (row) => { |
| | | ElMessageBox.confirm('确认删除该报价单吗?', '提示', { |
| | | confirmButtonText: '确定', |
| | | cancelButtonText: '取消', |
| | | type: 'warning' |
| | | }).then(() => { |
| | | const index = quotationList.value.findIndex(item => item.id === row.id) |
| | | if (index > -1) { |
| | | deleteQuotation(row.id).then(res=>{ |
| | | // console.log(res) |
| | | if(res.code===200){ |
| | | ElMessage.success('删除成功') |
| | | handleSearch() |
| | | } |
| | | }) |
| | | // quotationList.value.splice(index, 1) |
| | | // pagination.total-- |
| | | // ElMessage.success('删除成功') |
| | | // 计算属性 |
| | | const filteredList = computed(() => { |
| | | let list = quotationList.value; |
| | | if (searchForm.quotationNo) { |
| | | list = list.filter(item => |
| | | item.quotationNo.includes(searchForm.quotationNo) |
| | | ); |
| | | } |
| | | }) |
| | | } |
| | | |
| | | const resetForm = () => { |
| | | form.customer = '' |
| | | form.salesperson = '' |
| | | form.quotationDate = '' |
| | | form.validDate = '' |
| | | form.paymentMethod = '' |
| | | form.deliveryPeriod = '' |
| | | form.status = '草稿' |
| | | form.remark = '' |
| | | form.products = [] |
| | | form.subtotal = 0 |
| | | form.freight = 0 |
| | | form.otherFee = 0 |
| | | form.discountRate = 0 |
| | | form.discountAmount = 0 |
| | | form.totalAmount = 0 |
| | | } |
| | | |
| | | const addProduct = () => { |
| | | form.products.push({ |
| | | productId: '', |
| | | product: '', |
| | | productName: '', |
| | | specificationId: '', |
| | | specification: '', |
| | | quantity: 1, |
| | | unit: '', |
| | | unitPrice: 0, |
| | | amount: 0 |
| | | }) |
| | | } |
| | | |
| | | const removeProduct = (index) => { |
| | | form.products.splice(index, 1) |
| | | calculateSubtotal() |
| | | } |
| | | |
| | | const calculateAmount = (product) => { |
| | | product.amount = product.quantity * product.unitPrice |
| | | calculateSubtotal() |
| | | } |
| | | |
| | | const calculateSubtotal = () => { |
| | | form.subtotal = form.products.reduce((sum, product) => sum + product.amount, 0) |
| | | calculateTotal() |
| | | } |
| | | |
| | | const calculateTotal = () => { |
| | | form.discountAmount = form.subtotal * (form.discountRate / 100) |
| | | form.totalAmount = form.subtotal + form.freight + form.otherFee - form.discountAmount |
| | | } |
| | | |
| | | const handleCustomerChange = () => { |
| | | // 可以根据客户信息自动填充一些默认值 |
| | | } |
| | | |
| | | const handleSubmit = () => { |
| | | formRef.value.validate((valid) => { |
| | | if (valid) { |
| | | if (form.products.length === 0) { |
| | | ElMessage.warning('请至少添加一个产品') |
| | | return |
| | | } |
| | | |
| | | if (isEdit.value) { |
| | | // 编辑 |
| | | const index = quotationList.value.findIndex(item => item.id === editId.value) |
| | | if (index > -1) { |
| | | updateQuotation(form).then(res=>{ |
| | | // console.log(res) |
| | | if(res.code===200){ |
| | | ElMessage.success('编辑成功') |
| | | dialogVisible.value = false |
| | | handleSearch() |
| | | } |
| | | }) |
| | | // quotationList.value[index] = { ...form, id: editId.value } |
| | | // ElMessage.success('编辑成功') |
| | | } |
| | | } else { |
| | | // 新增 |
| | | // const newId = Math.max(...quotationList.value.map(item => item.id)) + 1 |
| | | form.quotationNo = `QT${new Date().getFullYear()}${String(new Date().getMonth() + 1).padStart(2, '0')}${String(new Date().getDate()).padStart(2, '0')}` |
| | | |
| | | addQuotation(form).then(res=>{ |
| | | // console.log(res) |
| | | if(res.code===200){ |
| | | ElMessage.success('新增成功') |
| | | dialogVisible.value = false |
| | | handleSearch() |
| | | } |
| | | }) |
| | | |
| | | // quotationList.value.push({ |
| | | // ...form, |
| | | // // id: newId, |
| | | // quotationNo: quotationNo |
| | | // }) |
| | | // pagination.total++ |
| | | // ElMessage.success('新增成功') |
| | | } |
| | | |
| | | if (searchForm.customer) { |
| | | list = list.filter(item => item.customer === searchForm.customer); |
| | | } |
| | | }) |
| | | } |
| | | if (searchForm.status) { |
| | | list = list.filter(item => item.status === searchForm.status); |
| | | } |
| | | return list; |
| | | }); |
| | | |
| | | const handleCurrentChange = (val) => { |
| | | pagination.currentPage = val.page |
| | | pagination.pageSize = val.limit |
| | | } |
| | | const handleSearch = ()=>{ |
| | | const params = { |
| | | page:pagination, |
| | | ...searchForm |
| | | } |
| | | getQuotationList(params).then(res=>{ |
| | | // console.log(res) |
| | | if(res.code===200){ |
| | | // 只复制需要的字段,避免将组件引用或其他对象放入响应式对象 |
| | | quotationList.value = (res.data.records || []).map(item => ({ |
| | | // 方法 |
| | | const getStatusType = status => { |
| | | const statusMap = { |
| | | 草稿: "info", |
| | | 已发送: "primary", |
| | | 客户确认: "success", |
| | | 已过期: "danger", |
| | | }; |
| | | return statusMap[status] || "info"; |
| | | }; |
| | | |
| | | const resetSearch = () => { |
| | | searchForm.quotationNo = ""; |
| | | searchForm.customer = ""; |
| | | searchForm.status = ""; |
| | | }; |
| | | |
| | | const handleAdd = async () => { |
| | | dialogTitle.value = "新增报价"; |
| | | isEdit.value = false; |
| | | resetForm(); |
| | | dialogVisible.value = true; |
| | | 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 => ({ |
| | | id: item.id, |
| | | quotationNo: item.quotationNo || '', |
| | | customer: item.customer || '', |
| | | salesperson: item.salesperson || '', |
| | | quotationDate: item.quotationDate || '', |
| | | validDate: item.validDate || '', |
| | | paymentMethod: item.paymentMethod || '', |
| | | deliveryPeriod: item.deliveryPeriod || '', |
| | | status: item.status || '草稿', |
| | | remark: item.remark || '', |
| | | products: item.products ? item.products.map(product => ({ |
| | | productId: product.productId || '', |
| | | product: product.product || product.productName || '', |
| | | specificationId: product.specificationId || '', |
| | | specification: product.specification || '', |
| | | quantity: product.quantity || 0, |
| | | unit: product.unit || '', |
| | | unitPrice: product.unitPrice || 0, |
| | | amount: product.amount || 0 |
| | | })) : [], |
| | | subtotal: item.subtotal || 0, |
| | | freight: item.freight || 0, |
| | | otherFee: item.otherFee || 0, |
| | | discountRate: item.discountRate || 0, |
| | | discountAmount: item.discountAmount || 0, |
| | | totalAmount: item.totalAmount || 0 |
| | | })) |
| | | pagination.total = res.data.total |
| | | } |
| | | }) |
| | | customerList().then((res) => { |
| | | // 只复制需要的字段,避免将组件引用放入响应式对象 |
| | | customerOption.value = (Array.isArray(res) ? res : []).map(item => ({ |
| | | id: item.id, |
| | | customerName: item.customerName || '', |
| | | taxpayerIdentificationNumber: item.taxpayerIdentificationNumber || '' |
| | | })) |
| | | }); |
| | | } |
| | | customerName: item.customerName || "", |
| | | taxpayerIdentificationNumber: item.taxpayerIdentificationNumber || "", |
| | | })); |
| | | }); |
| | | }; |
| | | const getProductOptions = () => { |
| | | productTreeList().then(res => { |
| | | productOptions.value = convertIdToValue(res); |
| | | }); |
| | | }; |
| | | 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); |
| | | } |
| | | |
| | | onMounted(()=>{ |
| | | handleSearch() |
| | | }) |
| | | return newItem; |
| | | }); |
| | | } |
| | | const getModels = (value, row) => { |
| | | if (!row) return; |
| | | // 如果清空选择,则清空相关字段 |
| | | if (!value) { |
| | | row.productId = ""; |
| | | row.product = ""; |
| | | modelOptions.value = []; |
| | | 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; |
| | | } |
| | | // 获取规格型号列表 |
| | | modelList({ id: value }).then(res => { |
| | | modelOptions.value = res || []; |
| | | }); |
| | | }; |
| | | const getProductModel = (value, row) => { |
| | | if (!row) return; |
| | | // 如果清空选择,则清空相关字段 |
| | | if (!value) { |
| | | row.specificationId = ""; |
| | | row.specification = ""; |
| | | row.unit = ""; |
| | | return; |
| | | } |
| | | // 更新 specificationId(v-model 已经自动更新,这里确保一致性) |
| | | row.specificationId = value; |
| | | const index = modelOptions.value.findIndex(item => item.id === value); |
| | | if (index !== -1) { |
| | | row.specification = modelOptions.value[index].model; |
| | | row.unit = modelOptions.value[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 |
| | | }; |
| | | const handleView = row => { |
| | | // 只复制需要的字段,避免将组件引用放入响应式对象 |
| | | currentQuotation.value = { |
| | | quotationNo: row.quotationNo || "", |
| | | customer: row.customer || "", |
| | | salesperson: row.salesperson || "", |
| | | quotationDate: row.quotationDate || "", |
| | | validDate: row.validDate || "", |
| | | paymentMethod: row.paymentMethod || "", |
| | | deliveryPeriod: row.deliveryPeriod || "", |
| | | status: row.status || "", |
| | | remark: row.remark || "", |
| | | products: row.products |
| | | ? row.products.map(product => ({ |
| | | productId: product.productId || "", |
| | | product: product.product || product.productName || "", |
| | | specificationId: product.specificationId || "", |
| | | specification: product.specification || "", |
| | | quantity: product.quantity || 0, |
| | | unit: product.unit || "", |
| | | unitPrice: product.unitPrice || 0, |
| | | amount: product.amount || 0, |
| | | })) |
| | | : [], |
| | | totalAmount: row.totalAmount || 0, |
| | | }; |
| | | viewDialogVisible.value = true; |
| | | }; |
| | | |
| | | const handleEdit = row => { |
| | | dialogTitle.value = "编辑报价"; |
| | | isEdit.value = true; |
| | | editId.value = row.id; |
| | | // 只复制需要的字段,避免将组件引用放入响应式对象 |
| | | form.quotationNo = row.quotationNo || ""; |
| | | form.customer = row.customer || ""; |
| | | form.salesperson = row.salesperson || ""; |
| | | form.quotationDate = row.quotationDate || ""; |
| | | form.validDate = row.validDate || ""; |
| | | form.paymentMethod = row.paymentMethod || ""; |
| | | form.deliveryPeriod = row.deliveryPeriod || ""; |
| | | form.status = row.status || "草稿"; |
| | | form.remark = row.remark || ""; |
| | | form.products = row.products |
| | | ? row.products.map(product => ({ |
| | | productId: product.productId || "", |
| | | product: product.product || product.productName || "", |
| | | specificationId: product.specificationId || "", |
| | | specification: product.specification || "", |
| | | quantity: product.quantity || 0, |
| | | unit: product.unit || "", |
| | | unitPrice: product.unitPrice || 0, |
| | | amount: product.amount || 0, |
| | | })) |
| | | : []; |
| | | form.subtotal = row.subtotal || 0; |
| | | form.freight = row.freight || 0; |
| | | form.otherFee = row.otherFee || 0; |
| | | form.discountRate = row.discountRate || 0; |
| | | form.discountAmount = row.discountAmount || 0; |
| | | form.totalAmount = row.totalAmount || 0; |
| | | dialogVisible.value = true; |
| | | }; |
| | | |
| | | const handleDelete = row => { |
| | | ElMessageBox.confirm("确认删除该报价单吗?", "提示", { |
| | | confirmButtonText: "确定", |
| | | cancelButtonText: "取消", |
| | | type: "warning", |
| | | }).then(() => { |
| | | const index = quotationList.value.findIndex(item => item.id === row.id); |
| | | if (index > -1) { |
| | | deleteQuotation(row.id).then(res => { |
| | | // console.log(res) |
| | | if (res.code === 200) { |
| | | ElMessage.success("删除成功"); |
| | | handleSearch(); |
| | | } |
| | | }); |
| | | // quotationList.value.splice(index, 1) |
| | | // pagination.total-- |
| | | // ElMessage.success('删除成功') |
| | | } |
| | | }); |
| | | }; |
| | | |
| | | const resetForm = () => { |
| | | form.customer = ""; |
| | | form.salesperson = ""; |
| | | form.quotationDate = ""; |
| | | form.validDate = ""; |
| | | form.paymentMethod = ""; |
| | | form.deliveryPeriod = ""; |
| | | form.status = "草稿"; |
| | | form.remark = ""; |
| | | form.products = []; |
| | | form.subtotal = 0; |
| | | form.freight = 0; |
| | | form.otherFee = 0; |
| | | form.discountRate = 0; |
| | | form.discountAmount = 0; |
| | | form.totalAmount = 0; |
| | | }; |
| | | |
| | | const addProduct = () => { |
| | | form.products.push({ |
| | | productId: "", |
| | | product: "", |
| | | productName: "", |
| | | specificationId: "", |
| | | specification: "", |
| | | quantity: 1, |
| | | unit: "", |
| | | unitPrice: 0, |
| | | amount: 0, |
| | | }); |
| | | }; |
| | | |
| | | const removeProduct = index => { |
| | | form.products.splice(index, 1); |
| | | calculateSubtotal(); |
| | | }; |
| | | |
| | | const calculateAmount = product => { |
| | | product.amount = product.quantity * product.unitPrice; |
| | | calculateSubtotal(); |
| | | }; |
| | | |
| | | const calculateSubtotal = () => { |
| | | form.subtotal = form.products.reduce( |
| | | (sum, product) => sum + product.amount, |
| | | 0 |
| | | ); |
| | | calculateTotal(); |
| | | }; |
| | | |
| | | const calculateTotal = () => { |
| | | form.discountAmount = form.subtotal * (form.discountRate / 100); |
| | | form.totalAmount = |
| | | form.subtotal + form.freight + form.otherFee - form.discountAmount; |
| | | }; |
| | | |
| | | const handleCustomerChange = () => { |
| | | // 可以根据客户信息自动填充一些默认值 |
| | | }; |
| | | |
| | | const handleSubmit = () => { |
| | | formRef.value.validate(valid => { |
| | | if (valid) { |
| | | if (form.products.length === 0) { |
| | | ElMessage.warning("请至少添加一个产品"); |
| | | return; |
| | | } |
| | | |
| | | if (isEdit.value) { |
| | | // 编辑 |
| | | const index = quotationList.value.findIndex( |
| | | item => item.id === editId.value |
| | | ); |
| | | if (index > -1) { |
| | | updateQuotation(form).then(res => { |
| | | // console.log(res) |
| | | if (res.code === 200) { |
| | | ElMessage.success("编辑成功"); |
| | | dialogVisible.value = false; |
| | | handleSearch(); |
| | | } |
| | | }); |
| | | // quotationList.value[index] = { ...form, id: editId.value } |
| | | // ElMessage.success('编辑成功') |
| | | } |
| | | } else { |
| | | // 新增 |
| | | // const newId = Math.max(...quotationList.value.map(item => item.id)) + 1 |
| | | form.quotationNo = `QT${new Date().getFullYear()}${String( |
| | | new Date().getMonth() + 1 |
| | | ).padStart(2, "0")}${String(new Date().getDate()).padStart(2, "0")}`; |
| | | |
| | | addQuotation(form).then(res => { |
| | | // console.log(res) |
| | | if (res.code === 200) { |
| | | ElMessage.success("新增成功"); |
| | | dialogVisible.value = false; |
| | | handleSearch(); |
| | | } |
| | | }); |
| | | |
| | | // quotationList.value.push({ |
| | | // ...form, |
| | | // // id: newId, |
| | | // quotationNo: quotationNo |
| | | // }) |
| | | // pagination.total++ |
| | | // ElMessage.success('新增成功') |
| | | } |
| | | } |
| | | }); |
| | | }; |
| | | |
| | | const handleCurrentChange = val => { |
| | | pagination.currentPage = val.page; |
| | | pagination.pageSize = val.limit; |
| | | }; |
| | | const handleSearch = () => { |
| | | const params = { |
| | | page: pagination, |
| | | ...searchForm, |
| | | }; |
| | | getQuotationList(params).then(res => { |
| | | // console.log(res) |
| | | if (res.code === 200) { |
| | | // 只复制需要的字段,避免将组件引用或其他对象放入响应式对象 |
| | | quotationList.value = (res.data.records || []).map(item => ({ |
| | | id: item.id, |
| | | quotationNo: item.quotationNo || "", |
| | | customer: item.customer || "", |
| | | salesperson: item.salesperson || "", |
| | | quotationDate: item.quotationDate || "", |
| | | validDate: item.validDate || "", |
| | | paymentMethod: item.paymentMethod || "", |
| | | deliveryPeriod: item.deliveryPeriod || "", |
| | | status: item.status || "草稿", |
| | | remark: item.remark || "", |
| | | products: item.products |
| | | ? item.products.map(product => ({ |
| | | productId: product.productId || "", |
| | | product: product.product || product.productName || "", |
| | | specificationId: product.specificationId || "", |
| | | specification: product.specification || "", |
| | | quantity: product.quantity || 0, |
| | | unit: product.unit || "", |
| | | unitPrice: product.unitPrice || 0, |
| | | amount: product.amount || 0, |
| | | })) |
| | | : [], |
| | | subtotal: item.subtotal || 0, |
| | | freight: item.freight || 0, |
| | | otherFee: item.otherFee || 0, |
| | | discountRate: item.discountRate || 0, |
| | | discountAmount: item.discountAmount || 0, |
| | | totalAmount: item.totalAmount || 0, |
| | | })); |
| | | pagination.total = res.data.total; |
| | | } |
| | | }); |
| | | customerList().then(res => { |
| | | // 只复制需要的字段,避免将组件引用放入响应式对象 |
| | | customerOption.value = (Array.isArray(res) ? res : []).map(item => ({ |
| | | id: item.id, |
| | | customerName: item.customerName || "", |
| | | taxpayerIdentificationNumber: item.taxpayerIdentificationNumber || "", |
| | | })); |
| | | }); |
| | | }; |
| | | |
| | | onMounted(() => { |
| | | handleSearch(); |
| | | }); |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .search-row { |
| | | margin-bottom: 20px; |
| | | } |
| | | .search-row { |
| | | margin-bottom: 20px; |
| | | } |
| | | |
| | | .form-card { |
| | | margin-bottom: 20px; |
| | | } |
| | | .form-card { |
| | | margin-bottom: 20px; |
| | | } |
| | | |
| | | .card-title { |
| | | font-weight: bold; |
| | | color: #303133; |
| | | } |
| | | .card-title { |
| | | font-weight: bold; |
| | | color: #303133; |
| | | } |
| | | |
| | | .card-header { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | } |
| | | .card-header { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | } |
| | | |
| | | .dialog-footer { |
| | | text-align: right; |
| | | } |
| | | .dialog-footer { |
| | | text-align: right; |
| | | } |
| | | </style> |