1332 lines
43 KiB
Vue
1332 lines
43 KiB
Vue
<template>
|
||
<div class="page-content">
|
||
<div v-if="!isMobile" class="dict-layout">
|
||
<div class="dict-type-card">
|
||
<div class="card-header">
|
||
<div style="display: flex; align-items: center">
|
||
<ElInput
|
||
v-model="brandKeyword"
|
||
placeholder="搜索品牌名称/编码"
|
||
clearable
|
||
@keyup.enter="getBrandListseach"
|
||
/>
|
||
<ElButton style="margin-left: 8px" @click="getBrandListseach" v-ripple>搜索</ElButton>
|
||
</div>
|
||
<ElButton
|
||
type="primary"
|
||
style="margin-left: 8px"
|
||
@click="showBrandDialog('add')"
|
||
v-if="hasAuth('brand@add')"
|
||
>新增品牌</ElButton
|
||
>
|
||
</div>
|
||
<ArtTable
|
||
ref="tableRef"
|
||
row-key="id"
|
||
:loading="brandLoading"
|
||
:data="brandTableData"
|
||
:currentPage="brandPagination.currentPage"
|
||
:pageSize="brandPagination.pageSize"
|
||
:total="brandPagination.total"
|
||
@size-change="handleBrandCurrentChange"
|
||
@current-change="handleBrandCurrentChange"
|
||
>
|
||
<el-table-column prop="brand_code" label="品牌编码" />
|
||
<el-table-column prop="brand_name" label="品牌名称" />
|
||
<el-table-column label="Logo">
|
||
<template #default="scope">
|
||
<div class="brand-logo-cell">
|
||
<img
|
||
v-if="scope.row.logo_full"
|
||
:src="scope.row.logo_full"
|
||
:alt="scope.row.brand_name"
|
||
/>
|
||
<div v-else class="logo-placeholder">{{ scope.row.brand_name?.charAt(0) }}</div>
|
||
</div>
|
||
</template>
|
||
</el-table-column>
|
||
<!-- <el-table-column prop="description" label="描述" show-overflow-tooltip /> -->
|
||
<!-- <el-table-column prop="sort" label="排序" width="80" /> -->
|
||
<el-table-column label="是否热门" prop="hot">
|
||
<template #default="scope">
|
||
<ElTag :type="scope.row.hot == 1 ? 'danger' : 'info'">
|
||
{{ scope.row.hot == 1 ? '是' : '否' }}
|
||
</ElTag>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="状态" prop="status">
|
||
<template #default="scope">
|
||
<ElTag :type="scope.row.status ? 'success' : 'info'">
|
||
{{ scope.row.status ? '启用' : '停用' }}
|
||
</ElTag>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column fixed="right" label="操作" min-width="120" align="center">
|
||
<template #default="scope">
|
||
<el-link
|
||
type="success"
|
||
underline="never"
|
||
style="margin-right: 8px"
|
||
@click="Vehicleseries(scope.row)"
|
||
>
|
||
<i class="iconfont-sys"> </i>采集车系
|
||
</el-link>
|
||
<el-link
|
||
type="success"
|
||
underline="never"
|
||
style="margin-right: 8px"
|
||
@click="showSeriesListDialog(scope.row)"
|
||
>
|
||
<i class="iconfont-sys"></i>查看车系
|
||
</el-link>
|
||
<el-link
|
||
type="primary"
|
||
underline="never"
|
||
style="margin-right: 8px"
|
||
@click="showBrandDialog('edit', scope.row)"
|
||
v-if="hasAuth('brand@update')"
|
||
>
|
||
<i class="iconfont-sys"> </i>编辑
|
||
</el-link>
|
||
<el-link
|
||
type="danger"
|
||
underline="never"
|
||
style="margin-right: 8px"
|
||
@click="handleBrandDelete(scope.row)"
|
||
v-if="hasAuth('brand@destroy')"
|
||
>
|
||
<i class="iconfont-sys"></i>删除
|
||
</el-link>
|
||
</template>
|
||
</el-table-column>
|
||
</ArtTable>
|
||
</div>
|
||
|
||
<ElDialog
|
||
v-model="brandDialogVisible"
|
||
:title="brandDialogType === 'add' ? '新增品牌' : '编辑品牌'"
|
||
width="750px"
|
||
align-center
|
||
>
|
||
<ElForm ref="brandFormRef" :model="brandForm" :rules="brandRules" label-width="100px">
|
||
<ElFormItem label="品牌编码" prop="brand_code">
|
||
<ElInput v-model="brandForm.brand_code" />
|
||
</ElFormItem>
|
||
<ElFormItem label="品牌名称" prop="brand_name">
|
||
<ElInput v-model="brandForm.brand_name" />
|
||
</ElFormItem>
|
||
<ElFormItem label="品牌Logo" prop="logo">
|
||
<ElUpload
|
||
class="logo-uploader"
|
||
:action="actionImageUplode"
|
||
:headers="uploadHeaders"
|
||
:show-file-list="false"
|
||
:on-success="handleLogoSuccess"
|
||
:before-upload="beforeLogoUpload"
|
||
accept="image/*"
|
||
>
|
||
<img v-if="brandForm.logo" :src="logourl" class="logo-preview" />
|
||
<ElIcon v-else class="logo-uploader-icon"><Plus /></ElIcon>
|
||
</ElUpload>
|
||
</ElFormItem>
|
||
<ElFormItem label="是否热门" prop="hot">
|
||
<el-radio-group v-model="brandForm.hot">
|
||
<el-radio :label="1">是</el-radio>
|
||
<el-radio :label="0">否</el-radio>
|
||
</el-radio-group>
|
||
</ElFormItem>
|
||
<ElFormItem label="品牌描述" prop="description">
|
||
<ElInput v-model="brandForm.description" type="textarea" :rows="3" />
|
||
</ElFormItem>
|
||
<ElFormItem label="排序" prop="sort">
|
||
<ElInputNumber v-model="brandForm.sort" :min="0" :max="999" style="width: 100%" />
|
||
</ElFormItem>
|
||
<ElFormItem label="状态" prop="status">
|
||
<ElSwitch v-model="brandForm.status" :active-value="1" :inactive-value="0" />
|
||
</ElFormItem>
|
||
</ElForm>
|
||
<template #footer>
|
||
<div class="dialog-footer">
|
||
<ElButton @click="brandDialogVisible = false">取消</ElButton>
|
||
<ElButton type="primary" @click="handleBrandSubmit(brandFormRef)">提交</ElButton>
|
||
</div>
|
||
</template>
|
||
</ElDialog>
|
||
|
||
<ElDialog
|
||
v-model="seriesDialogVisible"
|
||
:title="seriesDialogType === 'add' ? '新增系列' : '编辑系列'"
|
||
width="750px"
|
||
align-center
|
||
>
|
||
<ElForm ref="seriesFormRef" :model="seriesForm" :rules="seriesRules" label-width="100px">
|
||
<ElFormItem label="所属品牌">
|
||
<ElInput
|
||
:model-value="
|
||
(currentBrand || activeBrand) ? `${(currentBrand || activeBrand).brand_name}(${(currentBrand || activeBrand).brand_code})` : ''
|
||
"
|
||
disabled
|
||
/>
|
||
</ElFormItem>
|
||
<!-- <ElFormItem label="系列编码" prop="series_code">
|
||
<ElInput v-model="seriesForm.series_code" />
|
||
</ElFormItem> -->
|
||
<ElFormItem label="系列名称" prop="series_name">
|
||
<ElInput v-model="seriesForm.series_name" />
|
||
</ElFormItem>
|
||
<ElFormItem label="系列描述" prop="description">
|
||
<ElInput v-model="seriesForm.description" type="textarea" :rows="3" />
|
||
</ElFormItem>
|
||
<ElFormItem label="排序" prop="sort">
|
||
<ElInputNumber v-model="seriesForm.sort" :min="0" :max="999" style="width: 100%" />
|
||
</ElFormItem>
|
||
<ElFormItem label="状态" prop="status">
|
||
<ElSwitch v-model="seriesForm.status" :active-value="1" :inactive-value="0" />
|
||
</ElFormItem>
|
||
</ElForm>
|
||
<template #footer>
|
||
<div class="dialog-footer">
|
||
<ElButton @click="seriesDialogVisible = false">取消</ElButton>
|
||
<ElButton type="primary" @click="handleSeriesSubmit(seriesFormRef)">提交</ElButton>
|
||
</div>
|
||
</template>
|
||
</ElDialog>
|
||
|
||
<!-- 系列列表弹出层 -->
|
||
<ElDialog
|
||
v-model="seriesListDialogVisible"
|
||
:title="`${currentBrand?.brand_name} - 系列列表`"
|
||
width="900px"
|
||
align-center
|
||
>
|
||
<div class="series-list-container">
|
||
<div class="series-header">
|
||
<div style="display: flex; align-items: center">
|
||
<ElInput
|
||
v-model="seriesListKeyword"
|
||
placeholder="搜索系列名称"
|
||
clearable
|
||
@keyup.enter="getSeriesListForDialog"
|
||
style="width: 200px"
|
||
/>
|
||
<ElButton style="margin-left: 8px" @click="getSeriesListForDialog" v-ripple>搜索</ElButton>
|
||
</div>
|
||
<ElButton
|
||
type="primary"
|
||
style="margin-left: 8px"
|
||
@click="showSeriesDialog('add')"
|
||
v-if="hasAuth('series@add')"
|
||
>新增系列</ElButton
|
||
>
|
||
</div>
|
||
<ArtTable
|
||
ref="seriesListTableRef"
|
||
row-key="id"
|
||
:loading="seriesListLoading"
|
||
:data="seriesListTableData"
|
||
:currentPage="seriesListPagination.currentPage"
|
||
:pageSize="seriesListPagination.pageSize"
|
||
:total="seriesListPagination.total"
|
||
@size-change="handleSeriesListSizeChange"
|
||
@current-change="handleSeriesListCurrentChange"
|
||
>
|
||
<el-table-column prop="series_name" label="系列名称" />
|
||
<el-table-column prop="description" label="系列描述" show-overflow-tooltip />
|
||
<el-table-column prop="sort" label="排序" width="80" />
|
||
<el-table-column label="状态" prop="status" width="100">
|
||
<template #default="scope">
|
||
<ElTag :type="scope.row.status ? 'success' : 'info'">
|
||
{{ scope.row.status ? '启用' : '停用' }}
|
||
</ElTag>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column fixed="right" label="操作" min-width="140" align="center">
|
||
<template #default="scope">
|
||
<el-link
|
||
type="success"
|
||
underline="never"
|
||
style="margin-right: 8px"
|
||
@click="collectVehicleModels(scope.row)"
|
||
v-if="hasAuth('brand@update')"
|
||
>
|
||
<i class="iconfont-sys"> </i>采集车型
|
||
</el-link>
|
||
<el-link
|
||
type="success"
|
||
underline="never"
|
||
style="margin-right: 8px"
|
||
@click="showVehicleListDialog(scope.row)"
|
||
v-if="hasAuth('brand@destroy')"
|
||
>
|
||
<i class="iconfont-sys"></i>查看车型
|
||
</el-link>
|
||
<el-link
|
||
type="primary"
|
||
underline="never"
|
||
style="margin-right: 8px"
|
||
@click="showSeriesDialog('edit', scope.row)"
|
||
v-if="hasAuth('series@update')"
|
||
>
|
||
<i class="iconfont-sys"> </i>编辑
|
||
</el-link>
|
||
<el-link
|
||
type="danger"
|
||
underline="never"
|
||
style="margin-right: 8px"
|
||
@click="handleSeriesDelete(scope.row)"
|
||
v-if="hasAuth('series@destroy')"
|
||
>
|
||
<i class="iconfont-sys"></i>删除
|
||
</el-link>
|
||
</template>
|
||
</el-table-column>
|
||
</ArtTable>
|
||
</div>
|
||
<template #footer>
|
||
<div class="dialog-footer">
|
||
<ElButton @click="seriesListDialogVisible = false">关闭</ElButton>
|
||
</div>
|
||
</template>
|
||
</ElDialog>
|
||
|
||
<!-- 车系采集弹出层 -->
|
||
<ElDialog
|
||
v-model="collectSeriesDialogVisible"
|
||
:title="`${currentCollectBrand?.brand_name} - 车系采集`"
|
||
width="900px"
|
||
align-center
|
||
>
|
||
<div class="collect-series-container">
|
||
<div class="collect-series-header">
|
||
<div style="display: flex; align-items: center">
|
||
<ElButton
|
||
type="info"
|
||
size="small"
|
||
@click="selectAllCollectSeries"
|
||
style="margin-right: 8px"
|
||
>
|
||
全选
|
||
</ElButton>
|
||
<ElButton type="warning" size="small" @click="unselectAllCollectSeries">
|
||
取消全选
|
||
</ElButton>
|
||
</div>
|
||
</div>
|
||
<div class="collect-series-content">
|
||
<div v-if="collectSeriesLoading" class="loading-container">
|
||
<ElLoading text="正在采集车系数据..." />
|
||
</div>
|
||
<div v-else-if="collectSeriesData.length === 0" class="empty-container">
|
||
<ElEmpty description="暂无车系数据" />
|
||
</div>
|
||
<div v-else class="series-grid">
|
||
<div
|
||
v-for="series in collectSeriesData"
|
||
:key="series.id || series.series_name || series"
|
||
class="series-item"
|
||
:class="{ selected: isSeriesSelected(series) }"
|
||
@click="onSeriesItemClick(series)"
|
||
>
|
||
<div class="series-checkbox">
|
||
<ElCheckbox
|
||
:model-value="isSeriesSelected(series)"
|
||
@click.stop
|
||
@change="(val:any) => setSeriesSelected(series, val)"
|
||
/>
|
||
</div>
|
||
<div class="series-info">
|
||
<div class="series-name">{{ series.series_name || series }}</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<template #footer>
|
||
<div class="dialog-footer">
|
||
<ElButton @click="collectSeriesDialogVisible = false">取消</ElButton>
|
||
<ElButton
|
||
type="primary"
|
||
@click="saveCollectSeries"
|
||
:disabled="selectedSeriesIds.length === 0"
|
||
>
|
||
保存选中车系 ({{ selectedSeriesIds.length }})
|
||
</ElButton>
|
||
</div>
|
||
</template>
|
||
</ElDialog>
|
||
|
||
<!-- 车型列表弹出层 -->
|
||
<ElDialog
|
||
v-model="vehicleListDialogVisible"
|
||
:title="`${currentVehicleBrand?.series_name} - 车型列表`"
|
||
width="1200px"
|
||
align-center
|
||
>
|
||
<div class="vehicle-list-container">
|
||
<div class="vehicle-header">
|
||
<div style="display: flex; align-items: center">
|
||
<ElInput
|
||
v-model="vehicleListKeyword"
|
||
placeholder="搜索车型名称"
|
||
clearable
|
||
@keyup.enter="getVehicleListForDialog"
|
||
style="width: 200px"
|
||
/>
|
||
<ElButton style="margin-left: 8px" @click="getVehicleListForDialog" v-ripple>搜索</ElButton>
|
||
</div>
|
||
</div>
|
||
<ArtTable
|
||
ref="vehicleListTableRef"
|
||
row-key="id"
|
||
:loading="vehicleListLoading"
|
||
:data="vehicleListTableData"
|
||
:currentPage="vehicleListPagination.currentPage"
|
||
:pageSize="vehicleListPagination.pageSize"
|
||
:total="vehicleListPagination.total"
|
||
@size-change="handleVehicleListSizeChange"
|
||
@current-change="handleVehicleListCurrentChange"
|
||
>
|
||
<el-table-column prop="model_name" label="车型名称" min-width="200" show-overflow-tooltip />
|
||
<el-table-column prop="model_year" label="年款" width="100" />
|
||
<el-table-column prop="msrp" label="指导价(CNY)" width="120">
|
||
<template #default="scope">
|
||
<span v-if="scope.row.msrp" class="price-text">
|
||
¥{{ formatPrice(scope.row.msrp) }}
|
||
</span>
|
||
<span v-else>-</span>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="状态" prop="is_active" width="100">
|
||
<template #default="scope">
|
||
<ElTag :type="scope.row.is_active ? 'success' : 'danger'">
|
||
{{ scope.row.is_active ? '启用' : '禁用' }}
|
||
</ElTag>
|
||
</template>
|
||
</el-table-column>
|
||
</ArtTable>
|
||
</div>
|
||
<template #footer>
|
||
<div class="dialog-footer">
|
||
<ElButton @click="vehicleListDialogVisible = false">关闭</ElButton>
|
||
</div>
|
||
</template>
|
||
</ElDialog>
|
||
|
||
<!-- 车型采集弹出层 -->
|
||
<ElDialog
|
||
v-model="collectModelsDialogVisible"
|
||
:title="`${currentCollectSeries?.series_name} - 车型采集`"
|
||
width="900px"
|
||
align-center
|
||
>
|
||
<div class="collect-models-container">
|
||
<div class="collect-models-header">
|
||
<div style="display: flex; align-items: center">
|
||
<ElButton
|
||
type="info"
|
||
size="small"
|
||
@click="selectAllCollectModels"
|
||
style="margin-right: 8px"
|
||
>
|
||
全选
|
||
</ElButton>
|
||
<ElButton type="warning" size="small" @click="unselectAllCollectModels">
|
||
取消全选
|
||
</ElButton>
|
||
</div>
|
||
</div>
|
||
<div class="collect-models-content">
|
||
<div v-if="collectModelsLoading" class="loading-container">
|
||
<ElLoading text="正在采集车型数据..." />
|
||
</div>
|
||
<div v-else-if="collectModelsData.length === 0" class="empty-container">
|
||
<ElEmpty description="暂无车型数据" />
|
||
</div>
|
||
<div v-else class="models-table-container">
|
||
<ElTable
|
||
:data="collectModelsData"
|
||
style="width: 100%"
|
||
max-height="400"
|
||
@selection-change="handleModelSelectionChange"
|
||
>
|
||
<ElTableColumn type="selection" width="55" />
|
||
<ElTableColumn prop="brand_name" label="品牌" width="100" />
|
||
<ElTableColumn prop="series_name" label="车系" width="120" />
|
||
<ElTableColumn prop="title" label="车型名称" min-width="200" show-overflow-tooltip />
|
||
<ElTableColumn prop="year" label="年款" width="80" />
|
||
<ElTableColumn prop="msrp" label="指导价" width="120">
|
||
<template #default="scope">
|
||
<span v-if="scope.row.msrp" class="price-text">
|
||
¥{{ formatPrice(scope.row.msrp) }}
|
||
</span>
|
||
<span v-else>-</span>
|
||
</template>
|
||
</ElTableColumn>
|
||
</ElTable>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<template #footer>
|
||
<div class="dialog-footer">
|
||
<ElButton @click="collectModelsDialogVisible = false">取消</ElButton>
|
||
<ElButton
|
||
type="primary"
|
||
@click="saveCollectModels"
|
||
:disabled="selectedModels.length === 0"
|
||
>
|
||
保存选中车型 ({{ selectedModels.length }})
|
||
</ElButton>
|
||
</div>
|
||
</template>
|
||
</ElDialog>
|
||
</div>
|
||
<div v-else class="mobile-hide-tip">
|
||
<p>该页面暂不支持移动端访问</p>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||
import type { FormInstance, FormRules } from 'element-plus'
|
||
import { Plus } from '@element-plus/icons-vue'
|
||
import { vehicleService } from '@/api/crmpc/vehicleApi'
|
||
import { useAuth } from '@/composables/useAuth'
|
||
import { useWindowSize } from '@vueuse/core'
|
||
import { useUploadHeaders } from '@/composables/useUploadHeaders'
|
||
import { ElLoading } from 'element-plus'
|
||
const { uploadHeaders } = useUploadHeaders()
|
||
let actionImageUplode = `${import.meta.env.VITE_API_URL}/ticket/upload`
|
||
const { hasAuth } = useAuth()
|
||
defineOptions({ name: 'CrmVehicleBrand' })
|
||
const { width } = useWindowSize()
|
||
const isMobile = computed(() => width.value < 500)
|
||
const logourl = ref('')
|
||
// 左侧 品牌
|
||
const brandLoading = ref(false)
|
||
const brandKeyword = ref<string>('')
|
||
const brandTableData = ref<any[]>([])
|
||
const brandPagination = reactive({ currentPage: 1, pageSize: 10, total: 0 })
|
||
const activeBrand = ref<any | null>(null)
|
||
|
||
const brandFormRef = ref<FormInstance>()
|
||
const brandDialogVisible = ref(false)
|
||
const brandDialogType = ref<'add' | 'edit'>('add')
|
||
const brandForm = reactive<any>({
|
||
id: '',
|
||
brand_code: '',
|
||
brand_name: '',
|
||
logo: '',
|
||
hot: 0,
|
||
description: '',
|
||
sort: 0,
|
||
status: 1
|
||
})
|
||
const brandRules = reactive<FormRules>({
|
||
brand_code: [{ required: true, message: '请输入品牌编码', trigger: 'blur' }],
|
||
brand_name: [{ required: true, message: '请输入品牌名称', trigger: 'blur' }]
|
||
})
|
||
|
||
const getBrandList = async () => {
|
||
brandLoading.value = true
|
||
try {
|
||
const { currentPage, pageSize } = brandPagination
|
||
const { records, current, size, total } = await vehicleService.getBrandList({
|
||
current: currentPage,
|
||
size: pageSize,
|
||
keyword: brandKeyword.value
|
||
})
|
||
brandTableData.value = records
|
||
Object.assign(brandPagination, { currentPage: current, pageSize: size, total })
|
||
if (!activeBrand.value && records && records.length) {
|
||
activeBrand.value = records[0]
|
||
await getSeriesList()
|
||
}
|
||
} finally {
|
||
brandLoading.value = false
|
||
}
|
||
}
|
||
const getBrandListseach = async () => {
|
||
brandPagination.currentPage = 1
|
||
await getBrandList()
|
||
}
|
||
const handleBrandRowClick = async (row: any) => {
|
||
activeBrand.value = row
|
||
seriesPagination.currentPage = 1
|
||
await getSeriesList()
|
||
}
|
||
const handleBrandSizeChange = (val: number) => {
|
||
brandPagination.pageSize = val
|
||
getBrandList()
|
||
}
|
||
const handleBrandCurrentChange = (val: number) => {
|
||
brandPagination.currentPage = val
|
||
getBrandList()
|
||
}
|
||
|
||
const showBrandDialog = (type: 'add' | 'edit', row?: any) => {
|
||
brandDialogVisible.value = true
|
||
brandDialogType.value = type
|
||
if (type === 'edit' && row) {
|
||
logourl.value = row.logo_full
|
||
Object.assign(brandForm, {
|
||
id: row.id,
|
||
brand_code: row.brand_code,
|
||
brand_name: row.brand_name,
|
||
hot: row.hot,
|
||
logo: row.logo,
|
||
description: row.description,
|
||
sort: row.sort ?? 0,
|
||
status: row.status ?? 1
|
||
})
|
||
} else {
|
||
Object.assign(brandForm, {
|
||
id: '',
|
||
brand_code: '',
|
||
brand_name: '',
|
||
logo: '',
|
||
hot: 0,
|
||
description: '',
|
||
sort: 0,
|
||
status: 1
|
||
})
|
||
}
|
||
}
|
||
const handleBrandDelete = (row: any) => {
|
||
ElMessageBox.confirm('确定删除该品牌吗?删除后其系列也可能被清理。', '删除确认', {
|
||
confirmButtonText: '确定',
|
||
cancelButtonText: '取消',
|
||
type: 'error'
|
||
}).then(async () => {
|
||
const { message } = await vehicleService.brandDelete({ id: row.id })
|
||
ElMessage.success(message)
|
||
if (activeBrand.value && activeBrand.value.id === row.id) {
|
||
activeBrand.value = null
|
||
seriesTableData.value = []
|
||
seriesPagination.total = 0
|
||
}
|
||
getBrandList()
|
||
})
|
||
}
|
||
const handleBrandSubmit = async (formEl: FormInstance | undefined) => {
|
||
if (!formEl) return
|
||
await formEl.validate(async (valid) => {
|
||
if (!valid) return
|
||
if (brandDialogType.value === 'add') {
|
||
const { message } = await vehicleService.brandAdd({
|
||
brand_code: brandForm.brand_code,
|
||
brand_name: brandForm.brand_name,
|
||
logo: brandForm.logo,
|
||
hot: brandForm.hot,
|
||
description: brandForm.description,
|
||
sort: brandForm.sort,
|
||
status: brandForm.status
|
||
})
|
||
ElMessage.success(message)
|
||
} else {
|
||
const { message } = await vehicleService.brandEdit(brandForm)
|
||
ElMessage.success(message)
|
||
}
|
||
brandDialogVisible.value = false
|
||
getBrandList()
|
||
})
|
||
}
|
||
|
||
// 右侧 系列
|
||
const seriesLoading = ref(false)
|
||
const seriesKeyword = ref<string>('')
|
||
const seriesTableData = ref<any[]>([])
|
||
const seriesTableRef = ref()
|
||
const seriesPagination = reactive({ currentPage: 1, pageSize: 10, total: 0 })
|
||
|
||
const seriesFormRef = ref<FormInstance>()
|
||
const seriesDialogVisible = ref(false)
|
||
const seriesDialogType = ref<'add' | 'edit'>('add')
|
||
const seriesForm = reactive<any>({
|
||
series_code: '',
|
||
series_name: '',
|
||
description: '',
|
||
sort: 0,
|
||
status: 1
|
||
})
|
||
const seriesRules = reactive<FormRules>({
|
||
series_name: [{ required: true, message: '请输入系列名称', trigger: 'blur' }]
|
||
})
|
||
|
||
// 系列列表弹出层相关
|
||
const seriesListDialogVisible = ref(false)
|
||
const currentBrand = ref<any | null>(null)
|
||
const seriesListLoading = ref(false)
|
||
const seriesListKeyword = ref<string>('')
|
||
const seriesListTableData = ref<any[]>([])
|
||
const seriesListTableRef = ref()
|
||
const seriesListPagination = reactive({ currentPage: 1, pageSize: 10, total: 0 })
|
||
|
||
// 车系采集弹出层相关
|
||
const collectSeriesDialogVisible = ref(false)
|
||
const currentCollectBrand = ref<any | null>(null)
|
||
const collectSeriesLoading = ref(false)
|
||
const collectSeriesData = ref<any[]>([])
|
||
const selectedSeriesIds = ref<string[]>([])
|
||
|
||
// 车型列表弹出层相关
|
||
const vehicleListDialogVisible = ref(false)
|
||
const currentVehicleBrand = ref<any | null>(null)
|
||
const vehicleListLoading = ref(false)
|
||
const vehicleListKeyword = ref<string>('')
|
||
const vehicleListTableData = ref<any[]>([])
|
||
const vehicleListTableRef = ref()
|
||
const vehicleListPagination = reactive({ currentPage: 1, pageSize: 10, total: 0 })
|
||
|
||
// 车型采集弹出层相关
|
||
const collectModelsDialogVisible = ref(false)
|
||
const currentCollectSeries = ref<any | null>(null)
|
||
const collectModelsLoading = ref(false)
|
||
const collectModelsData = ref<any[]>([])
|
||
const selectedModels = ref<any[]>([])
|
||
|
||
const getSeriesList = async () => {
|
||
if (!activeBrand.value) return
|
||
seriesLoading.value = true
|
||
try {
|
||
const { currentPage, pageSize } = seriesPagination
|
||
const { records, current, size, total } = await vehicleService.getSeriesList({
|
||
current: currentPage,
|
||
size: pageSize,
|
||
keyword: seriesKeyword.value,
|
||
brand_id: activeBrand.value.id
|
||
})
|
||
seriesTableData.value = records
|
||
Object.assign(seriesPagination, { currentPage: current, pageSize: size, total })
|
||
} finally {
|
||
seriesLoading.value = false
|
||
}
|
||
}
|
||
const handleSeriesSizeChange = (val: number) => {
|
||
seriesPagination.pageSize = val
|
||
getSeriesList()
|
||
}
|
||
const handleSeriesCurrentChange = (val: number) => {
|
||
seriesPagination.currentPage = val
|
||
getSeriesList()
|
||
}
|
||
const showSeriesDialog = (type: 'add' | 'edit', row?: any) => {
|
||
seriesDialogVisible.value = true
|
||
seriesDialogType.value = type
|
||
if (type === 'edit' && row) {
|
||
Object.assign(seriesForm, {
|
||
id: row.id,
|
||
series_code: row.series_code,
|
||
series_name: row.series_name,
|
||
description: row.description,
|
||
sort: row.sort ?? 0,
|
||
status: row.status ?? 1
|
||
})
|
||
} else {
|
||
Object.assign(seriesForm, {
|
||
id: '',
|
||
series_code: '',
|
||
series_name: '',
|
||
description: '',
|
||
sort: 0,
|
||
status: 1
|
||
})
|
||
}
|
||
}
|
||
const handleSeriesDelete = (row: any) => {
|
||
ElMessageBox.confirm('确定删除该系列吗?', '删除确认', {
|
||
confirmButtonText: '确定',
|
||
cancelButtonText: '取消',
|
||
type: 'error'
|
||
}).then(async () => {
|
||
const { message } = await vehicleService.seriesDelete({ id: row.id })
|
||
ElMessage.success(message)
|
||
getSeriesList()
|
||
// 如果系列列表弹出层打开,也要刷新弹出层的数据
|
||
if (seriesListDialogVisible.value) {
|
||
getSeriesListForDialog()
|
||
}
|
||
})
|
||
}
|
||
const handleSeriesSubmit = async (formEl: FormInstance | undefined) => {
|
||
if (!formEl) return
|
||
// 优先使用当前品牌(弹出层中的品牌),否则使用活跃品牌
|
||
const brandId = currentBrand.value?.id || activeBrand.value?.id
|
||
if (!brandId) return
|
||
await formEl.validate(async (valid) => {
|
||
if (!valid) return
|
||
if (seriesDialogType.value === 'add') {
|
||
const { message } = await vehicleService.seriesAdd({
|
||
brand_id: brandId,
|
||
series_name: seriesForm.series_name,
|
||
description: seriesForm.description,
|
||
sort: seriesForm.sort,
|
||
status: seriesForm.status
|
||
})
|
||
ElMessage.success(message)
|
||
} else {
|
||
const { message } = await vehicleService.seriesEdit({
|
||
id: seriesForm.id,
|
||
brand_id: brandId,
|
||
series_name: seriesForm.series_name,
|
||
description: seriesForm.description,
|
||
sort: seriesForm.sort,
|
||
status: seriesForm.status
|
||
})
|
||
ElMessage.success(message)
|
||
}
|
||
seriesDialogVisible.value = false
|
||
getSeriesList()
|
||
// 如果系列列表弹出层打开,也要刷新弹出层的数据
|
||
if (seriesListDialogVisible.value) {
|
||
getSeriesListForDialog()
|
||
}
|
||
})
|
||
}
|
||
|
||
// 显示系列列表弹出层
|
||
const showSeriesListDialog = async (brand: any) => {
|
||
currentBrand.value = brand
|
||
seriesListDialogVisible.value = true
|
||
seriesListPagination.currentPage = 1
|
||
seriesListKeyword.value = ''
|
||
await getSeriesListForDialog()
|
||
}
|
||
|
||
// 获取系列列表数据(用于弹出层)
|
||
const getSeriesListForDialog = async () => {
|
||
if (!currentBrand.value) return
|
||
seriesListLoading.value = true
|
||
try {
|
||
const { currentPage, pageSize } = seriesListPagination
|
||
const { records, current, size, total } = await vehicleService.getSeriesList({
|
||
current: currentPage,
|
||
size: pageSize,
|
||
keyword: seriesListKeyword.value,
|
||
brand_id: currentBrand.value.id
|
||
})
|
||
seriesListTableData.value = records
|
||
Object.assign(seriesListPagination, { currentPage: current, pageSize: size, total })
|
||
} finally {
|
||
seriesListLoading.value = false
|
||
}
|
||
}
|
||
|
||
// 系列列表分页处理
|
||
const handleSeriesListSizeChange = (val: number) => {
|
||
seriesListPagination.pageSize = val
|
||
getSeriesListForDialog()
|
||
}
|
||
const handleSeriesListCurrentChange = (val: number) => {
|
||
seriesListPagination.currentPage = val
|
||
getSeriesListForDialog()
|
||
}
|
||
|
||
// 车型列表弹出层相关方法
|
||
const showVehicleListDialog = async (brand: any) => {
|
||
currentVehicleBrand.value = brand
|
||
vehicleListDialogVisible.value = true
|
||
vehicleListPagination.currentPage = 1
|
||
vehicleListKeyword.value = ''
|
||
await getVehicleListForDialog()
|
||
}
|
||
|
||
// 获取车型列表数据(用于弹出层)
|
||
const getVehicleListForDialog = async () => {
|
||
if (!currentVehicleBrand.value) return
|
||
vehicleListLoading.value = true
|
||
try {
|
||
const { currentPage, pageSize } = vehicleListPagination
|
||
const { records, current, size, total } = await vehicleService.getVehicleList({
|
||
current: currentPage,
|
||
size: pageSize,
|
||
keyword: vehicleListKeyword.value,
|
||
series_id: currentVehicleBrand.value.id
|
||
})
|
||
vehicleListTableData.value = records
|
||
Object.assign(vehicleListPagination, { currentPage: current, pageSize: size, total })
|
||
} finally {
|
||
vehicleListLoading.value = false
|
||
}
|
||
}
|
||
|
||
// 车型列表分页处理
|
||
const handleVehicleListSizeChange = (val: number) => {
|
||
vehicleListPagination.pageSize = val
|
||
getVehicleListForDialog()
|
||
}
|
||
const handleVehicleListCurrentChange = (val: number) => {
|
||
vehicleListPagination.currentPage = val
|
||
getVehicleListForDialog()
|
||
}
|
||
|
||
// Logo上传处理collectVehicleSeries
|
||
const handleLogoSuccess = (response: any) => {
|
||
brandForm.logo = response.url || (response.data && response.data.file_name) || ''
|
||
// 如需同步到 form.picture
|
||
logourl.value = response.url || (response.data && response.data.file_url) || ''
|
||
ElMessage.success('Logo上传成功')
|
||
}
|
||
|
||
const beforeLogoUpload = (file: File) => {
|
||
const isImage = file.type.startsWith('image/')
|
||
const isLt2M = file.size / 1024 / 1024 < 2
|
||
|
||
if (!isImage) {
|
||
ElMessage.error('只能上传图片文件!')
|
||
return false
|
||
}
|
||
if (!isLt2M) {
|
||
ElMessage.error('图片大小不能超过 2MB!')
|
||
return false
|
||
}
|
||
return true
|
||
}
|
||
// 车系采集
|
||
const Vehicleseries = async (row: any) => {
|
||
try {
|
||
currentCollectBrand.value = row
|
||
collectSeriesDialogVisible.value = true
|
||
collectSeriesLoading.value = true
|
||
collectSeriesData.value = []
|
||
selectedSeriesIds.value = []
|
||
// 调用后端接口获取车系数据
|
||
const response = await vehicleService.collectVehicleSeries({ brand_id: row.id })
|
||
// 假设后端返回的数据格式为 { data: [...] } 或直接是数组
|
||
collectSeriesData.value = response.series || response || []
|
||
ElMessage.success(`成功采集到 ${collectSeriesData.value.length} 个车系`)
|
||
} catch (error: any) {
|
||
ElMessage.error(error.message || '车系采集失败')
|
||
} finally {
|
||
collectSeriesLoading.value = false
|
||
}
|
||
}
|
||
|
||
// 车系采集相关方法
|
||
const getSeriesKey = (series: any) => (series && (series.id ?? series.series_name)) ?? series
|
||
const isSeriesSelected = (series: any) => selectedSeriesIds.value.includes(getSeriesKey(series))
|
||
const setSeriesSelected = (series: any, checked: boolean) => {
|
||
const key = getSeriesKey(series)
|
||
const index = selectedSeriesIds.value.indexOf(key)
|
||
if (checked) {
|
||
if (index === -1) selectedSeriesIds.value.push(key)
|
||
} else {
|
||
if (index > -1) selectedSeriesIds.value.splice(index, 1)
|
||
}
|
||
}
|
||
const onSeriesItemClick = (series: any) => {
|
||
setSeriesSelected(series, !isSeriesSelected(series))
|
||
}
|
||
|
||
const selectAllCollectSeries = () => {
|
||
selectedSeriesIds.value = collectSeriesData.value.map((series) => getSeriesKey(series))
|
||
}
|
||
|
||
const unselectAllCollectSeries = () => {
|
||
selectedSeriesIds.value = []
|
||
}
|
||
|
||
const saveCollectSeries = async () => {
|
||
if (!currentCollectBrand.value || selectedSeriesIds.value.length === 0) {
|
||
ElMessage.warning('请选择要保存的车系')
|
||
return
|
||
}
|
||
|
||
try {
|
||
// 支持后端返回为字符串数组或对象数组两种格式
|
||
const selectedSeriesRaw = collectSeriesData.value.filter((series: any) => {
|
||
const key = (series && (series.id ?? series.series_name)) ?? series
|
||
return selectedSeriesIds.value.includes(key)
|
||
})
|
||
// const selectedSeries = selectedSeriesRaw.map((item: any) =>
|
||
// typeof item === 'string' ? { series_name: item } : item
|
||
// )
|
||
await vehicleService.saveVehicleSeries({
|
||
brand_id: currentCollectBrand.value.id,
|
||
series: selectedSeriesRaw
|
||
})
|
||
ElMessage.success(`成功保存 ${selectedSeriesIds.value.length} 个车系`)
|
||
collectSeriesDialogVisible.value = false
|
||
// 刷新品牌列表和系列列表
|
||
await getBrandList()
|
||
if (activeBrand.value && activeBrand.value.id === currentCollectBrand.value.id) {
|
||
await getSeriesList()
|
||
}
|
||
} catch (error: any) {
|
||
ElMessage.error(error.message || '保存车系失败')
|
||
}
|
||
}
|
||
|
||
// 车型采集相关方法
|
||
const collectVehicleModels = async (series: any) => {
|
||
try {
|
||
currentCollectSeries.value = series
|
||
collectModelsDialogVisible.value = true
|
||
collectModelsData.value = []
|
||
selectedModels.value = []
|
||
|
||
collectModelsLoading.value = true
|
||
|
||
// 调用后端接口获取车型数据
|
||
const response = await vehicleService.collectVehicleModels({ brand_series_id: series.id })
|
||
|
||
// 后端返回的数据格式为数组
|
||
collectModelsData.value = response || []
|
||
|
||
ElMessage.success(`成功采集到 ${collectModelsData.value.length} 个车型`)
|
||
} catch (error: any) {
|
||
ElMessage.error(error.message || '车型采集失败')
|
||
} finally {
|
||
collectModelsLoading.value = false
|
||
}
|
||
}
|
||
|
||
const handleModelSelectionChange = (selection: any[]) => {
|
||
selectedModels.value = selection
|
||
}
|
||
|
||
const selectAllCollectModels = () => {
|
||
// 表格全选通过ref调用
|
||
const tableRef = document.querySelector('.models-table-container .el-table')
|
||
if (tableRef) {
|
||
// 触发全选
|
||
const checkboxes = tableRef.querySelectorAll('thead .el-checkbox')
|
||
if (checkboxes.length > 0) {
|
||
(checkboxes[0] as HTMLElement).click()
|
||
}
|
||
}
|
||
}
|
||
|
||
const unselectAllCollectModels = () => {
|
||
selectedModels.value = []
|
||
// 清空表格选择
|
||
const tableRef = document.querySelector('.models-table-container .el-table')
|
||
if (tableRef) {
|
||
const checkboxes = tableRef.querySelectorAll('tbody .el-checkbox')
|
||
checkboxes.forEach((checkbox: any) => {
|
||
if (checkbox.querySelector('input')?.checked) {
|
||
checkbox.click()
|
||
}
|
||
})
|
||
}
|
||
}
|
||
|
||
const saveCollectModels = async () => {
|
||
if (!currentCollectSeries.value || selectedModels.value.length === 0) {
|
||
ElMessage.warning('请选择要保存的车型')
|
||
return
|
||
}
|
||
|
||
try {
|
||
await vehicleService.saveVehicleModels({
|
||
series_id: currentCollectSeries.value.id,
|
||
models: selectedModels.value
|
||
})
|
||
|
||
ElMessage.success(`成功保存 ${selectedModels.value.length} 个车型`)
|
||
collectModelsDialogVisible.value = false
|
||
|
||
// 刷新品牌列表和系列列表
|
||
await getBrandList()
|
||
if (activeBrand.value) {
|
||
await getSeriesList()
|
||
}
|
||
} catch (error: any) {
|
||
ElMessage.error(error.message || '保存车型失败')
|
||
}
|
||
}
|
||
|
||
// 格式化价格显示
|
||
const formatPrice = (price: number | string) => {
|
||
if (!price) return '0'
|
||
const numPrice = typeof price === 'string' ? parseFloat(price) : price
|
||
return numPrice.toLocaleString()
|
||
}
|
||
|
||
onMounted(async () => {
|
||
await getBrandList()
|
||
})
|
||
</script>
|
||
|
||
<style lang="scss" scoped>
|
||
:deep(.el-table__empty-block) {
|
||
height: 100% !important;
|
||
min-height: 200px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
.dict-layout {
|
||
.dict-type-card,
|
||
.dict-item-card {
|
||
background: var(--el-bg-color);
|
||
padding: 12px;
|
||
border-radius: 8px;
|
||
}
|
||
.card-header {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
margin-bottom: 20px;
|
||
.left {
|
||
font-weight: 500;
|
||
}
|
||
.right {
|
||
display: flex;
|
||
align-items: center;
|
||
}
|
||
.muted {
|
||
color: var(--el-text-color-secondary);
|
||
}
|
||
}
|
||
.type-pagination {
|
||
margin-top: 8px;
|
||
text-align: center;
|
||
}
|
||
}
|
||
|
||
.brand-logo-cell {
|
||
display: flex;
|
||
//justify-content: center;
|
||
align-items: center;
|
||
|
||
img {
|
||
width: 40px;
|
||
height: 40px;
|
||
object-fit: cover;
|
||
border-radius: 4px;
|
||
}
|
||
|
||
.logo-placeholder {
|
||
width: 40px;
|
||
height: 40px;
|
||
background: var(--el-color-primary-light-8);
|
||
color: var(--el-color-primary);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-size: 16px;
|
||
font-weight: bold;
|
||
border-radius: 4px;
|
||
}
|
||
}
|
||
|
||
.logo-uploader {
|
||
:deep(.el-upload) {
|
||
border: 1px dashed var(--el-border-color);
|
||
border-radius: 6px;
|
||
cursor: pointer;
|
||
position: relative;
|
||
overflow: hidden;
|
||
transition: var(--el-transition-duration-fast);
|
||
|
||
&:hover {
|
||
border-color: var(--el-color-primary);
|
||
}
|
||
}
|
||
}
|
||
|
||
.logo-uploader-icon {
|
||
font-size: 28px;
|
||
color: #8c939d;
|
||
width: 100px;
|
||
height: 100px;
|
||
text-align: center;
|
||
line-height: 100px;
|
||
}
|
||
|
||
.logo-preview {
|
||
width: 100px;
|
||
height: 100px;
|
||
display: block;
|
||
object-fit: cover;
|
||
}
|
||
|
||
.dialog-footer {
|
||
text-align: right;
|
||
}
|
||
|
||
.series-list-container {
|
||
background: transparent;
|
||
.series-header {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
margin-bottom: 20px;
|
||
}
|
||
:deep(.art-table) {
|
||
background: transparent;
|
||
.el-table {
|
||
background: transparent;
|
||
}
|
||
.el-table tr {
|
||
background-color: transparent;
|
||
}
|
||
.el-table th.el-table__cell {
|
||
background-color: transparent;
|
||
}
|
||
.el-table__header-wrapper {
|
||
background: transparent;
|
||
}
|
||
.el-table__body-wrapper {
|
||
background: transparent;
|
||
}
|
||
.el-table__empty-block {
|
||
background: transparent;
|
||
}
|
||
}
|
||
}
|
||
|
||
.collect-series-container {
|
||
background: transparent;
|
||
|
||
.collect-series-header {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: flex-end;
|
||
margin-bottom: 20px;
|
||
padding-bottom: 15px;
|
||
border-bottom: 1px solid var(--el-border-color-lighter);
|
||
}
|
||
|
||
.collect-series-content {
|
||
min-height: 400px;
|
||
|
||
.loading-container {
|
||
display: flex;
|
||
justify-content: center;
|
||
align-items: center;
|
||
height: 400px;
|
||
}
|
||
|
||
.empty-container {
|
||
display: flex;
|
||
justify-content: center;
|
||
align-items: center;
|
||
height: 400px;
|
||
}
|
||
|
||
.series-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
|
||
gap: 12px;
|
||
max-height: 500px;
|
||
overflow-y: auto;
|
||
padding: 8px;
|
||
|
||
.series-item {
|
||
display: flex;
|
||
align-items: center;
|
||
padding:5px 12px;
|
||
border: 1px solid var(--el-border-color-lighter);
|
||
border-radius: 8px;
|
||
cursor: pointer;
|
||
transition: all 0.3s ease;
|
||
background: var(--el-bg-color);
|
||
|
||
&:hover {
|
||
border-color: var(--el-color-primary);
|
||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||
}
|
||
|
||
&.selected {
|
||
border-color: var(--el-color-primary);
|
||
background: var(--el-color-primary-light-9);
|
||
}
|
||
|
||
.series-checkbox {
|
||
margin-right: 12px;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.series-info {
|
||
flex: 1;
|
||
min-width: 0;
|
||
|
||
.series-name {
|
||
font-weight: 500;
|
||
color: var(--el-text-color-primary);
|
||
margin-bottom: 4px;
|
||
white-space: nowrap;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
}
|
||
|
||
.series-description {
|
||
font-size: 12px;
|
||
color: var(--el-text-color-secondary);
|
||
margin-bottom: 2px;
|
||
white-space: nowrap;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
}
|
||
|
||
.series-code {
|
||
font-size: 12px;
|
||
color: var(--el-text-color-placeholder);
|
||
white-space: nowrap;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
.collect-models-container {
|
||
background: transparent;
|
||
|
||
.collect-models-header {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: flex-end;
|
||
margin-bottom: 20px;
|
||
padding-bottom: 15px;
|
||
border-bottom: 1px solid var(--el-border-color-lighter);
|
||
}
|
||
|
||
.collect-models-content {
|
||
min-height: 400px;
|
||
|
||
.loading-container {
|
||
display: flex;
|
||
justify-content: center;
|
||
align-items: center;
|
||
height: 400px;
|
||
}
|
||
|
||
.empty-container {
|
||
display: flex;
|
||
justify-content: center;
|
||
align-items: center;
|
||
height: 400px;
|
||
}
|
||
|
||
.models-table-container {
|
||
.price-text {
|
||
color: var(--el-color-danger);
|
||
font-weight: 500;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// 车型列表弹出层样式
|
||
.vehicle-list-container {
|
||
.vehicle-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: 16px;
|
||
padding-bottom: 12px;
|
||
border-bottom: 1px solid var(--el-border-color-lighter);
|
||
}
|
||
|
||
.price-text {
|
||
color: var(--el-color-danger);
|
||
font-weight: 500;
|
||
}
|
||
}
|
||
</style>
|