备货单 采购单

This commit is contained in:
浅念
2025-09-24 18:50:35 +08:00
parent 7b1fdf3883
commit 90df51097f
25 changed files with 2935 additions and 107 deletions
+74
View File
@@ -0,0 +1,74 @@
import request from '@/utils/http'
export class financeService {
// 收款记录-列表
static getReceiptList(params: Api.Common.PaginatingParams) {
return request.get<Api.Common.CommonListData>({
url: '/admin/receipt/list',
params
})
}
// 收款记录-详情
static getReceiptDetail(params: any) {
return request.get<any>({
url: '/admin/receipt/show',
params
})
}
// 收款记录-添加
static addReceipt(params: any) {
return request.post<Api.Common.AddResponse>({
url: '/admin/receipt/store',
params
})
}
// 收款记录-更新
static updateReceipt(params: any) {
return request.post<Api.Common.AddResponse>({
url: '/admin/receipt/update',
params
})
}
// 收款记录-删除
static deleteReceipt(params: any) {
return request.post<Api.Common.AddResponse>({
url: '/admin/receipt/destroy',
params
})
}
//付款申请-列表
static getPaymentList(params: Api.Common.PaginatingParams) {
return request.get<Api.Common.CommonListData>({
url: '/admin/payment/list',
params
})
}
//付款申请-详情
static getPaymentDetail(params: any) {
return request.get<any>({
url: '/admin/payment/show',
params
})
}
//付款申请-添加
static addPayment(params: any) {
return request.post<Api.Common.AddResponse>({
url: '/admin/payment/store',
params
})
}
//付款申请-更新
static updatePayment(params: any) {
return request.post<Api.Common.AddResponse>({
url: '/admin/payment/update',
params
})
}
//付款申请-删除
static deletePayment(params: any) {
return request.post<Api.Common.AddResponse>({
url: '/admin/payment/destroy',
params
})
}
}
+46
View File
@@ -0,0 +1,46 @@
import request from '@/utils/http'
export class purchaseService {
// 采购单预置
static purchaseorderpredata(params: any) {
return request.get<Api.Common.AddResponse>({
url: '/admin/purchase/order/predata',
params
})
}
// 采购单列表
static purchaseorderlist(params: Api.Common.PaginatingParams) {
return request.get<Api.Common.CommonListData>({
url: '/admin/purchase/order/list',
params
})
}
// 采购单添加
static purchaseorderadd(params: any) {
return request.post<Api.Common.AddResponse>({
url: '/admin/purchase/order/store',
params
})
}
// 采购单更新
static purchaseorderupdate(params: any) {
return request.post<Api.Common.AddResponse>({
url: '/admin/purchase/order/update',
params
})
}
// 采购单删除
static purchaseorderdelete(params: any) {
return request.post<Api.Common.AddResponse>({
url: '/admin/purchase/order/destroy',
params
})
}
// 采购单详情
static purchaseorderdetail(params: any) {
return request.get<any>({
url: '/admin/purchase/order/show',
params
})
}
}
+21
View File
@@ -305,4 +305,25 @@ export class manageService {
params params
}) })
} }
//审核详情
static stockapprovaldetailborder(params: any) {
return request.get<any>({
url: '/admin/approval/records/border',
params
})
}
//审批转交
static stockapprovaltransfer(params: any) {
return request.post<Api.Common.AddResponse>({
url: '/admin/approval/records/transfer',
params
})
}
//审批时间轴
static stockapprovaltimeline(params: any) {
return request.get<any>({
url: '/admin/approval/records/timeline',
params
})
}
} }
+4 -6
View File
@@ -184,12 +184,11 @@
const elTable = tableRef.value const elTable = tableRef.value
if (!elTable) return if (!elTable) return
// 递归处理树形数据 // 支持普通与树形数据行展开
const processRows = (rows: any[]) => { const processRows = (rows: any[]) => {
rows.forEach((row) => { rows.forEach((row) => {
const hasChildren = row.children?.length > 0
if (hasChildren) {
elTable.toggleRowExpansion(row, true) elTable.toggleRowExpansion(row, true)
if (row.children?.length > 0) {
processRows(row.children) processRows(row.children)
} }
}) })
@@ -203,12 +202,11 @@
const elTable = tableRef.value const elTable = tableRef.value
if (!elTable) return if (!elTable) return
// 递归处理树形数据 // 支持普通与树形数据行收起
const processRows = (rows: any[]) => { const processRows = (rows: any[]) => {
rows.forEach((row) => { rows.forEach((row) => {
const hasChildren = row.children?.length > 0
if (hasChildren) {
elTable.toggleRowExpansion(row, false) elTable.toggleRowExpansion(row, false)
if (row.children?.length > 0) {
processRows(row.children) processRows(row.children)
} }
}) })
+132
View File
@@ -0,0 +1,132 @@
<template>
<div class="approval-timeline">
<div v-for="(rec, idx) in records" :key="idx" class="timeline-item">
<div class="left">
<div class="avatar-box">
<span class="initial">{{ rec.name?.slice(0, 1) || '—' }}</span>
<i v-if="rec.approved" class="status-dot iconfont-sys">&#xe621;</i>
<i v-if="rec.rejected" class="status-dot rejected">&#xe83a;</i>
<i v-else-if="rec.pending" class="status-dot pending iconfont-sys">&#xe839;</i>
</div>
</div>
<div class="right">
<div class="row name-role">
<span class="name">{{ rec.name }}</span>
<span class="role">({{ rec.role }})</span>
</div>
<div >
审核说明:{{ rec.comment }}
</div>
<div class="row time">{{ rec.time }}</div>
</div>
</div>
<div v-if="!records || !records.length" class="empty">暂无审批记录</div>
</div>
</template>
<script setup lang="ts">
interface ApprovalRecordItem {
name?: string
role?: string
type?: string
time?: string
approved?: boolean
rejected?: boolean
pending?: boolean
comment?: string
}
defineProps<{ records: ApprovalRecordItem[] }>()
</script>
<style scoped lang="scss">
/* 审批记录 时间线 */
.approval-timeline {
padding: 8px 4px 8px 0;
}
.timeline-item {
display: flex;
align-items: flex-start;
gap: 10px;
position: relative;
}
.timeline-item + .timeline-item {
margin-top: 50px;
}
.left {
width: 46px;
display: flex;
flex-direction: column;
align-items: center;
position: relative;
}
.avatar-box {
width: 46px;
height: 46px;
background: #3d7bff;
border-radius: 10px;
color: #fff;
position: relative;
display: flex;
align-items: center;
justify-content: center;
font-weight: 700;
font-size: 20px;
}
.status-dot {
position: absolute;
right: -4px;
bottom: -4px;
font-size: 9px;
padding: 3px 1px;
border-radius: 50%;
background: #22c55e;
border: 2px solid #fff;
box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.05);
}
.status-dot.pending {
background: #3471ff;
padding: 3px 3px;
}
.status-dot.rejected {
background: #ff0000;
padding: 3px 3px;
}
/* 连接下一个节点的虚线 */
.timeline-item:not(:last-child) .left::after {
content: '';
position: absolute;
top: 52px; /* avatar 46px + 间距 6px */
left: 23px; /* 盒子宽度一半,确保竖线居中 */
width: 0;
height: calc(100% + 16px); /* 加上相邻项之间的间距 */
border-left: 2px dotted #bfbfbf;
}
.right {
flex: 1;
padding-top: 2px;
}
.row.name-role {
color: #222;
margin-bottom: 10px;
}
.name {
font-weight: 600;
}
.role {
color: #666;
margin-left: 4px;
}
.time {
color: #888;
font-size: 12px;
margin-top: 2px;
margin-top: 10px;
}
.empty {
color: #999;
padding: 8px 0 4px 4px;
}
</style>
+197 -33
View File
@@ -8,7 +8,9 @@
</div> </div>
<div class="basic-info-item"> <div class="basic-info-item">
<span class="label">业务员:</span> <span class="label">业务员:</span>
<span class="value">{{ orderData.salesperson_snapshot || orderData.salesperson || '—' }}</span> <span class="value">{{
orderData.salesperson_snapshot || orderData.salesperson || '—'
}}</span>
</div> </div>
</div> </div>
@@ -26,19 +28,27 @@
</td> </td>
<td> <td>
<span class="info-label"> 清关目的国:</span> <span class="info-label"> 清关目的国:</span>
<span class="info-value">{{ orderData.clearance_country_snapshot || orderData.clearanceCountry || '—' }}</span> <span class="info-value">{{
orderData.clearance_country_snapshot || orderData.clearanceCountry || '—'
}}</span>
</td> </td>
<td> <td>
<span class="info-label"> 客户来源:</span> <span class="info-label"> 客户来源:</span>
<span class="info-value">{{ orderData.customer_source_snapshot || orderData.customerSource || '—' }}</span> <span class="info-value">{{
orderData.customer_source_snapshot || orderData.customerSource || '—'
}}</span>
</td> </td>
<td> <td>
<span class="info-label"> 客户名称:</span> <span class="info-label"> 客户名称:</span>
<span class="info-value">{{ orderData.customer_snapshot || orderData.customerName || '—' }}</span> <span class="info-value">{{
orderData.customer_snapshot || orderData.customerName || '—'
}}</span>
</td> </td>
<td> <td>
<span class="info-label"> 客户所在国:</span> <span class="info-label"> 客户所在国:</span>
<span class="info-value">{{ orderData.customer_country_snapshot || orderData.customerCountry || '—' }}</span> <span class="info-value">{{
orderData.customer_country_snapshot || orderData.customerCountry || '—'
}}</span>
</td> </td>
</tr> </tr>
</table> </table>
@@ -46,8 +56,16 @@
<!-- 订单详情表格(ArtTable) --> <!-- 订单详情表格(ArtTable) -->
<div class="table-section"> <div class="table-section">
<ArtTable :data="vehItems" border :pagination="false"> <ArtTable ref="tableRef" :data="vehItems" border :pagination="false">
<template #default> <template #default>
<ElTableColumn type="expand" width="30">
<template #default="scope">
<div style="padding: 6px">
<strong>备注:</strong>
<span>{{ scope.row.remark || '暂无备注' }}</span>
</div>
</template>
</ElTableColumn>
<ElTableColumn label="编号" width="60" align="center"> <ElTableColumn label="编号" width="60" align="center">
<template #default="scope">{{ scope.$index + 1 }}</template> <template #default="scope">{{ scope.$index + 1 }}</template>
</ElTableColumn> </ElTableColumn>
@@ -61,66 +79,163 @@
<template #default="scope">{{ scope.row.num ?? scope.row.quantity ?? 1 }}</template> <template #default="scope">{{ scope.row.num ?? scope.row.quantity ?? 1 }}</template>
</ElTableColumn> </ElTableColumn>
<ElTableColumn label="颜色/外" align="center" prop="color_exterior_snapshot" /> <ElTableColumn label="颜色/外" align="center" prop="color_exterior_snapshot" />
<ElTableColumn label="内饰" min-width="120" align="center" prop="color_interior_snapshot" /> <ElTableColumn
label="内饰"
min-width="120"
align="center"
prop="color_interior_snapshot"
/>
<ElTableColumn label="改装" min-width="120" align="center" prop="modify" /> <ElTableColumn label="改装" min-width="120" align="center" prop="modify" />
<ElTableColumn label="官方指导价/二手挂牌价" min-width="175" align="center"> <ElTableColumn label="官方指导价/二手挂牌价" min-width="175" align="center">
<template #default="scope"> <template #default="scope">
<span v-if="scope.row.mileage > 0"> <span v-if="scope.row.mileage > 0">
{{ scope.row.listing_price?.currency_info || scope.row.listing_price?.amount || scope.row.discount || '—' }} {{
scope.row.listing_price?.currency_info ||
scope.row.listing_price?.amount ||
scope.row.discount ||
'—'
}}
</span> </span>
<span v-else> <span v-else>
{{ scope.row.official_price?.currency_info || scope.row.official_price?.amount || scope.row.discount || '—' }} {{
scope.row.official_price?.currency_info ||
scope.row.official_price?.amount ||
scope.row.discount ||
'—'
}}
</span> </span>
</template> </template>
</ElTableColumn> </ElTableColumn>
<ElTableColumn label="优惠" min-width="120" align="center"> <ElTableColumn label="优惠" min-width="120" align="center">
<template #default="scope"> <template #default="scope">
<span v-if="scope.row.mileage > 0"> <span v-if="scope.row.mileage > 0">
{{ scope.row.reduction_price?.currency_info || scope.row.reduction_price?.amount || scope.row.discount || '—' }} {{
scope.row.reduction_price?.currency_info ||
scope.row.reduction_price?.amount ||
scope.row.discount ||
'—'
}}
</span> </span>
<span v-else> <span v-else>
{{ scope.row.discount_price?.currency_info || scope.row.discount_price?.amount || scope.row.discount || '—' }} {{
scope.row.discount_price?.currency_info ||
scope.row.discount_price?.amount ||
scope.row.discount ||
'—'
}}
</span> </span>
</template> </template>
</ElTableColumn> </ElTableColumn>
<ElTableColumn label="手续费" min-width="120" align="center"> <ElTableColumn label="手续费" min-width="120" align="center">
<template #default="scope">{{ scope.row.handling_fee?.currency_info || scope.row.handling_fee?.amount || scope.row.discount || '—' }}</template> <template #default="scope">{{
scope.row.handling_fee?.currency_info ||
scope.row.handling_fee?.amount ||
scope.row.discount ||
'—'
}}</template>
</ElTableColumn> </ElTableColumn>
<ElTableColumn label="改装费" min-width="120" align="center"> <ElTableColumn label="改装费" min-width="120" align="center">
<template #default="scope">{{ scope.row.retrofit_fee?.currency_info || scope.row.retrofit_fee?.amount || scope.row.discount || '—' }}</template> <template #default="scope">{{
scope.row.retrofit_fee?.currency_info ||
scope.row.retrofit_fee?.amount ||
scope.row.discount ||
'—'
}}</template>
</ElTableColumn> </ElTableColumn>
<ElTableColumn label="FOB运费" min-width="120" align="center"> <ElTableColumn label="FOB运费" min-width="120" align="center">
<template #default="scope">{{ scope.row.fob_transport_fee?.currency_info || scope.row.fob_transport_fee?.amount || scope.row.discount || '—' }}</template> <template #default="scope">{{
scope.row.fob_transport_fee?.currency_info ||
scope.row.fob_transport_fee?.amount ||
scope.row.discount ||
'—'
}}</template>
</ElTableColumn> </ElTableColumn>
<ElTableColumn label="港杂费" min-width="120" align="center"> <ElTableColumn label="港杂费" min-width="120" align="center">
<template #default="scope">{{ scope.row.port_misc_fee?.currency_info || scope.row.port_misc_fee?.amount || scope.row.discount || '—' }}</template> <template #default="scope">{{
scope.row.port_misc_fee?.currency_info ||
scope.row.port_misc_fee?.amount ||
scope.row.discount ||
'—'
}}</template>
</ElTableColumn> </ElTableColumn>
<ElTableColumn label="海运费" min-width="120" align="center"> <ElTableColumn label="海运费" min-width="120" align="center">
<template #default="scope">{{ scope.row.ocean_freight?.currency_info || scope.row.ocean_freight?.amount || scope.row.discount || '—' }}</template> <template #default="scope">{{
scope.row.ocean_freight?.currency_info ||
scope.row.ocean_freight?.amount ||
scope.row.discount ||
'—'
}}</template>
</ElTableColumn>
<ElTableColumn label="佣金" min-width="120" align="center">
<template #default="scope">{{
scope.row.commission?.currency_info ||
scope.row.commission?.amount ||
scope.row.discount ||
'—'
}}</template>
</ElTableColumn>
<ElTableColumn label="购置税" min-width="120" align="center">
<template #default="scope">{{
scope.row.purchase_tax?.currency_info ||
scope.row.purchase_tax?.amount ||
scope.row.discount ||
'—'
}}</template>
</ElTableColumn>
<ElTableColumn label="预估退税额" min-width="120" align="center">
<template #default="scope">{{
scope.row.tax_refund?.currency_info ||
scope.row.tax_refund?.amount ||
scope.row.discount ||
'—'
}}</template>
</ElTableColumn> </ElTableColumn>
<ElTableColumn label="单价" min-width="120" align="center"> <ElTableColumn label="单价" min-width="120" align="center">
<template #default="scope">{{ scope.row.unit_price?.currency_info || scope.row.unit_price?.amount || scope.row.discount || '—' }}</template> <template #default="scope">{{
scope.row.unit_price?.currency_info ||
scope.row.unit_price?.amount ||
scope.row.discount ||
'—'
}}</template>
</ElTableColumn> </ElTableColumn>
<ElTableColumn label="总价" min-width="120" align="center"> <ElTableColumn label="总价" min-width="120" align="center">
<template #default="scope">{{ scope.row.total_price?.currency_info || scope.row.total_price?.amount || scope.row.discount || '—' }}</template> <template #default="scope">{{
scope.row.total_price?.currency_info ||
scope.row.total_price?.amount ||
scope.row.discount ||
'—'
}}</template>
</ElTableColumn> </ElTableColumn>
<ElTableColumn fixed="right" label="利润" min-width="120" align="center"> <ElTableColumn fixed="right" label="利润" min-width="120" align="center">
<template #default="scope">{{ scope.row.profit?.currency_info || scope.row.profit?.amount || scope.row.discount || '—' }}</template> <template #default="scope">{{
scope.row.profit?.currency_info ||
scope.row.profit?.amount ||
scope.row.discount ||
'—'
}}</template>
</ElTableColumn> </ElTableColumn>
</template> </template>
</ArtTable> </ArtTable>
<div class="order-summary"> <div class="order-summary">
<div class="summary-item"> <div class="summary-item">
<span class="label">合同汇率:</span> <span class="label">合同汇率:</span>
<span class="value">{{ orderData.contract_exchange_rate || orderData.contractRate || '—' }}</span> <span class="value">{{
orderData.contract_exchange_rate || orderData.contractRate || '—'
}}</span>
</div> </div>
<div class="summary-item"> <div class="summary-item">
<span class="label">合计到岸价:</span> <span class="label">合计到岸价:</span>
<span class="value">${{ formatPrice(Number(orderData.cif_total_amount?.amount ?? orderData.totalCif ?? 0)) }}</span> <span class="value"
>${{
formatPrice(Number(orderData.cif_total_amount?.amount ?? orderData.totalCif ?? 0))
}}</span
>
</div> </div>
<div class="summary-item"> <div class="summary-item">
<span class="label">利润:</span> <span class="label">利润:</span>
<span class="value">{{ orderData.profit_total?.currency_info || orderData.profit_total?.amount || '—' }}</span> <span class="value">{{
orderData.profit_total?.currency_info || orderData.profit_total?.amount || '—'
}}</span>
</div> </div>
</div> </div>
</div> </div>
@@ -131,34 +246,54 @@
<tr> <tr>
<td> <td>
<span class="info-label"> 清关人:</span> <span class="info-label"> 清关人:</span>
<span class="info-value">{{ orderData.declarant_name || orderData.declarant || '—' }}</span> <span class="info-value">{{
orderData.declarant_name || orderData.declarant || '—'
}}</span>
</td> </td>
<td> <td>
<span class="info-label"> 预计发运日期:</span> <span class="info-label"> 预计发运日期:</span>
<span class="info-value">{{ orderData.eta_ship_date || orderData.etaShipDate || '—' }}</span> <span class="info-value">{{
orderData.eta_ship_date || orderData.etaShipDate || '—'
}}</span>
</td> </td>
<td> <td>
<span class="info-label"> 出运方式:</span> <span class="info-label"> 出运方式:</span>
<span class="info-value">{{ orderData.shipping_method_snapshot || orderData.shippingMethod || '—' }}</span> <span class="info-value">{{
orderData.shipping_method_snapshot || orderData.shippingMethod || '—'
}}</span>
</td> </td>
<td> <td>
<span class="info-label"> 国内收货港:</span> <span class="info-label"> 国内收货港:</span>
<span class="info-value">{{ orderData.domestic_port_snapshot || orderData.domesticPort || '—' }}</span> <span class="info-value">{{
orderData.domestic_port_snapshot || orderData.domesticPort || '—'
}}</span>
</td> </td>
<td> <td>
<span class="info-label"> 贸易条款/清关目的港:</span> <span class="info-label"> 贸易条款/清关目的港:</span>
<span class="info-value">{{ orderData.trade_term_snapshot || orderData.tradeTerms || '—' }}/{{ orderData.clearance_port_snapshot || '—' }}</span> <span class="info-value"
>{{ orderData.trade_term_snapshot || orderData.tradeTerms || '—' }}/{{
orderData.clearance_port_snapshot || '—'
}}</span
>
</td> </td>
<td> <td>
<span class="info-label"> 最迟到港时间:</span> <span class="info-label"> 最迟到港时间:</span>
<span class="info-value">{{ orderData.latest_arrival_date || orderData.latestArrival || '—' }}</span> <span class="info-value">{{
orderData.latest_arrival_date || orderData.latestArrival || '—'
}}</span>
</td> </td>
</tr> </tr>
</table> </table>
</div> </div>
<!-- 计划收款信息 --> <!-- 计划收款信息 -->
<div class="payment-section" v-if="(orderData.account_list && orderData.account_list.length) || (orderData.payments && orderData.payments.length)"> <div
class="payment-section"
v-if="
(orderData.account_list && orderData.account_list.length) ||
(orderData.payments && orderData.payments.length)
"
>
<div class="table-wrapper"> <div class="table-wrapper">
<table class="payment-table"> <table class="payment-table">
<thead> <thead>
@@ -192,8 +327,7 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent, computed } from 'vue' import { defineComponent, computed, ref, onMounted, nextTick, watch } from 'vue'
export default defineComponent({ export default defineComponent({
name: 'OrderDetailContent', name: 'OrderDetailContent',
props: { props: {
@@ -231,11 +365,34 @@
const vehItems = computed(() => { const vehItems = computed(() => {
return (props.orderData as any).veh_list || (props.orderData as any).items || [] return (props.orderData as any).veh_list || (props.orderData as any).items || []
}) })
const tableRef = ref()
const expandWhenReady = () => {
nextTick(() => {
nextTick(() => {
if (vehItems.value && vehItems.value.length) {
tableRef.value?.expandAll?.()
}
})
})
}
onMounted(() => {
// 默认展开所有行(等数据和表格渲染完成后再触发)
expandWhenReady()
})
watch(
() => vehItems.value?.length,
(len) => {
if (len && len > 0) expandWhenReady()
},
{ immediate: true }
)
return { return {
paymentTableData, paymentTableData,
vehItems, vehItems,
formatPrice formatPrice,
tableRef
} }
} }
}) })
@@ -406,7 +563,14 @@
padding: 10px 8px; padding: 10px 8px;
vertical-align: middle; vertical-align: middle;
} }
.el-table__expand-cell {
padding: 0;
border-top: none;
}
.el-table__expand-wrapper {
padding: 10px;
background-color: #f9f9f9;
}
/* 打印优化 */ /* 打印优化 */
@media print { @media print {
.order-detail-content { .order-detail-content {
@@ -0,0 +1,175 @@
<template>
<div class="detail-wrap">
<div class="detail-grid">
<div class="item">
<span class="label">采购单号:</span>
<span class="value">{{ detailData.order_no || '—' }}</span>
</div>
<div class="item">
<span class="label">PI号:</span>
<span class="value">{{ detailData.pi_no || '—' }}</span>
</div>
<div class="item">
<span class="label">提交人:</span>
<span class="value">{{ detailData.buyer_name || '—' }}</span>
</div>
<div class="item full">
<span class="label">备注:</span>
<span class="value">{{ detailData.remark || '—' }}</span>
</div>
</div>
<div class="detail-block">
<div class="block-title">合同文件</div>
<div
v-if="detailData.contract_file_url && detailData.contract_file_url.length"
class="files-list"
>
<div class="file-row" v-for="(f, idx) in detailData.contract_file_url" :key="idx">
<el-icon class="file-icon"><Document /></el-icon>
<span class="file-name">{{ f.name }}</span>
<a class="el-link el-link--primary" :href="f.url" target="_blank" rel="noopener">查看</a>
<a class="el-link el-link--info" :href="f.url" :download="f.name">下载</a>
</div>
</div>
<div v-else class="empty-tip">无合同文件</div>
</div>
<div class="detail-block">
<div class="block-title">备货单组</div>
<div v-if="detailData.stock_order && detailData.stock_order.length" class="groups">
<div class="group" v-for="(g, gi) in detailData.stock_order" :key="gi">
<div class="group-head">
<span class="order">{{ g.order_no || '备货单' }}</span>
<span class="seller" v-if="g.salesperson_snapshot"
>({{ g.salesperson_snapshot }})</span
>
<span class="date" v-if="g.biz_date">{{ g.biz_date }}</span>
</div>
<div class="chips">
<span class="chip" v-for="(v, vi) in g.stock_order_items || []" :key="vi">
{{ v.model_snapshot }}
</span>
</div>
</div>
</div>
<div v-else class="empty-tip">无备货单数据</div>
</div>
</div>
</template>
<script lang="ts">
import { Document } from '@element-plus/icons-vue'
import { PropType } from 'vue'
// 定义组件的数据类型
interface DetailData {
order_no?: string
pi_no?: string
buyer_name?: string
remark?: string
contract_file_url?: Array<{ name: string; url: string }>
stock_order?: Array<{
order_no?: string
salesperson_snapshot?: string
biz_date?: string
stock_order_items?: Array<{ model_snapshot: string }>
}>
}
// 使用 Vue 的 props 传递数据
export default {
name: 'ProcurementDetailView',
props: {
detailData: {
type: Object as PropType<DetailData>,
required: true
}
}
}
</script>
<style scoped lang="scss">
.detail-wrap {
display: flex;
flex-direction: column;
gap: 16px;
}
.detail-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 10px 16px;
}
.detail-grid .item {
display: flex;
align-items: center;
gap: 8px;
}
.detail-grid .item.full {
grid-column: 1 / -1;
}
.detail-grid .label {
color: var(--el-text-color-regular);
}
.detail-grid .value {
font-weight: 600;
color: var(--el-text-color-primary);
}
.detail-block {
border-top: 1px dashed var(--el-border-color-lighter);
padding-top: 8px;
}
.block-title {
font-weight: 600;
margin-bottom: 8px;
}
.files-list {
display: flex;
flex-direction: column;
gap: 8px;
}
.file-row {
display: flex;
align-items: center;
gap: 10px;
}
.file-name {
flex: 1;
}
.empty-tip {
color: var(--el-text-color-secondary);
}
.groups {
display: flex;
flex-direction: column;
max-height: 38vh;
overflow-y: auto;
gap: 12px;
}
.group {
border: 1px solid var(--el-border-color-lighter);
border-radius: 10px;
padding: 14px 6px 8px;
background: var(--el-fill-color-blank);
}
.group-head {
display: flex;
gap: 8px;
margin-bottom: 10px;
align-content: center;
}
.chips {
display: flex;
flex-wrap: wrap;
gap: 6px;
}
.chip {
padding: 4px 8px;
border-radius: 12px;
background: #f0f6ff;
color: #3a7af3;
border: 1px solid #e3edff;
font-size: 12px;
line-height: 18px;
}
</style>
@@ -0,0 +1,169 @@
<template>
<div class="detail-wrap">
<div class="detail-grid">
<div class="item">
<span class="label">采购单号:</span>
<span class="value">{{ detailData.order_no || '—' }}</span>
</div>
<div class="item">
<span class="label">PI号:</span>
<span class="value">{{ detailData.pi_no || '—' }}</span>
</div>
<div class="item">
<span class="label">提交人:</span>
<span class="value">{{ detailData.buyer_name || '—' }}</span>
</div>
<div class="item full">
<span class="label">备注:</span>
<span class="value">{{ detailData.remark || '—' }}</span>
</div>
</div>
<div class="detail-block">
<div class="block-title">合同文件</div>
<div
v-if="detailData.contract_file_url && detailData.contract_file_url.length"
class="files-list"
>
<div class="file-row" v-for="(f, idx) in detailData.contract_file_url" :key="idx">
<el-icon class="file-icon"><Document /></el-icon>
<span class="file-name">{{ f.name }}</span>
<a class="el-link el-link--primary" :href="f.url" target="_blank" rel="noopener">查看</a>
<a class="el-link el-link--info" :href="f.url" :download="f.name">下载</a>
</div>
</div>
<div v-else class="empty-tip">无合同文件</div>
</div>
<div class="detail-block">
<div class="block-title">备货单组</div>
<div v-if="detailData.stock_order && detailData.stock_order.length" class="groups">
<div class="group" v-for="(g, gi) in detailData.stock_order" :key="gi">
<div class="group-head">
<span class="order">{{ g.order_no || '备货单' }}</span>
<span class="seller" v-if="g.salesperson_snapshot"
>({{ g.salesperson_snapshot }})</span
>
<span class="date" v-if="g.biz_date">{{ g.biz_date }}</span>
</div>
<div class="chips">
<span class="chip" v-for="(v, vi) in g.stock_order_items || []" :key="vi">
{{ v.model_snapshot }}
</span>
</div>
</div>
</div>
<div v-else class="empty-tip">无备货单数据</div>
</div>
</div>
</template>
<script setup lang="ts">
import { Document } from '@element-plus/icons-vue'
import { PropType } from 'vue'
interface DetailData {
order_no?: string
pi_no?: string
buyer_name?: string
remark?: string
contract_file_url?: Array<{ name: string; url: string }>
stock_order?: Array<{
order_no?: string
salesperson_snapshot?: string
biz_date?: string
stock_order_items?: Array<{ model_snapshot: string }>
}>
}
defineProps({
detailData: {
type: Object as PropType<DetailData>,
required: true
}
})
</script>
<style scoped lang="scss">
.detail-wrap {
display: flex;
flex-direction: column;
gap: 16px;
}
.detail-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 10px 16px;
}
.detail-grid .item {
display: flex;
align-items: center;
gap: 8px;
}
.detail-grid .item.full {
grid-column: 1 / -1;
}
.detail-grid .label {
color: var(--el-text-color-regular);
}
.detail-grid .value {
font-weight: 600;
color: var(--el-text-color-primary);
}
.detail-block {
border-top: 1px dashed var(--el-border-color-lighter);
padding-top: 8px;
}
.block-title {
font-weight: 600;
margin-bottom: 8px;
}
.files-list {
display: flex;
flex-direction: column;
gap: 8px;
}
.file-row {
display: flex;
align-items: center;
gap: 10px;
}
.file-name {
flex: 1;
}
.empty-tip {
color: var(--el-text-color-secondary);
}
.groups {
display: flex;
flex-direction: column;
max-height: 38vh;
overflow-y: auto;
gap: 12px;
}
.group {
border: 1px solid var(--el-border-color-lighter);
border-radius: 10px;
padding: 14px 6px 8px;
background: var(--el-fill-color-blank);
}
.group-head {
display: flex;
gap: 8px;
margin-bottom: 10px;
align-content: center;
}
.chips {
display: flex;
flex-wrap: wrap;
gap: 6px;
}
.chip {
padding: 4px 8px;
border-radius: 12px;
background: #f0f6ff;
color: #3a7af3;
border: 1px solid #e3edff;
font-size: 12px;
line-height: 18px;
}
</style>
@@ -0,0 +1,213 @@
<template>
<el-drawer :with-header="false" :model-value="modelValue" :size="isMobile ? '100%' : '480px'" @close="$emit('update:modelValue', false)">
<div class="chat-container">
<div class="header">
<div class="header-left">
<span class="name">{{ title }}</span>
</div>
<div class="header-right">
<el-icon class="icon-close" :size="20" @click="$emit('update:modelValue', false)">
<Close />
</el-icon>
</div>
</div>
<div class="chat-messages" ref="messageContainer">
<template v-for="(message, index) in messages" :key="index">
<div :class="['message-item', String(message.user_id) === String(currentUserId) ? 'message-right' : 'message-left']">
<el-avatar :size="32" :src="message.user_avatar" class="message-avatar" />
<div class="message-content">
<div class="message-info">
<span class="sender-name">{{ message.user_name }}</span>
<span class="message-time">{{ message.updated_at }}</span>
</div>
<div class="message-text" v-html="message.content"></div>
<div class="message-attachments" v-if="message.attachment && message.attachment.length > 0">
<div class="attachment-item" v-for="(attachment, attIndex) in message.attachment" :key="attIndex">
<template v-if="/\.(jpg|jpeg|png|gif|webp|bmp)$/i.test(attachment.original_name)">
<img :src="attachment.storage_path_full" :alt="attachment.original_name" class="attachment-thumb" @click="handleChatImageClick(attachment.storage_path_full)" />
</template>
<template v-else-if="/\.(mp4|webm|ogg)$/i.test(attachment.original_name)">
<video :src="attachment.storage_path_full" controls class="attachment-thumb" @click.prevent="handleChatVideoClick(attachment.storage_path_full)"></video>
</template>
<template v-else>
<div class="file-attachment">
<el-icon class="file-icon"><Document /></el-icon>
<span class="file-name">{{ attachment.original_name }}</span>
<el-button type="primary" size="small" link @click="$emit('download', attachment)">下载</el-button>
</div>
</template>
</div>
</div>
</div>
</div>
</template>
</div>
<div class="chat-input" v-if="canAdd">
<div class="chat-attachment-upload">
<el-upload v-model:file-list="chatFileList" class="chat-upload" :action="actionImageUplode" :on-success="handleChatUploadSuccess" :headers="uploadHeaders" multiple :limit="5" :on-exceed="handleChatExceed" :show-file-list="false">
<el-button type="primary" link>
<el-icon><Paperclip /></el-icon>
添加附件
</el-button>
</el-upload>
<div class="uploaded-attachments" v-if="chatFileList.length > 0">
<div class="attachment-preview-item" v-for="(file, idx) in chatFileList" :key="idx">
<template v-if="/\.(jpg|jpeg|png|gif|webp|bmp)$/i.test(file.name)">
<img :src="file.file_url || file.response?.data?.file_url" :alt="file.name" class="preview-thumb" />
</template>
<template v-else>
<el-icon class="file-icon"><Document /></el-icon>
</template>
<span class="file-name">{{ file.name }}</span>
<el-button type="danger" size="small" link @click="removeChatFile(idx)">
<el-icon><Close /></el-icon>
</el-button>
</div>
</div>
</div>
<el-input v-model="messageText" type="textarea" :rows="3" placeholder="输入消息" resize="none" />
<div class="chat-input-actions">
<el-button type="primary" @click="emitSend">发送</el-button>
</div>
</div>
</div>
<!-- 内置预览 -->
<ElImageViewer v-if="imageViewerVisible" :url-list="chatImageList" :initial-index="imageViewerIndex" @close="closeImageViewer" />
<ElDialog v-model="videoDialogVisible" width="60%" top="10vh" :show-close="true" append-to-body>
<video v-if="currentVideoUrl" :src="currentVideoUrl" controls autoplay style="width: 100%; max-height: 70vh; display: block"></video>
</ElDialog>
</el-drawer>
</template>
<script setup lang="ts">
import { ElImageViewer } from 'element-plus'
import { Paperclip, Document, Close } from '@element-plus/icons-vue'
const props = defineProps<{
modelValue: boolean
title?: string
messages: any[]
currentUserId: string | number
canAdd?: boolean
actionImageUplode?: string
uploadHeaders?: Record<string, any>
}>()
const emit = defineEmits<{
(e: 'update:modelValue', v: boolean): void
(e: 'send', payload: { text: string; attachments: any[] }): void
(e: 'download', attachment: any): void
}>()
const { width } = useWindowSize()
const isMobile = computed(() => width.value < 500)
const messageText = ref('')
const chatFileList = ref<any[]>([])
const messageContainer = ref<HTMLElement | null>(null)
watch(
() => props.messages,
() => {
nextTick(() => {
if (messageContainer.value) {
messageContainer.value.scrollTop = messageContainer.value.scrollHeight
}
})
},
{ immediate: true, deep: true }
)
const handleChatUploadSuccess = (response: any, uploadFile: any, uploadFiles: any[]) => {
chatFileList.value = uploadFiles
.filter((item) => item.status === 'success')
.map((item) => {
const data = item.response?.data
if (data) {
return {
name: data.original_name,
file_url: data.storage_path_full,
status: 'success',
response: { data }
}
}
return null
})
.filter(Boolean) as any[]
}
const handleChatExceed = () => {
// no-op; parent can handle toast
}
const removeChatFile = (index: number) => {
chatFileList.value.splice(index, 1)
}
const emitSend = () => {
const text = messageText.value.trim()
if (!text && chatFileList.value.length === 0) return
const attachments = chatFileList.value.map((file) => {
const data = file.response?.data
if (data) {
return {
original_name: data.original_name,
storage_path: data.storage_path || data.file_name
}
}
return null
}).filter(Boolean) as any[]
emit('send', { text, attachments })
messageText.value = ''
chatFileList.value = []
}
// 预览
const chatImageList = ref<string[]>([])
const imageViewerVisible = ref(false)
const imageViewerIndex = ref(0)
function handleChatImageClick(url: string) {
chatImageList.value = [url]
imageViewerIndex.value = 0
imageViewerVisible.value = true
}
const currentVideoUrl = ref('')
const videoDialogVisible = ref(false)
function handleChatVideoClick(url: string) {
currentVideoUrl.value = url
videoDialogVisible.value = true
}
function closeImageViewer() {
imageViewerVisible.value = false
chatImageList.value = []
}
</script>
<style scoped lang="scss">
.header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 20px;
.header-left .name { font-size: 16px; font-weight: 500; }
.header-right .icon-close { cursor: pointer; }
}
.chat-container { display: flex; flex-direction: column; height: calc(100% - 70px); }
.chat-messages { flex: 1; padding: 30px 16px; overflow-y: auto; border-top: 1px solid var(--el-border-color-lighter); }
.message-item { display: flex; gap: 8px; align-items: flex-start; width: 100%; margin-bottom: 30px; }
.message-item.message-left { justify-content: flex-start; }
.message-item.message-right { flex-direction: row-reverse; }
.message-content { display: flex; flex-direction: column; max-width: 70%; }
.message-info { display: flex; gap: 8px; margin-bottom: 4px; font-size: 12px; }
.message-text { font-size: 14px; color: var(--art-gray-900); border-radius: 6px; padding: 10px 14px; line-height: 1.4; white-space: pre-line; }
.message-left .message-text { background-color: #f8f5ff; }
.message-right .message-text { background-color: #e9f3ff; }
.message-attachments { margin-top: 8px; }
.message-attachments .attachment-thumb { max-width: 200px; max-height: 150px; border-radius: 6px; cursor: pointer; border: 1px solid var(--el-border-color-lighter); }
.chat-input { padding: 16px 16px 0; }
.chat-input-actions { display: flex; justify-content: right; margin-top: 12px; }
.chat-attachment-upload { margin-bottom: 12px; }
.uploaded-attachments { display: flex; flex-wrap: wrap; gap: 8px; }
.attachment-preview-item { display: flex; align-items: center; gap: 6px; padding: 6px 10px; background: var(--el-fill-color-light); border-radius: 6px; border: 1px solid var(--el-border-color-lighter); max-width: 200px; }
.preview-thumb { width: 40px; height: 40px; border-radius: 4px; object-fit: cover; }
</style>
+6 -1
View File
@@ -22,6 +22,7 @@ export interface PaymentItem {
} }
export interface OrderData { export interface OrderData {
contract_file_url: { name: string; url: string }[]
orderNo?: string orderNo?: string
salesperson?: string salesperson?: string
piNo?: string piNo?: string
@@ -45,7 +46,11 @@ export interface OrderData {
export function useOrderDetailDialog() { export function useOrderDetailDialog() {
const visible = ref(false) const visible = ref(false)
const title = ref('订单详情') const title = ref('订单详情')
const orderData = ref<OrderData>({}) const orderData = ref<OrderData>({
contract_file_url: [],
items: [],
payments: []
})
const show = (data: OrderData, dialogTitle?: string) => { const show = (data: OrderData, dialogTitle?: string) => {
orderData.value = data orderData.value = data
+68 -3
View File
@@ -1,5 +1,5 @@
import { ref, reactive, watch, onMounted } from 'vue' import { ref, reactive, watch, onMounted } from 'vue'
import { ElMessage } from 'element-plus' import { componentSizeMap, ElMessage } from 'element-plus'
import { CrmService } from '@/api/crmApi' import { CrmService } from '@/api/crmApi'
import { commonService } from '@/api/commonApi' import { commonService } from '@/api/commonApi'
import { vehicleService } from '@/api/vehicleApi' import { vehicleService } from '@/api/vehicleApi'
@@ -62,9 +62,19 @@ export interface ItemRow {
handling_fee: string handling_fee: string
fob_transport_fee: number | undefined fob_transport_fee: number | undefined
port_misc_fee: string port_misc_fee: string
remark: string
ocean_freight: string ocean_freight: string
ocean_freight_currency: string ocean_freight_currency: string
ocean_freight_currency_snapshot: string ocean_freight_currency_snapshot: string
commission: string
commission_currency: string
commission_currency_snapshot: string
purchase_tax: string
purchase_tax_currency: string
purchase_tax_currency_snapshot: string
tax_refund: string
tax_refund_currency: string
tax_refund_currency_snapshot: string
retrofit_fee: string retrofit_fee: string
total_price: number | undefined total_price: number | undefined
official_price: number | undefined official_price: number | undefined
@@ -195,9 +205,19 @@ export function useStockingForm(isFinal = false, stockingId?: string | number) {
handling_fee: '', handling_fee: '',
fob_transport_fee: undefined, fob_transport_fee: undefined,
port_misc_fee: '', port_misc_fee: '',
remark: '',
ocean_freight: '', ocean_freight: '',
ocean_freight_currency: 'CNY', ocean_freight_currency: 'CNY',
ocean_freight_currency_snapshot: '人民币', ocean_freight_currency_snapshot: '人民币',
commission: '',
commission_currency: 'CNY',
commission_currency_snapshot: '人民币',
purchase_tax: '',
purchase_tax_currency: 'CNY',
purchase_tax_currency_snapshot: '人民币',
tax_refund: '',
tax_refund_currency: 'CNY',
tax_refund_currency_snapshot: '人民币',
retrofit_fee: '', retrofit_fee: '',
total_price: undefined, total_price: undefined,
official_price: undefined, official_price: undefined,
@@ -434,6 +454,21 @@ export function useStockingForm(isFinal = false, stockingId?: string | number) {
(it as any).ocean_freight_currency, (it as any).ocean_freight_currency,
targetCurrency targetCurrency
) )
const commission = convertCurrency(
it.commission ?? (it as any).commission,
(it as any).commission_currency,
targetCurrency
)
const purchaseTax = convertCurrency(
it.purchase_tax ?? (it as any).purchase_tax,
(it as any).purchase_tax_currency,
targetCurrency
)
const taxRefund = convertCurrency(
it.tax_refund ?? (it as any).tax_refund,
(it as any).tax_refund_currency,
targetCurrency
)
const mileageVal = Number(it.mileage ?? 0) const mileageVal = Number(it.mileage ?? 0)
if (mileageVal > 0) { if (mileageVal > 0) {
// 二手车:用挂牌价与二手减价替代官方指导价与优惠 // 二手车:用挂牌价与二手减价替代官方指导价与优惠
@@ -447,7 +482,19 @@ export function useStockingForm(isFinal = false, stockingId?: string | number) {
(it as any).reduction_price_currency, (it as any).reduction_price_currency,
targetCurrency targetCurrency
) )
return unit - (retrofit + fob + listing + reduction + handling + port + ocean) return (
unit -
(retrofit +
fob +
listing +
reduction +
handling +
port +
ocean +
commission +
purchaseTax) +
taxRefund
)
} }
// 新车/里程<=0:保持原有计算(官方指导价与优惠) // 新车/里程<=0:保持原有计算(官方指导价与优惠)
@@ -461,7 +508,11 @@ export function useStockingForm(isFinal = false, stockingId?: string | number) {
(it as any).discount_currency, (it as any).discount_currency,
targetCurrency targetCurrency
) )
return unit - (retrofit + fob + guide + discount + handling + port + ocean) return (
unit -
(retrofit + fob + guide + discount + handling + port + ocean + commission + purchaseTax) +
taxRefund
)
} }
// 合同文件相关方法 // 合同文件相关方法
@@ -678,9 +729,19 @@ export function useStockingForm(isFinal = false, stockingId?: string | number) {
handling_fee: it.handling_fee?.amount ?? '', handling_fee: it.handling_fee?.amount ?? '',
fob_transport_fee: it.fob_transport_fee?.amount ? Number(it.fob_transport_fee.amount) : undefined, fob_transport_fee: it.fob_transport_fee?.amount ? Number(it.fob_transport_fee.amount) : undefined,
port_misc_fee: it.port_misc_fee?.amount ?? '', port_misc_fee: it.port_misc_fee?.amount ?? '',
remark: it.remark || '',
ocean_freight: it.ocean_freight?.amount ?? '', ocean_freight: it.ocean_freight?.amount ?? '',
ocean_freight_currency: it.ocean_freight?.currency || 'CNY', ocean_freight_currency: it.ocean_freight?.currency || 'CNY',
ocean_freight_currency_snapshot: '', ocean_freight_currency_snapshot: '',
commission: it.commission?.amount ?? '',
commission_currency: it.commission?.currency || 'CNY',
commission_currency_snapshot: '',
purchase_tax: it.purchase_tax?.amount ?? '',
purchase_tax_currency: it.purchase_tax?.currency || 'CNY',
purchase_tax_currency_snapshot: '',
tax_refund: it.tax_refund?.amount ?? '',
tax_refund_currency: it.tax_refund?.currency || 'CNY',
tax_refund_currency_snapshot: '',
retrofit_fee: it.retrofit_fee?.amount ?? '', retrofit_fee: it.retrofit_fee?.amount ?? '',
total_price: it.total_price?.amount ? Number(it.total_price.amount) : undefined, total_price: it.total_price?.amount ? Number(it.total_price.amount) : undefined,
official_price: it.official_price?.amount ? Number(it.official_price.amount) : undefined, official_price: it.official_price?.amount ? Number(it.official_price.amount) : undefined,
@@ -759,6 +820,7 @@ export function useStockingForm(isFinal = false, stockingId?: string | number) {
color_interior_snapshot: it.color_interior_snapshot, color_interior_snapshot: it.color_interior_snapshot,
color_exterior_code: it.color_exterior_code, color_exterior_code: it.color_exterior_code,
color_exterior_snapshot: it.color_exterior_snapshot, color_exterior_snapshot: it.color_exterior_snapshot,
remark: it.remark,
unit_price: wrapMoney(it.unit_price, (it as any).unit_price_currency, '单价'), unit_price: wrapMoney(it.unit_price, (it as any).unit_price_currency, '单价'),
handling_fee: wrapMoney(it.handling_fee, (it as any).helper_fee_currency, '手续费'), handling_fee: wrapMoney(it.handling_fee, (it as any).helper_fee_currency, '手续费'),
fob_transport_fee: wrapMoney( fob_transport_fee: wrapMoney(
@@ -772,6 +834,9 @@ export function useStockingForm(isFinal = false, stockingId?: string | number) {
'港杂费' '港杂费'
), ),
ocean_freight: wrapMoney(it.ocean_freight, (it as any).ocean_freight_currency, '海运费'), ocean_freight: wrapMoney(it.ocean_freight, (it as any).ocean_freight_currency, '海运费'),
commission: wrapMoney(it.commission, (it as any).commission_currency, '佣金'),
purchase_tax: wrapMoney(it.purchase_tax, (it as any).purchase_tax_currency, '购置税'),
tax_refund: wrapMoney(it.tax_refund, (it as any).tax_refund_currency, '退税'),
retrofit_fee: wrapMoney((it as any).retrofit_fee, (it as any).modify_fee_currency, '改装费'), retrofit_fee: wrapMoney((it as any).retrofit_fee, (it as any).modify_fee_currency, '改装费'),
modify_list: it.modify_list, modify_list: it.modify_list,
// total_price: wrapMoney(it.total_price, (it as any).total_price_currency, '总价'), // total_price: wrapMoney(it.total_price, (it as any).total_price_currency, '总价'),
+135 -25
View File
@@ -71,7 +71,7 @@
<!-- 快速审批弹出框 --> <!-- 快速审批弹出框 -->
<el-dialog <el-dialog
v-model="approvalDialogVisible" v-model="approvalDialogVisible"
title="合塑实业-汽车事业部-备货单(客户下定)" :title="orderDialog.title"
width="80%" width="80%"
:close-on-click-modal="false" :close-on-click-modal="false"
:close-on-press-escape="false" :close-on-press-escape="false"
@@ -79,7 +79,12 @@
> >
<div class="approval-content"> <div class="approval-content">
<!-- 头部信息 --> <!-- 头部信息 -->
<div v-if="orderDialog.type == 'stock_orders'">
<OrderDetailContent :orderData="orderDialog.orderData" /> <OrderDetailContent :orderData="orderDialog.orderData" />
</div>
<div v-if="orderDialog.type == 'purchase_orders'">
<ProcurementDetailView :detailData="orderDialog.orderData" />
</div>
<!-- 审批备注 --> <!-- 审批备注 -->
<div class="info-section"> <div class="info-section">
<ElRow :gutter="12"> <ElRow :gutter="12">
@@ -98,15 +103,50 @@
<template #footer> <template #footer>
<div class="dialog-footer"> <div class="dialog-footer">
<ElButton type="primary" plain size="large" class="submit-btn" @click="handleApprove('reject')" <ElButton
type="primary"
plain
size="large"
class="submit-btn"
@click="handleApprove('reject')"
>驳回</ElButton >驳回</ElButton
> >
<ElButton type="primary" size="large" class="submit-btn" @click="handleApprove('approve')" <ElButton type="primary" size="large" class="submit-btn" @click="handleApprove('approve')"
>同意</ElButton >同意</ElButton
> >
<ElButton type="warning" size="large" class="submit-btn" @click="handleTransfer()"
>转交</ElButton
>
</div> </div>
</template> </template>
</el-dialog> </el-dialog>
<!-- 转交弹出框 -->
<el-dialog v-model="zjDialogVisible" title="转交" width="450px">
<el-form :model="zjForm" label-width="80px">
<el-form-item label="转交人" prop="to_uid">
<el-select
v-model="zjForm.to_uid"
filterable
placeholder="请选择转交人"
style="width: 100%"
>
<el-option
v-for="item in userOptions"
:key="item.id"
:label="item.name"
:value="item.id"
/>
</el-select>
</el-form-item>
<ElFormItem label="转交原因" prop="reason">
<ElInput v-model="zjForm.reason" type="textarea" :rows="3" placeholder="请输入转交原因" />
</ElFormItem>
<div style="display: flex; justify-content: space-around; margin-top: 24px">
<el-button @click="zjDialogVisible = false">取消</el-button>
<el-button type="primary" @click="handlezjSubmit">确定</el-button>
</div>
</el-form>
</el-dialog>
</div> </div>
</template> </template>
@@ -117,9 +157,13 @@
import { commonService } from '@/api/commonApi' import { commonService } from '@/api/commonApi'
import OrderDetailContent from '@/components/custom/OrderDetailContent.vue' import OrderDetailContent from '@/components/custom/OrderDetailContent.vue'
import type { OrderData } from '@/composables/useOrderDetailDialog' import type { OrderData } from '@/composables/useOrderDetailDialog'
import ProcurementDetailView from '@/components/custom/ProcurementDetailView.vue'
defineOptions({ name: 'AdminStockingPage' }) defineOptions({ name: 'AdminStockingPage' })
const zjDialogVisible = ref(false)
const orderDialog = reactive({ const orderDialog = reactive({
orderData: {} as OrderData orderData: {} as OrderData,
title: '',
type: ''
}) })
const { hasAuth } = useAuth() const { hasAuth } = useAuth()
const loading = ref(false) const loading = ref(false)
@@ -127,7 +171,19 @@
const tableRef = ref() const tableRef = ref()
const tableData = ref<any[]>([]) const tableData = ref<any[]>([])
const pagination = reactive({ currentPage: 1, pageSize: 10, total: 0 }) const pagination = reactive({ currentPage: 1, pageSize: 10, total: 0 })
const userOptions = ref<any>()
const getuserList = async () => {
// 这里调用获取用户列表的接口
const res = await manageService.presetusers()
userOptions.value = res || []
// return res || []
}
const zjForm = reactive({
to_uid: '',
instance_id: '',
step_index: '',
reason: ''
})
// 审批弹出框相关 // 审批弹出框相关
const approvalDialogVisible = ref(false) const approvalDialogVisible = ref(false)
@@ -170,36 +226,58 @@
pagination.currentPage = page pagination.currentPage = page
getList() getList()
} }
const handleCreate = () => {
// TODO: 跳转到创建备货单页面
router.push('/crmpc/stocking_list/Add')
}
const handleView = (row: any) => { const handleView = (row: any) => {
router.push({ router.push({
path: '/admin/approval/stocking_auth_detil', path: '/admin/approval/stocking_auth_detil',
query: { id: row.id, biz_id: row.biz_id, instance_id: row.instance_id, status: row.status } // 可带客户名等参数 query: {
id: row.id,
biz_id: row.biz_id,
instance_id: row.instance_id,
status: row.status,
flow_type: row.flow_type,
step_index: row.step_index
} // 可带客户名等参数
}) })
} }
const handleView1 = (row: any) => {
router.push('/crmpc/stocking_list/final_show')
// TODO: 跳转或弹窗查看备货单
}
const toMoneyView = (m: any) => ({
amount: m?.amount ?? '',
currency: m?.currency ?? ''
})
const getDictItemSelectlist = ref<any[]>([])
const getDictItemSelect = async () => {
const res = await commonService.getDictItemSelect({ dict_id: '18' })
getDictItemSelectlist.value = res
}
const instance_id = ref('') const instance_id = ref('')
function extractFileName(pathOrUrl: string | undefined | null): string {
if (!pathOrUrl) return '合同文件'
try {
const clean = String(pathOrUrl)
const afterSlash = clean.split(/[\\/]/).pop() || '合同文件'
return decodeURIComponent(afterSlash)
} catch {
return '合同文件'
}
}
const activerow = ref()
const handleCreateRepair = async (row: any) => { const handleCreateRepair = async (row: any) => {
activerow.value = row
instance_id.value = row.instance_id instance_id.value = row.instance_id
const res = await manageService.checkStock({ id: row.biz_id }) const res = await manageService.stockapprovaldetailborder({
id: row.id,
flow_type: row.flow_type
})
if (row.flow_type == 'stock_orders') {
const orderData: OrderData = res const orderData: OrderData = res
orderDialog.orderData = orderData orderDialog.orderData = orderData
orderDialog.type = row.flow_type
orderDialog.title = '合塑实业-汽车事业部-备货单(客户下定)'
} else if (row.flow_type == 'purchase_orders') {
const orderData: OrderData = res
const urls = res.contract_file_url
const paths = res.contract_file
const list: Array<{ name: string; url: string }> = []
const nameFromPath = (p: string) => extractFileName(p)
urls.forEach((u: string, i: number) => {
const p = paths[i] || ''
list.push({ name: p ? nameFromPath(p) : extractFileName(u), url: u })
})
orderData.contract_file_url = list
orderDialog.orderData = orderData
orderDialog.type = row.flow_type
orderDialog.title = '合塑实业-汽车事业部-采购单(采购)'
}
approvalDialogVisible.value = true approvalDialogVisible.value = true
} }
@@ -224,6 +302,38 @@
ElMessage.error('审批失败') ElMessage.error('审批失败')
} }
} }
// 转交操作
const handleTransfer = async () => {
zjForm.instance_id = activerow.value.instance_id
zjForm.step_index = activerow.value.step_index
getuserList()
zjDialogVisible.value = true
}
const handlezjSubmit = async () => {
if (!zjForm.to_uid) {
ElMessage.warning('请选择转交人')
return
}
if (!zjForm.reason) {
ElMessage.warning('请输入转交原因')
return
}
try {
const { message } = await manageService.stockapprovaltransfer(zjForm)
ElMessage.success(message)
zjDialogVisible.value = false
approvalDialogVisible.value = false
approvalNotes.value = ''
zjForm.to_uid = ''
zjForm.reason = ''
instance_id.value = ''
// 刷新列表
getList()
} catch (error) {
console.error('转交失败:', error)
// ElMessage.error('转交失败')
}
}
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
@@ -275,7 +385,7 @@
} }
.info-section { .info-section {
margin-bottom: 24px; margin: 24px 0px;
.section-title { .section-title {
display: flex; display: flex;
@@ -1,8 +1,13 @@
<template> <template>
<div class="stocking-show-page"> <div class="stocking-show-page">
<div class="boxshadow"> <div class="boxshadow">
<div v-if="flow_type == 'stock_orders'">
<OrderDetailContent :orderData="orderDialog.orderData" /> <OrderDetailContent :orderData="orderDialog.orderData" />
</div> </div>
<div v-if="flow_type == 'purchase_orders'">
<ProcurementDetailView :detailData="orderDialog.orderData" />
</div>
</div>
<!-- 上传合同 --> <!-- 上传合同 -->
<div class="boxshadow"> <div class="boxshadow">
<div class="form-title"> <div class="form-title">
@@ -99,6 +104,9 @@
<ElButton type="primary" size="large" class="submit-btn" @click="handleSubmit('approve')" <ElButton type="primary" size="large" class="submit-btn" @click="handleSubmit('approve')"
>同意</ElButton >同意</ElButton
> >
<ElButton type="warning" size="large" class="submit-btn" @click="handleTransfer()"
>转交</ElButton
>
</div> </div>
</div> </div>
<!-- PDF 预览弹窗 --> <!-- PDF 预览弹窗 -->
@@ -122,30 +130,102 @@
/> />
</div> </div>
</el-dialog> </el-dialog>
<!-- 转交弹出框 -->
<el-dialog v-model="zjDialogVisible" title="转交" width="450px">
<el-form :model="zjForm" label-width="80px">
<el-form-item label="转交人" prop="to_uid">
<el-select
v-model="zjForm.to_uid"
filterable
placeholder="请选择转交人"
style="width: 100%"
>
<el-option
v-for="item in userOptions"
:key="item.id"
:label="item.name"
:value="item.id"
/>
</el-select>
</el-form-item>
<ElFormItem label="转交原因" prop="reason">
<ElInput v-model="zjForm.reason" type="textarea" :rows="3" placeholder="请输入转交原因" />
</ElFormItem>
<div style="display: flex; justify-content: space-around; margin-top: 24px">
<el-button @click="zjDialogVisible = false">取消</el-button>
<el-button type="primary" @click="handlezjSubmit">确定</el-button>
</div>
</el-form>
</el-dialog>
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { CrmService } from '@/api/crmApi'
import { commonService } from '@/api/commonApi' import { commonService } from '@/api/commonApi'
import { manageService } from '@/api/adminApi' import { manageService } from '@/api/adminApi'
import { useRoute } from 'vue-router' import { useRoute } from 'vue-router'
import { ElMessage } from 'element-plus' import { ElMessage } from 'element-plus'
import { usePdfPreview } from '@/composables/usePdfPreview' import { usePdfPreview } from '@/composables/usePdfPreview'
import OrderDetailContent from '@/components/custom/OrderDetailContent.vue' import OrderDetailContent from '@/components/custom/OrderDetailContent.vue'
import ProcurementDetailView from '@/components/custom/ProcurementDetailView.vue'
import type { OrderData } from '@/composables/useOrderDetailDialog' import type { OrderData } from '@/composables/useOrderDetailDialog'
import { router } from '@/router' import { router } from '@/router'
defineOptions({ name: 'AdminStockingAuthDetil' }) defineOptions({ name: 'AdminStockingAuthDetil' })
const { pdf, frameRef, download } = usePdfPreview() const { pdf, frameRef, download } = usePdfPreview()
const zjDialogVisible = ref(false)
const route = useRoute() const route = useRoute()
// const approval_id = route.query.id const approval_id = route.query.id
const stocking_id = route.query.biz_id const stocking_id = route.query.biz_id
const instance_id = route.query.instance_id const instance_id = route.query.instance_id
const step_index = route.query.step_index
const status = route.query.status const status = route.query.status
const flow_type = route.query.flow_type
const approvalNotes = ref('') const approvalNotes = ref('')
const orderDialog = reactive({ const orderDialog = reactive({
orderData: {} as OrderData orderData: {} as OrderData
}) })
const userOptions = ref<any>()
const getuserList = async () => {
// 这里调用获取用户列表的接口
const res = await manageService.presetusers()
userOptions.value = res || []
// return res || []
}
const zjForm = reactive({
to_uid: '',
instance_id: '',
step_index: '',
reason: ''
})
// 转交操作
const handleTransfer = async () => {
getuserList()
zjDialogVisible.value = true
}
const handlezjSubmit = async () => {
if (!zjForm.to_uid) {
ElMessage.warning('请选择转交人')
return
}
if (!zjForm.reason) {
ElMessage.warning('请输入转交原因')
return
}
try {
const zjForms = {
to_uid: zjForm.to_uid,
instance_id: instance_id,
step_index: step_index,
reason: zjForm.reason
}
const { message } = await manageService.stockapprovaltransfer(zjForms)
ElMessage.success(message)
router.push('/admin/approval/stocking_auth')
} catch (error) {
console.error('转交失败:', error)
// ElMessage.error('转交失败')
}
}
const handleSubmit = async (approvalAction: string) => { const handleSubmit = async (approvalAction: string) => {
try { try {
const { message } = await manageService.stockapprovalpass({ const { message } = await manageService.stockapprovalpass({
@@ -169,17 +249,38 @@
// 合同文件 // 合同文件
const contractFile = ref<{ name: string; url: string; file_name: string } | null>(null) const contractFile = ref<{ name: string; url: string; file_name: string } | null>(null)
function extractFileName(pathOrUrl: string | undefined | null): string {
const toMoneyView = (m: any) => ({ if (!pathOrUrl) return '合同文件'
amount: m?.amount ?? '', try {
currency: m?.currency ?? '' const clean = String(pathOrUrl)
}) const afterSlash = clean.split(/[\\/]/).pop() || '合同文件'
return decodeURIComponent(afterSlash)
} catch {
return '合同文件'
}
}
//获取详情并映射到回显模型 //获取详情并映射到回显模型
const getdetilfn = async () => { const getdetilfn = async () => {
const res = await manageService.checkStock({ id: stocking_id }) const res = await manageService.stockapprovaldetailborder({
id: approval_id,
flow_type: flow_type
})
if (flow_type == 'stock_orders') {
const orderData: OrderData = res const orderData: OrderData = res
orderDialog.orderData = orderData orderDialog.orderData = orderData
} else if (flow_type == 'purchase_orders') {
const orderData: OrderData = res
const urls = res.contract_file_url
const paths = res.contract_file
const list: Array<{ name: string; url: string }> = []
const nameFromPath = (p: string) => extractFileName(p)
urls.forEach((u: string, i: number) => {
const p = paths[i] || ''
list.push({ name: p ? nameFromPath(p) : extractFileName(u), url: u })
})
orderData.contract_file_url = list
orderDialog.orderData = orderData
}
// 合同文件 // 合同文件
if (res?.contract) { if (res?.contract) {
contractFile.value = { contractFile.value = {
@@ -196,7 +196,14 @@
const handleView = (row: any) => { const handleView = (row: any) => {
router.push({ router.push({
path: '/admin/approval/stocking_auth_detil', path: '/admin/approval/stocking_auth_detil',
query: { id: row.id, biz_id: row.biz_id, instance_id: row.instance_id, status: row.status } // 可带客户名等参数 query: {
id: row.id,
biz_id: row.biz_id,
instance_id: row.instance_id,
status: row.status,
flow_type: row.flow_type,
step_index: row.step_index
} // 可带客户名等参数
}) })
} }
const instance_id = ref('') const instance_id = ref('')
File diff suppressed because it is too large Load Diff
@@ -66,6 +66,9 @@
<el-link type="primary" underline="never" @click="handleView1(scope.row)"> <el-link type="primary" underline="never" @click="handleView1(scope.row)">
<i class="iconfont-sys">&#xe76c;</i> 查看终版 <i class="iconfont-sys">&#xe76c;</i> 查看终版
</el-link> </el-link>
<el-link type="warning" underline="never" @click="showDialogtimeline(scope.row)">
<i class="iconfont-sys">&#xe728;</i> 查看审批
</el-link>
</ElRow> </ElRow>
</template> </template>
</ElTableColumn> </ElTableColumn>
@@ -156,6 +159,19 @@
/> />
</div> </div>
</el-dialog> </el-dialog>
<el-dialog
v-model="timelineDialogVisible"
title="审批历史"
width="600px"
:close-on-click-modal="false"
:close-on-press-escape="false"
class="approval-dialog"
>
<div class="approval-content">
<!-- 头部信息 -->
<ApprovalTimeline :records="approvalRecords" />
</div>
</el-dialog>
</div> </div>
</template> </template>
@@ -165,8 +181,11 @@
import { manageService } from '@/api/adminApi' import { manageService } from '@/api/adminApi'
import { useAuth } from '@/composables/useAuth' import { useAuth } from '@/composables/useAuth'
import OrderDetailContent from '@/components/custom/OrderDetailContent.vue' import OrderDetailContent from '@/components/custom/OrderDetailContent.vue'
import ApprovalTimeline from '@/components/custom/ApprovalTimeline.vue'
import type { OrderData } from '@/composables/useOrderDetailDialog' import type { OrderData } from '@/composables/useOrderDetailDialog'
import { usePdfPreview } from '@/composables/usePdfPreview' import { usePdfPreview } from '@/composables/usePdfPreview'
const timelineDialogVisible = ref(false)
const approvalRecords = ref<any[]>([])
// PDF 预览/打印(公共) // PDF 预览/打印(公共)
const { pdf, frameRef, openWithResponse, download, print, preparePreview, prepareImages } = usePdfPreview() const { pdf, frameRef, openWithResponse, download, print, preparePreview, prepareImages } = usePdfPreview()
defineOptions({ name: 'AdminFinalListPage' }) defineOptions({ name: 'AdminFinalListPage' })
@@ -370,6 +389,28 @@
ElMessage.error('下载失败,请稍后重试') ElMessage.error('下载失败,请稍后重试')
} }
} }
//审批进度
const showDialogtimeline = async (row: any) => {
// 审批记录(兼容多种字段)
const resstockapprovallogs = await manageService.stockapprovaltimeline({
id: row.id,
flow_type: 'stock_orders'
})
console.log(resstockapprovallogs)
const historyList = resstockapprovallogs
if (historyList.length > 0) {
approvalRecords.value = historyList.map((h: any) => ({
name: h?.initiator_name ? h?.initiator_name : h?.name,
role: h?.status_desc,
time: h?.time || '',
approved: h?.status === 'approved' || h?.status === 'submitted',
rejected: h?.status === 'rejected',
pending: h?.status != 'approved' && h?.status != 'rejected' && h?.status != 'submitted',
comment: h?.comment || h?.remark || ''
}))
}
timelineDialogVisible.value = true
}
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
@@ -80,6 +80,9 @@
> >
<i class="iconfont-sys">&#xe76c;</i> 查看终版 <i class="iconfont-sys">&#xe76c;</i> 查看终版
</el-link> </el-link>
<el-link type="warning" underline="never" @click="showDialogtimeline(scope.row)">
<i class="iconfont-sys">&#xe728;</i> 查看审批
</el-link>
</ElRow> </ElRow>
</template> </template>
</ElTableColumn> </ElTableColumn>
@@ -171,6 +174,19 @@
</div> </div>
</template> </template>
</el-dialog> </el-dialog>
<el-dialog
v-model="timelineDialogVisible"
title="审批历史"
width="600px"
:close-on-click-modal="false"
:close-on-press-escape="false"
class="approval-dialog"
>
<div class="approval-content">
<!-- 头部信息 -->
<ApprovalTimeline :records="approvalRecords" />
</div>
</el-dialog>
</div> </div>
</template> </template>
@@ -178,7 +194,10 @@
import { manageService } from '@/api/adminApi' import { manageService } from '@/api/adminApi'
import { usePdfPreview } from '@/composables/usePdfPreview' import { usePdfPreview } from '@/composables/usePdfPreview'
import OrderDetailContent from '@/components/custom/OrderDetailContent.vue' import OrderDetailContent from '@/components/custom/OrderDetailContent.vue'
import ApprovalTimeline from '@/components/custom/ApprovalTimeline.vue'
import type { OrderData } from '@/composables/useOrderDetailDialog' import type { OrderData } from '@/composables/useOrderDetailDialog'
const timelineDialogVisible = ref(false)
const approvalRecords = ref<any[]>([])
defineOptions({ name: 'StockingListAdmin' }) defineOptions({ name: 'StockingListAdmin' })
// import { useAuth } from '@/composables/useAuth' // import { useAuth } from '@/composables/useAuth'
// const { hasAuth } = useAuth() // const { hasAuth } = useAuth()
@@ -413,6 +432,28 @@
console.log(err) console.log(err)
}) })
} }
//审批进度
const showDialogtimeline = async (row: any) => {
// 审批记录(兼容多种字段)
const resstockapprovallogs = await manageService.stockapprovaltimeline({
id: row.id,
flow_type: 'stock_orders'
})
console.log(resstockapprovallogs)
const historyList = resstockapprovallogs
if (historyList.length > 0) {
approvalRecords.value = historyList.map((h: any) => ({
name: h?.initiator_name ? h?.initiator_name : h?.name,
role: h?.status_desc,
time: h?.time || '',
approved: h?.status === 'approved' || h?.status === 'submitted',
rejected: h?.status === 'rejected',
pending: h?.status != 'approved' && h?.status != 'rejected' && h?.status != 'submitted',
comment: h?.comment || h?.remark || ''
}))
}
timelineDialogVisible.value = true
}
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
+2 -2
View File
@@ -109,7 +109,7 @@
<ElRow :gutter="16"> <ElRow :gutter="16">
<ElCol :span="8"> <ElCol :span="8">
<ElFormItem label="品牌名称" prop="brand_name"> <ElFormItem label="品牌名称" prop="brand_name">
<ElSelect v-model="formData.brand_name" placeholder="请选择品牌名称"> <ElSelect v-model="formData.brand_name" filterable placeholder="请选择品牌名称">
<ElOption <ElOption
v-for="item in brandOptions" v-for="item in brandOptions"
:key="item.brand_code || item.code" :key="item.brand_code || item.code"
@@ -122,7 +122,7 @@
</ElCol> </ElCol>
<ElCol :span="8"> <ElCol :span="8">
<ElFormItem label="车系名称" prop="series_name"> <ElFormItem label="车系名称" prop="series_name">
<ElSelect v-model="formData.series_name" placeholder="请选择车系名称"> <ElSelect v-model="formData.series_name" filterable placeholder="请选择车系名称">
<ElOption <ElOption
v-for="s in seriesOptions" v-for="s in seriesOptions"
:key="s.series_code || s.code" :key="s.series_code || s.code"
+58
View File
@@ -373,6 +373,57 @@
<ElInput v-model="item.ocean_freight_currency_snapshot" type="hidden" /> <ElInput v-model="item.ocean_freight_currency_snapshot" type="hidden" />
</ElFormItem> </ElFormItem>
</ElCol> </ElCol>
<ElCol :sm="12" :md="12" :lg="6" :xl="4">
<ElFormItem label="佣金" label-width="85px">
<ElInput v-model="item.commission" placeholder="请输入佣金">
<template #suffix>
<ElSelect v-model="item.commission_currency" style="width: 64px">
<ElOption
v-for="c in currencyOptions"
:key="c.currency_code"
:label="c.currency_name"
:value="c.currency_code"
/>
</ElSelect>
</template>
</ElInput>
<ElInput v-model="item.commission_currency_snapshot" type="hidden" />
</ElFormItem>
</ElCol>
<ElCol :sm="12" :md="12" :lg="6" :xl="4">
<ElFormItem label="购置税" label-width="85px">
<ElInput v-model="item.purchase_tax" placeholder="请输入购置税">
<template #suffix>
<ElSelect v-model="item.purchase_tax_currency" style="width: 64px">
<ElOption
v-for="c in currencyOptions"
:key="c.currency_code"
:label="c.currency_name"
:value="c.currency_code"
/>
</ElSelect>
</template>
</ElInput>
<ElInput v-model="item.purchase_tax_currency_snapshot" type="hidden" />
</ElFormItem>
</ElCol>
<ElCol :sm="12" :md="12" :lg="6" :xl="4">
<ElFormItem label="预估退税额" label-width="85px">
<ElInput v-model="item.tax_refund" placeholder="请输入预估退税额">
<template #suffix>
<ElSelect v-model="item.tax_refund_currency" style="width: 64px">
<ElOption
v-for="c in currencyOptions"
:key="c.currency_code"
:label="c.currency_name"
:value="c.currency_code"
/>
</ElSelect>
</template>
</ElInput>
<ElInput v-model="item.tax_refund_currency_snapshot" type="hidden" />
</ElFormItem>
</ElCol>
</ElRow> </ElRow>
<ElRow :gutter="12" class="item-row-wrap"> <ElRow :gutter="12" class="item-row-wrap">
<ElCol :sm="12" :md="12" :lg="6" :xl="4"> <ElCol :sm="12" :md="12" :lg="6" :xl="4">
@@ -429,6 +480,13 @@
</ElFormItem> </ElFormItem>
</ElCol> </ElCol>
</ElRow> </ElRow>
<ElRow :gutter="12" class="item-row-wrap">
<ElCol :sm="24" :md="24" :lg="24" :xl="24">
<ElFormItem label="备注" label-width="85px">
<ElInput type="textarea" :rows="2" v-model="item.remark" placeholder="请输入备注" />
</ElFormItem>
</ElCol>
</ElRow>
<ElRow :gutter="12"> <ElRow :gutter="12">
<ElCol :span="24" style="text-align: right"> <ElCol :span="24" style="text-align: right">
<el-tag effect="light" type="success" @click="copyRow(idx)" class="row-actions"> <el-tag effect="light" type="success" @click="copyRow(idx)" class="row-actions">
+58
View File
@@ -374,6 +374,57 @@
<ElInput v-model="item.ocean_freight_currency_snapshot" type="hidden" /> <ElInput v-model="item.ocean_freight_currency_snapshot" type="hidden" />
</ElFormItem> </ElFormItem>
</ElCol> </ElCol>
<ElCol :sm="12" :md="12" :lg="6" :xl="4">
<ElFormItem label="佣金" label-width="85px">
<ElInput v-model="item.commission" placeholder="请输入佣金">
<template #suffix>
<ElSelect v-model="item.commission_currency" style="width: 64px">
<ElOption
v-for="c in currencyOptions"
:key="c.currency_code"
:label="c.currency_name"
:value="c.currency_code"
/>
</ElSelect>
</template>
</ElInput>
<ElInput v-model="item.commission_currency_snapshot" type="hidden" />
</ElFormItem>
</ElCol>
<ElCol :sm="12" :md="12" :lg="6" :xl="4">
<ElFormItem label="购置税" label-width="85px">
<ElInput v-model="item.purchase_tax" placeholder="请输入购置税">
<template #suffix>
<ElSelect v-model="item.purchase_tax_currency" style="width: 64px">
<ElOption
v-for="c in currencyOptions"
:key="c.currency_code"
:label="c.currency_name"
:value="c.currency_code"
/>
</ElSelect>
</template>
</ElInput>
<ElInput v-model="item.purchase_tax_currency_snapshot" type="hidden" />
</ElFormItem>
</ElCol>
<ElCol :sm="12" :md="12" :lg="6" :xl="4">
<ElFormItem label="预估退税额" label-width="85px">
<ElInput v-model="item.tax_refund" placeholder="请输入预估退税额">
<template #suffix>
<ElSelect v-model="item.tax_refund_currency" style="width: 64px">
<ElOption
v-for="c in currencyOptions"
:key="c.currency_code"
:label="c.currency_name"
:value="c.currency_code"
/>
</ElSelect>
</template>
</ElInput>
<ElInput v-model="item.tax_refund_currency_snapshot" type="hidden" />
</ElFormItem>
</ElCol>
</ElRow> </ElRow>
<ElRow :gutter="12" class="item-row-wrap"> <ElRow :gutter="12" class="item-row-wrap">
<ElCol :sm="12" :md="12" :lg="6" :xl="4"> <ElCol :sm="12" :md="12" :lg="6" :xl="4">
@@ -435,6 +486,13 @@
</ElFormItem> </ElFormItem>
</ElCol> </ElCol>
</ElRow> </ElRow>
<ElRow :gutter="12" class="item-row-wrap">
<ElCol :sm="24" :md="24" :lg="24" :xl="24">
<ElFormItem label="备注" label-width="85px">
<ElInput type="textarea" :rows="2" v-model="item.remark" placeholder="请输入备注" />
</ElFormItem>
</ElCol>
</ElRow>
</div> </div>
<ElRow :gutter="12"> <ElRow :gutter="12">
<ElCol :sm="12" :md="12" :lg="6" :xl="6"> <ElCol :sm="12" :md="12" :lg="6" :xl="6">
+58
View File
@@ -453,6 +453,57 @@
<ElInput v-model="item.ocean_freight_currency_snapshot" type="hidden" /> <ElInput v-model="item.ocean_freight_currency_snapshot" type="hidden" />
</ElFormItem> </ElFormItem>
</ElCol> </ElCol>
<ElCol :sm="12" :md="12" :lg="6" :xl="4">
<ElFormItem label="佣金" label-width="85px">
<ElInput v-model="item.commission" placeholder="请输入佣金">
<template #suffix>
<ElSelect v-model="item.commission_currency" style="width: 64px">
<ElOption
v-for="c in currencyOptions"
:key="c.currency_code"
:label="c.currency_name"
:value="c.currency_code"
/>
</ElSelect>
</template>
</ElInput>
<ElInput v-model="item.commission_currency_snapshot" type="hidden" />
</ElFormItem>
</ElCol>
<ElCol :sm="12" :md="12" :lg="6" :xl="4">
<ElFormItem label="购置税" label-width="85px">
<ElInput v-model="item.purchase_tax" placeholder="请输入购置税">
<template #suffix>
<ElSelect v-model="item.purchase_tax_currency" style="width: 64px">
<ElOption
v-for="c in currencyOptions"
:key="c.currency_code"
:label="c.currency_name"
:value="c.currency_code"
/>
</ElSelect>
</template>
</ElInput>
<ElInput v-model="item.purchase_tax_currency_snapshot" type="hidden" />
</ElFormItem>
</ElCol>
<ElCol :sm="12" :md="12" :lg="6" :xl="4">
<ElFormItem label="预估退税额" label-width="85px">
<ElInput v-model="item.tax_refund" placeholder="请输入预估退税额">
<template #suffix>
<ElSelect v-model="item.tax_refund_currency" style="width: 64px">
<ElOption
v-for="c in currencyOptions"
:key="c.currency_code"
:label="c.currency_name"
:value="c.currency_code"
/>
</ElSelect>
</template>
</ElInput>
<ElInput v-model="item.tax_refund_currency_snapshot" type="hidden" />
</ElFormItem>
</ElCol>
</ElRow> </ElRow>
<ElRow :gutter="12" class="item-row-wrap"> <ElRow :gutter="12" class="item-row-wrap">
<ElCol :sm="12" :md="12" :lg="6" :xl="6"> <ElCol :sm="12" :md="12" :lg="6" :xl="6">
@@ -526,6 +577,13 @@
</ElFormItem> </ElFormItem>
</ElCol> </ElCol>
</ElRow> </ElRow>
<ElRow :gutter="12" class="item-row-wrap">
<ElCol :sm="24" :md="24" :lg="24" :xl="24">
<ElFormItem label="备注" label-width="85px">
<ElInput type="textarea" :rows="2" v-model="item.remark" placeholder="请输入备注" />
</ElFormItem>
</ElCol>
</ElRow>
</div> </div>
<ElRow :gutter="12"> <ElRow :gutter="12">
<ElCol :sm="12" :md="12" :lg="6" :xl="6"> <ElCol :sm="12" :md="12" :lg="6" :xl="6">
+42 -1
View File
@@ -85,6 +85,9 @@
<el-link type="primary" underline="never" @click="handleView1(scope.row)"> <el-link type="primary" underline="never" @click="handleView1(scope.row)">
<i class="iconfont-sys">&#xe76c;</i> 查看终版 <i class="iconfont-sys">&#xe76c;</i> 查看终版
</el-link> </el-link>
<el-link type="warning" underline="never" @click="showDialogtimeline(scope.row)">
<i class="iconfont-sys">&#xe728;</i> 查看审批
</el-link>
</template> </template>
</ElTableColumn> </ElTableColumn>
</template> </template>
@@ -174,6 +177,19 @@
/> />
</div> </div>
</el-dialog> </el-dialog>
<el-dialog
v-model="timelineDialogVisible"
title="审批历史"
width="600px"
:close-on-click-modal="false"
:close-on-press-escape="false"
class="approval-dialog"
>
<div class="approval-content">
<!-- 头部信息 -->
<ApprovalTimeline :records="approvalRecords" />
</div>
</el-dialog>
</div> </div>
</template> </template>
@@ -184,6 +200,10 @@
import { usePdfPreview } from '@/composables/usePdfPreview' import { usePdfPreview } from '@/composables/usePdfPreview'
import OrderDetailContent from '@/components/custom/OrderDetailContent.vue' import OrderDetailContent from '@/components/custom/OrderDetailContent.vue'
import type { OrderData } from '@/composables/useOrderDetailDialog' import type { OrderData } from '@/composables/useOrderDetailDialog'
import ApprovalTimeline from '@/components/custom/ApprovalTimeline.vue'
import { manageService } from '@/api/adminApi'
const timelineDialogVisible = ref(false)
const approvalRecords = ref<any[]>([])
defineOptions({ name: 'CrmFinalList' }) defineOptions({ name: 'CrmFinalList' })
// 订单详情弹窗 // 订单详情弹窗
const orderDialog = reactive({ const orderDialog = reactive({
@@ -207,7 +227,28 @@
onMounted(() => { onMounted(() => {
getList() getList()
}) })
//审批进度
const showDialogtimeline = async (row: any) => {
// 审批记录(兼容多种字段)
const resstockapprovallogs = await manageService.stockapprovaltimeline({
id: row.id,
flow_type: 'stock_orders'
})
console.log(resstockapprovallogs)
const historyList = resstockapprovallogs
if (historyList.length > 0) {
approvalRecords.value = historyList.map((h: any) => ({
name: h?.initiator_name ? h?.initiator_name : h?.name,
role: h?.status_desc,
time: h?.finished_at || '',
approved: h?.status === 'approved' || h?.status === 'submitted',
rejected: h?.status === 'rejected',
pending: h?.status != 'approved' && h?.status != 'rejected' && h?.status != 'submitted',
comment: h?.comment || h?.remark || ''
}))
}
timelineDialogVisible.value = true
}
watch([keyword], () => { watch([keyword], () => {
pagination.currentPage = 1 pagination.currentPage = 1
// 不自动请求,保持与截图行为一致,点击搜索或回车触发 // 不自动请求,保持与截图行为一致,点击搜索或回车触发
@@ -373,6 +373,57 @@
<ElInput v-model="item.ocean_freight_currency_snapshot" type="hidden" /> <ElInput v-model="item.ocean_freight_currency_snapshot" type="hidden" />
</ElFormItem> </ElFormItem>
</ElCol> </ElCol>
<ElCol :sm="12" :md="12" :lg="6" :xl="4">
<ElFormItem label="佣金" label-width="85px">
<ElInput v-model="item.commission" placeholder="请输入佣金">
<template #suffix>
<ElSelect v-model="item.commission_currency" style="width: 64px">
<ElOption
v-for="c in currencyOptions"
:key="c.currency_code"
:label="c.currency_name"
:value="c.currency_code"
/>
</ElSelect>
</template>
</ElInput>
<ElInput v-model="item.commission_currency_snapshot" type="hidden" />
</ElFormItem>
</ElCol>
<ElCol :sm="12" :md="12" :lg="6" :xl="4">
<ElFormItem label="购置税" label-width="85px">
<ElInput v-model="item.purchase_tax" placeholder="请输入购置税">
<template #suffix>
<ElSelect v-model="item.purchase_tax_currency" style="width: 64px">
<ElOption
v-for="c in currencyOptions"
:key="c.currency_code"
:label="c.currency_name"
:value="c.currency_code"
/>
</ElSelect>
</template>
</ElInput>
<ElInput v-model="item.purchase_tax_currency_snapshot" type="hidden" />
</ElFormItem>
</ElCol>
<ElCol :sm="12" :md="12" :lg="6" :xl="4">
<ElFormItem label="预估退税额" label-width="85px">
<ElInput v-model="item.tax_refund" placeholder="请输入预估退税额">
<template #suffix>
<ElSelect v-model="item.tax_refund_currency" style="width: 64px">
<ElOption
v-for="c in currencyOptions"
:key="c.currency_code"
:label="c.currency_name"
:value="c.currency_code"
/>
</ElSelect>
</template>
</ElInput>
<ElInput v-model="item.tax_refund_currency_snapshot" type="hidden" />
</ElFormItem>
</ElCol>
</ElRow> </ElRow>
<ElRow :gutter="12" class="item-row-wrap"> <ElRow :gutter="12" class="item-row-wrap">
<ElCol :sm="12" :md="12" :lg="6" :xl="6"> <ElCol :sm="12" :md="12" :lg="6" :xl="6">
@@ -429,6 +480,13 @@
</ElFormItem> </ElFormItem>
</ElCol> </ElCol>
</ElRow> </ElRow>
<ElRow :gutter="12" class="item-row-wrap">
<ElCol :sm="24" :md="24" :lg="24" :xl="24">
<ElFormItem label="备注" label-width="85px">
<ElInput type="textarea" :rows="2" v-model="item.remark" placeholder="请输入备注" />
</ElFormItem>
</ElCol>
</ElRow>
<ElRow :gutter="12"> <ElRow :gutter="12">
<ElCol :span="24" style="text-align: right"> <ElCol :span="24" style="text-align: right">
<el-tag effect="light" class="row-actions" type="success" @click="copyRow(idx)"> 复制 </el-tag> <el-tag effect="light" class="row-actions" type="success" @click="copyRow(idx)"> 复制 </el-tag>
@@ -114,6 +114,9 @@
<el-link type="warning" underline="never" @click="copys(scope.row)"> <el-link type="warning" underline="never" @click="copys(scope.row)">
<i class="iconfont-sys">&#xe8b7;</i> 复制 <i class="iconfont-sys">&#xe8b7;</i> 复制
</el-link> </el-link>
<el-link type="warning" underline="never" @click="showDialogtimeline(scope.row)">
<i class="iconfont-sys">&#xe728;</i> 查看审批
</el-link>
</ElRow> </ElRow>
</template> </template>
</ElTableColumn> </ElTableColumn>
@@ -207,6 +210,19 @@
</template> </template>
</el-dialog> </el-dialog>
<!-- 终版详情 --> <!-- 终版详情 -->
<el-dialog
v-model="timelineDialogVisible"
title="审批历史"
width="600px"
:close-on-click-modal="false"
:close-on-press-escape="false"
class="approval-dialog"
>
<div class="approval-content">
<!-- 头部信息 -->
<ApprovalTimeline :records="approvalRecords" />
</div>
</el-dialog>
</div> </div>
</template> </template>
@@ -216,6 +232,10 @@
import { usePdfPreview } from '@/composables/usePdfPreview' import { usePdfPreview } from '@/composables/usePdfPreview'
import OrderDetailContent from '@/components/custom/OrderDetailContent.vue' import OrderDetailContent from '@/components/custom/OrderDetailContent.vue'
import type { OrderData } from '@/composables/useOrderDetailDialog' import type { OrderData } from '@/composables/useOrderDetailDialog'
import ApprovalTimeline from '@/components/custom/ApprovalTimeline.vue'
import { manageService } from '@/api/adminApi'
const timelineDialogVisible = ref(false)
const approvalRecords = ref<any[]>([])
// import { useAuth } from '@/composables/useAuth' // import { useAuth } from '@/composables/useAuth'
// const { hasAuth } = useAuth() // const { hasAuth } = useAuth()
@@ -241,6 +261,28 @@
onMounted(() => { onMounted(() => {
getList() getList()
}) })
//审批进度
const showDialogtimeline = async (row: any) => {
// 审批记录(兼容多种字段)
const resstockapprovallogs = await manageService.stockapprovaltimeline({
id: row.id,
flow_type: 'stock_orders'
})
console.log(resstockapprovallogs)
const historyList = resstockapprovallogs
if (historyList.length > 0) {
approvalRecords.value = historyList.map((h: any) => ({
name: h?.initiator_name ? h?.initiator_name : h?.name,
role: h?.status_desc,
time: h?.time || '',
approved: h?.status === 'approved' || h?.status === 'submitted',
rejected: h?.status === 'rejected',
pending: h?.status != 'approved' && h?.status != 'rejected' && h?.status != 'submitted',
comment: h?.comment || h?.remark || ''
}))
}
timelineDialogVisible.value = true
}
function statusClass(status: any) { function statusClass(status: any) {
if (status === 'rejected') return 'status-lost' if (status === 'rejected') return 'status-lost'
if (status === 'approved') return 'status-following' if (status === 'approved') return 'status-following'
+2 -2
View File
@@ -109,7 +109,7 @@
<ElRow :gutter="16"> <ElRow :gutter="16">
<ElCol :span="8"> <ElCol :span="8">
<ElFormItem label="品牌名称" prop="brand_name"> <ElFormItem label="品牌名称" prop="brand_name">
<ElSelect v-model="formData.brand_name" placeholder="请选择品牌名称"> <ElSelect v-model="formData.brand_name" filterable placeholder="请选择品牌名称">
<ElOption <ElOption
v-for="item in brandOptions" v-for="item in brandOptions"
:key="item.brand_code || item.code" :key="item.brand_code || item.code"
@@ -122,7 +122,7 @@
</ElCol> </ElCol>
<ElCol :span="8"> <ElCol :span="8">
<ElFormItem label="车系名称" prop="series_name"> <ElFormItem label="车系名称" prop="series_name">
<ElSelect v-model="formData.series_name" placeholder="请选择车系名称"> <ElSelect v-model="formData.series_name" filterable placeholder="请选择车系名称">
<ElOption <ElOption
v-for="s in seriesOptions" v-for="s in seriesOptions"
:key="s.series_code || s.code" :key="s.series_code || s.code"