154 lines
6.3 KiB
Vue
154 lines
6.3 KiB
Vue
<template>
|
|
<div>
|
|
<el-card shadow="never">
|
|
<div style="display:flex;gap:12px;margin-bottom:16px;flex-wrap:wrap">
|
|
<el-select v-model="statusFilter" placeholder="状态" clearable style="width:140px" @change="load">
|
|
<el-option label="草稿" value="draft" />
|
|
<el-option label="已发送" value="sent" />
|
|
<el-option label="已接受" value="accepted" />
|
|
</el-select>
|
|
<el-button type="primary" @click="showForm = true">新建报价</el-button>
|
|
<el-button @click="showInquiry = true">AI 智能报价</el-button>
|
|
</div>
|
|
<el-table :data="list" v-loading="loading" stripe style="width:100%">
|
|
<el-table-column prop="title" label="标题" min-width="160" />
|
|
<el-table-column label="客户" width="140">
|
|
<template #default="{ row }">{{ row.customer_name || row.customer?.name || '-' }}</template>
|
|
</el-table-column>
|
|
<el-table-column label="金额" width="120">
|
|
<template #default="{ row }">{{ row.total_amount }} {{ row.currency || 'USD' }}</template>
|
|
</el-table-column>
|
|
<el-table-column label="状态" width="100">
|
|
<template #default="{ row }">
|
|
<el-tag :type="statusType(row.status)" size="small">{{ statusLabel(row.status) }}</el-tag>
|
|
</template>
|
|
</el-table-column>
|
|
<el-table-column prop="created_at" label="创建时间" width="170" />
|
|
<el-table-column label="操作" width="200" fixed="right">
|
|
<template #default="{ row }">
|
|
<el-button v-if="row.status === 'draft'" type="primary" link size="small" @click="markSent(row)">标记已发</el-button>
|
|
<el-button type="primary" link size="small" @click="editRow(row)">编辑</el-button>
|
|
</template>
|
|
</el-table-column>
|
|
</el-table>
|
|
<el-empty v-if="!loading && !list.length" description="暂无报价单" />
|
|
</el-card>
|
|
|
|
<el-dialog v-model="showForm" :title="editing ? '编辑报价' : '新建报价'" width="600">
|
|
<el-form :model="form" label-width="80">
|
|
<el-form-item label="标题"><el-input v-model="form.title" /></el-form-item>
|
|
<el-form-item label="客户">
|
|
<el-select v-model="form.customer_id" filterable style="width:100%">
|
|
<el-option v-for="c in customerOptions" :key="c.id" :label="c.name" :value="c.id" />
|
|
</el-select>
|
|
</el-form-item>
|
|
<el-form-item label="金额"><el-input-number v-model="form.total_amount" :min="0" style="width:200px" /></el-form-item>
|
|
<el-form-item label="币种">
|
|
<el-select v-model="form.currency" style="width:120px">
|
|
<el-option label="USD" value="USD" />
|
|
<el-option label="CNY" value="CNY" />
|
|
<el-option label="EUR" value="EUR" />
|
|
</el-select>
|
|
</el-form-item>
|
|
<el-form-item label="备注"><el-input v-model="form.notes" type="textarea" :rows="2" /></el-form-item>
|
|
</el-form>
|
|
<template #footer>
|
|
<el-button @click="showForm = false">取消</el-button>
|
|
<el-button type="primary" :loading="saving" @click="save">保存</el-button>
|
|
</template>
|
|
</el-dialog>
|
|
|
|
<el-dialog v-model="showInquiry" title="AI 智能报价" width="500">
|
|
<el-input v-model="inquiryText" type="textarea" :rows="5" placeholder="输入客户询盘内容..." />
|
|
<div style="margin-top:12px">
|
|
<el-button type="primary" :loading="genLoading" @click="generateFromInquiry">生成报价</el-button>
|
|
</div>
|
|
<div v-if="genResult" style="margin-top:12px;padding:12px;background:#f5f5f5;border-radius:6px;white-space:pre-wrap">{{ genResult }}</div>
|
|
</el-dialog>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { ref, onMounted, reactive } from 'vue'
|
|
import { listQuotations, createQuotation, updateQuotationStatus, generateQuoteFromInquiry, listCustomers } from '@/api'
|
|
import { ElMessage } from 'element-plus'
|
|
|
|
const loading = ref(false)
|
|
const list = ref([])
|
|
const statusFilter = ref('')
|
|
const showForm = ref(false)
|
|
const showInquiry = ref(false)
|
|
const editing = ref(false)
|
|
const saving = ref(false)
|
|
const customerOptions = ref([])
|
|
const inquiryText = ref('')
|
|
const genLoading = ref(false)
|
|
const genResult = ref('')
|
|
const form = reactive({ title: '', customer_id: null, total_amount: 0, currency: 'USD', notes: '' })
|
|
|
|
function statusType(s) { return { draft: 'info', sent: 'warning', accepted: 'success' }[s] || 'info' }
|
|
function statusLabel(s) { return { draft: '草稿', sent: '已发送', accepted: '已接受' }[s] || s }
|
|
|
|
onMounted(() => { load(); loadCustomers() })
|
|
|
|
async function load() {
|
|
loading.value = true
|
|
try {
|
|
const params = { page: 1, size: 50 }
|
|
if (statusFilter.value) params.status = statusFilter.value
|
|
const res = await listQuotations(params)
|
|
const d = res.data || res
|
|
list.value = d.items || d.rows || d.data || d || []
|
|
} catch { /* ignore */ }
|
|
finally { loading.value = false }
|
|
}
|
|
|
|
async function loadCustomers() {
|
|
try {
|
|
const res = await listCustomers({ page: 1, size: 200 })
|
|
const d = res.data || res
|
|
customerOptions.value = d.items || d.rows || d || []
|
|
} catch { /* ignore */ }
|
|
}
|
|
|
|
async function save() {
|
|
saving.value = true
|
|
try {
|
|
if (editing.value) {
|
|
await createQuotation(form)
|
|
ElMessage.success('已更新')
|
|
} else {
|
|
await createQuotation(form)
|
|
ElMessage.success('已创建')
|
|
}
|
|
showForm.value = false
|
|
load()
|
|
} catch (e) { ElMessage.error(e?.detail || '操作失败') }
|
|
finally { saving.value = false }
|
|
}
|
|
|
|
async function markSent(row) {
|
|
try {
|
|
await updateQuotationStatus(row.id, 'sent')
|
|
ElMessage.success('已标记为已发送')
|
|
load()
|
|
} catch { ElMessage.error('操作失败') }
|
|
}
|
|
|
|
async function generateFromInquiry() {
|
|
if (!inquiryText.value.trim()) return
|
|
genLoading.value = true
|
|
try {
|
|
const res = await generateQuoteFromInquiry({ text: inquiryText.value })
|
|
genResult.value = res.data?.quotation || res.quotation || JSON.stringify(res.data || res, null, 2)
|
|
} catch { ElMessage.error('生成失败') }
|
|
finally { genLoading.value = false }
|
|
}
|
|
|
|
function editRow(row) {
|
|
editing.value = row.id
|
|
Object.assign(form, { title: row.title, customer_id: row.customer_id || (row.customer?.id || null), total_amount: row.total_amount || 0, currency: row.currency || 'USD', notes: row.notes || '' })
|
|
showForm.value = true
|
|
}
|
|
</script>
|