1757 lines
55 KiB
Vue
1757 lines
55 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="80px">
|
||
<el-select v-model="auditStatus" placeholder="请选择状态" style="min-width: 120px">
|
||
<el-option label="全部" value="" />
|
||
<el-option label="待审核" :value="0" />
|
||
<el-option label="审核通过" :value="1" />
|
||
<el-option label="审核拒绝" :value="2" />
|
||
<el-option label="抓取成功" :value="3" />
|
||
<el-option label="推送成功" :value="4" />
|
||
<el-option label="推送失败" :value="5" />
|
||
</el-select>
|
||
</el-form-item>
|
||
</div>
|
||
</div>
|
||
<div class="boxshadow">
|
||
<div style="text-align: right; padding: 12px 0">
|
||
<el-button type="primary" @click="fetchpublishfn"> 抓取 </el-button>
|
||
</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" width="120px" />
|
||
<ElTableColumn label="产品名称" prop="product_name" width="150px" />
|
||
<ElTableColumn label="产品图" width="80px">
|
||
<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" width="100px" />
|
||
<ElTableColumn label="车源地" prop="source_place" width="120px" />
|
||
<ElTableColumn label="上牌时间" prop="plate_time" width="120px" />
|
||
<ElTableColumn label="排量" prop="displacement" width="100px" />
|
||
<ElTableColumn label="里程(Km)" prop="mileage" width="100px" />
|
||
<ElTableColumn label="马力" prop="power" width="80px" />
|
||
<ElTableColumn label="车身颜色" prop="body_color" width="100px" />
|
||
<ElTableColumn label="审核状态" prop="status" width="100px">
|
||
<template #default="scope">
|
||
<el-tag :type="getAuditStatusColor(scope.row.status)">
|
||
{{ getAuditStatusText(scope.row.status) }}
|
||
</el-tag>
|
||
</template>
|
||
</ElTableColumn>
|
||
<ElTableColumn label="创建时间" prop="created_at" width="160px" />
|
||
<ElTableColumn fixed="right" label="操作" width="240px">
|
||
<template #default="scope">
|
||
<ElRow>
|
||
<el-button link @click="showDialog('detail', scope.row)"> 详情 </el-button>
|
||
<el-button
|
||
link
|
||
type="primary"
|
||
@click="editFn(scope.row)"
|
||
v-if="scope.row.status === 0 || scope.row.status === 2"
|
||
>
|
||
编辑
|
||
</el-button>
|
||
<el-button link type="success" @click="showAuditRecords(scope.row)"> 审核记录 </el-button>
|
||
<el-button link type="danger" @click="deleteFn(scope.row)"> 删除 </el-button>
|
||
</ElRow>
|
||
</template>
|
||
</ElTableColumn>
|
||
</template>
|
||
</ArtTable>
|
||
</div>
|
||
|
||
<!-- 详情弹窗 -->
|
||
<el-dialog v-model="detailDialogVisible" title="车辆详情" width="800px">
|
||
<el-scrollbar max-height="60vh">
|
||
<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="产品_name">{{ 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="审核状态">
|
||
<el-tag :type="getAuditStatusColor(detailData.status)">
|
||
{{ getAuditStatusText(detailData.status) }}
|
||
</el-tag>
|
||
</el-descriptions-item>
|
||
<el-descriptions-item label="创建时间" :span="2">{{ detailData.created_at || '--' }}</el-descriptions-item>
|
||
<el-descriptions-item label="审核意见" :span="2" v-if="detailData.audit_opinion">
|
||
{{ detailData.audit_opinion }}
|
||
</el-descriptions-item>
|
||
<el-descriptions-item label="车况介绍" :span="2">
|
||
{{ detailData.condition_desc || '--' }}
|
||
</el-descriptions-item>
|
||
<!-- 视频展示 -->
|
||
<el-descriptions-item label="介绍视频" :span="2" v-if="detailVideoList.length">
|
||
<div class="video-preview-list">
|
||
<div v-for="(video, index) in detailVideoList" :key="index" class="video-item">
|
||
<video :src="video.url" controls class="video-preview"></video>
|
||
</div>
|
||
</div>
|
||
</el-descriptions-item>
|
||
<!-- FAQ 展示 -->
|
||
<!-- <el-descriptions-item label="FAQ 问答" :span="2" v-if="detailData.faqs && detailData.faqs.length">
|
||
<div class="faq-display">
|
||
<div v-for="(faq, index) in detailData.faqs" :key="index" class="faq-item">
|
||
<div class="faq-question">
|
||
<span class="faq-label">Q{{ index + 1 }}:</span>
|
||
{{ faq.question || '--' }}
|
||
</div>
|
||
<div class="faq-answer">
|
||
<span class="faq-label">A{{ index + 1 }}:</span>
|
||
{{ faq.answer || '--' }}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</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>
|
||
</el-scrollbar>
|
||
</el-dialog>
|
||
|
||
<!-- 编辑弹窗 -->
|
||
<el-dialog v-model="editDialogVisible" title="编辑发布" width="900px" :close-on-click-modal="false">
|
||
<el-scrollbar max-height="60vh">
|
||
<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="file"
|
||
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="介绍视频">
|
||
<el-upload
|
||
v-model:file-list="editVideoFileList"
|
||
class="upload-list"
|
||
:limit="maxVideos"
|
||
:action="uploadActionVideo"
|
||
:headers="uploadHeaders"
|
||
name="file"
|
||
accept="video/*"
|
||
multiple
|
||
:before-upload="handleVideoPreview"
|
||
:on-success="handleEditVideoSuccess"
|
||
:on-remove="handleEditVideoRemove"
|
||
:on-exceed="handleVideoExceed"
|
||
>
|
||
<el-button type="primary">
|
||
<el-icon><Plus /></el-icon>
|
||
上传视频
|
||
</el-button>
|
||
<template #tip>
|
||
<div class="el-upload__tip">支持 mp4/avi/mov 等视频格式</div>
|
||
</template>
|
||
</el-upload>
|
||
<!-- 视频预览列表 -->
|
||
<div v-if="editVideoPreviewList.length" class="video-preview-list">
|
||
<div v-for="(video, index) in editVideoPreviewList" :key="index" class="video-item">
|
||
<video :src="video.url" controls class="video-preview"></video>
|
||
<el-button link type="danger" @click="removeEditVideoByIndex(index)" size="small">
|
||
删除
|
||
</el-button>
|
||
</div>
|
||
</div>
|
||
</el-form-item>
|
||
</ElCol>
|
||
<ElCol :sm="24" :md="24" :lg="24">
|
||
<el-form-item label="附件">
|
||
<MultiFileUpload
|
||
v-model="editAttachmentFileList"
|
||
upload-text="点击上传附件"
|
||
hint-text="仅支持单个文件上传"
|
||
:max-count="1"
|
||
:action-image-uplode="uploadActionFile"
|
||
@change="handleEditAttachmentChange"
|
||
/>
|
||
</el-form-item>
|
||
</ElCol>
|
||
<!-- FAQ 编辑 -->
|
||
<!-- <ElCol :sm="24" :md="24" :lg="24">
|
||
<el-form-item label="FAQ 问答">
|
||
<div class="faq-container">
|
||
<div v-for="(faq, index) in editForm.faqs" :key="index" class="faq-item">
|
||
<div class="faq-header">
|
||
<span class="faq-index">Q{{ index + 1 }}</span>
|
||
<el-button
|
||
link
|
||
type="danger"
|
||
@click="removeEditFaq(index)"
|
||
v-if="editForm.faqs.length > 1"
|
||
>
|
||
删除
|
||
</el-button>
|
||
</div>
|
||
<el-form-item :label="`问题 ${index + 1}`">
|
||
<el-input v-model="faq.question" placeholder="请输入问题" clearable />
|
||
</el-form-item>
|
||
<el-form-item :label="`答案 ${index + 1}`">
|
||
<el-input
|
||
v-model="faq.answer"
|
||
type="textarea"
|
||
:rows="3"
|
||
placeholder="请输入答案"
|
||
/>
|
||
</el-form-item>
|
||
</div>
|
||
<el-button type="primary" @click="addEditFaq" :icon="Plus">
|
||
添加问答
|
||
</el-button>
|
||
</div>
|
||
</el-form-item>
|
||
</ElCol> -->
|
||
</ElRow>
|
||
</el-form>
|
||
</el-scrollbar>
|
||
<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="auditRecordsDialogVisible" title="审核记录" width="700px">
|
||
<div v-loading="auditRecordsLoading" class="audit-records-container">
|
||
<el-empty v-if="!auditRecordsList.length" description="暂无审核记录" />
|
||
<el-timeline v-else>
|
||
<el-timeline-item
|
||
v-for="(record, index) in auditRecordsList"
|
||
:key="index"
|
||
:timestamp="record.created_at"
|
||
placement="top"
|
||
:type="getAuditRecordStatusColor(record.audit_status) === 'success' ? 'success' :
|
||
getAuditRecordStatusColor(record.audit_status) === 'danger' ? 'danger' : 'warning'"
|
||
>
|
||
<el-card class="audit-record-card">
|
||
<div class="audit-record-header">
|
||
<el-tag :type="getAuditRecordStatusColor(record.audit_status)">
|
||
{{ record.audit_status_text || '--' }}
|
||
</el-tag>
|
||
<span class="auditor-info">
|
||
<el-icon><User /></el-icon>
|
||
{{ record.auditor_name || '--' }}
|
||
</span>
|
||
</div>
|
||
<div class="audit-record-content" v-if="record.audit_reason">
|
||
<div class="content-label">审核理由:</div>
|
||
<div class="content-value">{{ record.audit_reason }}</div>
|
||
</div>
|
||
<div class="audit-record-status-change">
|
||
<span class="status-before">{{ getStatusText(record.before_status) }}</span>
|
||
<el-icon class="status-arrow"><ArrowRight /></el-icon>
|
||
<span class="status-after">{{ getStatusText(record.after_status) }}</span>
|
||
</div>
|
||
</el-card>
|
||
</el-timeline-item>
|
||
</el-timeline>
|
||
</div>
|
||
<template #footer>
|
||
<span class="dialog-footer">
|
||
<el-button @click="auditRecordsDialogVisible = false">关 闭</el-button>
|
||
</span>
|
||
</template>
|
||
</el-dialog>
|
||
|
||
<!-- 抓取弹窗 -->
|
||
<el-dialog
|
||
v-model="fetchDialogVisible"
|
||
title="抓取数据"
|
||
width="800px"
|
||
: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>
|
||
|
||
<el-form v-else-if="fetchData" :model="fetchForm" label-width="100px" class="fetch-result-form">
|
||
<ElRow :gutter="12">
|
||
<ElCol :sm="24" :md="12" :lg="12">
|
||
<el-form-item label="产品名称">
|
||
<el-input v-model="fetchForm.product_name" placeholder="请输入产品名称" />
|
||
</el-form-item>
|
||
</ElCol>
|
||
<ElCol :sm="24" :md="12" :lg="12">
|
||
<el-form-item label="车价(¥)">
|
||
<el-input v-model="fetchForm.price" placeholder="请输入车价" />
|
||
</el-form-item>
|
||
</ElCol>
|
||
<ElCol :sm="24" :md="12" :lg="12">
|
||
<el-form-item label="车源地">
|
||
<el-input v-model="fetchForm.source_place" placeholder="请输入车源地" />
|
||
</el-form-item>
|
||
</ElCol>
|
||
<ElCol :sm="24" :md="12" :lg="12">
|
||
<el-form-item label="上牌时间">
|
||
<el-input v-model="fetchForm.plate_time" placeholder="请输入上牌时间" />
|
||
</el-form-item>
|
||
</ElCol>
|
||
<ElCol :sm="24" :md="12" :lg="12">
|
||
<el-form-item label="排量">
|
||
<el-input v-model="fetchForm.displacement" placeholder="请输入排量" />
|
||
</el-form-item>
|
||
</ElCol>
|
||
<ElCol :sm="24" :md="12" :lg="12">
|
||
<el-form-item label="里程(Km)">
|
||
<el-input v-model="fetchForm.mileage" placeholder="请输入里程" />
|
||
</el-form-item>
|
||
</ElCol>
|
||
<ElCol :sm="24" :md="12" :lg="12">
|
||
<el-form-item label="马力">
|
||
<el-input v-model="fetchForm.power" placeholder="请输入马力" />
|
||
</el-form-item>
|
||
</ElCol>
|
||
<ElCol :sm="24" :md="12" :lg="12">
|
||
<el-form-item label="车身颜色">
|
||
<el-input v-model="fetchForm.body_color" placeholder="请输入车身颜色" />
|
||
</el-form-item>
|
||
</ElCol>
|
||
<ElCol :sm="24" :md="24" :lg="24">
|
||
<el-form-item label="车况介绍">
|
||
<el-input
|
||
v-model="fetchForm.condition_desc"
|
||
type="textarea"
|
||
:rows="3"
|
||
placeholder="请输入车况介绍"
|
||
/>
|
||
</el-form-item>
|
||
</ElCol>
|
||
</ElRow>
|
||
</el-form>
|
||
</div>
|
||
<template #footer>
|
||
<span class="dialog-footer">
|
||
<el-button @click="fetchDialogVisible = false">关 闭</el-button>
|
||
<el-button type="primary" @click="handleFetchConfirm" :disabled="!fetchData">确认添加</el-button>
|
||
</span>
|
||
</template>
|
||
</el-dialog>
|
||
|
||
<!-- 图片预览组件 -->
|
||
<ElImageViewer
|
||
v-if="showImageViewer1"
|
||
:url-list="[currentPdfFiles]"
|
||
:hide-on-click-modal="true"
|
||
@close="showImageViewer1 = false"
|
||
/>
|
||
<ElImageViewer
|
||
v-if="showImageViewer"
|
||
:url-list="imgList"
|
||
:initial-index="viewerIndex"
|
||
:hide-on-click-modal="true"
|
||
@close="showImageViewer = false"
|
||
/>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
import { reactive, ref, computed } from 'vue'
|
||
import {
|
||
ElMessage,
|
||
ElMessageBox,
|
||
ElLoading,
|
||
ElRow,
|
||
ElCol,
|
||
type FormInstance,
|
||
type FormRules,
|
||
type UploadFile
|
||
} from 'element-plus'
|
||
import { Plus, Loading, User, ArrowRight } from '@element-plus/icons-vue'
|
||
import { manageService } from '@/api/adminApi'
|
||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||
// @ts-ignore
|
||
import FilePreview from '@/components/custom/FilePreview.vue'
|
||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||
// @ts-ignore
|
||
import MultiFileUpload from '@/components/core/forms/MultiFileUpload.vue'
|
||
import { useUploadHeaders } from '@/composables/useUploadHeaders'
|
||
import request from '@/utils/http'
|
||
|
||
defineOptions({ name: 'PublishMyListPro' })
|
||
|
||
const { uploadHeaders } = useUploadHeaders()
|
||
|
||
const maxMedia = 200
|
||
const maxVideos = 5
|
||
|
||
interface FileItem {
|
||
name: string
|
||
url: string
|
||
file_name: string
|
||
}
|
||
|
||
interface FAQItem {
|
||
question: string
|
||
answer: string
|
||
}
|
||
|
||
const keyword = ref<string>('')
|
||
const auditStatus = ref<string | number>('')
|
||
const loading = ref(false)
|
||
const pagination = reactive({
|
||
currentPage: 1,
|
||
pageSize: 10,
|
||
total: 0
|
||
})
|
||
const tableData = ref<any[]>([])
|
||
|
||
// 审核记录相关
|
||
const auditRecordsDialogVisible = ref(false)
|
||
const auditRecordsList = ref<any[]>([])
|
||
const auditRecordsLoading = ref(false)
|
||
|
||
// 详情相关
|
||
const detailDialogVisible = ref(false)
|
||
const detailData = ref<any>(null)
|
||
const contractFiles = ref<FileItem[]>([])
|
||
const detailVideoList = ref<{ url: string; name: string }[]>([])
|
||
|
||
// 编辑相关
|
||
const editDialogVisible = ref(false)
|
||
const editSubmitLoading = ref(false)
|
||
const editFormRef = ref<FormInstance>()
|
||
|
||
interface EditForm {
|
||
id: number | string
|
||
product_name: string
|
||
price: string
|
||
source_place: string
|
||
plate_time: string
|
||
displacement: string
|
||
power: string
|
||
body_color: string
|
||
condition_desc: string
|
||
mileage: string
|
||
images: string[] // 用于显示(images_pull)
|
||
original_images: string[] // 用于保存提交(原始 images)
|
||
intro_video: string[]
|
||
attachment: { name: string; src: string }[]
|
||
faqs: FAQItem[]
|
||
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<EditForm>({
|
||
id: '',
|
||
product_name: '',
|
||
price: '',
|
||
source_place: '',
|
||
plate_time: '',
|
||
displacement: '',
|
||
power: '',
|
||
body_color: '',
|
||
condition_desc: '',
|
||
mileage: '',
|
||
images: [],
|
||
original_images: [],
|
||
intro_video: [],
|
||
attachment: [],
|
||
faqs: [{ question: '', answer: '' }],
|
||
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 uploadAction = `${import.meta.env.VITE_API_URL}/upload?scene=usedcarimg`
|
||
const uploadActionFile = `${import.meta.env.VITE_API_URL}/upload?scene=usedcarfile`
|
||
const uploadActionVideo = `${import.meta.env.VITE_API_URL}/upload?scene=usedcarvideo`
|
||
|
||
type UploadFileWithResponse = UploadFile & {
|
||
response?: any
|
||
}
|
||
|
||
const editImageFileList = ref<UploadFileWithResponse[]>([])
|
||
const editVideoFileList = ref<UploadFileWithResponse[]>([])
|
||
const editAttachmentFileList = ref<FileItem[]>([])
|
||
const editVideoPreviewList = ref<{ url: string; name: string }[]>([])
|
||
|
||
// 图片预览相关
|
||
const showImageViewer = ref(false)
|
||
const showImageViewer1 = ref(false)
|
||
const viewerIndex = ref(0)
|
||
const currentPdfFiles = ref('')
|
||
const fetchImgList = ref<string[]>([])
|
||
const imgList = computed(() => {
|
||
// 如果抓取弹窗打开且有图片,显示抓取的图片
|
||
if (fetchDialogVisible.value && fetchImgList.value.length > 0) {
|
||
return fetchImgList.value
|
||
}
|
||
return parsePreviewList(detailData.value?.images_pull || detailData.value?.images || [])
|
||
})
|
||
|
||
const handleAvatarClick1 = (index: string) => {
|
||
if (index !== null && index !== undefined) {
|
||
currentPdfFiles.value = index
|
||
showImageViewer1.value = true
|
||
}
|
||
}
|
||
|
||
const handleAvatarClick = (index: number) => {
|
||
if (index !== null && index !== undefined) {
|
||
viewerIndex.value = index
|
||
showImageViewer.value = true
|
||
}
|
||
}
|
||
|
||
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
|
||
}
|
||
return value
|
||
.split(',')
|
||
.map((p: string) => p.trim())
|
||
.filter(Boolean)
|
||
}
|
||
return []
|
||
}
|
||
|
||
// 审核状态相关方法
|
||
const getAuditStatusText = (status: number) => {
|
||
const statusMap: Record<number, string> = {
|
||
0: '待审核',
|
||
1: '审核通过',
|
||
2: '审核拒绝',
|
||
3: '推送中',
|
||
4: '推送成功',
|
||
5: '推送失败'
|
||
}
|
||
return statusMap[status] || '未知'
|
||
}
|
||
|
||
const getAuditStatusColor = (status: number): 'success' | 'warning' | 'danger' | 'info' => {
|
||
const colorMap: Record<number, 'success' | 'warning' | 'danger' | 'info'> = {
|
||
0: 'warning',
|
||
1: 'success',
|
||
2: 'danger',
|
||
3: 'info',
|
||
4: 'success',
|
||
5: 'danger'
|
||
}
|
||
return colorMap[status] || 'info'
|
||
}
|
||
|
||
// 审核记录相关函数
|
||
const getStatusText = (status: number) => getAuditStatusText(status)
|
||
|
||
const getAuditRecordStatusColor = (status: number): 'success' | 'warning' | 'danger' | 'info' => {
|
||
const colorMap: Record<number, 'success' | 'warning' | 'danger' | 'info'> = {
|
||
0: 'warning',
|
||
1: 'success',
|
||
2: 'danger',
|
||
3: 'info',
|
||
4: 'success',
|
||
5: 'danger'
|
||
}
|
||
return colorMap[status] || 'info'
|
||
}
|
||
|
||
const showAuditRecords = async (row: any) => {
|
||
auditRecordsLoading.value = true
|
||
auditRecordsDialogVisible.value = true
|
||
try {
|
||
const records = await manageService.getAuditRecords({ id: row.id })
|
||
auditRecordsList.value = records || []
|
||
} catch (error) {
|
||
console.error('获取审核记录失败:', error)
|
||
ElMessage.error('获取审核记录失败')
|
||
} finally {
|
||
auditRecordsLoading.value = false
|
||
}
|
||
}
|
||
|
||
const parsePreviewList = (value: any): string[] => parsePaths(value)
|
||
|
||
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 showDialog = async (_type: string, row: any) => {
|
||
try {
|
||
// 根据新的 API 获取详情
|
||
const res: any = await request.get({
|
||
url: '/common/usedcardetail/show',
|
||
params: { id: row.id }
|
||
})
|
||
detailData.value = res
|
||
|
||
// 处理合同文件
|
||
const file_list = res?.attachment_pull
|
||
if (file_list) {
|
||
contractFiles.value = processFiles(file_list)
|
||
} else {
|
||
contractFiles.value = []
|
||
}
|
||
|
||
// 处理视频列表
|
||
const videoList = res?.video_pull || []
|
||
detailVideoList.value = Array.isArray(videoList)
|
||
? videoList.map((v: any) => ({
|
||
url: v.file_url || v.src_pull || v,
|
||
name: v.name || '视频'
|
||
}))
|
||
: []
|
||
|
||
detailDialogVisible.value = true
|
||
} catch (error) {
|
||
console.error('获取详情失败:', error)
|
||
ElMessage.error('获取详情失败')
|
||
}
|
||
}
|
||
|
||
// 编辑功能
|
||
const editFn = async (row: any) => {
|
||
try {
|
||
const res: any = await request.get({
|
||
url: '/common/usedcardetail/show',
|
||
params: { id: row.id }
|
||
})
|
||
|
||
// 填充表单
|
||
editForm.id = res.id
|
||
editForm.product_name = res.product_name || ''
|
||
editForm.price = res.price || ''
|
||
editForm.source_place = res.source_place || ''
|
||
editForm.plate_time = res.plate_time || ''
|
||
editForm.displacement = res.displacement || ''
|
||
editForm.power = res.power || ''
|
||
editForm.body_color = res.body_color || ''
|
||
editForm.condition_desc = res.condition_desc || ''
|
||
editForm.mileage = res.mileage || ''
|
||
|
||
// 处理图片:images_pull 用于显示,images 用于保存
|
||
const displayImages = parsePreviewList(res.images_pull || res.images || [])
|
||
const saveImages = parsePreviewList(res.images || [])
|
||
|
||
editForm.images = displayImages // 用于显示
|
||
editForm.original_images = saveImages // 用于保存提交
|
||
|
||
editImageFileList.value = displayImages.map((url, idx) => ({
|
||
name: `image_${idx}`,
|
||
url,
|
||
status: 'success' as const,
|
||
uid: idx,
|
||
response: { file_url: url, file_name: saveImages[idx] || url }
|
||
}))
|
||
|
||
// 处理视频
|
||
const videoList = res.video_pull || []
|
||
const videos = Array.isArray(videoList) ? videoList : []
|
||
editForm.intro_video = videos.map((v: any) => v.file_name || v.src || v)
|
||
editVideoPreviewList.value = videos.map((v: any) => ({
|
||
url: v.file_url || v.src_pull || v,
|
||
name: v.name || '视频'
|
||
}))
|
||
editVideoFileList.value = videos.map((v: any, idx: number) => ({
|
||
name: v.name || 'video',
|
||
url: v.file_url || v.src_pull || v,
|
||
status: 'success',
|
||
uid: idx,
|
||
response: { file_url: v.file_url || v.src_pull || v, file_name: v.file_name || v.src || v }
|
||
}))
|
||
|
||
// 处理附件
|
||
const attachmentList = res.attachment_pull || []
|
||
editForm.attachment = attachmentList.map((a: any) => ({
|
||
name: a.name,
|
||
src: a.src
|
||
}))
|
||
editAttachmentFileList.value = attachmentList.map((a: any, idx: number) => ({
|
||
name: a.name,
|
||
url: a.src_pull || a.file_url,
|
||
file_name: a.src,
|
||
status: 'success',
|
||
uid: idx,
|
||
response: { file_url: a.src_pull || a.file_url, file_name: a.src }
|
||
}))
|
||
|
||
// 处理 FAQ
|
||
editForm.faqs =
|
||
res.faqs && Array.isArray(res.faqs) && res.faqs.length > 0
|
||
? res.faqs
|
||
: [{ question: '', answer: '' }]
|
||
|
||
// 处理绑定信息
|
||
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
|
||
}
|
||
editForm.crawl_title = res.crawl_title || ''
|
||
editForm.crawl_brand = res.crawl_brand || ''
|
||
editForm.crawl_series = res.crawl_series || ''
|
||
editForm.crawl_year = res.crawl_year || ''
|
||
editForm.crawl_msrp = res.crawl_msrp || ''
|
||
editForm.crawl_car_id = res.crawl_car_id || ''
|
||
editForm.crawl_brand_id = res.crawl_brand_id || ''
|
||
editForm.crawl_series_id = res.crawl_series_id || ''
|
||
}
|
||
|
||
editDialogVisible.value = true
|
||
} catch (error) {
|
||
console.error('获取编辑数据失败:', error)
|
||
ElMessage.error('获取编辑数据失败')
|
||
}
|
||
}
|
||
|
||
// 编辑绑定功能
|
||
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 loadingInstance = ref<ReturnType<typeof ElLoading.service> | null>(null)
|
||
|
||
const handleImagePreview = () => {
|
||
loadingInstance.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 && !editForm.images.includes(fileName)) {
|
||
editForm.images.push(fileName)
|
||
editForm.original_images.push(fileName)
|
||
}
|
||
editImageFileList.value = fileList as UploadFileWithResponse[]
|
||
loadingInstance.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)
|
||
editForm.original_images.splice(idx, 1)
|
||
}
|
||
}
|
||
editImageFileList.value = fileList as UploadFileWithResponse[]
|
||
}
|
||
|
||
const handleEditImageExceed = () => {
|
||
ElMessage.warning(`最多只能上传 ${maxMedia} 张图片`)
|
||
}
|
||
|
||
// 视频上传处理
|
||
const handleVideoPreview = () => {
|
||
loadingInstance.value = ElLoading.service({
|
||
lock: true,
|
||
text: '正在上传视频…',
|
||
background: 'rgba(0, 0, 0, 0.45)'
|
||
})
|
||
return true
|
||
}
|
||
|
||
const handleEditVideoSuccess = (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 && !editForm.intro_video.includes(fileName)) {
|
||
editForm.intro_video.push(fileName)
|
||
}
|
||
if (!editVideoPreviewList.value.find((v) => v.url === url)) {
|
||
editVideoPreviewList.value.push({ url, name: file.name })
|
||
}
|
||
editVideoFileList.value = fileList as UploadFileWithResponse[]
|
||
loadingInstance.value?.close()
|
||
}
|
||
|
||
const handleEditVideoRemove = (file: UploadFile, fileList: UploadFile[]) => {
|
||
const resp = (file as UploadFileWithResponse).response
|
||
const fileName = resp?.file_name || resp?.data?.file_name || ''
|
||
if (fileName) {
|
||
const idx = editForm.intro_video.indexOf(fileName)
|
||
if (idx > -1) editForm.intro_video.splice(idx, 1)
|
||
}
|
||
const url = resp?.file_url || file.url || ''
|
||
const previewIdx = editVideoPreviewList.value.findIndex((v) => v.url === url)
|
||
if (previewIdx > -1) {
|
||
editVideoPreviewList.value.splice(previewIdx, 1)
|
||
}
|
||
editVideoFileList.value = fileList as UploadFileWithResponse[]
|
||
}
|
||
|
||
const handleVideoExceed = () => {
|
||
ElMessage.warning(`最多只能上传 ${maxVideos} 个视频`)
|
||
}
|
||
|
||
const removeEditVideoByIndex = (index: number) => {
|
||
editForm.intro_video.splice(index, 1)
|
||
editVideoPreviewList.value.splice(index, 1)
|
||
editVideoFileList.value.splice(index, 1)
|
||
}
|
||
|
||
// 附件变化处理
|
||
const handleEditAttachmentChange = (files: FileItem[]) => {
|
||
editForm.attachment = files.map((file) => ({
|
||
name: file.name,
|
||
src: file.file_name
|
||
}))
|
||
}
|
||
|
||
// FAQ 操作
|
||
const addEditFaq = () => {
|
||
editForm.faqs.push({ question: '', answer: '' })
|
||
}
|
||
|
||
const removeEditFaq = (index: number) => {
|
||
if (editForm.faqs.length > 1) {
|
||
editForm.faqs.splice(index, 1)
|
||
}
|
||
}
|
||
|
||
// 编辑提交
|
||
const handleEditSubmit = () => {
|
||
if (!editFormRef.value) return
|
||
editFormRef.value.validate(async (valid) => {
|
||
if (!valid) return
|
||
|
||
editSubmitLoading.value = true
|
||
try {
|
||
const validFaqs = editForm.faqs.filter((faq) => faq.question.trim() || faq.answer.trim())
|
||
|
||
const payload: any = {
|
||
id: editForm.id,
|
||
product_name: editForm.product_name,
|
||
price: editForm.price,
|
||
source_place: editForm.source_place,
|
||
plate_time: editForm.plate_time,
|
||
displacement: editForm.displacement,
|
||
power: editForm.power,
|
||
body_color: editForm.body_color,
|
||
condition_desc: editForm.condition_desc,
|
||
mileage: editForm.mileage,
|
||
images: editForm.original_images.map((s) => String(s || '').trim()).filter(Boolean).join(','),
|
||
intro_video: editForm.intro_video.map((s) => String(s || '').trim()).filter(Boolean).join(','),
|
||
attachment: editForm.attachment,
|
||
faqs: validFaqs.length > 0 ? validFaqs : undefined,
|
||
crawl_title: editForm.crawl_title,
|
||
crawl_brand: editForm.crawl_brand,
|
||
crawl_series: editForm.crawl_series,
|
||
crawl_year: editForm.crawl_year,
|
||
crawl_msrp: editForm.crawl_msrp,
|
||
crawl_car_id: editForm.crawl_car_id,
|
||
crawl_brand_id: editForm.crawl_brand_id,
|
||
crawl_series_id: editForm.crawl_series_id
|
||
}
|
||
|
||
await manageService.publisheditpro(payload)
|
||
ElMessage.success('编辑成功')
|
||
editDialogVisible.value = false
|
||
getWorkList()
|
||
} catch (error: any) {
|
||
ElMessage.error(error?.message || '编辑失败,请稍后重试')
|
||
} finally {
|
||
editSubmitLoading.value = false
|
||
}
|
||
})
|
||
}
|
||
|
||
// 标记售出
|
||
const statusfn = (row: any) => {
|
||
ElMessageBox.confirm('确定要标记售出吗?', '提示', {
|
||
confirmButtonText: '确定',
|
||
cancelButtonText: '取消',
|
||
type: 'warning'
|
||
})
|
||
.then(async () => {
|
||
await manageService.updatestatus({ id: row.id, status: 2 })
|
||
ElMessage.success('标记售出成功')
|
||
getWorkList()
|
||
})
|
||
.catch(() => {
|
||
// 用户取消
|
||
})
|
||
}
|
||
|
||
// 删除
|
||
const deleteFn = (row: any) => {
|
||
ElMessageBox.confirm('确定要删除此发布吗?', '提示', {
|
||
confirmButtonText: '确定',
|
||
cancelButtonText: '取消',
|
||
type: 'warning'
|
||
})
|
||
.then(async () => {
|
||
await manageService.publishdeletepro({ id: row.id })
|
||
ElMessage.success('删除成功')
|
||
getWorkList()
|
||
})
|
||
.catch(() => {
|
||
// 用户取消
|
||
})
|
||
}
|
||
|
||
// 跳转新增
|
||
const goToAdd = () => {
|
||
window.location.href = '/admin/publish/publish_addpro'
|
||
}
|
||
|
||
// 获取列表
|
||
const getWorkList = async () => {
|
||
loading.value = true
|
||
try {
|
||
const { currentPage, pageSize } = pagination
|
||
const params: any = {
|
||
current: currentPage,
|
||
size: pageSize,
|
||
scene: 'self'
|
||
}
|
||
if (keyword.value) params.keyword = keyword.value
|
||
if (auditStatus.value !== '') params.audit_status = auditStatus.value
|
||
|
||
// 使用新的 API
|
||
const { records, current, size, total } = await manageService.getpublishListpro(params)
|
||
tableData.value = records
|
||
Object.assign(pagination, { currentPage: current, pageSize: size, total })
|
||
} catch (error) {
|
||
console.error('获取列表失败:', error)
|
||
} finally {
|
||
loading.value = false
|
||
}
|
||
}
|
||
|
||
watch([keyword, auditStatus], () => {
|
||
pagination.currentPage = 1
|
||
getWorkList()
|
||
})
|
||
|
||
const handleSizeChange = (newPageSize: number) => {
|
||
pagination.pageSize = newPageSize
|
||
getWorkList()
|
||
}
|
||
|
||
const handleCurrentChange = (newCurrentPage: number) => {
|
||
pagination.currentPage = newCurrentPage
|
||
getWorkList()
|
||
}
|
||
|
||
// 抓取相关
|
||
const fetchDialogVisible = ref(false)
|
||
const fetchForm = reactive({
|
||
url: '',
|
||
product_name: '',
|
||
price: '',
|
||
source_place: '',
|
||
plate_time: '',
|
||
displacement: '',
|
||
power: '',
|
||
body_color: '',
|
||
condition_desc: '',
|
||
mileage: ''
|
||
})
|
||
const fetchLoading = ref(false)
|
||
const fetchData = ref<any>(null)
|
||
|
||
// 打开抓取弹窗
|
||
const fetchpublishfn = () => {
|
||
fetchDialogVisible.value = true
|
||
fetchForm.url = ''
|
||
fetchForm.product_name = ''
|
||
fetchForm.price = ''
|
||
fetchForm.source_place = ''
|
||
fetchForm.plate_time = ''
|
||
fetchForm.displacement = ''
|
||
fetchForm.power = ''
|
||
fetchForm.body_color = ''
|
||
fetchForm.condition_desc = ''
|
||
fetchForm.mileage = ''
|
||
fetchData.value = null
|
||
fetchImgList.value = []
|
||
}
|
||
|
||
// 执行抓取
|
||
const handleFetch = async () => {
|
||
if (!fetchForm.url || !fetchForm.url.trim()) {
|
||
ElMessage.warning('请输入抓取链接')
|
||
return
|
||
}
|
||
|
||
fetchLoading.value = true
|
||
fetchData.value = null
|
||
|
||
try {
|
||
const res: any = await request.post({
|
||
url: '/common/usedcardetail/crawl',
|
||
params: { url: fetchForm.url.trim() }
|
||
})
|
||
fetchData.value = res?.data || res || {}
|
||
|
||
// 填充表单数据
|
||
fetchForm.product_name = fetchData.value.product_name || ''
|
||
fetchForm.price = fetchData.value.price || ''
|
||
fetchForm.source_place = fetchData.value.source_place || ''
|
||
fetchForm.plate_time = fetchData.value.plate_time || ''
|
||
fetchForm.displacement = fetchData.value.displacement || ''
|
||
fetchForm.power = fetchData.value.power || ''
|
||
fetchForm.body_color = fetchData.value.body_color || ''
|
||
fetchForm.condition_desc = fetchData.value.condition_desc || ''
|
||
fetchForm.mileage = fetchData.value.mileage || ''
|
||
|
||
// 处理图片列表用于预览
|
||
fetchImgList.value = parsePreviewList(fetchData.value.image_pull || fetchData.value.image || [])
|
||
ElMessage.success(res?.message || '抓取成功')
|
||
console.log('抓取接口返回数据:', fetchData.value)
|
||
} catch (error: any) {
|
||
console.error('抓取失败:', error)
|
||
fetchData.value = null
|
||
ElMessage.error(error?.message || '抓取失败,请稍后重试')
|
||
} finally {
|
||
fetchLoading.value = false
|
||
}
|
||
}
|
||
|
||
// 确认添加抓取的数据
|
||
// 确认添加抓取的数据
|
||
const handleFetchConfirm = async () => {
|
||
// 合并抓取接口返回的原始数据和用户修改的数据
|
||
const formData = {
|
||
...fetchData.value, // 保存所有抓取接口返回的原始数据
|
||
// 用表单中修改的值替换对应字段
|
||
product_name: fetchForm.product_name,
|
||
price: fetchForm.price,
|
||
source_place: fetchForm.source_place,
|
||
plate_time: fetchForm.plate_time,
|
||
displacement: fetchForm.displacement,
|
||
power: fetchForm.power,
|
||
body_color: fetchForm.body_color,
|
||
condition_desc: fetchForm.condition_desc,
|
||
mileage: fetchForm.mileage
|
||
}
|
||
console.log('保存抓取数据:', formData)
|
||
|
||
try {
|
||
const res: any = await request.post({
|
||
url: '/common/usedcardetail/crawl/store',
|
||
params: formData
|
||
})
|
||
ElMessage.success(res?.message)
|
||
fetchDialogVisible.value = false
|
||
getWorkList() // 刷新列表
|
||
} catch (error: any) {
|
||
console.error('保存抓取数据失败:', error)
|
||
ElMessage.error(error?.message || '保存失败,请稍后重试')
|
||
}
|
||
}
|
||
|
||
// 抓取图片预览点击
|
||
const handleFetchAvatarClick = (index: number) => {
|
||
viewerIndex.value = index
|
||
showImageViewer.value = true
|
||
}
|
||
|
||
// 格式化年月(如:2024年01月 -> 2024-01-01)
|
||
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`
|
||
}
|
||
|
||
onMounted(() => {
|
||
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;
|
||
cursor: pointer;
|
||
transition: opacity 0.2s;
|
||
&:hover {
|
||
opacity: 0.8;
|
||
}
|
||
}
|
||
|
||
.customer-search-group {
|
||
display: flex;
|
||
gap: 12px;
|
||
}
|
||
|
||
.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;
|
||
}
|
||
}
|
||
|
||
.video-preview-list {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 12px;
|
||
|
||
.video-item {
|
||
position: relative;
|
||
width: 200px;
|
||
|
||
.video-preview {
|
||
width: 100%;
|
||
height: auto;
|
||
border-radius: 4px;
|
||
border: 1px solid #dcdfe6;
|
||
}
|
||
|
||
.el-button {
|
||
margin-top: 4px;
|
||
}
|
||
}
|
||
}
|
||
|
||
.faq-display {
|
||
.faq-item {
|
||
padding: 12px;
|
||
margin-bottom: 12px;
|
||
background-color: #f5f7fa;
|
||
border-radius: 6px;
|
||
|
||
&:last-child {
|
||
margin-bottom: 0;
|
||
}
|
||
|
||
.faq-question,
|
||
.faq-answer {
|
||
margin-bottom: 8px;
|
||
|
||
&:last-child {
|
||
margin-bottom: 0;
|
||
}
|
||
}
|
||
|
||
.faq-label {
|
||
font-weight: 500;
|
||
color: #409eff;
|
||
}
|
||
}
|
||
}
|
||
|
||
.faq-container {
|
||
width: 100%;
|
||
|
||
.faq-item {
|
||
padding: 16px;
|
||
margin-bottom: 12px;
|
||
background-color: #f5f7fa;
|
||
border-radius: 6px;
|
||
|
||
.faq-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: 12px;
|
||
|
||
.faq-index {
|
||
font-weight: 500;
|
||
color: #409eff;
|
||
}
|
||
}
|
||
|
||
:deep(.el-form-item) {
|
||
margin-bottom: 12px;
|
||
|
||
&:last-child {
|
||
margin-bottom: 0;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
.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;
|
||
}
|
||
}
|
||
|
||
// 审核记录时间轴样式
|
||
.audit-records-container {
|
||
max-height: 500px;
|
||
overflow-y: auto;
|
||
padding: 10px 0;
|
||
}
|
||
|
||
.audit-record-card {
|
||
border-radius: 8px;
|
||
|
||
.audit-record-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: 12px;
|
||
|
||
.auditor-info {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 4px;
|
||
color: #606266;
|
||
font-size: 14px;
|
||
}
|
||
}
|
||
|
||
.audit-record-content {
|
||
margin-bottom: 12px;
|
||
|
||
.content-label {
|
||
font-weight: 500;
|
||
color: #606266;
|
||
margin-bottom: 4px;
|
||
font-size: 14px;
|
||
}
|
||
|
||
.content-value {
|
||
color: #303133;
|
||
font-size: 14px;
|
||
line-height: 1.5;
|
||
}
|
||
}
|
||
|
||
.audit-record-status-change {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
padding: 8px 12px;
|
||
background: #f5f7fa;
|
||
border-radius: 4px;
|
||
|
||
.status-before,
|
||
.status-after {
|
||
font-size: 14px;
|
||
font-weight: 500;
|
||
}
|
||
|
||
.status-before {
|
||
color: #909399;
|
||
}
|
||
|
||
.status-after {
|
||
color: #67c23a;
|
||
}
|
||
|
||
.status-arrow {
|
||
color: #409eff;
|
||
}
|
||
}
|
||
}
|
||
|
||
// 抓取弹窗样式
|
||
.fetch-dialog-content {
|
||
.fetch-loading {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
padding: 40px 0;
|
||
color: #909399;
|
||
}
|
||
|
||
.fetch-result {
|
||
.thumb-list {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 6px;
|
||
}
|
||
|
||
.avatarimg {
|
||
width: 60px;
|
||
height: 60px;
|
||
object-fit: cover;
|
||
border-radius: 4px;
|
||
cursor: pointer;
|
||
transition: opacity 0.2s;
|
||
&:hover {
|
||
opacity: 0.8;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
</style>
|