first commit

This commit is contained in:
2026-03-05 16:52:12 +08:00
commit 8ca2e6d52f
1899 changed files with 321565 additions and 0 deletions

View File

@@ -0,0 +1,414 @@
<template>
<el-drawer
v-model="dialogVisible"
title="自动比价"
direction="rtl"
size="100%"
:close-on-press-escape="true"
:destroy-on-close="true"
>
<div class="mobile-compare" v-loading="loading">
<!-- 比价单信息 -->
<div class="mobile-compare__section">
<div class="mobile-compare__section-title">比价单信息</div>
<div class="mobile-compare__info-row">
<span class="mobile-compare__info-label">比价单号</span>
<span>{{ inquiryData?.no }}</span>
</div>
<div class="mobile-compare__info-row">
<span class="mobile-compare__info-label">产品名称</span>
<span>{{ inquiryData?.productName }}</span>
</div>
<div class="mobile-compare__info-row">
<span class="mobile-compare__info-label">需求数量</span>
<span>{{ inquiryData?.requireCount }}</span>
</div>
</div>
<!-- 权重配置 -->
<div class="mobile-compare__section">
<div class="mobile-compare__section-title">
评分权重配置
<el-tag size="small" :type="totalWeight === 100 ? 'success' : 'danger'" style="margin-left: 8px">
合计: {{ totalWeight }}%
</el-tag>
</div>
<el-form :model="weightConfig" label-position="top">
<div class="mobile-compare__weight-grid">
<el-form-item label="价格权重">
<el-input-number v-model="weightConfig.priceWeight" :min="0" :max="100" :precision="0" controls-position="right" style="width: 100%" />
</el-form-item>
<el-form-item label="质量权重">
<el-input-number v-model="weightConfig.qualityWeight" :min="0" :max="100" :precision="0" controls-position="right" style="width: 100%" />
</el-form-item>
<el-form-item label="交付权重">
<el-input-number v-model="weightConfig.deliveryWeight" :min="0" :max="100" :precision="0" controls-position="right" style="width: 100%" />
</el-form-item>
<el-form-item label="服务权重">
<el-input-number v-model="weightConfig.serviceWeight" :min="0" :max="100" :precision="0" controls-position="right" style="width: 100%" />
</el-form-item>
</div>
</el-form>
</div>
<!-- 供应商报价评分卡片 -->
<div class="mobile-compare__section">
<div class="mobile-compare__section-title">供应商报价评分</div>
<div class="mobile-compare__card-list">
<div
v-for="(row, idx) in quoteList"
:key="idx"
class="mobile-compare__card"
:class="{ 'mobile-compare__card--best': row.isSelected }"
>
<div class="mobile-compare__card-header">
<span class="mobile-compare__card-name">{{ row.supplierName || '-' }}</span>
<el-tag v-if="row.isSelected" type="success" size="small">推荐</el-tag>
</div>
<div class="mobile-compare__card-info">
<div class="mobile-compare__info-row">
<span class="mobile-compare__info-label">单价</span>
<span>{{ row.unitPrice?.toFixed(2) ?? '-' }}</span>
</div>
<div class="mobile-compare__info-row">
<span class="mobile-compare__info-label">总价</span>
<span>{{ row.totalPrice?.toFixed(2) ?? '-' }}</span>
</div>
<div class="mobile-compare__info-row">
<span class="mobile-compare__info-label">交货周期</span>
<span>{{ row.deliveryCycle != null ? row.deliveryCycle + '天' : '-' }}</span>
</div>
</div>
<div class="mobile-compare__card-scores">
<div class="mobile-compare__score-input-group">
<div class="mobile-compare__score-input">
<span class="mobile-compare__score-input-label">质量评分</span>
<el-input-number v-model="row.qualityScore" :min="0" :max="100" :precision="0" size="small" controls-position="right" style="width: 100%" />
</div>
<div class="mobile-compare__score-input">
<span class="mobile-compare__score-input-label">服务评分</span>
<el-input-number v-model="row.serviceScore" :min="0" :max="100" :precision="0" size="small" controls-position="right" style="width: 100%" />
</div>
</div>
<div class="mobile-compare__score-results">
<div class="mobile-compare__score-item">
<span class="mobile-compare__score-item-label">价格</span>
<span>{{ row.priceScore?.toFixed(1) ?? '-' }}</span>
</div>
<div class="mobile-compare__score-item">
<span class="mobile-compare__score-item-label">交付</span>
<span>{{ row.deliveryScore?.toFixed(1) ?? '-' }}</span>
</div>
<div class="mobile-compare__score-item mobile-compare__score-item--total">
<span class="mobile-compare__score-item-label">综合</span>
<el-tag v-if="row.totalScore != null" :type="row.isSelected ? 'success' : 'info'" size="small">{{ row.totalScore?.toFixed(2) }}</el-tag>
<span v-else style="color:#c0c4cc">-</span>
</div>
</div>
</div>
</div>
</div>
<div class="mobile-compare__tips">
<p>* 价格得分价格越低得分越高最低价得100分</p>
<p>* 交付得分周期越短得分越高最短周期得100分</p>
<p>* 质量/服务评分手动输入0-100未填按0分计</p>
</div>
</div>
<!-- 比价结果 -->
<el-alert
v-if="bestQuote"
:title="`推荐: ${bestQuote.supplierName},综合得分: ${bestQuote.totalScore?.toFixed(2)}`"
type="success"
show-icon
:closable="false"
style="margin-bottom: 12px"
/>
</div>
<template #footer>
<el-button @click="handleCompare" type="primary">开始比价</el-button>
<el-button @click="handleSave" type="success" :disabled="!bestQuote">保存结果</el-button>
<el-button @click="dialogVisible = false"> </el-button>
</template>
</el-drawer>
</template>
<script setup lang="ts">
import { PurchaseInquiryApi, PurchaseInquiryVO, PurchaseInquiryQuoteVO } from '@/api/erp/purchase/inquiry'
defineOptions({ name: 'AutoCompareDialog' })
const message = useMessage()
const emit = defineEmits(['success'])
const dialogVisible = ref(false)
const loading = ref(false)
const inquiryData = ref<PurchaseInquiryVO | null>(null)
const quoteList = ref<PurchaseInquiryQuoteVO[]>([])
/** 权重配置 */
const weightConfig = reactive({
priceWeight: 40,
qualityWeight: 25,
deliveryWeight: 20,
serviceWeight: 15
})
/** 计算权重合计 */
const totalWeight = computed(() => {
return weightConfig.priceWeight + weightConfig.qualityWeight +
weightConfig.deliveryWeight + weightConfig.serviceWeight
})
/** 最佳报价供应商 */
const bestQuote = ref<PurchaseInquiryQuoteVO | null>(null)
/** 打开弹窗 */
const open = async (id: number) => {
dialogVisible.value = true
loading.value = true
bestQuote.value = null
try {
inquiryData.value = await PurchaseInquiryApi.getPurchaseInquiry(id)
quoteList.value = inquiryData.value?.quotes || []
// 自动计算各项得分
calculateScores()
} finally {
loading.value = false
}
}
defineExpose({ open })
/** 自动计算各项得分 */
const calculateScores = () => {
if (!quoteList.value || quoteList.value.length === 0) return
// 计算总价
const requireCount = inquiryData.value?.requireCount || 1
quoteList.value.forEach(quote => {
if (quote.unitPrice != null && quote.unitPrice > 0) {
quote.totalPrice = quote.unitPrice * requireCount
} else {
quote.totalPrice = 0
}
})
// 计算价格得分(价格越低得分越高)
const prices = quoteList.value.filter(q => q.unitPrice != null && q.unitPrice > 0).map(q => q.unitPrice!)
if (prices.length > 0) {
const minPrice = Math.min(...prices)
quoteList.value.forEach(quote => {
if (quote.unitPrice != null && quote.unitPrice > 0) {
quote.priceScore = (minPrice / quote.unitPrice) * 100
} else {
quote.priceScore = 0
}
})
}
// 计算交付得分(交货周期越短得分越高)
const cycles = quoteList.value.filter(q => q.deliveryCycle != null && q.deliveryCycle > 0).map(q => q.deliveryCycle!)
if (cycles.length > 0) {
const minCycle = Math.min(...cycles)
quoteList.value.forEach(quote => {
if (quote.deliveryCycle != null && quote.deliveryCycle > 0) {
quote.deliveryScore = (minCycle / quote.deliveryCycle) * 100
} else {
quote.deliveryScore = 0
}
})
} else {
quoteList.value.forEach(quote => {
quote.deliveryScore = 0
})
}
// 计算综合得分
quoteList.value.forEach(quote => {
quote.totalScore =
(quote.priceScore || 0) * (weightConfig.priceWeight / 100) +
(quote.qualityScore || 0) * (weightConfig.qualityWeight / 100) +
(quote.deliveryScore || 0) * (weightConfig.deliveryWeight / 100) +
(quote.serviceScore || 0) * (weightConfig.serviceWeight / 100)
})
}
/** 开始比价 */
const handleCompare = () => {
// 校验权重合计
if (totalWeight.value !== 100) {
message.warning('权重合计必须等于100%')
return
}
// 校验是否有报价
if (quoteList.value.length < 2) {
message.warning('至少需要2个供应商报价才能进行比价')
return
}
// 校验是否有单价
const noPriceQuotes = quoteList.value.filter(q => q.unitPrice == null || q.unitPrice <= 0)
if (noPriceQuotes.length > 0) {
message.warning('所有供应商必须填写单价')
return
}
// 使用已有的价格得分和交付得分(在明细中已自动计算),根据自定义权重重新计算综合得分
quoteList.value.forEach(quote => {
quote.totalScore =
(quote.priceScore || 0) * (weightConfig.priceWeight / 100) +
(quote.qualityScore || 0) * (weightConfig.qualityWeight / 100) +
(quote.deliveryScore || 0) * (weightConfig.deliveryWeight / 100) +
(quote.serviceScore || 0) * (weightConfig.serviceWeight / 100)
})
// 找出得分最高的供应商
const sortedQuotes = [...quoteList.value].sort((a, b) =>
(b.totalScore || 0) - (a.totalScore || 0)
)
bestQuote.value = sortedQuotes[0]
// 标记选中状态
quoteList.value.forEach(quote => {
quote.isSelected = quote === bestQuote.value
})
message.success(`比价完成,推荐供应商: ${bestQuote.value.supplierName}`)
}
/** 保存比价结果 */
const handleSave = async () => {
if (!inquiryData.value || !bestQuote.value) {
return
}
loading.value = true
try {
// 更新比价单数据
const data = {
...inquiryData.value,
quotes: quoteList.value
}
await PurchaseInquiryApi.updatePurchaseInquiry(data)
message.success('保存成功')
dialogVisible.value = false
emit('success')
} finally {
loading.value = false
}
}
</script>
<style lang="scss" scoped>
.mobile-compare {
padding: 12px;
&__section {
background: #fff;
border-radius: 10px;
padding: 14px;
margin-bottom: 12px;
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.06);
}
&__section-title {
font-size: 15px;
font-weight: 600;
color: #303133;
margin-bottom: 12px;
padding-bottom: 8px;
border-bottom: 1px solid #f0f0f0;
display: flex;
align-items: center;
}
&__info-row {
display: flex;
justify-content: space-between;
padding: 4px 0;
font-size: 13px;
color: #606266;
}
&__info-label {
color: #909399;
flex-shrink: 0;
margin-right: 10px;
}
&__weight-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 8px;
}
&__card-list {
display: flex;
flex-direction: column;
gap: 10px;
}
&__card {
border: 2px solid #f0f0f0;
border-radius: 8px;
overflow: hidden;
transition: border-color 0.2s;
&--best {
border-color: #67c23a;
background: #f0f9eb;
}
}
&__card-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px 12px;
background: #fafafa;
border-bottom: 1px solid #f0f0f0;
}
&__card-name {
font-weight: 600;
font-size: 14px;
color: #303133;
}
&__card-info {
padding: 8px 12px;
}
&__card-scores {
padding: 8px 12px;
border-top: 1px dashed #e4e7ed;
}
&__score-input-group {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 8px;
margin-bottom: 8px;
}
&__score-input-label {
font-size: 12px;
color: #909399;
display: block;
margin-bottom: 4px;
}
&__score-results {
display: flex;
justify-content: space-around;
padding-top: 8px;
border-top: 1px solid #f0f0f0;
}
&__score-item {
text-align: center;
font-size: 13px;
&--total {
font-weight: 600;
}
}
&__score-item-label {
display: block;
font-size: 11px;
color: #909399;
margin-bottom: 2px;
}
&__tips {
margin-top: 10px;
font-size: 11px;
color: #909399;
line-height: 1.6;
p { margin: 0; }
}
}
</style>

View File

@@ -0,0 +1,322 @@
<template>
<el-drawer
v-model="dialogVisible"
:title="dialogTitle"
direction="rtl"
size="100%"
:close-on-press-escape="true"
:destroy-on-close="true"
:append-to-body="true"
class="mobile-form-drawer"
>
<div class="mobile-form" v-loading="formLoading">
<el-form
ref="formRef"
:model="formData"
:rules="formRules"
label-position="top"
:disabled="disabled"
>
<!-- 基本信息 -->
<div class="mobile-form__section">
<div class="mobile-form__section-title">基本信息</div>
<el-form-item label="比价单号" prop="no">
<el-input disabled v-model="formData.no" placeholder="保存时自动生成" />
</el-form-item>
<el-form-item label="询价时间" prop="inquiryTime">
<el-date-picker
v-model="formData.inquiryTime"
type="date"
value-format="x"
placeholder="选择询价时间"
style="width: 100%"
/>
</el-form-item>
<el-form-item label="截止日期" prop="deadline">
<el-date-picker
v-model="formData.deadline"
type="date"
value-format="x"
placeholder="选择截止日期"
style="width: 100%"
/>
</el-form-item>
<el-form-item label="产品" prop="productId">
<el-select
v-model="formData.productId"
clearable
filterable
placeholder="请选择产品"
style="width: 100%"
@change="onChangeProduct"
>
<el-option
v-for="item in productList"
:key="item.id"
:label="item.name"
:value="item.id"
/>
</el-select>
</el-form-item>
<div class="mobile-form__input-group">
<el-form-item label="需求数量" prop="requireCount">
<el-input-number
v-model="formData.requireCount"
controls-position="right"
:min="0.0001"
:precision="4"
placeholder="需求数量"
style="width: 100%"
/>
</el-form-item>
<el-form-item label="预算金额" prop="budgetPrice">
<el-input-number
v-model="formData.budgetPrice"
controls-position="right"
:min="0"
:precision="2"
placeholder="预算金额"
style="width: 100%"
/>
</el-form-item>
</div>
<el-form-item label="备注" prop="remark">
<el-input type="textarea" v-model="formData.remark" :rows="2" placeholder="请输入备注" />
</el-form-item>
</div>
<!-- 供应商报价 -->
<div class="mobile-form__section">
<div class="mobile-form__section-title">供应商报价</div>
<InquiryQuoteForm ref="quoteFormRef" :quotes="formData.quotes" :disabled="disabled" :require-count="formData.requireCount" />
</div>
<!-- 比价汇总 -->
<div class="mobile-form__section" v-if="formData.quotes && formData.quotes.length > 0">
<div class="mobile-form__section-title">比价汇总</div>
<div class="mobile-form__info-row">
<span class="mobile-form__info-label">报价数量</span>
<span class="mobile-form__info-value">{{ formData.quotes.length }}</span>
</div>
<div class="mobile-form__info-row">
<span class="mobile-form__info-label">最低报价</span>
<span class="mobile-form__info-value" style="color: #67c23a">{{ erpPriceInputFormatter(minQuotePrice) }}</span>
</div>
<div class="mobile-form__info-row">
<span class="mobile-form__info-label">最高报价</span>
<span class="mobile-form__info-value" style="color: #e6a23c">{{ erpPriceInputFormatter(maxQuotePrice) }}</span>
</div>
</div>
</el-form>
<!-- 底部操作按钮 -->
<div class="mobile-form__footer">
<el-button @click="dialogVisible = false"> </el-button>
<el-button @click="submitForm" type="primary" :disabled="formLoading" v-if="!disabled"> </el-button>
</div>
</div>
</el-drawer>
</template>
<script setup lang="ts">
import { nextTick } from 'vue'
import { PurchaseInquiryApi, PurchaseInquiryVO } from '@/api/erp/purchase/inquiry'
import InquiryQuoteForm from './components/InquiryQuoteForm.vue'
import { ProductApi, ProductVO } from '@/api/erp/product/product'
import { erpPriceInputFormatter } from '@/utils'
/** ERP 采购比价表单 */
defineOptions({ name: 'InquiryForm' })
const { t } = useI18n() // 国际化
const message = useMessage() // 消息弹窗
// 类型声明
interface InquiryFormModel {
id?: number
productId?: number
requireCount: number
budgetPrice: number
inquiryTime?: number
deadline?: number
remark?: string
quotes: PurchaseInquiryQuoteVO[]
no?: string
}
const dialogVisible = ref(false) // 弹窗的是否展示
const dialogTitle = ref('') // 弹窗的标题
const formLoading = ref(false) // 表单的加载中
const formType = ref('') // 表单的类型create - 新增update - 修改detail - 详情
const formData = ref<InquiryFormModel>({
id: undefined,
productId: undefined,
requireCount: 1,
budgetPrice: 0,
inquiryTime: Date.now(),
deadline: undefined,
remark: undefined,
quotes: [],
no: undefined
})
const formRules = reactive({
productId: [{ required: true, message: '产品不能为空', trigger: 'blur' }],
requireCount: [{ required: true, message: '需求数量不能为空', trigger: 'blur' }],
inquiryTime: [{ required: true, message: '询价时间不能为空', trigger: 'blur' }]
})
const disabled = computed(() => formType.value === 'detail')
const formRef = ref() // 表单 Ref
const productList = ref<ProductVO[]>([]) // 产品列表
const quoteFormRef = ref()
/** 计算最低报价 */
const minQuotePrice = computed(() => {
if (!formData.value.quotes || formData.value.quotes.length === 0) {
return 0
}
const prices = formData.value.quotes
.filter((q) => q.unitPrice != null && q.unitPrice > 0)
.map((q) => q.unitPrice)
return prices.length > 0 ? Math.min(...prices) : 0
})
/** 计算最高报价 */
const maxQuotePrice = computed(() => {
if (!formData.value.quotes || formData.value.quotes.length === 0) {
return 0
}
const prices = formData.value.quotes
.filter((q) => q.unitPrice != null && q.unitPrice > 0)
.map((q) => q.unitPrice)
return prices.length > 0 ? Math.max(...prices) : 0
})
/** 处理产品变更 */
const onChangeProduct = (productId: number) => {
const product = productList.value.find((item) => item.id === productId)
if (product) {
// 可以根据产品设置默认值
}
}
/** 打开弹窗 */
const open = async (type: string, id?: number) => {
dialogVisible.value = true
dialogTitle.value = t('action.' + type)
formType.value = type
await nextTick()
resetForm()
// 修改时,设置数据
if (id) {
formLoading.value = true
try {
formData.value = (await PurchaseInquiryApi.getPurchaseInquiry(id)) as unknown as InquiryFormModel
} finally {
formLoading.value = false
}
}
// 加载产品列表
productList.value = await ProductApi.getProductSimpleList()
}
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
/** 提交表单 */
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
const submitForm = async () => {
// 校验表单
await formRef.value.validate()
await quoteFormRef.value.validate()
// 提交请求
formLoading.value = true
try {
const data = formData.value as unknown as PurchaseInquiryVO
if (formType.value === 'create') {
await PurchaseInquiryApi.createPurchaseInquiry(data)
message.success(t('common.createSuccess'))
} else {
await PurchaseInquiryApi.updatePurchaseInquiry(data)
message.success(t('common.updateSuccess'))
}
dialogVisible.value = false
// 发送操作成功的事件
emit('success')
} finally {
formLoading.value = false
}
}
/** 重置表单 */
const resetForm = () => {
formData.value = {
id: undefined,
productId: undefined,
requireCount: 1,
budgetPrice: 0,
inquiryTime: Date.now(),
deadline: undefined,
remark: undefined,
quotes: []
}
formRef.value?.resetFields()
}
</script>
<style lang="scss" scoped>
.mobile-form {
padding: 12px;
}
.mobile-form__section {
background: #fff;
border-radius: 10px;
padding: 14px;
margin-bottom: 12px;
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.06);
}
.mobile-form__section-title {
font-size: 15px;
font-weight: 600;
color: #303133;
margin-bottom: 12px;
padding-bottom: 8px;
border-bottom: 1px solid #f0f0f0;
}
.mobile-form__input-group {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 8px;
}
.mobile-form__info-row {
display: flex;
justify-content: space-between;
align-items: center;
padding: 6px 0;
font-size: 14px;
}
.mobile-form__info-label {
color: #909399;
}
.mobile-form__info-value {
font-weight: 600;
color: #303133;
}
.mobile-form__footer {
position: sticky;
bottom: 0;
background: #fff;
padding: 12px 16px;
padding-bottom: calc(12px + constant(safe-area-inset-bottom));
padding-bottom: calc(12px + env(safe-area-inset-bottom));
border-top: 1px solid #eee;
display: flex;
justify-content: flex-end;
gap: 12px;
z-index: 10;
margin: 0 -12px -12px;
.el-button {
flex: 1;
height: 40px;
font-size: 15px;
}
}
</style>

View File

@@ -0,0 +1,511 @@
<template>
<el-form
ref="formRef"
:model="formData"
:rules="formRules"
v-loading="formLoading"
label-position="top"
:inline-message="true"
:disabled="disabled"
>
<div class="mobile-quote-list">
<div
v-for="(row, $index) in formData"
:key="$index"
class="mobile-quote-card"
:class="{ 'mobile-quote-card--selected': row.isSelected }"
>
<div class="mobile-quote-card__header">
<div class="mobile-quote-card__header-left">
<el-checkbox v-model="row.isSelected" @change="onSelectChange(row, $index)" :disabled="disabled" />
<span class="mobile-quote-card__index">#{{ $index + 1 }}</span>
<span class="mobile-quote-card__name">{{ row.supplierName || '未选择供应商' }}</span>
</div>
<el-button @click="handleDelete($index)" link type="danger" size="small" :disabled="disabled">删除</el-button>
</div>
<div class="mobile-quote-card__body">
<el-form-item label="供应商" :prop="`${$index}.supplierId`" :rules="formRules.supplierId">
<div class="mobile-quote-card__supplier-row">
<el-select
v-model="row.supplierId"
clearable
filterable
@change="onChangeSupplier($event, row)"
placeholder="请选择供应商"
style="flex: 1"
>
<el-option v-for="item in supplierList" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
<el-button v-if="!disabled" type="primary" size="small" @click.stop="openQuickSupplierForm($index)">新增</el-button>
</div>
</el-form-item>
<div class="mobile-quote-card__input-group">
<el-form-item label="联系人" :prop="`${$index}.contactName`">
<el-input v-model="row.contactName" placeholder="联系人" />
</el-form-item>
<el-form-item label="联系电话" :prop="`${$index}.contactPhone`">
<el-input v-model="row.contactPhone" placeholder="联系电话" />
</el-form-item>
</div>
<el-form-item label="报价日期" :prop="`${$index}.quoteDate`">
<el-date-picker v-model="row.quoteDate" type="date" value-format="x" placeholder="报价日期" style="width: 100%" />
</el-form-item>
<div class="mobile-quote-card__input-group">
<el-form-item label="单价" :prop="`${$index}.unitPrice`" :rules="formRules.unitPrice">
<el-input-number v-model="row.unitPrice" controls-position="right" :min="0" :precision="4" style="width: 100%" placeholder="单价" />
</el-form-item>
<el-form-item label="总价">
<el-input disabled v-model="row.totalPrice" :formatter="erpPriceInputFormatter" />
</el-form-item>
</div>
<div class="mobile-quote-card__input-group">
<el-form-item label="付款条件" :prop="`${$index}.paymentTerms`">
<el-select v-model="row.paymentTerms" placeholder="付款条件" clearable style="width: 100%">
<el-option label="款到发货" value="款到发货" />
<el-option label="货到付款" value="货到付款" />
<el-option label="月结30天" value="月结30天" />
<el-option label="月结60天" value="月结60天" />
<el-option label="月结90天" value="月结90天" />
<el-option label="预付30%" value="预付30%" />
<el-option label="预付50%" value="预付50%" />
<el-option label="其他" value="其他" />
</el-select>
</el-form-item>
<el-form-item label="交货周期(天)" :prop="`${$index}.deliveryCycle`">
<el-input-number v-model="row.deliveryCycle" controls-position="right" :min="0" :precision="0" style="width: 100%" placeholder="天数" />
</el-form-item>
</div>
<el-form-item label="资质文件" :prop="`${$index}.qualificationFile`">
<UploadFile :is-show-tip="false" v-model="row.qualificationFile" :limit="1" />
</el-form-item>
<el-form-item label="备注" :prop="`${$index}.remark`">
<el-input v-model="row.remark" placeholder="请输入备注" />
</el-form-item>
<!-- 评分区域 -->
<div class="mobile-quote-card__scores">
<div class="mobile-quote-card__scores-title">评分</div>
<div class="mobile-quote-card__input-group">
<el-form-item label="质量评分" :prop="`${$index}.qualityScore`">
<el-input-number v-model="row.qualityScore" controls-position="right" :min="0" :max="100" :precision="0" style="width: 100%" placeholder="0-100" />
</el-form-item>
<el-form-item label="服务评分" :prop="`${$index}.serviceScore`">
<el-input-number v-model="row.serviceScore" controls-position="right" :min="0" :max="100" :precision="0" style="width: 100%" placeholder="0-100" />
</el-form-item>
</div>
<div class="mobile-quote-card__score-row">
<span class="mobile-quote-card__score-label">价格得分</span>
<span :class="row.priceScore >= 100 ? 'mobile-quote-card__score-best' : ''">{{ row.priceScore?.toFixed(2) ?? '-' }}</span>
</div>
<div class="mobile-quote-card__score-row">
<span class="mobile-quote-card__score-label">交付得分</span>
<span :class="row.deliveryScore >= 100 ? 'mobile-quote-card__score-best' : ''">{{ row.deliveryScore?.toFixed(2) ?? '-' }}</span>
</div>
<div class="mobile-quote-card__score-row mobile-quote-card__score-row--total">
<span class="mobile-quote-card__score-label">综合得分</span>
<el-tag v-if="row.totalScore != null" :type="row.isSelected ? 'success' : 'info'" size="small">{{ row.totalScore?.toFixed(2) }}</el-tag>
<span v-else style="color: #c0c4cc">-</span>
</div>
</div>
</div>
</div>
<!-- 合计 -->
<div class="mobile-quote-summary" v-if="formData.length > 0">
<div class="mobile-quote-summary__row">
<span>合计总价</span>
<span>{{ erpPriceInputFormatter(summaryTotalPrice) }}</span>
</div>
</div>
<!-- 添加按钮 -->
<div class="mobile-quote-add" v-if="!disabled">
<el-button @click="handleAdd" round>+ 添加供应商报价</el-button>
</div>
</div>
</el-form>
<!-- 快速新增供应商弹窗 -->
<QuickSupplierForm ref="quickSupplierFormRef" @success="onQuickSupplierSuccess" />
</template>
<script setup lang="ts">
import { SupplierApi, SupplierVO } from '@/api/erp/purchase/supplier'
import { SupplierEvaluationApi } from '@/api/erp/purchase/supplierEvaluation'
import { erpPriceInputFormatter, erpPriceMultiply, getSumValue } from '@/utils'
import { PurchaseInquiryQuoteVO } from '@/api/erp/purchase/inquiry'
import QuickSupplierForm from './QuickSupplierForm.vue'
const props = withDefaults(
defineProps<{
quotes: PurchaseInquiryQuoteVO[]
disabled?: boolean
requireCount?: number
}>(),
{
quotes: () => [],
disabled: false,
requireCount: 1
}
)
const formLoading = ref(false) // 表单的加载中
const formData = ref<PurchaseInquiryQuoteVO[]>([])
const formRules = reactive({
supplierId: [{ required: true, message: '供应商不能为空', trigger: 'blur' }],
unitPrice: [{ required: true, message: '单价不能为空', trigger: 'blur' }]
})
const formRef = ref<any>([]) // 表单 Ref
const supplierList = ref<SupplierVO[]>([]) // 供应商列表
/** 初始化设置报价项 */
watch(
() => props.quotes,
async (val) => {
formData.value = val as PurchaseInquiryQuoteVO[]
},
{ immediate: true }
)
/** 监听报价变化,计算总价和得分 */
watch(
() => formData.value,
(val) => {
if (!val || val.length === 0) {
return
}
// 计算总价
val.forEach((item: PurchaseInquiryQuoteVO) => {
if (item.unitPrice != null) {
item.totalPrice = erpPriceMultiply(item.unitPrice, props.requireCount) || 0
} else {
item.totalPrice = 0
}
})
// 自动计算得分
calculateScores(val)
},
{ deep: true }
)
/** 自动计算各项得分 */
const calculateScores = (quotes: PurchaseInquiryQuoteVO[]) => {
if (!quotes || quotes.length === 0) return
// 计算价格得分(价格越低得分越高)
const prices = quotes.filter(q => q.unitPrice != null && q.unitPrice > 0).map(q => q.unitPrice!)
if (prices.length > 0) {
const minPrice = Math.min(...prices)
quotes.forEach(quote => {
if (quote.unitPrice != null && quote.unitPrice > 0) {
quote.priceScore = (minPrice / quote.unitPrice) * 100
} else {
quote.priceScore = 0
}
})
}
// 计算交付得分(交货周期越短得分越高)
const cycles = quotes.filter(q => q.deliveryCycle != null && q.deliveryCycle > 0).map(q => q.deliveryCycle!)
if (cycles.length > 0) {
const minCycle = Math.min(...cycles)
quotes.forEach(quote => {
if (quote.deliveryCycle != null && quote.deliveryCycle > 0) {
quote.deliveryScore = (minCycle / quote.deliveryCycle) * 100
} else {
quote.deliveryScore = 0
}
})
} else {
quotes.forEach(quote => {
quote.deliveryScore = 0
})
}
// 计算综合得分默认权重价格40%、质量25%、交付20%、服务15%
quotes.forEach(quote => {
const priceScore = quote.priceScore || 0
const qualityScore = quote.qualityScore || 0
const deliveryScore = quote.deliveryScore || 0
const serviceScore = quote.serviceScore || 0
quote.totalScore = priceScore * 0.4 + qualityScore * 0.25 + deliveryScore * 0.2 + serviceScore * 0.15
})
}
/** 监听需求数量变化,重新计算总价 */
watch(
() => props.requireCount,
() => {
formData.value.forEach((item: PurchaseInquiryQuoteVO) => {
if (item.unitPrice != null) {
item.totalPrice = erpPriceMultiply(item.unitPrice, props.requireCount) || 0
}
})
}
)
/** 合计总价 */
const summaryTotalPrice = computed(() => {
return getSumValue(formData.value.map((item) => Number(item.totalPrice || 0)))
})
/** 新增按钮操作 */
const handleAdd = () => {
const row: PurchaseInquiryQuoteVO = {
id: undefined as any,
inquiryId: undefined as any,
supplierId: undefined as any,
supplierName: undefined as any,
contactName: undefined as any,
contactPhone: undefined as any,
quoteDate: Date.now() as any,
unitPrice: undefined as any,
totalPrice: 0,
paymentTerms: undefined as any,
deliveryCycle: undefined as any,
qualificationFile: undefined as any,
remark: undefined as any,
isSelected: false,
priceScore: undefined as any,
qualityScore: undefined as any,
deliveryScore: undefined as any,
serviceScore: undefined as any,
totalScore: undefined
}
formData.value.push(row)
}
/** 删除按钮操作 */
const handleDelete = (index: number) => {
formData.value.splice(index, 1)
}
/** 处理供应商变更 */
const onChangeSupplier = async (supplierId: number, row: PurchaseInquiryQuoteVO) => {
console.log('onChangeSupplier called with supplierId:', supplierId)
console.log('Current row before changes:', JSON.parse(JSON.stringify(row)))
const supplier = supplierList.value.find((item) => item.id === supplierId)
console.log('Found supplier:', supplier)
if (supplier) {
// 使用 Object.assign 确保响应式更新
Object.assign(row, {
supplierName: supplier.name,
contactName: supplier.contact || '',
contactPhone: supplier.mobile || supplier.telephone || ''
})
console.log('Basic supplier info assigned:', {
supplierName: row.supplierName,
contactName: row.contactName,
contactPhone: row.contactPhone
})
// 自动获取供应商历史评分
try {
console.log('Fetching supplier average score for supplierId:', supplierId)
const averageScore = await SupplierEvaluationApi.getSupplierAverageScore(supplierId)
debugger;
console.log('API response averageScore:', averageScore)
if (averageScore) {
const qualityScore = Math.round(averageScore.avgQualityScore || 0)
const serviceScore = Math.round(averageScore.avgServiceScore || 0)
const priceScore = Math.round(averageScore.avgPriceScore || 0)
const deliveryScore = Math.round(averageScore.avgDeliveryScore || 0)
console.log('Calculated scores:', { qualityScore, serviceScore })
// 使用 Object.assign 确保响应式更新
Object.assign(row, {
qualityScore: qualityScore,
serviceScore: serviceScore,
priceScore: priceScore,
deliveryScore: deliveryScore
})
console.log('Scores assigned to row:', {
qualityScore: row.qualityScore,
serviceScore: row.serviceScore
})
// 强制触发响应式更新
await nextTick()
console.log('After nextTick - row scores:', {
qualityScore: row.qualityScore,
serviceScore: row.serviceScore
})
// 注意priceScore 和 deliveryScore 会在输入单价和交货周期后自动计算
} else {
console.log('No averageScore data returned from API')
Object.assign(row, {
qualityScore: 0,
serviceScore: 0
})
}
} catch (error) {
console.error('获取供应商评分失败:', error)
// 如果获取评分失败,使用默认值
Object.assign(row, {
qualityScore: 0,
serviceScore: 0
})
}
console.log('Final row after all changes:', JSON.parse(JSON.stringify(row)))
} else {
console.log('No supplier found with id:', supplierId)
}
}
/** 处理选中变更 - 单选逻辑 */
const onSelectChange = (row: PurchaseInquiryQuoteVO, index: number) => {
if (row.isSelected) {
// 取消其他选中
formData.value.forEach((item, i) => {
if (i !== index) {
item.isSelected = false
}
})
}
}
/** 表格行样式 - 选中行变绿 */
const tableRowClassName = ({ row }: { row: PurchaseInquiryQuoteVO }) => {
return row.isSelected ? 'selected-row' : ''
}
/** 表单校验 */
const validate = () => {
return (formRef.value as any).validate()
}
defineExpose({ validate })
/** 快速新增供应商 */
const quickSupplierFormRef = ref()
const currentRowIndex = ref<number>(-1)
const openQuickSupplierForm = (index: number) => {
currentRowIndex.value = index
quickSupplierFormRef.value?.open()
}
/** 快速新增供应商成功回调 */
const onQuickSupplierSuccess = async (supplierId: number) => {
// 刷新供应商列表
supplierList.value = await SupplierApi.getSupplierSimpleList()
// 自动选中新增的供应商
if (currentRowIndex.value >= 0 && currentRowIndex.value < formData.value.length) {
const row = formData.value[currentRowIndex.value]
row.supplierId = supplierId
onChangeSupplier(supplierId, row)
}
}
/** 初始化 */
onMounted(async () => {
supplierList.value = await SupplierApi.getSupplierSimpleList()
})
</script>
<style lang="scss" scoped>
.mobile-quote-list {
display: flex;
flex-direction: column;
gap: 10px;
}
.mobile-quote-card {
background: #fff;
border-radius: 10px;
border: 2px solid #f0f0f0;
overflow: hidden;
transition: border-color 0.2s;
&--selected {
border-color: #67c23a;
background: #f0f9eb;
}
&__header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px 12px;
background: #fafafa;
border-bottom: 1px solid #f0f0f0;
}
&__header-left {
display: flex;
align-items: center;
gap: 6px;
}
&__index {
font-weight: 600;
font-size: 13px;
color: #909399;
}
&__name {
font-weight: 600;
font-size: 14px;
color: #303133;
}
&__body {
padding: 12px;
}
&__supplier-row {
display: flex;
gap: 8px;
width: 100%;
}
&__input-group {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 8px;
}
&__scores {
margin-top: 8px;
padding-top: 8px;
border-top: 1px dashed #e4e7ed;
}
&__scores-title {
font-size: 13px;
font-weight: 600;
color: #606266;
margin-bottom: 8px;
}
&__score-row {
display: flex;
justify-content: space-between;
padding: 3px 0;
font-size: 13px;
color: #606266;
&--total {
padding-top: 6px;
margin-top: 4px;
border-top: 1px solid #f0f0f0;
font-weight: 600;
}
}
&__score-label {
color: #909399;
}
&__score-best {
color: #67c23a;
font-weight: 600;
}
}
.mobile-quote-summary {
background: #fff;
border-radius: 10px;
padding: 12px;
&__row {
display: flex;
justify-content: space-between;
font-size: 14px;
font-weight: 600;
color: #303133;
}
}
.mobile-quote-add {
display: flex;
justify-content: center;
padding: 8px 0;
}
</style>

View File

@@ -0,0 +1,115 @@
<template>
<el-drawer
v-model="dialogVisible"
title="快速新增供应商"
direction="rtl"
size="100%"
:close-on-press-escape="true"
:destroy-on-close="true"
>
<div class="mobile-quick-supplier" v-loading="formLoading">
<el-form
ref="formRef"
:model="formData"
:rules="formRules"
label-position="top"
>
<el-form-item label="供应商名称" prop="name">
<el-input v-model="formData.name" placeholder="请输入供应商名称" />
</el-form-item>
<el-form-item label="联系人" prop="contact">
<el-input v-model="formData.contact" placeholder="请输入联系人" />
</el-form-item>
<el-form-item label="手机号码" prop="mobile">
<el-input v-model="formData.mobile" placeholder="请输入手机号码" />
</el-form-item>
<el-form-item label="联系电话" prop="telephone">
<el-input v-model="formData.telephone" placeholder="请输入联系电话" />
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input type="textarea" v-model="formData.remark" placeholder="请输入备注" :rows="2" />
</el-form-item>
</el-form>
</div>
<template #footer>
<el-button @click="dialogVisible = false"> </el-button>
<el-button @click="submitForm" type="primary" :loading="formLoading"> </el-button>
</template>
</el-drawer>
</template>
<script setup lang="ts">
import { SupplierApi, SupplierVO } from '@/api/erp/purchase/supplier'
import { CommonStatusEnum } from '@/utils/constants'
defineOptions({ name: 'QuickSupplierForm' })
const message = useMessage()
const dialogVisible = ref(false)
const formLoading = ref(false)
const formRef = ref()
const formData = ref({
name: undefined as string | undefined,
contact: undefined as string | undefined,
mobile: undefined as string | undefined,
telephone: undefined as string | undefined,
remark: undefined as string | undefined,
status: CommonStatusEnum.ENABLE,
sort: 0,
type: 1 // 供应商类型
})
const formRules = reactive({
name: [{ required: true, message: '供应商名称不能为空', trigger: 'blur' }],
mobile: [{ pattern: /^1[3-9]\d{9}$/, message: '手机号码格式不正确', trigger: 'blur' }]
})
/** 打开弹窗 */
const open = () => {
dialogVisible.value = true
resetForm()
}
/** 提交表单 */
const emit = defineEmits<{
success: [supplierId: number]
}>()
const submitForm = async () => {
await formRef.value?.validate()
formLoading.value = true
try {
const data = formData.value as unknown as SupplierVO
const supplierId = await SupplierApi.createSupplier(data)
message.success('新增供应商成功')
dialogVisible.value = false
emit('success', supplierId)
} finally {
formLoading.value = false
}
}
/** 重置表单 */
const resetForm = () => {
formData.value = {
name: undefined,
contact: undefined,
mobile: undefined,
telephone: undefined,
remark: undefined,
status: CommonStatusEnum.ENABLE,
sort: 0,
type: 1
}
formRef.value?.resetFields()
}
defineExpose({ open })
</script>
<style lang="scss" scoped>
.mobile-quick-supplier {
padding: 12px;
}
</style>

View File

@@ -0,0 +1,269 @@
<template>
<div class="mobile-purchase-inquiry">
<!-- 顶部操作栏 -->
<div class="mobile-header">
<div class="mobile-header__search">
<el-input v-model="queryParams.no" placeholder="搜索比价单号" clearable @keyup.enter="handleQuery" :prefix-icon="Search" />
</div>
<div class="mobile-header__actions">
<el-button :icon="Filter" circle @click="filterVisible = true" />
<el-button type="primary" :icon="Plus" circle @click="openForm('create')" v-hasPermi="['erp:purchase-inquiry:create']" />
</div>
</div>
<!-- 卡片列表 -->
<div class="mobile-list" v-loading="loading">
<div v-if="list.length === 0 && !loading" class="mobile-empty">
<el-empty description="暂无比价记录" />
</div>
<div v-for="item in list" :key="item.id" class="mobile-card" @click="handleCardClick(item)">
<div class="mobile-card__header">
<span class="mobile-card__no">{{ item.no }}</span>
<el-tag v-if="item.status === 10" type="info" size="small">待询价</el-tag>
<el-tag v-else-if="item.status === 20" type="warning" size="small">询价中</el-tag>
<el-tag v-else-if="item.status === 30" type="success" size="small">已完成</el-tag>
</div>
<div class="mobile-card__body">
<div class="mobile-card__row">
<span class="mobile-card__label">产品</span>
<span class="mobile-card__value">{{ item.productName || '-' }}</span>
</div>
<div class="mobile-card__row">
<span class="mobile-card__label">询价时间</span>
<span class="mobile-card__value">{{ formatDate2(item.inquiryTime) }}</span>
</div>
<div class="mobile-card__row">
<span class="mobile-card__label">截止日期</span>
<span class="mobile-card__value">{{ formatDate2(item.deadline) }}</span>
</div>
<div class="mobile-card__row">
<span class="mobile-card__label">创建人</span>
<span class="mobile-card__value">{{ item.creatorName || '-' }}</span>
</div>
<div class="mobile-card__nums">
<div class="mobile-card__num-item">
<div class="mobile-card__num-val">{{ erpCountInputFormatter(item.requireCount) }}</div>
<div class="mobile-card__num-label">需求数量</div>
</div>
<div class="mobile-card__num-item">
<div class="mobile-card__num-val mobile-card__num-val--price">¥{{ erpPriceInputFormatter(item.budgetPrice) }}</div>
<div class="mobile-card__num-label">预算金额</div>
</div>
<div class="mobile-card__num-item">
<div class="mobile-card__num-val">{{ item.quoteCount || 0 }}</div>
<div class="mobile-card__num-label">报价数</div>
</div>
<div class="mobile-card__num-item">
<div class="mobile-card__num-val" style="color:#67c23a">¥{{ erpPriceInputFormatter(item.minQuotePrice) }}</div>
<div class="mobile-card__num-label">最低报价</div>
</div>
</div>
</div>
<div class="mobile-card__footer">
<el-button size="small" @click.stop="openForm('detail', item.id)" v-hasPermi="['erp:purchase-inquiry:query']">详情</el-button>
<el-button size="small" type="primary" @click.stop="openForm('update', item.id)" v-hasPermi="['erp:purchase-inquiry:update']" :disabled="item.status === 30">编辑</el-button>
<el-button size="small" type="primary" @click.stop="handleUpdateStatus(item.id, 20)" v-hasPermi="['erp:purchase-inquiry:update-status']" v-if="item.status === 10">询价</el-button>
<el-button size="small" type="warning" @click.stop="openAutoCompare(item.id)" v-hasPermi="['erp:purchase-inquiry:update']" v-if="item.status === 20 && item.quoteCount >= 2">比价</el-button>
<el-button size="small" type="success" @click.stop="handleUpdateStatus(item.id, 30)" v-hasPermi="['erp:purchase-inquiry:update-status']" v-if="item.status === 20">完成</el-button>
<el-button size="small" type="danger" @click.stop="handleDelete([item.id])" v-hasPermi="['erp:purchase-inquiry:delete']">删除</el-button>
</div>
</div>
</div>
<!-- 分页 -->
<div class="mobile-pagination" v-if="total > 0">
<Pagination :total="total" v-model:page="queryParams.pageNo" v-model:limit="queryParams.pageSize" :page-sizes="[10, 20]" layout="total, prev, pager, next" :pager-count="5" @pagination="getList" />
</div>
<!-- 筛选抽屉 -->
<el-drawer v-model="filterVisible" title="筛选条件" direction="btt" size="60%">
<el-form :model="queryParams" ref="queryFormRef" label-position="top">
<el-form-item label="产品" prop="productId">
<el-select v-model="queryParams.productId" clearable filterable placeholder="请选择产品" style="width:100%">
<el-option v-for="item in productList" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
</el-form-item>
<el-form-item label="询价时间" prop="inquiryTime">
<el-date-picker v-model="queryParams.inquiryTime" value-format="YYYY-MM-DD HH:mm:ss" type="daterange" start-placeholder="开始" end-placeholder="结束" :default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]" style="width:100%" />
</el-form-item>
<el-form-item label="状态" prop="status">
<el-select v-model="queryParams.status" placeholder="请选择状态" clearable style="width:100%">
<el-option label="待询价" :value="10" />
<el-option label="询价中" :value="20" />
<el-option label="已完成" :value="30" />
</el-select>
</el-form-item>
<el-form-item label="创建人" prop="creator">
<el-select v-model="queryParams.creator" clearable filterable placeholder="请选择创建人" style="width:100%">
<el-option v-for="item in userList" :key="item.id" :label="item.nickname" :value="item.id" />
</el-select>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="resetQuery">重置</el-button>
<el-button type="primary" @click="handleFilterConfirm">确认筛选</el-button>
</template>
</el-drawer>
<!-- 表单弹窗添加/修改 -->
<InquiryForm ref="formRef" @success="getList" />
<!-- 自动比价弹窗 -->
<AutoCompareDialog ref="autoCompareRef" @success="getList" />
</div>
</template>
<script setup lang="ts">
import { Search, Filter, Plus } from '@element-plus/icons-vue'
import { formatDate } from '@/utils/formatTime'
import download from '@/utils/download'
import { PurchaseInquiryApi, PurchaseInquiryVO } from '@/api/erp/purchase/inquiry'
import InquiryForm from './InquiryForm.vue'
import AutoCompareDialog from './AutoCompareDialog.vue'
import { ProductApi, ProductVO } from '@/api/erp/product/product'
import { UserVO } from '@/api/system/user'
import * as UserApi from '@/api/system/user'
import { erpCountInputFormatter, erpPriceInputFormatter } from '@/utils'
/** ERP 采购比价列表 */
defineOptions({ name: 'ErpPurchaseInquiry' })
const message = useMessage()
const { t } = useI18n()
const loading = ref(true)
const list = ref<PurchaseInquiryVO[]>([])
const total = ref(0)
const filterVisible = ref(false)
const queryParams = reactive({
pageNo: 1,
pageSize: 10,
no: undefined,
productId: undefined,
inquiryTime: [],
status: undefined,
creator: undefined
})
const queryFormRef = ref()
const exportLoading = ref(false)
const productList = ref<ProductVO[]>([])
const userList = ref<UserVO[]>([])
const formatDate2 = (date: any) => {
if (!date) return '-'
return formatDate(new Date(date), 'YYYY-MM-DD')
}
const getList = async () => {
loading.value = true
try {
const data = await PurchaseInquiryApi.getPurchaseInquiryPage(queryParams)
list.value = data.list
total.value = data.total
} finally {
loading.value = false
}
}
const handleQuery = () => {
queryParams.pageNo = 1
getList()
}
const resetQuery = () => {
queryFormRef.value?.resetFields()
handleQuery()
}
const handleFilterConfirm = () => {
filterVisible.value = false
handleQuery()
}
const formRef = ref()
const openForm = (type: string, id?: number) => {
formRef.value.open(type, id)
}
const autoCompareRef = ref()
const openAutoCompare = (id: number) => {
autoCompareRef.value.open(id)
}
const handleCardClick = (row: PurchaseInquiryVO) => {
if (row.status === 30) {
openForm('detail', row.id)
} else {
openForm('update', row.id)
}
}
const handleDelete = async (ids: number[]) => {
try {
await message.delConfirm()
await PurchaseInquiryApi.deletePurchaseInquiry(ids)
message.success(t('common.delSuccess'))
await getList()
} catch {}
}
const handleUpdateStatus = async (id: number, status: number) => {
try {
const statusText = status === 20 ? '开始询价' : '完成'
await message.confirm(`确定${statusText}该比价单吗?`)
await PurchaseInquiryApi.updatePurchaseInquiryStatus(id, status)
message.success(`${statusText}成功`)
await getList()
} catch {}
}
const handleExport = async () => {
try {
await message.exportConfirm()
exportLoading.value = true
const data = await PurchaseInquiryApi.exportPurchaseInquiry(queryParams)
download.excel(data, '采购比价.xls')
} catch {
} finally {
exportLoading.value = false
}
}
onMounted(async () => {
await getList()
productList.value = await ProductApi.getProductSimpleList()
userList.value = await UserApi.getSimpleUserList()
})
</script>
<style lang="scss" scoped>
.mobile-purchase-inquiry {
padding: 12px;
background: #f5f5f5;
min-height: 100vh;
}
.mobile-header {
display: flex; gap: 8px; align-items: center; margin-bottom: 12px;
&__search { flex: 1; }
&__actions { display: flex; gap: 4px; flex-shrink: 0; }
}
.mobile-list { display: flex; flex-direction: column; gap: 10px; }
.mobile-empty { padding: 40px 0; }
.mobile-card {
background: #fff; border-radius: 10px; padding: 14px; box-shadow: 0 1px 4px rgba(0,0,0,0.06);
&__header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px; }
&__no { font-weight: 600; font-size: 15px; color: #303133; }
&__body { font-size: 13px; }
&__row { display: flex; justify-content: space-between; padding: 3px 0; }
&__label { color: #909399; flex-shrink: 0; margin-right: 12px; }
&__value { color: #606266; text-align: right; }
&__nums { display: flex; justify-content: space-around; margin-top: 10px; padding: 10px 0; border-top: 1px solid #f0f0f0; border-bottom: 1px solid #f0f0f0; }
&__num-item { text-align: center; }
&__num-val { font-size: 15px; font-weight: 600; color: #303133; &--price { color: #e6a23c; } }
&__num-label { font-size: 11px; color: #909399; margin-top: 2px; }
&__footer { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 10px; }
}
.mobile-pagination {
margin-top: 12px; display: flex; justify-content: center;
:deep(.el-pagination) { flex-wrap: wrap; justify-content: center; }
}
</style>