Files
art-design/src/views/crmpc/stocking/final_list.vue
T
浅念 9925e2e2e4 Squashed commit of the following:
commit 90df51097f
Author: 浅念 <email@sample.com>
Date:   Wed Sep 24 18:50:35 2025 +0800

    备货单 采购单

commit 7b1fdf3883
Author: 浅念 <email@sample.com>
Date:   Wed Sep 24 11:15:31 2025 +0800

    渠道筛选

commit 1c81d3108c
Author: 浅念 <email@sample.com>
Date:   Wed Sep 24 10:50:43 2025 +0800

    客户列表 添加渠道筛选

commit 2b877e9b4d
Author: 浅念 <email@sample.com>
Date:   Mon Sep 22 10:20:46 2025 +0800

    去掉编码显示

commit bee80af5af
Author: 浅念 <email@sample.com>
Date:   Fri Sep 19 16:43:30 2025 +0800

    港口添加 英文

commit 045ce9289c
Author: 浅念 <email@sample.com>
Date:   Thu Sep 18 15:17:30 2025 +0800

    审批历史 和代办审批

commit c5ec4bb5e1
Author: 浅念 <email@sample.com>
Date:   Thu Sep 18 11:31:22 2025 +0800

    修改kpi

commit 9e260ddcd8
Author: 浅念 <email@sample.com>
Date:   Thu Sep 18 09:49:33 2025 +0800

    修改 kpi

commit 6feb3ace75
Author: 浅念 <email@sample.com>
Date:   Thu Sep 18 09:42:44 2025 +0800

    kpi 统计

commit ec257a246d
Author: 浅念 <email@sample.com>
Date:   Wed Sep 17 17:04:35 2025 +0800

    修改 弹框表格

commit fa764852ab
Author: 浅念 <email@sample.com>
Date:   Wed Sep 17 15:54:35 2025 +0800

    添加 账户简称

commit 65f7f4a123
Author: 浅念 <email@sample.com>
Date:   Wed Sep 17 15:10:28 2025 +0800

    品牌加热门  修改 仪表盘接口

commit 922cfec81d
Author: 浅念 <email@sample.com>
Date:   Tue Sep 16 15:57:17 2025 +0800

    调整备货单  crm 端页面

commit 5c8df36f12
Author: 浅念 <email@sample.com>
Date:   Mon Sep 15 17:14:38 2025 +0800

    调整备货单 及各页面名称

commit 5503a14408
Author: 浅念 <email@sample.com>
Date:   Fri Sep 12 18:10:03 2025 +0800

    账户添加公司

commit 3439b45d9a
Author: 浅念 <email@sample.com>
Date:   Fri Sep 12 17:53:26 2025 +0800

    修改终版备货单

commit 045d13eb7b
Author: 浅念 <email@sample.com>
Date:   Fri Sep 12 17:49:07 2025 +0800

    修改备货单 等页面

commit da690dfa06
Author: 浅念 <email@sample.com>
Date:   Thu Sep 11 17:44:37 2025 +0800

    修改 备货单 详情

commit 853e9591b3
Author: 浅念 <email@sample.com>
Date:   Wed Sep 10 18:11:39 2025 +0800

    修改 备货单页面

commit 9052bd1027
Author: 浅念 <email@sample.com>
Date:   Tue Sep 9 20:53:30 2025 +0800

    备货单  审核功能

commit 079d9babb8
Merge: ebd7479 91d810b
Author: 浅念 <email@sample.com>
Date:   Tue Sep 9 14:51:43 2025 +0800

    Merge branch 'feature/crm_pc' of http://219.157.255.213:25311/xinyouhe/art-design into feature/crm_vehicle

commit ebd7479573
Merge: 64e53b6 459d306
Author: 浅念 <email@sample.com>
Date:   Tue Sep 9 13:57:28 2025 +0800

    Merge branch 'feature/crm_pc' of http://219.157.255.213:25311/xinyouhe/art-design into feature/crm_vehicle

commit 64e53b6ee9
Author: 浅念 <email@sample.com>
Date:   Mon Sep 8 19:09:55 2025 +0800

    备货单 加 审批流程

commit 9bd7c89cb4
Author: 浅念 <email@sample.com>
Date:   Thu Sep 4 15:09:17 2025 +0800

    车辆信息,备货单  等页面

# Conflicts:
#	src/api/adminApi.ts
#	src/api/crmApi.ts
#	src/components/custom/OrderDetailContent.vue
#	src/composables/useOrderDetailDialog.ts
#	src/composables/useStockingForm.ts
#	src/views/admin/approval/stocking_auth.vue
#	src/views/admin/approval/stocking_auth_detil.vue
#	src/views/admin/stock/final_list_admin.vue
#	src/views/admin/stock/stocking_list_admin.vue
#	src/views/admin/user/index.vue
#	src/views/admin/vehicle/color.vue
#	src/views/admin/vehicle/modification.vue
#	src/views/admin/vehicle/port.vue
#	src/views/admin/vehicle/vehicle_list.vue
#	src/views/crmpc/approval/stocking_auth.vue
#	src/views/crmpc/stocking/Add.vue
#	src/views/crmpc/stocking/final_add.vue
#	src/views/crmpc/stocking/final_edit.vue
#	src/views/crmpc/stocking/final_list.vue
#	src/views/crmpc/stocking/stocking_edit.vue
#	src/views/crmpc/stocking/stocking_list.vue
#	src/views/crmpc/vehicle/color.vue
#	src/views/crmpc/vehicle/modification.vue
#	src/views/crmpc/vehicle/vehicle_list.vue
2025-09-24 19:06:26 +08:00

512 lines
16 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div class="stocking-page">
<div class="stocking-toolbar boxshadow">
<!-- <div class="toolbar-left">
<ElButton type="primary" v-ripple @click="handleCreate">创建备货单</ElButton>
</div> -->
<div class="toolbar-right">
<el-input
v-model="keyword"
placeholder="请输入客户名称"
clearable
style="width: 260px"
@keyup.enter="getList"
>
<template #suffix>
<el-icon class="clickable" @click="getList"><Search /></el-icon>
</template>
</el-input>
</div>
</div>
<div class="boxshadow">
<ArtTable
ref="tableRef"
row-key="id"
:loading="loading"
:data="tableData"
:currentPage="pagination.currentPage"
:pageSize="pagination.pageSize"
:total="pagination.total"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
>
<template #default>
<ElTableColumn label="备货单编号" prop="order_no" min-width="140" />
<ElTableColumn label="阿里订单号/PI号" prop="pi_no" min-width="160" />
<ElTableColumn label="客户名称" prop="customer_snapshot" min-width="120" />
<ElTableColumn label="客户来源" prop="customer_source_snapshot" min-width="120" />
<ElTableColumn label="业务员" prop="salesperson_snapshot" min-width="100" />
<ElTableColumn label="日期" prop="created_at" min-width="120" />
<ElTableColumn label="状态" prop="approval_status">
<template #default="scope">
<div v-if="scope.row.appro_status">
<div
><span :class="statusClass(scope.row.appro_status.status)">{{
scope.row.appro_status.status_desc ? scope.row.appro_status.status_desc : ''
}}</span>
<span v-if="scope.row.appro_status.name != ''"
>【{{ scope.row.appro_status.name }}】</span
>
</div>
</div>
<div v-else></div>
</template>
</ElTableColumn>
<ElTableColumn label="操作" fixed="right" min-width="160" align="center">
<template #default="scope">
<el-link
v-if="scope.row.file_url != null && scope.row.file_url != ''"
type="warning"
underline="never"
@click="printfn(scope.row)"
style="margin-right: 8px"
>
<i class="iconfont-sys">&#xe782;</i> 打印预览
</el-link>
<el-link
v-if="scope.row.approval_status == 0"
type="primary"
underline="never"
@click="handleEdit(scope.row)"
style="margin-right: 8px"
>
<i class="iconfont-sys">&#xe6bf;</i> 编辑
</el-link>
<el-link
v-if="scope.row.approval_status == 0"
type="success"
underline="never"
@click="handleapproval(scope.row)"
style="margin-right: 8px"
>
<i class="iconfont-sys">&#xe641;</i> 提交
</el-link>
<el-link type="primary" underline="never" @click="handleView1(scope.row)">
<i class="iconfont-sys">&#xe76c;</i> 查看终版
</el-link>
<el-link type="warning" underline="never" @click="showDialogtimeline(scope.row)">
<i class="iconfont-sys">&#xe728;</i> 查看审批
</el-link>
</template>
</ElTableColumn>
</template>
</ArtTable>
</div>
<!-- 订单详情弹窗(使用公共内容组件) -->
<el-dialog
v-model="orderDialog.visible"
:title="orderDialog.title"
width="95%"
:close-on-click-modal="false"
>
<OrderDetailContent :orderData="orderDialog.orderData" />
<div class="boxshadow">
<div class="form-title">
<!-- <span class="border"></span> -->
<span class="title">合同</span>
</div>
<div class="file-preview">
<div v-if="contractFile" class="contract-file">
<div v-if="isImageFile(contractFile)" class="image-preview">
<img
:src="contractFile.url"
:alt="contractFile.name"
class="preview-image"
@click="previewImage(contractFile)"
/>
<div class="file-info">
<span class="file-name">{{ contractFile.name }}</span>
<el-button type="text" size="small" @click="previewImage(contractFile)">
预览
</el-button>
</div>
</div>
<div v-else class="document-preview">
<div class="file-icon">
<i class="iconfont-sys">&#xe814;</i>
</div>
<div class="file-info">
<span class="file-name">{{ contractFile.name }}</span>
<el-button type="primary" size="small" @click="downloadFile(contractFile)">
下载
</el-button>
</div>
</div>
</div>
<div v-else class="no-file">
<i class="iconfont-sys">&#xe60c;</i>
<span>暂无合同文件</span>
</div>
</div>
</div>
<template #footer>
<div style="display: flex; justify-content: flex-end; gap: 8px">
<el-button @click="orderDialog.visible = false">关闭</el-button>
</div>
</template>
</el-dialog>
<!-- PDF 预览弹窗 -->
<el-dialog
v-model="pdf.visible"
title="打印预览"
width="80%"
top="40px"
:close-on-click-modal="false"
>
<div style="max-height: 70vh; display: flex; flex-direction: column; gap: 12px">
<div style="display: flex; justify-content: flex-end; gap: 8px">
<el-button @click="download">下载</el-button>
</div>
<div
v-if="pdf.images.length"
style="overflow: auto; border: 1px solid #eee; border-radius: 6px; padding: 8px"
>
<img
v-for="(img, idx) in pdf.images"
:key="idx"
:src="img"
style="display: block; width: 100%; margin-bottom: 8px; background: #fff"
/>
</div>
<iframe
v-else-if="pdf.blobUrl || pdf.url"
ref="frameRef"
:src="pdf.blobUrl || pdf.url"
style="flex: 1; width: 100%; border: 1px solid #eee; border-radius: 6px"
/>
</div>
</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>
</template>
<script setup lang="ts">
import { router } from '@/router'
import { CrmService } from '@/api/crmApi'
import { useAuth } from '@/composables/useAuth'
import { usePdfPreview } from '@/composables/usePdfPreview'
import OrderDetailContent from '@/components/custom/OrderDetailContent.vue'
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' })
// 订单详情弹窗
const orderDialog = reactive({
visible: false,
title: '订单详情',
orderData: {} as OrderData
})
const { hasAuth } = useAuth()
function statusClass(status: any) {
if (status === 'rejected') return 'status-lost'
if (status === 'approved') return 'status-following'
return 'status-dealed'
}
const loading = ref(false)
const keyword = ref('')
const tableRef = ref()
const tableData = ref<any[]>([])
const pagination = reactive({ currentPage: 1, pageSize: 10, total: 0 })
onMounted(() => {
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], () => {
pagination.currentPage = 1
// 不自动请求,保持与截图行为一致,点击搜索或回车触发
})
const getList = async () => {
loading.value = true
try {
const { currentPage, pageSize } = pagination
const params = {
current: currentPage,
size: pageSize,
is_final: 1, // 只获取备货单,非终版
keyword: keyword.value
}
const { records, current, size, total } = await CrmService.getStockList(params)
tableData.value = records || []
Object.assign(pagination, { currentPage: current, pageSize: size, total })
} catch (error) {
console.error('获取列表失败:', error)
} finally {
loading.value = false
}
}
const handleSizeChange = (size: number) => {
pagination.pageSize = size
getList()
}
const handleCurrentChange = (page: number) => {
pagination.currentPage = page
getList()
}
const handleEdit = (row: any) => {
router.push({
path: '/crmpc/stocking/final_edit',
query: { id: row.id } // 可带客户名等参数
})
}
const handleapproval = (row: any) => {
CrmService.stockultimateapproval({ id: row.id })
.then((res) => {
ElMessage.success(res?.message || '修改成功')
getList()
})
.catch((err) => {
console.log(err)
})
}
// PDF 预览/打印(公共)
const { pdf, frameRef, openWithResponse, download, preparePreview, prepareImages } =
usePdfPreview()
//打印
const printfn = (row: any) => {
CrmService.printStock({ id: row.id })
.then((res) => {
openWithResponse(res)
// 优先转成图片以适配不带插件的环境
prepareImages()
// 同时保留 blob 预览作为兜底
preparePreview()
})
.catch((err) => {
console.log(err)
})
}
const contractFile = ref<{ name: string; url: string; file_name: string } | null>(null)
const handleView1 = async (row: any) => {
const res = await CrmService.checkStock({ id: row.id })
if (res?.contract) {
contractFile.value = {
name: '合同文件',
url: res.contract_url || res.contract,
file_name: res.contract
}
}
if (res) {
// 获取订单详情成功
const orderData: OrderData = res
orderDialog.orderData = orderData
orderDialog.title = '合塑实业-汽车事业部-备货单(客户下定)'
orderDialog.visible = true
}
// router.push({
// path: '/crmpc/stocking/final_show',
// query: { id: row.object_id } // 可带客户名等参数
// })
// router.push('/crmpc/stocking/final_show')
// TODO: 跳转或弹窗查看备货单
}
// const handleView1 = (row: any) => {
// router.push({
// path: '/crmpc/stocking/final_show',
// query: { id: row.id } // 可带客户名等参数
// })
// // TODO: 跳转或弹窗查看备货单
// }
// 判断是否为图片文件
const isImageFile = (file: { name: string; url: string; file_name: string }) => {
// 检查文件名
if (file.name && /\.(jpg|jpeg|png|gif|webp)$/i.test(file.name)) {
return true
}
// 检查file_name字段
if (file.file_name && file.file_name.startsWith('image/')) {
return true
}
// 检查URL中的文件扩展名(去除查询参数)
if (file.url) {
const urlWithoutParams = file.url.split('?')[0] // 移除查询参数
if (/\.(jpg|jpeg|png|gif|webp)$/i.test(urlWithoutParams)) {
return true
}
}
return false
}
// 预览图片
const previewImage = (file: { name: string; url: string; file_name: string }) => {
// 创建图片预览弹窗
const imagePreview = document.createElement('div')
imagePreview.style.cssText = `
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.8);
display: flex;
align-items: center;
justify-content: center;
z-index: 9999;
cursor: pointer;
`
const image = document.createElement('img')
image.src = file.url
image.alt = file.name
image.style.cssText = `
max-width: 90%;
max-height: 90%;
object-fit: contain;
border-radius: 4px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.5);
`
const closeBtn = document.createElement('div')
closeBtn.innerHTML = '×'
closeBtn.style.cssText = `
position: absolute;
top: 20px;
right: 30px;
color: white;
font-size: 30px;
cursor: pointer;
z-index: 10000;
`
imagePreview.appendChild(image)
imagePreview.appendChild(closeBtn)
document.body.appendChild(imagePreview)
// 点击关闭
const closePreview = () => {
document.body.removeChild(imagePreview)
}
imagePreview.addEventListener('click', closePreview)
closeBtn.addEventListener('click', (e) => {
e.stopPropagation()
closePreview()
})
// ESC键关闭
const handleKeydown = (e: KeyboardEvent) => {
if (e.key === 'Escape') {
closePreview()
document.removeEventListener('keydown', handleKeydown)
}
}
document.addEventListener('keydown', handleKeydown)
}
// 下载文件
const downloadFile = (file: { name: string; url: string; file_name: string }) => {
try {
const link = document.createElement('a')
link.href = file.url
link.download = file.name
link.target = '_blank'
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
} catch (error) {
console.error('下载失败:', error)
ElMessage.error('下载失败,请稍后重试')
}
}
</script>
<style scoped lang="scss">
:deep(.el-table__empty-block) {
height: 100% !important;
min-height: 200px;
display: flex;
align-items: center;
justify-content: center;
}
.stocking-page {
background: var(--art-main-bg-color);
min-height: 100vh;
}
.stocking-toolbar {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16px;
padding: 12px 16px;
background: var(--art-main-bg-color);
}
.toolbar-left {
display: flex;
align-items: center;
gap: 8px;
}
.toolbar-right {
display: flex;
align-items: center;
}
.clickable { cursor: pointer; }
.status-following {
color: #00a43f;
font-weight: bold;
}
.status-dealed {
color: #3471ff;
font-weight: bold;
}
.status-lost {
color: #ff0000;
font-weight: bold;
}
.no-file {
display: flex;
flex-direction: column;
align-items: center;
gap: 12px;
color: #999;
font-size: 14px;
}
.no-file i {
font-size: 48px;
color: #ddd;
}
</style>