Files
art-design/src/views/crmpc/vehicle/brand.vue
T

1332 lines
43 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div class="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"> &#xe7d1;</i>采集车系
</el-link>
<el-link
type="success"
underline="never"
style="margin-right: 8px"
@click="showSeriesListDialog(scope.row)"
>
<i class="iconfont-sys">&#xe7ff;</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"> &#xe6bf;</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">&#xe6d4;</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"> &#xe7d1;</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">&#xe7ff;</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"> &#xe6bf;</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">&#xe6d4;</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>