添加 采集 修改车辆信息页面 备货单页面调整 审核菜单徽标

This commit is contained in:
浅念
2025-10-12 17:14:32 +08:00
parent dee800f90f
commit 0b89bade20
35 changed files with 5848 additions and 543 deletions
+22 -1
View File
@@ -425,7 +425,13 @@
// const businessUsers = ref([])
// 选项缓存,避免重复请求
const optionsCache = reactive({ role: [], user: [], department: [] })
const optionsCache = reactive({
role: [],
user: [],
department: [],
grouplist: [],
group_members: []
})
// 懒加载并缓存不同类型的选项
const ensureOptionsLoaded = async (group) => {
@@ -449,6 +455,21 @@
optionsCache.user = response || []
}
group.options = optionsCache.user
} else if (type === 'scenario_leader_of_initiator') {
if (!optionsCache.grouplist || optionsCache.grouplist.length === 0) {
const response = await commonService.getUserGroupScene()
optionsCache.grouplist = response || []
}
group.options = optionsCache.grouplist
} else if (type === 'group_members') {
if (!optionsCache.group_members || optionsCache.group_members.length === 0) {
const response = await commonService.getUserGroupSelect()
response.forEach((group) => {
group.name = group.title
})
optionsCache.group_members = response || []
}
group.options = optionsCache.group_members
} else {
group.options = []
}
+29 -3
View File
@@ -453,6 +453,21 @@
optionsCache.user = response || []
}
group.options = optionsCache.user
} else if (type === 'scenario_leader_of_initiator') {
if (!optionsCache.grouplist || optionsCache.grouplist.length === 0) {
const response = await commonService.getUserGroupScene()
optionsCache.grouplist = response || []
}
group.options = optionsCache.grouplist
} else if (type === 'group_members') {
if (!optionsCache.group_members || optionsCache.group_members.length === 0) {
const response = await commonService.getUserGroupSelect()
response.forEach((group) => {
group.name = group.title
})
optionsCache.group_members = response || []
}
group.options = optionsCache.group_members
} else {
group.options = []
}
@@ -590,10 +605,21 @@
if (ap.type === 'initiator_leader') {
group.users = []
} else if (ap.value) {
group.users = String(ap.value)
const values = String(ap.value)
.split(',')
.map((v) => Number((v || '').trim()))
.filter((n) => !Number.isNaN(n))
.map((v) => (v || '').trim())
.filter(Boolean)
// 判断是否为纯数字ID
const isNumericIds = values.every(
(v) => !Number.isNaN(Number(v)) && Number.isInteger(Number(v))
)
if (isNumericIds) {
// 如果是数字ID,转换为数字数组
group.users = values.map((v) => Number(v))
} else {
// 如果是字符串名称,保持为字符串数组
group.users = values
}
}
node.approverGroups.push(group)
}
+68 -22
View File
@@ -29,7 +29,13 @@
@current-change="handleCurrentChange"
>
<template #default>
<ElTableColumn label="审批单名称" prop="flows_name" min-width="140" />
<ElTableColumn label="审批单名称" prop="flows_name" min-width="140">
<template #default="scope">
<span :style="{ color: getFlowTypeColor(scope.row.flow_type) }">
{{ scope.row.flows_name }}
</span>
</template>
</ElTableColumn>
<ElTableColumn label="审批说明" prop="biz_desc" min-width="160" />
<ElTableColumn label="提交人" prop="initiator_name" min-width="120">
<template #default="scope">
@@ -79,7 +85,9 @@
>
<div class="approval-content">
<!-- 头部信息 -->
<div v-if="orderDialog.type == 'stock_orders'">
<div
v-if="orderDialog.type == 'stock_orders' || orderDialog.type == 'stock_orders_nullify'"
>
<OrderDetailContent :orderData="orderDialog.orderData" />
</div>
<div v-if="orderDialog.type == 'purchase_orders'">
@@ -114,7 +122,12 @@
<ElButton type="primary" size="large" class="submit-btn" @click="handleApprove('approve')"
>同意</ElButton
>
<ElButton type="warning" size="large" class="submit-btn" @click="handleTransfer()"
<ElButton
v-if="orderDialog.type == 'purchase_orders'"
type="warning"
size="large"
class="submit-btn"
@click="handleTransfer()"
>转交</ElButton
>
</div>
@@ -178,6 +191,26 @@
userOptions.value = res || []
// return res || []
}
// 根据 flow_type 获取审批单名称的字体颜色
const getFlowTypeColor = (flowType: string): string => {
switch (flowType) {
case 'stock_orders':
return '#409EFF' // 蓝色 - 备货单
case 'stock_orders_nullify':
return '#F56C6C' // 红色 - 备货单作废
case 'purchase_orders':
return '#67C23A' // 绿色 - 采购单
case 'purchase_orders_nullify':
return '#E6A23C' // 橙色 - 采购单作废
case 'return_orders':
return '#909399' // 灰色 - 退货单
case 'refund_orders':
return '#9C27B0' // 紫色 - 退款单
default:
return '#303133' // 默认黑色
}
}
const zjForm = reactive({
to_uid: '',
instance_id: '',
@@ -258,25 +291,38 @@
id: row.id,
flow_type: row.flow_type
})
if (row.flow_type == 'stock_orders') {
const orderData: OrderData = res
orderDialog.orderData = orderData
orderDialog.type = row.flow_type
orderDialog.title = '合塑实业-汽车事业部-备货单(客户下定)'
} else if (row.flow_type == 'purchase_orders') {
const orderData: OrderData = res
const urls = res.contract_file_url
const paths = res.contract_file
const list: Array<{ name: string; url: string }> = []
const nameFromPath = (p: string) => extractFileName(p)
urls.forEach((u: string, i: number) => {
const p = paths[i] || ''
list.push({ name: p ? nameFromPath(p) : extractFileName(u), url: u })
})
orderData.contract_file_url = list
orderDialog.orderData = orderData
orderDialog.type = row.flow_type
orderDialog.title = '合塑实业-汽车事业部-采购单(采购)'
console.log(res)
switch (row.flow_type) {
case 'stock_orders': {
const orderData: OrderData = res
orderDialog.orderData = orderData
orderDialog.type = row.flow_type
orderDialog.title = '合塑实业-汽车事业部-备货单(客户下定)'
break
}
case 'stock_orders_nullify': {
const orderData: OrderData = res
orderDialog.orderData = orderData
orderDialog.type = row.flow_type
orderDialog.title = '备货单-作废'
break
}
case 'purchase_orders': {
const orderData: OrderData = res
const urls = res.contract_file_url
const paths = res.contract_file
const list: Array<{ name: string; url: string }> = []
const nameFromPath = (p: string) => extractFileName(p)
urls.forEach((u: string, i: number) => {
const p = paths[i] || ''
list.push({ name: p ? nameFromPath(p) : extractFileName(u), url: u })
})
orderData.contract_file_url = list
orderDialog.orderData = orderData
orderDialog.type = row.flow_type
orderDialog.title = '合塑实业-汽车事业部-采购单(采购)'
break
}
}
approvalDialogVisible.value = true
}
@@ -98,13 +98,23 @@
<div class="form-card" v-if="status == 'pending'">
<!-- 底部信息 -->
<div class="submit-wrap">
<ElButton type="primary" plain size="large" class="submit-btn" @click="handleSubmit('reject')"
<ElButton
type="primary"
plain
size="large"
class="submit-btn"
@click="handleSubmit('reject')"
>驳回</ElButton
>
<ElButton type="primary" size="large" class="submit-btn" @click="handleSubmit('approve')"
>同意</ElButton
>
<ElButton type="warning" size="large" class="submit-btn" @click="handleTransfer()"
<ElButton
v-if="flow_type == 'purchase_orders'"
type="warning"
size="large"
class="submit-btn"
@click="handleTransfer()"
>转交</ElButton
>
</div>
@@ -32,7 +32,13 @@
@current-change="handleCurrentChange"
>
<template #default>
<ElTableColumn label="审批单名称" prop="flows_name" min-width="140" />
<ElTableColumn label="审批单名称" prop="flows_name" min-width="140">
<template #default="scope">
<span :style="{ color: getFlowTypeColor(scope.row.flow_type) }">
{{ scope.row.flows_name }}
</span>
</template>
</ElTableColumn>
<ElTableColumn label="审批说明" prop="biz_desc" min-width="160" />
<ElTableColumn label="审批结果" prop="action_desc" min-width="160" />
<ElTableColumn label="提交人" prop="initiator_name" min-width="120">
@@ -158,7 +164,25 @@
onMounted(() => {
getList()
})
// 根据 flow_type 获取审批单名称的字体颜色
const getFlowTypeColor = (flowType: string): string => {
switch (flowType) {
case 'stock_orders':
return '#409EFF' // 蓝色 - 备货单
case 'stock_orders_nullify':
return '#F56C6C' // 红色 - 备货单作废
case 'purchase_orders':
return '#67C23A' // 绿色 - 采购单
case 'purchase_orders_nullify':
return '#E6A23C' // 橙色 - 采购单作废
case 'return_orders':
return '#909399' // 灰色 - 退货单
case 'refund_orders':
return '#9C27B0' // 紫色 - 退款单
default:
return '#303133' // 默认黑色
}
}
watch([keyword], () => {
pagination.currentPage = 1
// 不自动请求,保持与截图行为一致,点击搜索或回车触发
@@ -221,7 +221,7 @@
getList()
})
function statusClass(status: any) {
if (status === 'rejected') return 'status-lost'
if (status === 'rejected' || status === 'voided') return 'status-lost'
if (status === 'approved') return 'status-following'
return 'status-dealed'
}
+281
View File
@@ -0,0 +1,281 @@
<template>
<div class="page-content">
<div v-if="!isMobile">
<ElRow>
<div style="width: 12px"></div>
<ElCol :xs="24" :sm="12" :lg="6" class="el-col2">
<ElButton @click="showDialog('add')" v-ripple v-if="hasAuth('group@add')">新增</ElButton>
</ElCol>
</ElRow>
<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="title" width="160px" />
<ElTableColumn label="负责人" prop="id" width="120px">
<template #default="scope">
<ElTag>{{ scope.row.leader_name }}</ElTag>
</template>
</ElTableColumn>
<ElTableColumn label="分组成员" prop="id">
<template #default="scope">
<span v-for="item in scope.row.user" :key="item.id" style="margin-right: 10px;"> {{ item.name }}</span>
</template>
</ElTableColumn>
<ElTableColumn label="创建时间" prop="created_at" width="160px" />
<ElTableColumn fixed="right" label="操作" width="110px">
<template #default="scope">
<ElRow>
<el-button
link
@click="showDialog('edit', scope.row)"
v-if="hasAuth('group@update')"
>
编辑
</el-button>
<el-button
type="danger"
link
@click="deleteRole(scope.row)"
v-if="hasAuth('group@destroy')"
>
删除
</el-button>
</ElRow>
</template>
</ElTableColumn>
</template>
</ArtTable>
<ElDialog
v-model="dialogVisible"
:title="dialogType === 'add' ? '新增' : '编辑'"
width="40%"
align-center
>
<ElForm ref="formRef" :model="form" :rules="rules" label-width="80px" class="form">
<ElFormItem label="分组名称" prop="title">
<ElInput v-model="form.title" />
</ElFormItem>
<ElFormItem label="分组成员" prop="user_ids">
<el-select
v-model="form.user_ids"
multiple
filterable
placeholder="请选择分组成员"
style="width: 100%"
>
<el-option
v-for="item in companyUserList"
:key="item.id"
:label="item.name"
:value="item.id"
/>
</el-select>
</ElFormItem>
<ElFormItem label="负责人" prop="leader_id">
<el-select
v-model="form.leader_id"
filterable
placeholder="请选择负责人"
style="width: 100%"
>
<el-option
v-for="item in companyUserList"
:key="item.id"
:label="item.name"
:value="item.id"
/>
</el-select>
</ElFormItem>
<ElFormItem label="业务类型" prop="scenario_code">
<ElInput v-model="form.scenario_code" />
</ElFormItem>
<ElFormItem label="排序" prop="sort">
<ElInputNumber v-model="form.sort" />
</ElFormItem>
<ElFormItem label="状态">
<ElSwitch v-model="form.status" :active-value="1" :inactive-value="0" />
</ElFormItem>
</ElForm>
<template #footer>
<div class="dialog-footer">
<ElButton @click="dialogVisible = false">取消</ElButton>
<ElButton type="primary" @click="handleSubmit(formRef)">提交</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 { userConService } from '@/api/admin/user'
import { useAuth } from '@/composables/useAuth'
import { manageService } from '@/api/adminApi'
const { hasAuth } = useAuth()
defineOptions({ name: 'Company' })
const loading = ref(false)
const dialogVisible = ref(false)
const formRef = ref<FormInstance>()
const { width } = useWindowSize()
const isMobile = computed(() => width.value < 500)
const rules = reactive<FormRules>({
title: [{ required: true, message: '请输入分组名称', trigger: 'blur' }],
user_ids: [{ required: true, message: '请选择分组成员', trigger: 'blur' }]
// tenant_key: [{ required: true, message: '请输入飞书标识', trigger: 'blur' }]
})
const companyUserList = ref<any>()
// 获取用户列表
const getCompanyUserList = async () => {
const response = await manageService.presetusers()
companyUserList.value = response || []
}
const form = reactive<any>({
title: '',
user_ids: [],
leader_id: '',
scenario_code: '',
sort: 0,
status: 1
})
const pagination = reactive({
currentPage: 1,
pageSize: 10,
total: 0
})
// 表格数据
const tableData = ref<any[]>([])
// 表格实例引用
const tableRef = ref()
onMounted(() => {
getWorkList()
})
// 处理表格分页变化
const handleSizeChange = (newPageSize: number) => {
pagination.pageSize = newPageSize
getWorkList()
}
const handleCurrentChange = (newCurrentPage: number) => {
pagination.currentPage = newCurrentPage
getWorkList()
}
// 获取列表数据
const getWorkList = async () => {
loading.value = true
try {
const { currentPage, pageSize } = pagination
const { records, current, size, total } = await userConService.getGroupList({
current: currentPage,
size: pageSize
})
tableData.value = records.map((item: any) => ({
...item
}))
Object.assign(pagination, { currentPage: current, pageSize: size, total })
} catch (error) {
console.error('获取列表失败:', error)
} finally {
loading.value = false
}
}
const dialogType = ref('add')
const showDialog = (type: string, row?: any) => {
getCompanyUserList()
dialogVisible.value = true
dialogType.value = type
if (type === 'edit' && row) {
form.title = row.title
form.id = row.id
form.leader_id = row.leader_id
form.scenario_code = row.scenario_code
form.user_ids = row.user.map((item: any) => item.id)
form.sort = row.sort
console.log(row.user_ids)
console.log(form.user_ids)
form.status = row.status
// form.tenant_key = row.tenant_key
} else {
form.title = ''
form.id = ''
form.scenario_code = ''
form.sort = 0
form.leader_id = ''
form.user_ids = []
form.status = 1
// form.tenant_key = ''
}
}
const deleteRole = (row?: any) => {
ElMessageBox.confirm('确定删除吗?', '删除确认', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'error'
}).then(async () => {
const { message } = await userConService.deleteGroup({
id: row.id
})
ElMessage.success(message)
getWorkList()
})
}
const handleSubmit = async (formEl: FormInstance | undefined) => {
if (!formEl) return
if (dialogType.value === 'add') {
const { message } = await userConService.addGroup({
title: form.title,
user_ids: form.user_ids,
leader_id: form.leader_id,
sort: form.sort,
scenario_code: form.scenario_code,
status: form.status
})
ElMessage.success(message)
dialogVisible.value = false
getWorkList()
} else {
const { message } = await userConService.editGroup(form)
ElMessage.success(message)
dialogVisible.value = false
getWorkList()
}
}
</script>
<style lang="scss" scoped>
:deep(.el-table__empty-block) {
height: 100% !important;
min-height: 200px;
display: flex;
align-items: center;
justify-content: center;
}
.page-content {
.svg-icon {
width: 1.8em;
height: 1.8em;
overflow: hidden;
vertical-align: -8px;
fill: currentcolor;
}
}
</style>
File diff suppressed because it is too large Load Diff
+540 -66
View File
@@ -3,25 +3,46 @@
<!-- 搜索工具栏 -->
<div class="search-toolbar boxshadow">
<div class="toolbar-left">
<ElButton type="primary" v-ripple @click="handleAdd">新增车辆</ElButton>
</div>
<div class="toolbar-right">
<el-input
v-model="searchForm.keyword"
placeholder="请输入关键字"
clearable
style="width: 300px"
@keyup.enter="getList"
>
<template #suffix>
<el-icon class="clickable" @click="getList"><Search /></el-icon>
</template>
</el-input>
<el-form-item label="关键字:" label-width="70px">
<el-input
v-model="searchForm.keyword"
placeholder="请输入关键字"
clearable
@keyup.enter="getList"
>
</el-input>
</el-form-item>
<el-form-item label="品牌:" label-width="60px">
<ElSelect v-model="searchForm.brand_id" filterable placeholder="请选择品牌">
<ElOption label="全部" value="" />
<ElOption
v-for="item in brandOptions"
:key="item.brand_code || item.code"
:label="item.brand_name || item.name"
:value="item.id"
/>
</ElSelect>
</el-form-item>
<el-form-item label="车系:" label-width="60px">
<ElSelect v-model="searchForm.series_id" filterable placeholder="请选择车系">
<ElOption label="全部" value="" />
<ElOption
v-for="s in seriesOptions"
:key="s.series_code || s.code"
:label="s.series_name || s.name"
:value="s.id"
/>
</ElSelect>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="getList" v-ripple>搜索</el-button>
</el-form-item>
</div>
</div>
<!-- 数据表格 -->
<div class="table-container boxshadow">
<ElButton type="primary" v-ripple @click="handleAdd">新增车辆</ElButton>
<ArtTable
ref="tableRef"
row-key="id"
@@ -34,12 +55,12 @@
@current-change="handleCurrentChange"
>
<template #default>
<el-table-column prop="id" label="编码" min-width="170" show-overflow-tooltip align="center">
<!-- <el-table-column prop="id" label="编码" min-width="170" show-overflow-tooltip align="center">
<template #default="scope">
<div class="ellipsis" :title="scope.row.fingerprint">指纹:{{ scope.row.fingerprint }}</div>
<div class="ellipsis" :title="scope.row.sku_code">sku_code:{{ scope.row.sku_code }}</div>
</template>
</el-table-column>
</el-table-column> -->
<ElTableColumn label="车型" prop="model_name" min-width="150" align="center">
<template #default="scope">
<span class="primary">{{ scope.row.model_name }}</span>
@@ -48,7 +69,7 @@
<ElTableColumn label="品牌" prop="brand_name" min-width="100" align="center" />
<ElTableColumn label="车系" prop="series_name" min-width="120" align="center" />
<ElTableColumn label="年款" prop="model_year" align="center" />
<ElTableColumn label="版型" prop="trim" align="center" />
<!-- <ElTableColumn label="版型" prop="trim" align="center" />
<ElTableColumn label="配置等级" prop="trim_level" align="center" />
<ElTableColumn label="排量" prop="engine_disp_l" align="center">
<template #default="scope">
@@ -64,7 +85,7 @@
<template #default="scope">
{{ scope.row.range_km ? scope.row.range_km + 'km' : '-' }}
</template>
</ElTableColumn>
</ElTableColumn> -->
<ElTableColumn label="指导价(CNY)" prop="msrp" align="center" min-width="100">
<template #default="scope">
{{ scope.row.msrp ? scope.row.msrp : '-' }}
@@ -93,7 +114,6 @@
v-model="dialogVisible"
:title="dialogTitle"
width="1200px"
top="40px"
:close-on-click-modal="false"
:close-on-press-escape="false"
>
@@ -117,7 +137,7 @@
:value="item.brand_name || item.name"
/>
</ElSelect>
<ElInput v-model="formData.brand_code" placeholder="品牌编码" readonly disabled />
<!-- <ElInput v-model="formData.brand_code" placeholder="品牌编码" readonly disabled /> -->
</ElFormItem>
</ElCol>
<ElCol :span="8">
@@ -130,17 +150,74 @@
:value="s.series_name || s.name"
/>
</ElSelect>
<ElInput v-model="formData.series_code" placeholder="车系编码" readonly disabled />
<!-- <ElInput v-model="formData.series_code" placeholder="车系编码" readonly disabled /> -->
</ElFormItem>
</ElCol>
<ElCol :span="8">
<ElFormItem label="车型/配置名称" prop="model_name">
<div style="display: flex; align-items: center; gap: 8px">
<!-- 情况1: 有关联车型数据时显示下拉选择和采集按钮 -->
<template v-if="modelsOptions.length > 0">
<ElSelect
v-model="formData.model_name"
filterable
placeholder="请选择车型名称"
style="flex: 1"
:disabled="!formData.series_code"
allow-create
default-first-option
@change="handleModelSelectChange"
>
<ElOption
v-for="model in modelsOptions"
:key="model.model_code || model.code"
:label="model.model_name || model.name"
:value="model.model_name || model.name"
/>
</ElSelect>
<ElButton type="primary" size="small" @click="showCollectModelsDialog">
采集车型
</ElButton>
</template>
<!-- 情况2: 没有车型数据时显示采集按钮 -->
<template v-else-if="formData.series_code">
<ElInput
v-model="formData.model_name"
placeholder="请输入车型名称"
style="flex: 1"
@input="handleModelInputChange"
/>
<ElButton type="primary" size="small" @click="showCollectModelsDialog">
采集车型
</ElButton>
</template>
<!-- 情况3: 没有车系时直接输入 -->
<ElInput
v-else
v-model="formData.model_name"
placeholder="请输入车型名称"
style="flex: 1"
@input="handleModelInputChange"
/>
</div>
<!-- <ElInput
v-model="formData.model_code"
placeholder="车型编码"
readonly
disabled
style="margin-top: 8px"
/> -->
</ElFormItem>
</ElCol>
<!-- <ElCol :span="8">
<ElFormItem label="排量(L)" prop="engine_disp_l">
<ElInput v-model="formData.engine_disp_l" placeholder="请输入排量" />
<el-text class="mx-1" type="warning">如:1.5;纯电为空</el-text>
</ElFormItem>
</ElCol>
</ElCol> -->
</ElRow>
<ElRow :gutter="16">
<!-- <ElRow :gutter="16">
<ElCol :span="8">
<ElFormItem label="版型/细分配置" prop="trim">
<ElSelect
@@ -208,14 +285,9 @@
<ElInput v-model="formData.edition_code" placeholder="版本编码" readonly disabled />
</ElFormItem>
</ElCol>
</ElRow>
<ElRow :gutter="16">
<ElCol :span="8">
<ElFormItem label="车型/配置名称" prop="model_name">
<ElInput v-model="formData.model_name" placeholder="请输入车型名称" />
<el-text class="mx-1" type="warning">如:伊兰特,海狮07(不填则自动生成)</el-text>
</ElFormItem>
</ElCol>
</ElRow> -->
<!-- <ElRow :gutter="16">
<ElCol :span="8">
<ElFormItem label="车型/配置代码" prop="model_code">
<ElInput v-model="formData.model_code" placeholder="请输入车型代码" />
@@ -228,7 +300,7 @@
<el-text class="mx-1" type="warning">如:EV多为1</el-text>
</ElFormItem>
</ElCol>
</ElRow>
</ElRow> -->
<ElRow :gutter="16">
<ElCol :span="8">
<ElFormItem label="官方指导价" prop="msrp">
@@ -240,13 +312,13 @@
<ElInput v-model="formData.model_year" placeholder="YYYY" />
</ElFormItem>
</ElCol>
<ElCol :span="8">
<!-- <ElCol :span="8">
<ElFormItem label="最大功率(kW)" prop="engine_power_kw">
<ElInput v-model="formData.engine_power_kw" placeholder="请输入最大功率" />
</ElFormItem>
</ElCol>
</ElCol> -->
</ElRow>
<ElRow :gutter="16">
<!-- <ElRow :gutter="16">
<ElCol :span="8">
<ElFormItem label="电池容量(kWh)" prop="battery_kwh">
<ElInput v-model="formData.battery_kwh" placeholder="请输入电池容量" />
@@ -262,8 +334,8 @@
<ElInput v-model="formData.doors" placeholder="请输入门数" />
</ElFormItem>
</ElCol>
</ElRow>
<ElRow :gutter="16">
</ElRow> -->
<!-- <ElRow :gutter="16">
<ElCol :span="8">
<ElFormItem label="是否涡轮" prop="engine_turbo">
<ElSelect v-model="formData.engine_turbo" placeholder="请选择">
@@ -282,8 +354,8 @@
<ElInput v-model="formData.seats" placeholder="请输入座位数" />
</ElFormItem>
</ElCol>
</ElRow>
<ElRow :gutter="16">
</ElRow> -->
<!-- <ElRow :gutter="16"> -->
<!-- <ElCol :span="8">
<ElFormItem label="原产国" prop="origin_country">
<ElSelect v-model="formData.origin_country" placeholder="请选择原产国" filterable>
@@ -304,7 +376,7 @@
</ElSelect>
</ElFormItem>
</ElCol> -->
<ElCol :span="8">
<!-- <ElCol :span="8">
<ElFormItem label="外观颜色" prop="color_exterior_text">
<ElSelect
v-model="formData.color_exterior_text"
@@ -327,8 +399,8 @@
disabled
/>
</ElFormItem>
</ElCol>
<ElCol :span="8">
</ElCol> -->
<!-- <ElCol :span="8">
<ElFormItem label="内饰颜色" prop="color_interior_text">
<ElSelect
v-model="formData.color_interior_text"
@@ -370,10 +442,10 @@
disabled
/>
</ElFormItem>
</ElCol>
</ElRow>
</ElCol> -->
<!-- </ElRow> -->
<!-- <el-divider /> -->
<ElRow :gutter="16">
<!-- <ElRow :gutter="16">
<ElCol :span="8">
<ElFormItem label="驱动类型" prop="drive_type">
<ElSelect v-model="formData.drive_type" placeholder="请选择驱动类型" clearable>
@@ -494,7 +566,7 @@
/>
</ElFormItem>
</ElCol>
</ElRow>
</ElRow> -->
<ElRow :gutter="16">
<ElCol :span="8">
<ElFormItem label="状态" prop="is_active">
@@ -852,18 +924,83 @@
</div>
</div>
</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;
color: var(--el-text-color-secondary);
font-size: 14px;
"
>
请选择一个车型
</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">
<ElTableColumn width="60" align="center">
<template #default="scope">
<ElRadio
:model-value="selectedModelId"
:value="scope.row.car_id || scope.row.title"
@change="selectModel(scope.row.car_id || scope.row.title)"
/>
</template>
</ElTableColumn>
<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="!selectedModelId">
保存车型
</ElButton>
</div>
</template>
</ElDialog>
</div>
</template>
<script setup lang="ts">
import { ElMessage, ElMessageBox } from 'element-plus'
import { Search } from '@element-plus/icons-vue'
import { vehicleService } from '@/api/vehicleApi'
import { commonService } from '@/api/commonApi'
defineOptions({ name: 'AdminVehicleList' })
// 搜索表单
const searchForm = reactive({
keyword: ''
keyword: '',
brand_id: '',
series_id: ''
})
// 分页
@@ -947,6 +1084,16 @@
// 车系选项
const seriesOptions = ref<Array<any>>([])
// 车型选项
const modelsOptions = ref<Array<any>>([])
// 车型采集弹出层相关
const collectModelsDialogVisible = ref(false)
const currentCollectSeries = ref<any | null>(null)
const collectModelsLoading = ref(false)
const collectModelsData = ref<Array<any>>([])
const selectedModelId = ref<string>('')
// 外观颜色
const wcolorOptions = ref<Array<any>>([])
// 内饰颜色
@@ -992,6 +1139,30 @@
ElMessage.error('获取车系列表失败')
}
}
// 获取车型列表
const loadModelsOptions = async (seriesCode?: string) => {
if (!seriesCode) {
modelsOptions.value = []
return
}
try {
const matched = seriesOptions.value.find((s: any) => s.series_code === seriesCode)
const seriesId = matched ? matched.id : ''
if (!seriesId) {
modelsOptions.value = []
return
}
const response = await commonService.getModelsSelect({
series_id: seriesId
})
modelsOptions.value = response
} catch (error) {
console.error('获取车型列表失败:', error)
ElMessage.error('获取车型列表失败')
}
}
//颜色下拉列表
const loadColorOptions = async () => {
try {
@@ -1036,7 +1207,9 @@
const params = {
current: currentPage,
size: pageSize,
keyword: searchForm.keyword
keyword: searchForm.keyword,
brand_id: searchForm.brand_id,
series_id: searchForm.series_id
}
const { records, current, size, total } = await vehicleService.getVehicleList(params)
@@ -1062,22 +1235,122 @@
}
// 新增车辆
const handleAdd = () => {
loadBrandOptions()
loadColorOptions()
loadDictOptions()
const handleAdd = async () => {
dialogTitle.value = '新增车辆'
resetForm()
await loadBrandOptions()
loadColorOptions()
loadDictOptions()
dialogVisible.value = true
}
// 编辑车辆
const handleEdit = (row: any) => {
loadBrandOptions()
const handleEdit = async (row: any) => {
loadColorOptions()
loadDictOptions()
dialogTitle.value = '编辑车辆'
// 先清空表单和选项数据,避免监听器干扰
resetForm()
// 使用 nextTick 确保 DOM 更新后再设置数据
await nextTick()
// 设置表单数据
Object.assign(formData, row)
// 立即检查车系名称是否被清空并重新设置
setTimeout(() => {
if (!formData.series_name) {
formData.series_name = row.series_name
}
}, 1)
// 先加载品牌选项
await loadBrandOptions()
// 如果品牌已选择,加载车系选项
if (row.brand_code) {
await loadSeriesOptions(row.brand_code)
} else if (row.brand_name) {
// 如果只有品牌名称没有品牌代码,尝试匹配品牌代码
const matchedBrand = brandOptions.value.find(
(b: any) => b.brand_name === row.brand_name || b.name === row.brand_name
)
if (matchedBrand && matchedBrand.brand_code) {
await loadSeriesOptions(matchedBrand.brand_code)
}
}
// 如果车系已选择,加载车型选项
if (row.series_code) {
// 先尝试用原始的车系代码加载
await loadModelsOptions(row.series_code)
// 如果车型选项为空,尝试用车系名称匹配的车系代码
if (modelsOptions.value.length === 0 && row.series_name) {
const matchedSeries = seriesOptions.value.find(
(s: any) => s.series_name === row.series_name || s.name === row.series_name
)
if (matchedSeries && matchedSeries.series_code) {
await loadModelsOptions(matchedSeries.series_code)
}
}
} else if (row.series_name) {
// 如果只有车系名称没有车系代码,尝试匹配车系代码
const matchedSeries = seriesOptions.value.find(
(s: any) => s.series_name === row.series_name || s.name === row.series_name
)
if (matchedSeries && matchedSeries.series_code) {
await loadModelsOptions(matchedSeries.series_code)
}
}
// 如果仍然没有车型选项,但有车系信息,尝试再次加载
if (modelsOptions.value.length === 0 && (row.series_code || row.series_name)) {
// 使用当前表单中的车系代码重新加载
if (formData.series_code) {
await loadModelsOptions(formData.series_code)
}
}
// 确保车系代码与车系名称匹配(在选项数据加载完成后)
// 如果车系名称被清空,重新设置
if (!formData.series_name && row.series_name) {
formData.series_name = row.series_name
}
if (formData.series_name && seriesOptions.value.length > 0) {
const matchedSeries = seriesOptions.value.find(
(s: any) => s.series_name === formData.series_name || s.name === formData.series_name
)
if (matchedSeries && matchedSeries.series_code) {
formData.series_code = matchedSeries.series_code
}
}
// 确保车型信息正确回显(在选项数据加载完成后)
if (formData.model_name && modelsOptions.value.length > 0) {
const matchedModel = modelsOptions.value.find(
(m: any) => m.model_name === formData.model_name || m.name === formData.model_name
)
if (matchedModel) {
// 如果找到匹配的车型,更新车型代码、年款和指导价
formData.model_code = matchedModel.model_code || matchedModel.code || formData.model_code
formData.model_year = matchedModel.model_year || matchedModel.year || formData.model_year
formData.msrp = matchedModel.msrp || formData.msrp
}
}
// 强制触发一次车系名称监听器(在回显逻辑完成后)
const currentSeriesName = formData.series_name
if (currentSeriesName) {
formData.series_name = ''
await nextTick()
formData.series_name = currentSeriesName
}
dialogVisible.value = true
}
@@ -1128,14 +1401,19 @@
const resetForm = () => {
Object.keys(formData).forEach((key) => {
if (key === 'is_active') {
(formData as any)[key] = 1
;(formData as any)[key] = 1
} else if (key === 'currency') {
(formData as any)[key] = 'CNY'
;(formData as any)[key] = 'CNY'
} else {
(formData as any)[key] = ''
;(formData as any)[key] = ''
}
})
formRef.value?.clearValidate()
// 重置选项数据
modelsOptions.value = []
seriesOptions.value = []
brandOptions.value = []
}
// 提交表单
@@ -1169,14 +1447,120 @@
() => searchForm.keyword,
() => {
pagination.currentPage = 1
})
}
)
// 车型采集相关方法
const showCollectModelsDialog = () => {
if (!formData.series_code) {
ElMessage.warning('请先选择车系')
return
}
const matched = seriesOptions.value.find((s: any) => s.series_code === formData.series_code)
if (!matched) {
ElMessage.warning('未找到对应的车系信息')
return
}
currentCollectSeries.value = matched
collectModelsDialogVisible.value = true
collectModelsData.value = []
selectedModelId.value = ''
// 调用车型采集
collectVehicleModels(matched)
}
const collectVehicleModels = async (series: any) => {
try {
collectModelsLoading.value = true
// 调用后端接口获取车型数据
const response = await vehicleService.collectVehicleModels({ brand_series_id: series.id })
// 后端返回的数据格式为数组
collectModelsData.value = response || []
selectedModelId.value = ''
ElMessage.success(`成功采集到 ${collectModelsData.value.length} 个车型`)
} catch (error: any) {
ElMessage.error(error.message || '车型采集失败')
} finally {
collectModelsLoading.value = false
}
}
// 选择车型
const selectModel = (modelId: string) => {
selectedModelId.value = modelId
}
const saveCollectModels = async () => {
if (!currentCollectSeries.value || !selectedModelId.value) {
ElMessage.warning('请选择一个车型')
return
}
try {
// 找到选中的车型数据
const selectedModel = collectModelsData.value.find(
(model: any) => (model.car_id || model.title) === selectedModelId.value
)
if (!selectedModel) {
ElMessage.warning('未找到选中的车型数据')
return
}
await vehicleService.saveVehicleModels({
series_id: currentCollectSeries.value.id,
models: [selectedModel]
})
ElMessage.success('成功保存车型')
collectModelsDialogVisible.value = false
// 将选中的车型信息回显到表单
formData.model_name = selectedModel.title || selectedModel.model_name || ''
formData.model_code = selectedModel.car_id || selectedModel.model_code || ''
formData.model_year = selectedModel.year || ''
formData.msrp = selectedModel.msrp || ''
// 刷新车型选项
await loadModelsOptions(formData.series_code)
} 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 () => {
loadBrandOptions()
await getList()
})
// 品牌名称变更时,联动设置品牌代码
watch(
() => searchForm.brand_id,
(newId) => {
const matched = brandOptions.value.find((b: any) => b.id === newId)
const brand_code = matched ? matched.brand_code : ''
// 品牌变更后,清空车系相关字段并加载对应车系
searchForm.series_id = ''
if (brand_code) {
loadSeriesOptions(brand_code)
} else {
seriesOptions.value = []
}
}
)
watch(
() => formData.brand_name,
(newName) => {
@@ -1196,14 +1580,71 @@
// 车系名称变更时,联动设置车系代码
watch(
() => formData.series_name,
(newSeriesName) => {
(newSeriesName, oldSeriesName) => {
if (!newSeriesName) return
const matchedSeries = seriesOptions.value.find(
(s: any) => s.series_name === newSeriesName || s.name === newSeriesName
)
formData.series_code = matchedSeries ? matchedSeries.series_code || matchedSeries.code : ''
if (matchedSeries) {
formData.series_code = matchedSeries.series_code || matchedSeries.code || ''
// 只有在车系真正发生变化时才清空车型相关字段
if (oldSeriesName && oldSeriesName !== newSeriesName) {
formData.model_name = ''
formData.model_code = ''
}
if (formData.series_code) {
loadModelsOptions(formData.series_code)
} else {
modelsOptions.value = []
}
} else {
// 如果找不到匹配的车系,清空车系代码
formData.series_code = ''
modelsOptions.value = []
}
}
)
// 车型名称变更时,联动设置车型代码
watch(
() => formData.model_name,
(newModelName) => {
const matchedModel = modelsOptions.value.find(
(m: any) => m.model_name === newModelName || m.name === newModelName
)
formData.model_code = matchedModel ? matchedModel.model_code || matchedModel.code : ''
}
)
// 处理车型下拉选择变化
const handleModelSelectChange = (value: string) => {
// 如果是新创建的值(不在现有选项中),清空车型代码
const matchedModel = modelsOptions.value.find(
(m: any) => m.model_name === value || m.name === value
)
if (!matchedModel) {
formData.model_code = ''
// 清空年款和指导价
formData.model_year = ''
formData.msrp = ''
} else {
// 如果找到匹配的车型,更新车型代码、年款和指导价
formData.model_code = matchedModel.model_code || matchedModel.code || ''
formData.model_year = matchedModel.model_year || matchedModel.year || ''
formData.msrp = matchedModel.msrp || ''
}
}
// 处理车型直接输入变化
const handleModelInputChange = () => {
// 直接输入时清空车型代码,让后端自动生成
formData.model_code = ''
}
// 外观颜色变更时,联动设置外观颜色代码
watch(
() => formData.color_exterior_text,
@@ -1353,11 +1794,6 @@
gap: 8px;
}
.toolbar-right {
display: flex;
align-items: center;
}
.clickable {
cursor: pointer;
}
@@ -1423,4 +1859,42 @@
white-space: nowrap;
margin: 0 auto;
}
.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;
}
}
}
}
</style>