1674 lines
51 KiB
Vue
1674 lines
51 KiB
Vue
<template>
|
|
<div class="page-content">
|
|
<div class="customer-toolbar boxshadow">
|
|
<div class="customer-search-group">
|
|
<el-form-item label="关键字:" label-width="70px">
|
|
<el-input style="min-width: 120px" v-model="keyword" placeholder="请输入关键字" />
|
|
</el-form-item>
|
|
<el-form-item label="状态:" label-width="70px">
|
|
<el-select v-model="status" placeholder="请选择状态" style="min-width: 120px">
|
|
<el-option label="未售" :value="1" />
|
|
<el-option label="已售出" :value="2" />
|
|
<el-option label="已归档" :value="3" />
|
|
</el-select>
|
|
</el-form-item>
|
|
</div>
|
|
</div>
|
|
<div class="boxshadow">
|
|
<div style="text-align: right">
|
|
<ElButton @click="fetchpublishfn()" v-ripple type="primary"> 抓取 </ElButton>
|
|
</div>
|
|
<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="code" />
|
|
<ElTableColumn label="产品名称" prop="product_name" />
|
|
<ElTableColumn label="产品图">
|
|
<template #default="scope">
|
|
<div class="thumb-list" v-if="parsePreviewList(scope.row.images_pull).length">
|
|
<img
|
|
:src="parsePreviewList(scope.row.images_pull)[0]"
|
|
alt="产品图"
|
|
class="avatarimg"
|
|
@click="handleAvatarClick1(parsePreviewList(scope.row.images_pull)[0])"
|
|
/>
|
|
</div>
|
|
<span v-else>--</span>
|
|
</template>
|
|
</ElTableColumn>
|
|
<ElTableColumn label="车价(¥)" prop="price" />
|
|
<ElTableColumn label="车源地" prop="source_place" />
|
|
<ElTableColumn label="上牌时间" prop="plate_time" />
|
|
<ElTableColumn label="排量" prop="displacement" />
|
|
<ElTableColumn label="里程(Km)" prop="mileage" />
|
|
<ElTableColumn label="马力" prop="power" />
|
|
<ElTableColumn label="车身颜色" prop="body_color" />
|
|
<ElTableColumn label="创建时间" prop="created_at" />
|
|
<ElTableColumn fixed="right" label="操作" width="200px">
|
|
<template #default="scope">
|
|
<ElRow>
|
|
<el-button link type="warning" @click="printfn(scope.row)"> 预览/下载 </el-button>
|
|
<el-button link type="success" @click="showDialog('detail', scope.row)">
|
|
详情
|
|
</el-button>
|
|
<el-button link @click="editFn(scope.row)"> 编辑 </el-button>
|
|
<el-button link type="success" @click="statusfn(scope.row)"> 标记售出 </el-button>
|
|
</ElRow>
|
|
</template>
|
|
</ElTableColumn>
|
|
</template>
|
|
</ArtTable>
|
|
</div>
|
|
|
|
<!-- 详情弹窗 -->
|
|
<el-dialog v-model="detailDialogVisible" title="车辆详情" width="700px">
|
|
<div>
|
|
<el-descriptions v-if="detailData" :column="2" border label-width="100px">
|
|
<el-descriptions-item label="编号">
|
|
{{ detailData.code || '--' }}
|
|
</el-descriptions-item>
|
|
<el-descriptions-item label="产品名称" :span="1">
|
|
{{ detailData.product_name || '--' }}
|
|
</el-descriptions-item>
|
|
<el-descriptions-item label="车价(¥)">
|
|
{{ detailData.price || '--' }}
|
|
</el-descriptions-item>
|
|
<el-descriptions-item label="车源地">
|
|
{{ detailData.source_place || '--' }}
|
|
</el-descriptions-item>
|
|
<el-descriptions-item label="上牌时间">
|
|
{{ detailData.plate_time || '--' }}
|
|
</el-descriptions-item>
|
|
<el-descriptions-item label="排量">
|
|
{{ detailData.displacement || '--' }}
|
|
</el-descriptions-item>
|
|
<el-descriptions-item label="里程(Km)">
|
|
{{ detailData.mileage || '--' }}
|
|
</el-descriptions-item>
|
|
<el-descriptions-item label="马力">
|
|
{{ detailData.power || '--' }}
|
|
</el-descriptions-item>
|
|
<el-descriptions-item label="车身颜色">
|
|
{{ detailData.body_color || '--' }}
|
|
</el-descriptions-item>
|
|
<el-descriptions-item label="创建时间">
|
|
{{ detailData.created_at || '--' }}
|
|
</el-descriptions-item>
|
|
<el-descriptions-item label="车况介绍" :span="2">
|
|
{{ detailData.condition_desc || '--' }}
|
|
</el-descriptions-item>
|
|
<el-descriptions-item label="产品图" :span="2">
|
|
<div
|
|
class="thumb-list"
|
|
v-if="parsePreviewList(detailData.images_pull || detailData.images).length"
|
|
>
|
|
<img
|
|
v-for="(img, index) in parsePreviewList(
|
|
detailData.images_pull || detailData.images
|
|
)"
|
|
:key="index"
|
|
:src="img"
|
|
alt="产品图"
|
|
class="avatarimg"
|
|
@click="handleAvatarClick(index)"
|
|
/>
|
|
</div>
|
|
<span v-else>--</span>
|
|
</el-descriptions-item>
|
|
</el-descriptions>
|
|
<div style="margin-top: 20px">
|
|
<FilePreview title="附件" :files="contractFiles" empty-text="暂无附件" />
|
|
</div>
|
|
</div>
|
|
<!-- <template #footer>
|
|
<span class="dialog-footer">
|
|
<el-button @click="detailDialogVisible = false">关 闭</el-button>
|
|
</span>
|
|
</template> -->
|
|
</el-dialog>
|
|
<!-- PDF 预览弹窗 -->
|
|
<el-dialog
|
|
v-model="pdf.visible"
|
|
:title="currentPdfFile?.name || '预览'"
|
|
width="90%"
|
|
top="5vh"
|
|
:close-on-click-modal="false"
|
|
class="pdf-preview-dialog"
|
|
>
|
|
<div class="pdf-preview-container">
|
|
<div class="pdf-preview-toolbar">
|
|
<div class="left"></div>
|
|
<div class="right">
|
|
<!-- <el-button type="primary" @click="downloadPdfFile" v-if="currentPdfFile">
|
|
下载
|
|
</el-button> -->
|
|
</div>
|
|
</div>
|
|
<div class="pdf-preview-body">
|
|
<template v-if="!currentPdfFile">
|
|
<div class="no-file">无要预览的文件</div>
|
|
</template>
|
|
<template v-else>
|
|
<!-- IMAGE -->
|
|
<div
|
|
v-if="pdfFileType === 'image'"
|
|
class="viewer-wrap"
|
|
:key="`image-${currentPdfFile.url}`"
|
|
>
|
|
<div class="viewer" :style="pdfViewerStyle">
|
|
<img
|
|
:src="currentPdfFile.url"
|
|
:key="currentPdfFile.url"
|
|
@error="onPdfImageError"
|
|
@load="onPdfImageLoad"
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- PDF -->
|
|
<div
|
|
v-else-if="pdfFileType === 'pdf'"
|
|
class="viewer-wrap"
|
|
:key="`pdf-${currentPdfFile.url}`"
|
|
>
|
|
<div class="viewer" :style="pdfViewerStyle">
|
|
<object
|
|
:key="`pdf-obj-${currentPdfFile.url}`"
|
|
:data="appendPdfParams(currentPdfFile.url)"
|
|
type="application/pdf"
|
|
width="100%"
|
|
height="100%"
|
|
>
|
|
<iframe
|
|
:key="`pdf-iframe-${currentPdfFile.url}`"
|
|
:src="appendPdfParams(currentPdfFile.url)"
|
|
width="100%"
|
|
height="100%"
|
|
></iframe>
|
|
<div class="fallback">
|
|
无法在浏览器内预览 PDF。
|
|
<a :href="currentPdfFile.url" target="_blank">点击打开</a>
|
|
</div>
|
|
</object>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- OFFICE (Word, Excel, PPT) -->
|
|
<div
|
|
v-else-if="pdfFileType === 'office'"
|
|
class="viewer-wrap"
|
|
:key="`office-${currentPdfFile.url}`"
|
|
>
|
|
<div class="viewer" :style="pdfViewerStyle">
|
|
<template v-if="isHttpUrl(currentPdfFile.url)">
|
|
<iframe
|
|
:key="`office-iframe-${currentPdfFile.url}`"
|
|
:src="officeViewerUrl(currentPdfFile.url)"
|
|
width="100%"
|
|
height="100%"
|
|
frameborder="0"
|
|
></iframe>
|
|
</template>
|
|
<template v-else>
|
|
<div class="fallback">
|
|
Office 文件必须是可通过公网访问的 URL 才能使用在线预览。
|
|
<a :href="currentPdfFile.url" target="_blank">点击打开或下载</a>
|
|
</div>
|
|
</template>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 文本 / 代码 -->
|
|
<div
|
|
v-else-if="pdfFileType === 'text'"
|
|
class="viewer-wrap"
|
|
:key="`text-${currentPdfFile.url}`"
|
|
>
|
|
<div class="viewer text-viewer" :style="pdfViewerStyle">
|
|
<pre v-if="pdfTextContent !== null">{{ pdfTextContent }}</pre>
|
|
<div v-else class="loading">正在加载文本内容...</div>
|
|
<div v-if="pdfTextError" class="error">加载文本失败:{{ pdfTextError }}</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 未知 -->
|
|
<div v-else class="viewer-wrap unknown-viewer" :key="`unknown-${currentPdfFile.url}`">
|
|
<div class="viewer" :style="pdfViewerStyle">
|
|
<div class="unknown">
|
|
<p>无法在线预览此类型文件。</p>
|
|
<p><a :href="currentPdfFile.url" target="_blank">点击打开或下载文件</a></p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
</div>
|
|
</div>
|
|
<template #footer>
|
|
<div class="footer-info">{{ currentPdfFile?.name || '' }}</div>
|
|
</template>
|
|
</el-dialog>
|
|
<!-- 图片预览组件 -->
|
|
<ElImageViewer
|
|
v-if="showImageViewer"
|
|
:url-list="imgList"
|
|
:initial-index="viewerIndex"
|
|
:hide-on-click-modal="true"
|
|
@close="showImageViewer = false"
|
|
/>
|
|
<ElImageViewer
|
|
v-if="showImageViewer1"
|
|
:url-list="[currentPdfFiles]"
|
|
:hide-on-click-modal="true"
|
|
@close="showImageViewer1 = false"
|
|
/>
|
|
<!-- 编辑弹窗 -->
|
|
<el-dialog
|
|
v-model="editDialogVisible"
|
|
title="编辑车辆信息"
|
|
width="90%"
|
|
:close-on-click-modal="false"
|
|
@close="handleEditDialogClose"
|
|
>
|
|
<el-form
|
|
ref="editFormRef"
|
|
:model="editForm"
|
|
:rules="editRules"
|
|
label-width="110px"
|
|
label-position="right"
|
|
>
|
|
<ElRow :gutter="12" class="item-row-wrap">
|
|
<ElCol :sm="24" :md="12" :lg="12">
|
|
<el-form-item label="产品名称" prop="product_name">
|
|
<div class="bind-name-wrapper">
|
|
<el-input
|
|
v-model="editForm.product_name"
|
|
placeholder="请输入车辆的品牌 系列查找车型"
|
|
clearable
|
|
/>
|
|
<el-button
|
|
type="primary"
|
|
@click="onEditBindByName"
|
|
:loading="editBindState.loading"
|
|
>
|
|
绑定
|
|
</el-button>
|
|
</div>
|
|
</el-form-item>
|
|
<!-- 绑定结果提示 -->
|
|
<el-form-item label=" ">
|
|
<div class="bind-tip" v-if="editBindState.boundProduct">
|
|
<span class="bind-tip-text">
|
|
已绑定:{{
|
|
editBindState.boundProduct.name ||
|
|
editBindState.boundProduct.product_name ||
|
|
editBindState.boundProduct.title ||
|
|
'-'
|
|
}}
|
|
</span>
|
|
<el-button link type="danger" @click="clearEditBind">清除</el-button>
|
|
</div>
|
|
<div class="bind-tip" v-else>
|
|
<span>例:比亚迪海狮07DM</span>
|
|
</div>
|
|
</el-form-item>
|
|
</ElCol>
|
|
<ElCol :sm="24" :md="12" :lg="12">
|
|
<el-form-item label="车价(¥)" prop="price">
|
|
<el-input v-model="editForm.price" placeholder="请输入车价" />
|
|
</el-form-item>
|
|
</ElCol>
|
|
<ElCol :sm="24" :md="12" :lg="12">
|
|
<el-form-item label="车源地" prop="source_place">
|
|
<el-input v-model="editForm.source_place" placeholder="请输入车源地" />
|
|
</el-form-item>
|
|
</ElCol>
|
|
<ElCol :sm="24" :md="12" :lg="12">
|
|
<el-form-item label="上牌时间" prop="plate_time">
|
|
<el-date-picker
|
|
v-model="editForm.plate_time"
|
|
type="date"
|
|
value-format="YYYY-MM-DD"
|
|
placeholder="请选择时间"
|
|
style="width: 100%"
|
|
/>
|
|
</el-form-item>
|
|
</ElCol>
|
|
<ElCol :sm="24" :md="12" :lg="12">
|
|
<el-form-item label="排量" prop="displacement">
|
|
<el-input v-model="editForm.displacement" placeholder="请输入排量" />
|
|
</el-form-item>
|
|
</ElCol>
|
|
<ElCol :sm="24" :md="12" :lg="12">
|
|
<el-form-item label="马力" prop="power">
|
|
<el-input v-model="editForm.power" placeholder="请输入马力" />
|
|
</el-form-item>
|
|
</ElCol>
|
|
<ElCol :sm="24" :md="12" :lg="12">
|
|
<el-form-item label="里程(Km)" prop="mileage">
|
|
<el-input v-model="editForm.mileage" placeholder="请输入里程" />
|
|
</el-form-item>
|
|
</ElCol>
|
|
<ElCol :sm="24" :md="12" :lg="12">
|
|
<el-form-item label="车身颜色" prop="body_color">
|
|
<el-input v-model="editForm.body_color" placeholder="请输入车身颜色" />
|
|
</el-form-item>
|
|
</ElCol>
|
|
<ElCol :sm="24" :md="24" :lg="24">
|
|
<el-form-item label="车况介绍">
|
|
<el-input
|
|
v-model="editForm.condition_desc"
|
|
type="textarea"
|
|
:rows="4"
|
|
placeholder="请输入车况介绍"
|
|
/>
|
|
</el-form-item>
|
|
</ElCol>
|
|
<ElCol :sm="24" :md="24" :lg="24">
|
|
<el-form-item label="产品图">
|
|
<el-upload
|
|
v-model:file-list="editImageFileList"
|
|
class="upload-list"
|
|
list-type="picture-card"
|
|
:limit="maxMedia"
|
|
:action="uploadAction"
|
|
:headers="uploadHeaders"
|
|
name="image"
|
|
multiple
|
|
:before-upload="handleImagePreview"
|
|
:on-success="handleEditImageSuccess"
|
|
:on-remove="handleEditImageRemove"
|
|
:on-exceed="handleEditImageExceed"
|
|
>
|
|
<el-icon><Plus /></el-icon>
|
|
</el-upload>
|
|
</el-form-item>
|
|
</ElCol>
|
|
<ElCol :sm="24" :md="24" :lg="24">
|
|
<el-form-item label="附件">
|
|
<MultiFileUpload
|
|
v-model="editAttachmentFileList"
|
|
upload-text="点击上传附件"
|
|
hint-text="支持多个文件上传"
|
|
:action-image-uplode="uploadActionFile"
|
|
@change="handleEditAttachmentChange"
|
|
/>
|
|
</el-form-item>
|
|
</ElCol>
|
|
</ElRow>
|
|
</el-form>
|
|
<template #footer>
|
|
<span class="dialog-footer">
|
|
<el-button @click="editDialogVisible = false">取 消</el-button>
|
|
<el-button type="primary" :loading="editSubmitLoading" @click="handleEditSubmit">
|
|
确 认
|
|
</el-button>
|
|
</span>
|
|
</template>
|
|
</el-dialog>
|
|
|
|
<!-- 编辑绑定产品弹窗 -->
|
|
<el-dialog
|
|
v-model="editBindState.showDialog"
|
|
title="选择要绑定的产品"
|
|
width="700px"
|
|
@close="closeEditBindDialog"
|
|
>
|
|
<div class="bind-search" v-if="editBindState.list && editBindState.list.length">
|
|
<el-input
|
|
v-model="editBindState.keyword"
|
|
placeholder="搜索产品名称"
|
|
clearable
|
|
style="max-width: 320px"
|
|
/>
|
|
</div>
|
|
|
|
<el-scrollbar max-height="360px">
|
|
<div v-if="editBindState.loading" class="bind-loading">加载中…</div>
|
|
<div v-else-if="!editBindState.list.length" class="bind-empty">未找到匹配的产品</div>
|
|
<div v-else-if="!filteredEditBindList.length" class="bind-empty">未找到匹配结果</div>
|
|
<div v-else>
|
|
<div
|
|
class="bind-item"
|
|
v-for="(it, idx) in filteredEditBindList"
|
|
:key="getEditBindItemId(it, idx)"
|
|
:class="{ active: editBindState.selectedId === getEditBindItemId(it, idx) }"
|
|
@click="selectEditBind(it, idx)"
|
|
>
|
|
<div class="bind-item-main">
|
|
<div class="bind-item-title">
|
|
{{ it.name || it.product_name || it.title || `产品 ${idx + 1}` }}
|
|
</div>
|
|
<div class="bind-item-sub">
|
|
{{ formatEditBindSub(it) }}
|
|
</div>
|
|
</div>
|
|
<div class="bind-item-check">
|
|
<span
|
|
class="check-dot"
|
|
v-if="editBindState.selectedId === getEditBindItemId(it, idx)"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</el-scrollbar>
|
|
|
|
<template #footer>
|
|
<el-button @click="closeEditBindDialog">取 消</el-button>
|
|
<el-button type="primary" :disabled="!editBindState.selectedId" @click="confirmEditBind">
|
|
确认绑定
|
|
</el-button>
|
|
</template>
|
|
</el-dialog>
|
|
|
|
<!-- 设置价格弹窗 -->
|
|
<el-dialog v-model="priceDialogVisible" title="设置价格" width="500px">
|
|
<el-form :model="priceForm" label-width="80px">
|
|
<el-form-item label="贸易条款">
|
|
<el-select
|
|
v-model="priceForm.trade_term"
|
|
placeholder="请选择贸易条款"
|
|
style="width: 100%"
|
|
>
|
|
<el-option label="FOB" value="FOB" />
|
|
<el-option label="CIF" value="CIF" />
|
|
<el-option label="EXW" value="EXW" />
|
|
<el-option label="DDP" value="DDP" />
|
|
<el-option label="CIP" value="CIP" />
|
|
<el-option label="FCA" value="FCA" />
|
|
<el-option label="CFR" value="CFR" />
|
|
</el-select>
|
|
</el-form-item>
|
|
<el-form-item label="价格">
|
|
<div class="price-input-wrapper">
|
|
<el-input
|
|
v-model="priceForm.price"
|
|
placeholder="请输入数字"
|
|
@input="handlePriceInput"
|
|
style="flex: 1"
|
|
/>
|
|
<el-select v-model="priceForm.currency" style="width: 120px; margin-left: 12px">
|
|
<el-option label="美元" value="USD" />
|
|
<el-option label="人民币" value="CNY" />
|
|
<el-option label="卢布" value="RUB" />
|
|
<el-option label="迪拉姆" value="AED" />
|
|
<el-option label="欧元" value="EUR" />
|
|
</el-select>
|
|
</div>
|
|
</el-form-item>
|
|
<el-form-item label="语言">
|
|
<el-radio-group v-model="priceForm.lang">
|
|
<el-radio label="en">英语</el-radio>
|
|
<el-radio label="ru">俄语</el-radio>
|
|
<el-radio label="es">西班牙语</el-radio>
|
|
<el-radio label="ar">阿拉伯语</el-radio>
|
|
</el-radio-group>
|
|
</el-form-item>
|
|
</el-form>
|
|
<template #footer>
|
|
<span class="dialog-footer">
|
|
<el-button @click="priceDialogVisible = false">取 消</el-button>
|
|
<el-button type="primary" @click="confirmPrice">确 认</el-button>
|
|
</span>
|
|
</template>
|
|
</el-dialog>
|
|
|
|
<!-- 抓取弹窗 -->
|
|
<el-dialog
|
|
v-model="fetchDialogVisible"
|
|
title="抓取数据"
|
|
width="80%"
|
|
:close-on-click-modal="false"
|
|
>
|
|
<div class="fetch-dialog-content">
|
|
<el-form :model="fetchForm" label-width="70px" style="margin-bottom: 20px; display: flex">
|
|
<el-form-item label="抓取链接">
|
|
<el-input
|
|
v-model="fetchForm.url"
|
|
placeholder="请输入要抓取的链接"
|
|
clearable
|
|
style="width: 100%"
|
|
/>
|
|
</el-form-item>
|
|
<el-form-item>
|
|
<el-button type="primary" @click="handleFetch" :loading="fetchLoading">抓取</el-button>
|
|
</el-form-item>
|
|
</el-form>
|
|
|
|
<!-- 抓取结果显示 -->
|
|
<div v-if="fetchLoading" class="fetch-loading">
|
|
<el-icon class="is-loading"><Loading /></el-icon>
|
|
<span style="margin-left: 8px">正在抓取中...</span>
|
|
</div>
|
|
|
|
<div v-else-if="fetchData" class="fetch-result">
|
|
<el-descriptions :column="2" border label-width="160px">
|
|
<el-descriptions-item label="产品名称">
|
|
{{ fetchData.product_name || '--' }}
|
|
</el-descriptions-item>
|
|
<el-descriptions-item label="车价(¥)">
|
|
{{ fetchData.price || '--' }}
|
|
</el-descriptions-item>
|
|
<el-descriptions-item label="车源地">
|
|
{{ fetchData.source_place || '--' }}
|
|
</el-descriptions-item>
|
|
<el-descriptions-item label="上牌时间">
|
|
{{ fetchData.plate_time || '--' }}
|
|
</el-descriptions-item>
|
|
<el-descriptions-item label="排量">
|
|
{{ fetchData.displacement || '--' }}
|
|
</el-descriptions-item>
|
|
<el-descriptions-item label="里程(Km)">
|
|
{{ fetchData.mileage || '--' }}
|
|
</el-descriptions-item>
|
|
<el-descriptions-item label="马力">
|
|
{{ fetchData.power || '--' }}
|
|
</el-descriptions-item>
|
|
<el-descriptions-item label="车身颜色">
|
|
{{ fetchData.body_color || '--' }}
|
|
</el-descriptions-item>
|
|
<el-descriptions-item label="车况介绍" :span="2">
|
|
{{ fetchData.condition_desc || '--' }}
|
|
</el-descriptions-item>
|
|
<el-descriptions-item label="产品图" :span="2">
|
|
<div
|
|
class="thumb-list"
|
|
v-if="parsePreviewList(fetchData.image_pull || fetchData.image).length"
|
|
>
|
|
<img
|
|
v-for="(img, index) in parsePreviewList(fetchData.image_pull || fetchData.image)"
|
|
:key="index"
|
|
:src="img"
|
|
alt="产品图"
|
|
class="avatarimg"
|
|
@click="handleAvatarClick(index)"
|
|
/>
|
|
</div>
|
|
<span v-else>--</span>
|
|
</el-descriptions-item>
|
|
</el-descriptions>
|
|
</div>
|
|
</div>
|
|
<template #footer>
|
|
<span class="dialog-footer">
|
|
<el-button plain type="info" @click="fetchDialogVisible = false">关 闭</el-button>
|
|
<el-button @click="addFn()">保 存</el-button>
|
|
</span>
|
|
</template>
|
|
</el-dialog>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import {
|
|
ElLoading,
|
|
ElMessage,
|
|
type FormInstance,
|
|
type FormRules,
|
|
type UploadFile
|
|
} from 'element-plus'
|
|
import { Plus, Loading } from '@element-plus/icons-vue'
|
|
import { manageService } from '@/api/adminApi'
|
|
import { usePdfPreview } from '@/composables/usePdfPreview'
|
|
import FilePreview from '@/components/custom/FilePreview.vue'
|
|
import request from '@/utils/http'
|
|
import { useUploadHeaders } from '@/composables/useUploadHeaders'
|
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
// @ts-ignore
|
|
import MultiFileUpload from '@/components/core/forms/MultiFileUpload.vue'
|
|
|
|
const { uploadHeaders } = useUploadHeaders()
|
|
const keyword = ref<string>('')
|
|
const status = ref<string | number>(1)
|
|
defineOptions({ name: 'Company' })
|
|
const loading = ref(false)
|
|
const contractFiles = ref<Array<{ name: string; url: string; file_name: string }>>([])
|
|
const pagination = reactive({
|
|
currentPage: 1,
|
|
pageSize: 10,
|
|
total: 0
|
|
})
|
|
// 表格数据
|
|
const tableData = ref<any[]>([])
|
|
|
|
// 表格实例引用
|
|
const tableRef = ref()
|
|
|
|
onMounted(() => {
|
|
getWorkList()
|
|
})
|
|
// 图片预览相关
|
|
const showImageViewer = ref(false)
|
|
const showImageViewer1 = ref(false)
|
|
const currentPdfFiles = ref('')
|
|
const viewerIndex = ref(0)
|
|
const imgList = computed(() =>
|
|
parsePreviewList(detailData.value.images_pull || detailData.value.images)
|
|
)
|
|
const handleAvatarClick1 = (avatarUrl: string) => {
|
|
if (avatarUrl) {
|
|
currentPdfFiles.value = avatarUrl
|
|
showImageViewer1.value = true
|
|
}
|
|
}
|
|
const handleAvatarClick = (index: number) => {
|
|
if (index !== null && index !== undefined) {
|
|
viewerIndex.value = index
|
|
showImageViewer.value = true
|
|
}
|
|
}
|
|
// 处理文件数据
|
|
const processFiles = (files: any[]) => {
|
|
if (!files || !Array.isArray(files)) return []
|
|
return files.map((file: any) => ({
|
|
name: file.name,
|
|
url: file.src_pull || file.file_url,
|
|
file_name: file.src
|
|
}))
|
|
}
|
|
// 详情弹窗相关
|
|
const detailDialogVisible = ref(false)
|
|
const detailData = ref<any | null>(null)
|
|
const showDialog = async (_type: string, row: any) => {
|
|
const res = await manageService.getpublishdetail({ id: row.id })
|
|
detailData.value = res
|
|
// 处理文件
|
|
const file_list = res?.attachment_pull
|
|
if (file_list) {
|
|
contractFiles.value = processFiles(file_list)
|
|
} else {
|
|
contractFiles.value = []
|
|
}
|
|
detailDialogVisible.value = true
|
|
}
|
|
// 处理表格分页变化
|
|
const handleSizeChange = (newPageSize: number) => {
|
|
pagination.pageSize = newPageSize
|
|
getWorkList()
|
|
}
|
|
const handleCurrentChange = (newCurrentPage: number) => {
|
|
pagination.currentPage = newCurrentPage
|
|
getWorkList()
|
|
}
|
|
watch([keyword, status], () => {
|
|
pagination.currentPage = 1
|
|
getWorkList()
|
|
})
|
|
// 获取列表数据
|
|
const getWorkList = async () => {
|
|
loading.value = true
|
|
try {
|
|
const { currentPage, pageSize } = pagination
|
|
const params: any = {
|
|
current: currentPage,
|
|
size: pageSize
|
|
}
|
|
if (keyword.value) {
|
|
params.keyword = keyword.value
|
|
}
|
|
params.status = status.value
|
|
params.scene = 'self'
|
|
const { records, current, size, total } = await manageService.getpublishList(params)
|
|
tableData.value = records.map((item: any) => ({
|
|
...item
|
|
}))
|
|
Object.assign(pagination, { currentPage: current, pageSize: size, total })
|
|
} catch (error) {
|
|
console.error('获取列表失败:', error)
|
|
} finally {
|
|
loading.value = false
|
|
}
|
|
}
|
|
|
|
const parsePaths = (value: any): string[] => {
|
|
if (!value) return []
|
|
if (Array.isArray(value)) return value.filter(Boolean)
|
|
if (typeof value === 'string') {
|
|
try {
|
|
const parsed = JSON.parse(value)
|
|
if (Array.isArray(parsed)) return parsed.filter(Boolean)
|
|
} catch {
|
|
// ignore json parse errors
|
|
}
|
|
return value
|
|
.split(',')
|
|
.map((p: string) => p.trim())
|
|
.filter(Boolean)
|
|
}
|
|
return []
|
|
}
|
|
|
|
const parsePreviewList = (value: any): string[] => parsePaths(value)
|
|
|
|
// PDF 预览/打印(公共)
|
|
const { pdf, openWithResponse, preparePreview, prepareImages } = usePdfPreview()
|
|
|
|
// 当前 PDF 文件信息(用于 FilesPreviews 组件)
|
|
const currentPdfFile = computed(() => {
|
|
const url = pdf.url || pdf.blobUrl
|
|
if (!url) return null
|
|
return {
|
|
name: pdf.name || 'document.pdf',
|
|
url: url,
|
|
file_name: pdf.name || 'document.pdf'
|
|
}
|
|
})
|
|
|
|
// PDF 预览相关状态和逻辑
|
|
const pdfZoom = ref(1)
|
|
const pdfRotate = ref(0)
|
|
const pdfTextContent = ref<string | null>(null)
|
|
const pdfTextError = ref<string | null>(null)
|
|
|
|
// 获取文件扩展名
|
|
const getExtension = (name = '') => {
|
|
const m = name.split('.')
|
|
return (m.length > 1 ? m[m.length - 1].toLowerCase() : '').trim()
|
|
}
|
|
|
|
// 从文件名获取 MIME 类型
|
|
const mimeFromName = (name = '') => {
|
|
const ext = getExtension(name)
|
|
const map: Record<string, string> = {
|
|
pdf: 'application/pdf',
|
|
png: 'image/png',
|
|
jpg: 'image/jpeg',
|
|
jpeg: 'image/jpeg',
|
|
gif: 'image/gif',
|
|
bmp: 'image/bmp',
|
|
webp: 'image/webp',
|
|
txt: 'text/plain',
|
|
md: 'text/markdown',
|
|
csv: 'text/csv',
|
|
doc: 'application/msword',
|
|
docx: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
|
xls: 'application/vnd.ms-excel',
|
|
xlsx: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
|
ppt: 'application/vnd.ms-powerpoint',
|
|
pptx: 'application/vnd.openxmlformats-officedocument.presentationml.presentation'
|
|
}
|
|
return map[ext] ?? ''
|
|
}
|
|
|
|
// 判断文件类型
|
|
const pdfFileType = computed(() => {
|
|
const f = currentPdfFile.value
|
|
if (!f) return 'unknown'
|
|
let names = f.name || ''
|
|
if (!names) {
|
|
names = f.file_name || ''
|
|
}
|
|
const mime = mimeFromName(names).toLowerCase()
|
|
const ext = getExtension(names)
|
|
|
|
if (mime.startsWith('image/') || ['png', 'jpg', 'jpeg', 'gif', 'bmp', 'webp'].includes(ext))
|
|
return 'image'
|
|
if (mime === 'application/pdf' || ext === 'pdf') return 'pdf'
|
|
if (['doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx'].includes(ext)) return 'office'
|
|
if (mime.startsWith('text/') || ['txt', 'md', 'csv', 'log', 'json', 'xml'].includes(ext))
|
|
return 'text'
|
|
return 'unknown'
|
|
})
|
|
|
|
// 判断是否为 HTTP URL
|
|
const isHttpUrl = (u?: string) => {
|
|
return !!u && /^https?:\/\//i.test(u)
|
|
}
|
|
|
|
// Office 查看器 URL
|
|
const officeViewerUrl = (url: string) => {
|
|
return `https://view.officeapps.live.com/op/embed.aspx?src=${encodeURIComponent(url)}`
|
|
}
|
|
|
|
// PDF 参数处理
|
|
const appendPdfParams = (url: string) => {
|
|
return url
|
|
}
|
|
|
|
// 预览样式
|
|
const pdfViewerStyle = computed(() => {
|
|
return {
|
|
transform: `scale(${pdfZoom.value}) rotate(${pdfRotate.value}deg)`,
|
|
transformOrigin: 'center top'
|
|
} as Record<string, string>
|
|
})
|
|
|
|
// 加载文本内容
|
|
const loadPdfTextIfNeeded = async () => {
|
|
if (!currentPdfFile.value) return
|
|
if (pdfFileType.value !== 'text') return
|
|
|
|
pdfTextContent.value = null
|
|
pdfTextError.value = null
|
|
|
|
try {
|
|
const resp = await fetch(currentPdfFile.value.url)
|
|
if (!resp.ok) throw new Error(`HTTP ${resp.status}`)
|
|
const txt = await resp.text()
|
|
pdfTextContent.value = txt
|
|
} catch (err: any) {
|
|
pdfTextError.value = String(err?.message ?? err)
|
|
}
|
|
}
|
|
|
|
// 图片错误处理
|
|
const onPdfImageError = () => {
|
|
ElMessage.warning('图片加载失败')
|
|
}
|
|
|
|
// 图片加载完成
|
|
const onPdfImageLoad = () => {
|
|
// 可以在这里获取自然尺寸进行布局优化
|
|
}
|
|
|
|
// 监听文件变化,加载文本内容
|
|
watch(
|
|
() => currentPdfFile.value?.url,
|
|
(newUrl, oldUrl) => {
|
|
if (newUrl !== oldUrl && newUrl) {
|
|
pdfZoom.value = 1
|
|
pdfRotate.value = 0
|
|
loadPdfTextIfNeeded()
|
|
}
|
|
}
|
|
)
|
|
|
|
// 监听文件类型变化
|
|
watch(
|
|
() => pdfFileType.value,
|
|
() => {
|
|
if (pdfFileType.value === 'text') {
|
|
loadPdfTextIfNeeded()
|
|
}
|
|
}
|
|
)
|
|
|
|
// 价格设置弹窗相关
|
|
const priceDialogVisible = ref(false)
|
|
const currentRow = ref<any>(null)
|
|
const priceForm = reactive({
|
|
trade_term: '',
|
|
price: '',
|
|
currency: 'USD',
|
|
lang: 'en'
|
|
})
|
|
|
|
// 价格输入限制:只允许数字和小数点
|
|
const handlePriceInput = (value: string) => {
|
|
// 只保留数字和小数点
|
|
priceForm.price = value.replace(/[^\d.]/g, '')
|
|
// 确保只有一个小数点
|
|
const parts = priceForm.price.split('.')
|
|
if (parts.length > 2) {
|
|
priceForm.price = parts[0] + '.' + parts.slice(1).join('')
|
|
}
|
|
}
|
|
|
|
// 打印/预览
|
|
const printfn = (row: any) => {
|
|
currentRow.value = row
|
|
priceForm.trade_term = ''
|
|
priceForm.price = ''
|
|
priceForm.currency = 'USD'
|
|
priceForm.lang = 'en'
|
|
priceDialogVisible.value = true
|
|
}
|
|
|
|
// 确认价格设置并执行预览/下载
|
|
const confirmPrice = () => {
|
|
if (!priceForm.trade_term) {
|
|
ElMessage.warning('请选择贸易条款')
|
|
return
|
|
}
|
|
if (!priceForm.price) {
|
|
ElMessage.warning('请输入价格')
|
|
return
|
|
}
|
|
priceDialogVisible.value = false
|
|
|
|
// 调用预览/下载接口,传递价格、币种、语言参数
|
|
const params: any = {
|
|
id: currentRow.value.id,
|
|
trade_term: priceForm.trade_term,
|
|
price: priceForm.price,
|
|
currency: priceForm.currency,
|
|
lang: priceForm.lang
|
|
}
|
|
|
|
const loading = ElLoading.service({
|
|
lock: true,
|
|
text: '正在生成中…',
|
|
background: 'rgba(0, 0, 0, 0.45)'
|
|
})
|
|
manageService
|
|
.generatePdfPreview(params)
|
|
.then(() => {
|
|
downloadfn(params)
|
|
})
|
|
.catch((err) => {
|
|
console.log(err)
|
|
ElMessage.error('预览失败,请稍后重试')
|
|
})
|
|
.finally(() => {
|
|
loading.close() // ✅ 不管成功/失败都关闭
|
|
})
|
|
}
|
|
const downloadfn = (row: any) => {
|
|
manageService
|
|
.downloadPdf({ id: row.id })
|
|
.then((res) => {
|
|
openWithResponse(res)
|
|
// 优先转成图片以适配不带插件的环境
|
|
prepareImages()
|
|
// 同时保留 blob 预览作为兜底
|
|
preparePreview()
|
|
})
|
|
.catch((err) => {
|
|
console.log(err)
|
|
ElMessage.error('下载失败,请稍后重试')
|
|
})
|
|
}
|
|
|
|
// 编辑相关
|
|
const maxMedia = 200
|
|
const editDialogVisible = ref(false)
|
|
const editFormRef = ref<FormInstance>()
|
|
const editSubmitLoading = ref(false)
|
|
const uploadAction = `${import.meta.env.VITE_API_URL}/upload/image?scene=usedcarimg`
|
|
const uploadActionFile = `${import.meta.env.VITE_API_URL}/upload?scene=usedcarfile`
|
|
|
|
type UploadFileWithResponse = UploadFile & {
|
|
response?: any
|
|
url?: string
|
|
}
|
|
|
|
interface FileItem {
|
|
name: string
|
|
url: string
|
|
file_name: string
|
|
}
|
|
|
|
const editImageFileList = ref<UploadFileWithResponse[]>([])
|
|
const editAttachmentFileList = ref<FileItem[]>([])
|
|
|
|
interface UsedCarForm {
|
|
id?: number
|
|
product_name: string
|
|
vin: string
|
|
price: string
|
|
source_place: string
|
|
plate_place: string
|
|
plate_time: string
|
|
displacement: string
|
|
power: string
|
|
body_color: string
|
|
images: string[]
|
|
condition_desc: string
|
|
mileage: string
|
|
attachment: { name: string; src: string }[]
|
|
crawl_title: string
|
|
crawl_brand: string
|
|
crawl_series: string
|
|
crawl_year: string
|
|
crawl_msrp: string
|
|
crawl_car_id: string
|
|
crawl_brand_id: string
|
|
crawl_series_id: string
|
|
}
|
|
|
|
const editForm = reactive<UsedCarForm>({
|
|
id: undefined,
|
|
product_name: '',
|
|
vin: '',
|
|
price: '',
|
|
source_place: '',
|
|
plate_place: '',
|
|
plate_time: '',
|
|
displacement: '',
|
|
power: '',
|
|
body_color: '',
|
|
images: [],
|
|
condition_desc: '',
|
|
mileage: '',
|
|
attachment: [],
|
|
crawl_title: '',
|
|
crawl_brand: '',
|
|
crawl_series: '',
|
|
crawl_year: '',
|
|
crawl_msrp: '',
|
|
crawl_car_id: '',
|
|
crawl_brand_id: '',
|
|
crawl_series_id: ''
|
|
})
|
|
|
|
const editRules = reactive<FormRules>({
|
|
product_name: [{ required: true, message: '请输入产品名称', trigger: 'blur' }],
|
|
price: [{ required: true, message: '请输入车价', trigger: 'blur' }]
|
|
})
|
|
|
|
// 编辑绑定相关状态
|
|
const editBindState = reactive<{
|
|
showDialog: boolean
|
|
loading: boolean
|
|
list: any[]
|
|
selectedId: string
|
|
boundProduct: any | null
|
|
keyword: string
|
|
_selected?: any
|
|
}>({
|
|
showDialog: false,
|
|
loading: false,
|
|
list: [],
|
|
selectedId: '',
|
|
boundProduct: null,
|
|
keyword: ''
|
|
})
|
|
|
|
const formatEditBindSub = (it: any) => {
|
|
const parts: string[] = []
|
|
if (it.sku || it.code) parts.push(`编码:${it.sku || it.code}`)
|
|
if (it.category_name) parts.push(`分类:${it.category_name}`)
|
|
if (it.price) parts.push(`价格:${it.price}`)
|
|
return parts.join(' · ')
|
|
}
|
|
|
|
const filteredEditBindList = computed(() => {
|
|
const list = Array.isArray(editBindState.list) ? editBindState.list : []
|
|
const kw = String(editBindState.keyword || '')
|
|
.trim()
|
|
.toLowerCase()
|
|
if (!kw) return list
|
|
return list.filter((it, idx) => {
|
|
const id = String(it.id || it.product_id || it.code || idx).toLowerCase()
|
|
const name = String(it.name || it.product_name || it.title || '').toLowerCase()
|
|
const sub = String(formatEditBindSub(it) || '').toLowerCase()
|
|
return id.includes(kw) || name.includes(kw) || sub.includes(kw)
|
|
})
|
|
})
|
|
|
|
// 编辑函数
|
|
const editFn = async (row: any) => {
|
|
try {
|
|
const res = await manageService.getpublishdetail({ id: row.id })
|
|
// 回显基本数据
|
|
Object.assign(editForm, {
|
|
id: res.id,
|
|
product_name: res.product_name || '',
|
|
vin: res.vin || '',
|
|
price: res.price || '',
|
|
source_place: res.source_place || '',
|
|
plate_place: res.plate_place || '',
|
|
plate_time: res.plate_time || '',
|
|
displacement: res.displacement || '',
|
|
power: res.power || '',
|
|
body_color: res.body_color || '',
|
|
condition_desc: res.condition_desc || '',
|
|
mileage: res.mileage || '',
|
|
crawl_title: res.crawl_title || '',
|
|
crawl_brand: res.crawl_brand || '',
|
|
crawl_series: res.crawl_series || '',
|
|
crawl_year: res.crawl_year || '',
|
|
crawl_msrp: res.crawl_msrp || '',
|
|
crawl_car_id: res.crawl_car_id || '',
|
|
crawl_brand_id: res.crawl_brand_id || '',
|
|
crawl_series_id: res.crawl_series_id || ''
|
|
})
|
|
|
|
// 回显图片
|
|
const images = parsePaths(res.images || res.images_pull || [])
|
|
editForm.images = images
|
|
editImageFileList.value = images.map((img: string, index: number) => {
|
|
const fullUrl =
|
|
Array.isArray(res.images_pull) && res.images_pull[index]
|
|
? res.images_pull[index]
|
|
: img.startsWith('http')
|
|
? img
|
|
: `${import.meta.env.VITE_API_URL}/${img}`
|
|
return {
|
|
uid: index,
|
|
name: `image-${index}.jpg`,
|
|
url: fullUrl,
|
|
status: 'success',
|
|
response: { file_name: img }
|
|
} as UploadFileWithResponse
|
|
})
|
|
|
|
// 回显附件
|
|
const attachments = res.attachment_pull || []
|
|
if (Array.isArray(attachments) && attachments.length > 0) {
|
|
editAttachmentFileList.value = attachments.map((att: any) => {
|
|
const url = att.src_pull
|
|
const fullUrl = url
|
|
const fileName = att.src || att.file_name || att
|
|
return {
|
|
name: att.name || att.file_name || '附件',
|
|
url: fullUrl,
|
|
file_name: typeof fileName === 'string' ? fileName : String(fileName)
|
|
} as FileItem
|
|
})
|
|
// 同步到 form.attachment
|
|
editForm.attachment = editAttachmentFileList.value.map((file) => ({
|
|
name: file.name,
|
|
src: file.file_name
|
|
}))
|
|
} else {
|
|
editForm.attachment = []
|
|
editAttachmentFileList.value = []
|
|
}
|
|
|
|
// 如果有绑定信息,回显绑定状态
|
|
if (res.crawl_title || res.crawl_brand) {
|
|
editBindState.boundProduct = {
|
|
title: res.crawl_title,
|
|
brand_name: res.crawl_brand,
|
|
series_name: res.crawl_series,
|
|
year: res.crawl_year,
|
|
msrp: res.crawl_msrp,
|
|
car_id: res.crawl_car_id,
|
|
brand_id: res.crawl_brand_id,
|
|
series_id: res.crawl_series_id
|
|
}
|
|
} else {
|
|
editBindState.boundProduct = null
|
|
}
|
|
|
|
editDialogVisible.value = true
|
|
} catch (error) {
|
|
console.error('获取详情失败:', error)
|
|
ElMessage.error('获取详情失败,请稍后重试')
|
|
}
|
|
}
|
|
|
|
// 编辑弹窗关闭
|
|
const handleEditDialogClose = () => {
|
|
editFormRef.value?.resetFields()
|
|
editImageFileList.value = []
|
|
editAttachmentFileList.value = []
|
|
editBindState.boundProduct = null
|
|
editForm.attachment = []
|
|
}
|
|
|
|
// 编辑提交
|
|
const handleEditSubmit = () => {
|
|
if (!editFormRef.value) return
|
|
editFormRef.value.validate(async (valid) => {
|
|
if (!valid) return
|
|
|
|
editSubmitLoading.value = true
|
|
try {
|
|
const payload: any = {
|
|
id: editForm.id,
|
|
...editForm,
|
|
images: (editForm.images || [])
|
|
.map((s) => String(s || '').trim())
|
|
.filter(Boolean)
|
|
.join(',')
|
|
}
|
|
|
|
const res = await manageService.publishedit(payload)
|
|
const msg = (res as any)?.message || (res as any)?.data?.message || '更新成功'
|
|
ElMessage.success(msg)
|
|
editDialogVisible.value = false
|
|
getWorkList() // 刷新列表
|
|
} catch (error: any) {
|
|
ElMessage.error(error?.message || '更新失败,请稍后重试')
|
|
} finally {
|
|
editSubmitLoading.value = false
|
|
}
|
|
})
|
|
}
|
|
|
|
// 编辑绑定相关函数
|
|
const onEditBindByName = () => {
|
|
const name = String(editForm.product_name || '').trim()
|
|
if (!name) {
|
|
ElMessage.warning('请先输入产品名称')
|
|
return
|
|
}
|
|
|
|
editBindState.showDialog = true
|
|
editBindState.loading = true
|
|
editBindState.list = []
|
|
editBindState.selectedId = ''
|
|
editBindState.keyword = ''
|
|
|
|
request
|
|
.get({
|
|
url: '/common/usedcar/crawl',
|
|
params: { keyword: name }
|
|
})
|
|
.then((res: any) => {
|
|
const data = res.model || []
|
|
editBindState.list = data
|
|
})
|
|
.catch(() => {
|
|
ElMessage.error('拉取产品失败,请稍后重试')
|
|
})
|
|
.finally(() => {
|
|
editBindState.loading = false
|
|
})
|
|
}
|
|
|
|
const closeEditBindDialog = () => {
|
|
editBindState.showDialog = false
|
|
editBindState.keyword = ''
|
|
}
|
|
|
|
const getEditBindItemId = (it: any, idx: number) =>
|
|
String(it.id || it.product_id || it.requisition_item_id || it.code || idx)
|
|
|
|
const selectEditBind = (it: any, idx: number) => {
|
|
editBindState.selectedId = getEditBindItemId(it, idx)
|
|
editBindState._selected = it
|
|
}
|
|
|
|
const confirmEditBind = () => {
|
|
if (!editBindState.selectedId || !editBindState._selected) return
|
|
const it = editBindState._selected
|
|
|
|
editBindState.boundProduct = it
|
|
editForm.crawl_title = it.title || ''
|
|
editForm.crawl_brand = it.brand_name || ''
|
|
editForm.crawl_series = it.series_name || ''
|
|
editForm.crawl_year = it.year || ''
|
|
editForm.crawl_msrp = it.msrp || ''
|
|
editForm.crawl_car_id = it.car_id || ''
|
|
editForm.crawl_brand_id = it.brand_id || ''
|
|
editForm.crawl_series_id = it.series_id || ''
|
|
|
|
closeEditBindDialog()
|
|
ElMessage.success('绑定成功')
|
|
}
|
|
|
|
const clearEditBind = () => {
|
|
editForm.crawl_title = ''
|
|
editForm.crawl_brand = ''
|
|
editForm.crawl_series = ''
|
|
editForm.crawl_year = ''
|
|
editForm.crawl_msrp = ''
|
|
editForm.crawl_car_id = ''
|
|
editForm.crawl_brand_id = ''
|
|
editForm.crawl_series_id = ''
|
|
editBindState.boundProduct = null
|
|
ElMessage.success('已清除绑定')
|
|
}
|
|
const loadings = ref<ReturnType<typeof ElLoading.service> | null>(null)
|
|
const handleImagePreview = () => {
|
|
loadings.value = ElLoading.service({
|
|
lock: true,
|
|
text: '正在上传中…',
|
|
background: 'rgba(0, 0, 0, 0.45)'
|
|
})
|
|
return true
|
|
}
|
|
// 编辑图片上传处理
|
|
const handleEditImageSuccess = (response: any, file: UploadFile, fileList: UploadFile[]) => {
|
|
const url = response?.file_url || response?.data?.file_url || ''
|
|
const fileName = response?.file_name || response?.data?.file_name || ''
|
|
;(file as UploadFileWithResponse).response = response
|
|
|
|
if (url && !file.url) file.url = url
|
|
|
|
if (fileName) {
|
|
if (!editForm.images.includes(fileName)) {
|
|
editForm.images.push(fileName)
|
|
}
|
|
}
|
|
editImageFileList.value = fileList as UploadFileWithResponse[]
|
|
loadings.value?.close()
|
|
}
|
|
|
|
const handleEditImageRemove = (file: UploadFile, fileList: UploadFile[]) => {
|
|
const resp = (file as UploadFileWithResponse).response
|
|
const fileName = resp?.file_name || resp?.data?.file_name || ''
|
|
if (fileName) {
|
|
const idx = editForm.images.indexOf(fileName)
|
|
if (idx > -1) editForm.images.splice(idx, 1)
|
|
}
|
|
editImageFileList.value = fileList as UploadFileWithResponse[]
|
|
}
|
|
|
|
const handleEditImageExceed = () => {
|
|
ElMessage.warning(`最多只能上传 ${maxMedia} 张图片`)
|
|
}
|
|
|
|
// 编辑附件变化处理
|
|
const handleEditAttachmentChange = (files: FileItem[]) => {
|
|
// 同步到 form.attachment,转换为后端需要的格式
|
|
editForm.attachment = files.map((file) => ({
|
|
name: file.name,
|
|
src: file.file_name
|
|
}))
|
|
}
|
|
// 标记售出
|
|
const statusfn = (row: any) => {
|
|
ElMessageBox.confirm('确定要标记售出吗?', '提示', {
|
|
confirmButtonText: '确定',
|
|
cancelButtonText: '取消',
|
|
type: 'warning'
|
|
})
|
|
.then(async () => {
|
|
manageService
|
|
.updatestatus({ id: row.id, status: 2 })
|
|
.then(() => {
|
|
ElMessage.success('标记售出成功')
|
|
getWorkList()
|
|
})
|
|
.catch((err) => {
|
|
console.log(err)
|
|
ElMessage.error('操作失败,请稍后重试')
|
|
})
|
|
})
|
|
.catch(() => {
|
|
// 用户点击了取消
|
|
})
|
|
}
|
|
|
|
// 抓取相关
|
|
const fetchDialogVisible = ref(false)
|
|
const fetchForm = reactive({
|
|
url: ''
|
|
})
|
|
const fetchLoading = ref(false)
|
|
const fetchData = ref<any>(null)
|
|
|
|
// 打开抓取弹窗
|
|
const fetchpublishfn = () => {
|
|
fetchDialogVisible.value = true
|
|
fetchForm.url = ''
|
|
fetchData.value = null
|
|
}
|
|
|
|
// 执行抓取
|
|
const handleFetch = async () => {
|
|
if (!fetchForm.url || !fetchForm.url.trim()) {
|
|
ElMessage.warning('请输入抓取链接')
|
|
return
|
|
}
|
|
|
|
fetchLoading.value = true
|
|
fetchData.value = null
|
|
|
|
try {
|
|
const res: any = await manageService.fetchpublish({ url: fetchForm.url.trim() })
|
|
// 根据实际返回的数据结构处理
|
|
fetchData.value = res?.data || res || {}
|
|
ElMessage.success('抓取成功')
|
|
} catch (error: any) {
|
|
console.error('抓取失败:', error)
|
|
fetchData.value = null
|
|
} finally {
|
|
fetchLoading.value = false
|
|
}
|
|
}
|
|
function formatCnYearMonth(str: string) {
|
|
const match = str.match(/(\d{4})年(\d{1,2})月/)
|
|
if (!match) return str
|
|
const year = match[1]
|
|
const month = match[2].padStart(2, '0')
|
|
return `${year}-${month}-01`
|
|
}
|
|
//保存抓取
|
|
const addFn = async () => {
|
|
fetchData.value.plate_time = formatCnYearMonth(String(fetchData.value.plate_time || '').trim())
|
|
fetchData.value.images = (fetchData.value.images || [])
|
|
.map((s: any) => String(s || '').trim())
|
|
.filter(Boolean)
|
|
.join(',')
|
|
const res = await manageService.publishAdd(fetchData.value)
|
|
const msg = (res as any)?.message || (res as any)?.data?.message || '提交成功'
|
|
ElMessage.success(msg)
|
|
fetchDialogVisible.value = false
|
|
getWorkList()
|
|
}
|
|
</script>
|
|
|
|
<style lang="scss" scoped>
|
|
:deep(.el-table__empty-block) {
|
|
height: 100% !important;
|
|
min-height: 200px;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
}
|
|
.page-content {
|
|
.svg-icon {
|
|
width: 1.8em;
|
|
height: 1.8em;
|
|
overflow: hidden;
|
|
vertical-align: -8px;
|
|
fill: currentcolor;
|
|
}
|
|
}
|
|
.thumb-list {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: 6px;
|
|
}
|
|
.avatarimg {
|
|
width: 48px;
|
|
height: 48px;
|
|
object-fit: cover;
|
|
border-radius: 4px;
|
|
transition: opacity 0.2s;
|
|
&:hover {
|
|
opacity: 0.8;
|
|
}
|
|
}
|
|
.customer-search-group {
|
|
display: flex;
|
|
gap: 12px;
|
|
}
|
|
.price-input-wrapper {
|
|
display: flex;
|
|
align-items: center;
|
|
width: 100%;
|
|
}
|
|
:deep(.pdf-preview-dialog) {
|
|
.el-dialog__body {
|
|
padding: 20px;
|
|
height: calc(90vh - 120px);
|
|
min-height: 500px;
|
|
display: flex;
|
|
flex-direction: column;
|
|
}
|
|
}
|
|
.pdf-preview-container {
|
|
display: flex;
|
|
flex-direction: column;
|
|
height: 100%;
|
|
min-height: 0;
|
|
}
|
|
.pdf-preview-toolbar {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
margin-bottom: 12px;
|
|
flex-shrink: 0;
|
|
}
|
|
.pdf-preview-body {
|
|
flex: 1;
|
|
min-height: 0;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
overflow: hidden;
|
|
}
|
|
.viewer-wrap {
|
|
width: 100%;
|
|
height: 100%;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
overflow: auto;
|
|
}
|
|
.viewer {
|
|
width: 100%;
|
|
height: 100%;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
min-height: 0;
|
|
}
|
|
.viewer img {
|
|
max-width: 100%;
|
|
max-height: 100%;
|
|
display: block;
|
|
object-fit: contain;
|
|
}
|
|
.viewer object,
|
|
.viewer iframe {
|
|
width: 100%;
|
|
height: 100%;
|
|
border: none;
|
|
min-height: 400px;
|
|
}
|
|
.text-viewer pre {
|
|
white-space: pre-wrap;
|
|
word-wrap: break-word;
|
|
text-align: left;
|
|
padding: 12px;
|
|
height: 100%;
|
|
overflow: auto;
|
|
width: 100%;
|
|
}
|
|
.unknown-viewer .unknown {
|
|
text-align: center;
|
|
color: #999;
|
|
}
|
|
.no-file {
|
|
color: #999;
|
|
text-align: center;
|
|
}
|
|
.footer-info {
|
|
color: #666;
|
|
}
|
|
.loading {
|
|
color: #999;
|
|
}
|
|
.error {
|
|
color: #f56c6c;
|
|
}
|
|
.fallback {
|
|
text-align: center;
|
|
color: #999;
|
|
padding: 20px;
|
|
}
|
|
.fallback a {
|
|
color: #409eff;
|
|
text-decoration: none;
|
|
}
|
|
.fallback a:hover {
|
|
text-decoration: underline;
|
|
}
|
|
.item-row-wrap {
|
|
margin-bottom: 0;
|
|
}
|
|
.bind-name-wrapper {
|
|
display: flex;
|
|
gap: 12px;
|
|
width: 100%;
|
|
|
|
:deep(.el-input) {
|
|
flex: 1;
|
|
}
|
|
}
|
|
.bind-tip {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
color: #909399;
|
|
|
|
.bind-tip-text {
|
|
color: #606266;
|
|
}
|
|
}
|
|
.upload-list {
|
|
:deep(.el-upload-list__item-thumbnail) {
|
|
object-fit: cover;
|
|
}
|
|
}
|
|
.bind-search {
|
|
margin-bottom: 12px;
|
|
}
|
|
.bind-loading,
|
|
.bind-empty {
|
|
padding: 16px;
|
|
text-align: center;
|
|
color: #909399;
|
|
}
|
|
.bind-item {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
padding: 10px 12px;
|
|
border-radius: 6px;
|
|
cursor: pointer;
|
|
transition: background-color 0.2s;
|
|
|
|
& + .bind-item {
|
|
margin-top: 6px;
|
|
}
|
|
|
|
&:hover {
|
|
background-color: #f5f7fa;
|
|
}
|
|
|
|
&.active {
|
|
background-color: #ecf5ff;
|
|
border: 1px solid #409eff;
|
|
}
|
|
}
|
|
.bind-item-main {
|
|
flex: 1;
|
|
min-width: 0;
|
|
}
|
|
.bind-item-title {
|
|
font-weight: 500;
|
|
color: #303133;
|
|
margin-bottom: 4px;
|
|
}
|
|
.bind-item-sub {
|
|
font-size: 12px;
|
|
color: #909399;
|
|
}
|
|
.bind-item-check {
|
|
width: 18px;
|
|
height: 18px;
|
|
border-radius: 50%;
|
|
border: 1px solid #dcdfe6;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
margin-left: 10px;
|
|
|
|
.check-dot {
|
|
width: 10px;
|
|
height: 10px;
|
|
border-radius: 50%;
|
|
background-color: #409eff;
|
|
}
|
|
}
|
|
.fetch-dialog-content {
|
|
min-height: 200px;
|
|
}
|
|
.fetch-loading {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
padding: 40px;
|
|
color: #909399;
|
|
}
|
|
.fetch-result {
|
|
margin-top: 20px;
|
|
}
|
|
</style>
|