车辆信息,备货单 等页面

This commit is contained in:
浅念
2025-09-04 15:09:17 +08:00
parent 8a0d4fc516
commit 9bd7c89cb4
25 changed files with 9254 additions and 0 deletions
+358
View File
@@ -0,0 +1,358 @@
<template>
<div class="account-page">
<div class="account-toolbar boxshadow">
<div class="account-search-group">
<el-form-item label="关键字:" label-width="70px">
<el-input
style="min-width: 180px"
v-model="keyword"
placeholder="请输入关键字"
clearable
@keyup.enter="getAccountList"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="getAccountList" v-ripple>搜索</el-button>
</el-form-item>
</div>
</div>
<div class="boxshadow">
<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('account@add')" type="primary"
>新增账户</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="account_name" min-width="160" />
<ElTableColumn label="账户号码" prop="account_number" min-width="160" />
<ElTableColumn label="银行名称" prop="bank_name" min-width="140" />
<ElTableColumn label="开户支行" prop="bank_branch" min-width="160" />
<ElTableColumn label="货币" prop="currency_id" min-width="120">
<template #default="scope">
<span>{{ getCurrencyLabel(scope.row.currency_id) }}</span>
</template>
</ElTableColumn>
<ElTableColumn label="排序" prop="sort" />
<ElTableColumn label="状态" prop="status">
<template #default="scope">
<ElTag :type="scope.row.status ? 'success' : 'info'">{{
scope.row.status ? '启用' : '停用'
}}</ElTag>
</template>
</ElTableColumn>
<ElTableColumn label="创建时间" prop="created_at" min-width="160" />
<ElTableColumn fixed="right" label="操作" min-width="160px" align="center">
<template #default="scope">
<ElRow>
<el-button
link
@click="showDialog('edit', scope.row)"
v-if="hasAuth('account@update')"
>编辑</el-button
>
<el-button
type="danger"
link
@click="deleteAccount(scope.row)"
v-if="hasAuth('account@destroy')"
>删除</el-button
>
</ElRow>
</template>
</ElTableColumn>
</template>
</ArtTable>
</div>
<ElDialog
v-model="dialogVisible"
:title="dialogType === 'add' ? '新增账户' : '编辑账户'"
align-center
width="700px"
>
<ElForm ref="formRef" :model="form" :rules="rules" label-width="110px" class="form">
<ElFormItem label="账户名称" prop="account_name">
<ElInput v-model="form.account_name" placeholder="请输入账户名称" />
</ElFormItem>
<ElFormItem label="账户号码" prop="account_number">
<ElInput v-model="form.account_number" placeholder="请输入账户号码" />
</ElFormItem>
<ElFormItem label="银行名称" prop="bank_name">
<ElInput v-model="form.bank_name" placeholder="请输入银行名称" />
</ElFormItem>
<ElFormItem label="开户支行" prop="bank_branch">
<ElInput v-model="form.bank_branch" placeholder="请输入开户支行" />
</ElFormItem>
<ElFormItem label="账户货币类型" prop="currency_id">
<el-select v-model="form.currency_id" placeholder="请选择货币" style="width: 100%">
<el-option
v-for="opt in currencyOptions"
:key="opt.id"
:label="opt.currency_name + ' (' + opt.currency_code + ')'"
:value="opt.id"
/>
</el-select>
</ElFormItem>
<ElFormItem label="排序" prop="sort">
<ElInputNumber v-model="form.sort" :min="0" :max="9999" style="width: 100%" />
</ElFormItem>
<ElFormItem label="状态">
<ElSwitch v-model="form.status" :active-value="1" :inactive-value="0" />
</ElFormItem>
<ElFormItem label="账户描述" prop="description">
<ElInput
v-model="form.description"
type="textarea"
:rows="3"
maxlength="300"
show-word-limit
placeholder="请输入账户描述"
/>
</ElFormItem>
</ElForm>
<template #footer>
<div class="dialog-footer">
<ElButton @click="dialogVisible = false">取消</ElButton>
<ElButton type="primary" @click="handleSubmit(formRef)">提交</ElButton>
</div>
</template>
</ElDialog>
</div>
</template>
<script setup lang="ts">
import { ElMessage, ElMessageBox } from 'element-plus'
import type { FormInstance, FormRules } from 'element-plus'
import { financeService } from '@/api/financeApi'
import { commonService } from '@/api/commonApi'
import { useAuth } from '@/composables/useAuth'
const { hasAuth } = useAuth()
defineOptions({ name: 'FinanceAccount' })
const loading = ref(false)
const dialogVisible = ref(false)
const dialogType = ref<'add' | 'edit'>('add')
const formRef = ref<FormInstance>()
const keyword = ref<string>('')
const currencyFilter = ref<number | undefined>(undefined)
const statusFilter = ref<number | undefined>(undefined)
// 下拉:货币
const currencyOptions = ref<any[]>([])
const loadCurrencyOptions = async () => {
try {
const res = await commonService.getCurrencySelect()
currencyOptions.value = res || []
} catch (e) {
currencyOptions.value = []
}
}
const form = reactive<any>({
id: '',
currency_id: undefined,
status: 1,
sort: 0,
bank_name: '',
bank_branch: '',
description: '',
account_number: '',
account_name: ''
})
const rules = reactive<FormRules>({
currency_id: [{ required: true, message: '请选择账户货币类型', trigger: 'change' }],
account_name: [{ required: true, message: '请输入账户名称', trigger: 'blur' }],
account_number: [{ required: true, message: '请输入账户号码', trigger: 'blur' }]
})
const pagination = reactive({
currentPage: 1,
pageSize: 10,
total: 0
})
const tableData = ref<any[]>([])
const tableRef = ref()
onMounted(() => {
loadCurrencyOptions()
getAccountList()
})
watch([keyword, currencyFilter, statusFilter], () => {
pagination.currentPage = 1
getAccountList()
})
const getAccountList = async () => {
loading.value = true
try {
const { currentPage, pageSize } = pagination
const params = {
current: currentPage,
size: pageSize,
keyword: keyword.value
}
const { records, current, size, total } = await financeService.getAccountList(params)
tableData.value = records || []
Object.assign(pagination, { currentPage: current, pageSize: size, total })
} catch (error) {
console.error('获取账户列表失败:', error)
ElMessage.error('获取账户列表失败')
} finally {
loading.value = false
}
}
const handleSizeChange = (newPageSize: number) => {
pagination.pageSize = newPageSize
getAccountList()
}
const handleCurrentChange = (newCurrentPage: number) => {
pagination.currentPage = newCurrentPage
getAccountList()
}
const showDialog = (type: 'add' | 'edit', row?: any) => {
dialogVisible.value = true
dialogType.value = type
if (type === 'edit' && row) {
Object.assign(form, {
id: row.id,
currency_id: row.currency_id,
status: row.status,
sort: row.sort ?? 0,
bank_name: row.bank_name,
bank_branch: row.bank_branch,
description: row.description,
account_number: row.account_number,
account_name: row.account_name
})
} else {
Object.assign(form, {
id: '',
currency_id: undefined,
status: 1,
sort: 0,
bank_name: '',
bank_branch: '',
description: '',
account_number: '',
account_name: ''
})
}
}
const deleteAccount = (row: any) => {
ElMessageBox.confirm('确定删除该账户吗?', '删除确认', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'error'
}).then(async () => {
try {
const { message } = await financeService.accountDelete({ id: row.id })
ElMessage.success(message)
getAccountList()
} catch (error) {
console.error('删除失败:', error)
ElMessage.error('删除失败')
}
})
}
const handleSubmit = async (formEl: FormInstance | undefined) => {
if (!formEl) return
await formEl.validate(async (valid) => {
if (!valid) return
try {
if (dialogType.value === 'add') {
const { message } = await financeService.accountAdd({
currency_id: form.currency_id,
status: form.status,
sort: form.sort,
bank_name: form.bank_name,
bank_branch: form.bank_branch,
description: form.description,
account_number: form.account_number,
account_name: form.account_name
})
ElMessage.success(message)
} else {
const { message } = await financeService.accountEdit({
id: form.id,
currency_id: form.currency_id,
status: form.status,
sort: form.sort,
bank_name: form.bank_name,
bank_branch: form.bank_branch,
description: form.description,
account_number: form.account_number,
account_name: form.account_name
})
ElMessage.success(message)
}
dialogVisible.value = false
getAccountList()
} catch (error) {
console.error('操作失败:', error)
ElMessage.error('操作失败')
}
})
}
const getCurrencyLabel = (id: number | undefined) => {
if (!id) return '-'
const found = currencyOptions.value.find((c: any) => c.id === id)
return found ? `${found.currency_name} (${found.currency_code})` : id
}
</script>
<style lang="scss" scoped>
:deep(.el-table__empty-block) {
height: 100% !important;
min-height: 200px;
display: flex;
align-items: center;
justify-content: center;
}
.account-page {
background: var(--art-main-bg-color);
min-height: 100vh;
}
.account-toolbar {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16px;
padding: 20px 20px;
background: var(--art-main-bg-color);
}
.account-search-group {
display: flex;
gap: 12px;
align-items: center;
}
.form {
padding: 20px 0;
}
.dialog-footer {
text-align: right;
}
.el-col2 {
margin-bottom: 16px;
}
</style>
+389
View File
@@ -0,0 +1,389 @@
<template>
<div class="customer-page">
<div class="customer-toolbar boxshadow">
<div class="customer-search-group">
<el-form-item label="关键字:" label-width="70px">
<el-input style="min-width: 120px" v-model="keyword" placeholder="请输入关键字" />
</el-form-item>
</div>
</div>
<div class="boxshadow">
<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('currency@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="currency_code" min-width="140" />
<ElTableColumn label="货币名称" prop="currency_name" min-width="140" />
<ElTableColumn label="符号" prop="currency_symbol" width="100" />
<ElTableColumn label="当前汇率" prop="exchange_rate" min-width="120" />
<ElTableColumn label="允许上浮动%" prop="float_percent_up" min-width="120" />
<ElTableColumn label="允许下浮动%" prop="float_percent_down" min-width="120" />
<ElTableColumn label="状态" prop="status" width="120">
<template #default="scope">
<ElTag :type="scope.row.status ? 'primary' : 'info'">
{{ scope.row.status ? '启用' : '停用' }}
</ElTag>
</template>
</ElTableColumn>
<ElTableColumn label="创建时间" prop="created_at" min-width="160" />
<ElTableColumn fixed="right" label="操作" width="240px">
<template #default="scope">
<ElRow>
<el-button link @click="showDetail(scope.row)" v-if="hasAuth('currency@show')">
详情
</el-button>
<el-button
link
@click="showDialog('edit', scope.row)"
v-if="hasAuth('currency@update')"
>
编辑
</el-button>
<el-button
type="danger"
link
@click="handleDelete(scope.row)"
v-if="hasAuth('currency@destroy')"
>
删除
</el-button>
</ElRow>
</template>
</ElTableColumn>
</template>
</ArtTable>
</div>
<ElDialog
v-model="dialogVisible"
:title="dialogType === 'add' ? '新增货币' : '编辑货币'"
align-center
width="640px"
>
<ElForm ref="formRef" :model="form" :rules="rules" label-width="120px" class="form">
<ElFormItem label="货币编码" prop="currency_code">
<ElInput v-model="form.currency_code" />
</ElFormItem>
<ElFormItem label="货币名称" prop="currency_name">
<ElInput v-model="form.currency_name" />
</ElFormItem>
<ElFormItem label="货币符号" prop="currency_symbol">
<ElInput v-model="form.currency_symbol" />
</ElFormItem>
<ElFormItem label="当前汇率" prop="exchange_rate">
<ElInputNumber
v-model="form.exchange_rate"
:min="0"
:step="0.0001"
:precision="6"
style="width: 100%"
/>
</ElFormItem>
<ElFormItem label="允许上浮动%" prop="float_percent_up">
<ElInputNumber
v-model="form.float_percent_up"
:min="0"
:max="100"
:step="0.01"
:precision="2"
style="width: 100%"
/>
</ElFormItem>
<ElFormItem label="允许下浮动%" prop="float_percent_down">
<ElInputNumber
v-model="form.float_percent_down"
:min="0"
:max="100"
:step="0.01"
:precision="2"
style="width: 100%"
/>
</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>
<ElDialog v-model="detailVisible" title="货币详情" align-center width="640px">
<div class="detail-dialog">
<div class="section">
<div class="info-grid">
<div class="label">货币编码</div>
<div class="value">{{ detailData.currency_code || '-' }}</div>
</div>
<div class="info-grid">
<div class="label">货币名称</div>
<div class="value">{{ detailData.currency_name || '-' }}</div>
</div>
<div class="info-grid">
<div class="label">货币符号</div>
<div class="value">{{ detailData.currency_symbol || '-' }}</div>
</div>
<div class="info-grid">
<div class="label">当前汇率</div>
<div class="value">{{ detailData.exchange_rate ?? '-' }}</div>
</div>
<div class="info-grid">
<div class="label">允许上浮动%</div>
<div class="value">{{ detailData.float_percent_up ?? '-' }}</div>
</div>
<div class="info-grid">
<div class="label">允许下浮动%</div>
<div class="value">{{ detailData.float_percent_down ?? '-' }}</div>
</div>
<div class="info-grid">
<div class="label">状态</div>
<div class="value">{{ detailData.status ? '启用' : '停用' }}</div>
</div>
<div class="info-grid">
<div class="label">创建时间</div>
<div class="value">{{ detailData.created_at || '-' }}</div>
</div>
<div class="info-grid">
<div class="label">更新时间</div>
<div class="value">{{ detailData.updated_at || '-' }}</div>
</div>
</div>
</div>
<template #footer>
<div class="dialog-footer">
<ElButton @click="detailVisible = false">关闭</ElButton>
</div>
</template>
</ElDialog>
</div>
</template>
<script setup lang="ts">
import { ElMessage, ElMessageBox } from 'element-plus'
import type { FormInstance, FormRules } from 'element-plus'
import { financeService } from '@/api/financeApi'
import { useAuth } from '@/composables/useAuth'
const { hasAuth } = useAuth()
defineOptions({ name: 'Type ' })
const loading = ref(false)
const dialogVisible = ref(false)
const formRef = ref<FormInstance>()
const keyword = ref<string>('')
const rules = reactive<FormRules>({
currency_code: [{ required: true, message: '请输入货币编码', trigger: 'blur' }],
currency_name: [{ required: true, message: '请输入货币名称', trigger: 'blur' }],
exchange_rate: [{ required: true, message: '请输入当前汇率', trigger: 'change' }]
})
const form = reactive<any>({
id: '',
currency_code: '',
currency_name: '',
currency_symbol: '',
exchange_rate: 0,
float_percent_up: 0,
float_percent_down: 0,
status: 1
})
const pagination = reactive({
currentPage: 1,
pageSize: 10,
total: 0
})
// 表格数据
const tableData = ref<any[]>([])
// 表格实例引用
const tableRef = ref()
onMounted(() => {
getCurrencyList()
})
watch([keyword], () => {
pagination.currentPage = 1
getCurrencyList()
})
// 处理表格分页变化
const handleSizeChange = (newPageSize: number) => {
pagination.pageSize = newPageSize
getCurrencyList()
}
const handleCurrentChange = (newCurrentPage: number) => {
pagination.currentPage = newCurrentPage
getCurrencyList()
}
// 获取货币列表
const getCurrencyList = async () => {
loading.value = true
try {
const { currentPage, pageSize } = pagination
const { records, current, size, total } = await financeService.getCurrencyList({
current: currentPage,
size: pageSize,
keyword: keyword.value
})
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 detailVisible = ref(false)
const detailData = ref<any>({})
const showDialog = (type: 'add' | 'edit', row?: any) => {
dialogVisible.value = true
dialogType.value = type
if (type === 'edit' && row) {
Object.assign(form, {
id: row.id,
currency_code: row.currency_code,
currency_name: row.currency_name,
currency_symbol: row.currency_symbol,
exchange_rate: row.exchange_rate ?? 0,
float_percent_up: row.float_percent_up ?? 0,
float_percent_down: row.float_percent_down ?? 0,
status: row.status ?? 1
})
} else {
Object.assign(form, {
id: '',
currency_code: '',
currency_name: '',
currency_symbol: '',
exchange_rate: 0,
float_percent_up: 0,
float_percent_down: 0,
status: 1
})
}
}
const handleDelete = (row?: any) => {
ElMessageBox.confirm('确定删除吗?', '删除确认', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'error'
}).then(async () => {
const { message } = await financeService.currencyDelete({ id: row.id })
ElMessage.success(message)
getCurrencyList()
})
}
const handleSubmit = async (formEl: FormInstance | undefined) => {
if (!formEl) return
await formEl.validate(async (valid) => {
if (!valid) return
if (dialogType.value === 'add') {
const { id, ...payload } = form
const { message } = await financeService.currencyAdd(payload)
ElMessage.success(message)
} else {
const { message } = await financeService.currencyEdit(form)
ElMessage.success(message)
}
dialogVisible.value = false
getCurrencyList()
})
}
const showDetail = async (row: any) => {
detailVisible.value = true
const res = await financeService.currencyDetail({ id: row.id })
detailData.value = res || {}
}
</script>
<style lang="scss" scoped>
:deep(.el-table__empty-block) {
height: 100% !important;
min-height: 200px;
display: flex;
align-items: center;
justify-content: center;
}
.customer-page {
background: var(--art-main-bg-color);
min-height: 100vh;
}
.page-content {
.svg-icon {
width: 1.8em;
height: 1.8em;
overflow: hidden;
vertical-align: -8px;
fill: currentcolor;
}
}
.customer-toolbar {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16px;
padding: 20px 20px;
background: var(--art-main-bg-color);
}
.customer-search-group {
display: flex;
gap: 12px;
}
.message-text {
padding: 10px 14px;
line-height: 1.7;
white-space: pre-line;
word-break: break-word;
}
.detail-dialog {
display: flex;
flex-direction: column;
gap: 16px;
}
.section {
background: var(--el-fill-color-blank);
border: 1px solid var(--el-border-color-light);
border-radius: 8px;
padding: 12px 14px;
}
.info-grid {
display: grid;
grid-template-columns: 96px 1fr;
gap: 6px 12px;
align-items: center;
padding: 6px 0;
}
.label {
color: var(--el-text-color-secondary);
font-size: 13px;
}
.value {
color: var(--el-text-color-primary);
font-size: 14px;
}
.content-box {
background: var(--el-fill-color-lighter);
border: 1px dashed var(--el-border-color);
border-radius: 8px;
padding: 10px 12px;
max-height: 320px;
overflow: auto;
}
</style>
+524
View File
@@ -0,0 +1,524 @@
<template>
<div class="page-content">
<div v-if="!isMobile" class="dict-layout">
<ElRow :gutter="16">
<ElCol :xs="24" :sm="8" :lg="8">
<div class="dict-type-card">
<div class="card-header">
<ElInput
v-model="typeKeyword"
placeholder="搜索类型名称/编码"
clearable
@keyup.enter="getTypeList"
/>
<ElButton style="margin-left: 8px" @click="getTypeList" v-ripple>搜索</ElButton>
<ElButton
type="primary"
style="margin-left: 8px"
@click="showTypeDialog('add')"
v-if="hasAuth('system@dictionary_add')"
>新增类型</ElButton
>
</div>
<el-table
:data="typeTableData"
v-loading="typeLoading"
height="calc(100vh - 280px)"
@row-click="handleTypeRowClick"
:highlight-current-row="true"
:row-key="'id'"
>
<el-table-column prop="dict_code" label="字典编码" />
<el-table-column prop="dict_name" label="字典名称" />
<el-table-column prop="category" label="字典分类" />
<el-table-column prop="description" label="描述" show-overflow-tooltip />
<el-table-column fixed="right" label="操作" min-width="140" align="center">
<template #default="scope">
<el-button
link
size="small"
@click.stop="showTypeDialog('edit', scope.row)"
v-if="hasAuth('system@dictionary_update')"
>编辑</el-button
>
<el-button
link
type="danger"
size="small"
@click.stop="handleTypeDelete(scope.row)"
v-if="hasAuth('system@dictionary_destroy')"
>删除</el-button
>
</template>
</el-table-column>
<template #empty>
<el-empty description="暂无类型" v-show="!typeLoading" />
</template>
</el-table>
<div class="type-pagination">
<el-pagination
background
layout="total, sizes, prev, pager, next"
:current-page="typePagination.currentPage"
:page-size="typePagination.pageSize"
:total="typePagination.total"
:page-sizes="[10, 20, 30, 50]"
@current-change="handleTypeCurrentChange"
@size-change="handleTypeSizeChange"
/>
</div>
</div>
</ElCol>
<ElCol :xs="24" :sm="16" :lg="16">
<div class="dict-item-card">
<div class="card-header">
<div class="left">
<span v-if="activeType"
>当前类型:{{ activeType.dict_name }}({{ activeType.dict_code }})</span
>
<span v-else class="muted">请选择左侧字典类型</span>
</div>
<div class="right">
<ElInput
v-model="itemKeyword"
placeholder="搜索字典项名称/值"
clearable
style="width: 240px"
@keyup.enter="getItemList"
/>
<ElButton style="margin-left: 8px" @click="getItemList" v-ripple>搜索</ElButton>
<ElButton
type="primary"
style="margin-left: 8px"
@click="showItemDialog('add')"
:disabled="!activeType"
v-if="hasAuth('system@dictionary_add')"
>新增字典项</ElButton
>
</div>
</div>
<ArtTable
ref="itemTableRef"
row-key="id"
:loading="itemLoading"
:data="itemTableData"
:currentPage="itemPagination.currentPage"
:pageSize="itemPagination.pageSize"
:total="itemPagination.total"
@size-change="handleItemSizeChange"
@current-change="handleItemCurrentChange"
>
<template #default>
<ElTableColumn label="ID" prop="id" width="80" />
<ElTableColumn label="字典项名称" prop="item_label" min-width="160" />
<ElTableColumn
label="字典项值"
prop="item_value"
min-width="160"
show-overflow-tooltip
/>
<ElTableColumn label="备注" prop="remark" min-width="160" show-overflow-tooltip />
<!-- <ElTableColumn label="排序" prop="sort" width="100" /> -->
<ElTableColumn label="状态" prop="status" width="120">
<template #default="scope">
<ElTag :type="scope.row.status ? 'primary' : 'info'">{{
scope.row.status ? '启用' : '禁用'
}}</ElTag>
</template>
</ElTableColumn>
<ElTableColumn label="创建时间" prop="created_at" min-width="160" />
<ElTableColumn fixed="right" label="操作" min-width="140px" align="center">
<template #default="scope">
<ElRow>
<el-button
link
@click="showItemDialog('edit', scope.row)"
v-if="hasAuth('system@dictionary_update')"
>编辑</el-button
>
<el-button
type="danger"
link
@click="handleItemDelete(scope.row)"
v-if="hasAuth('system@dictionary_destroy')"
>删除</el-button
>
</ElRow>
</template>
</ElTableColumn>
</template>
</ArtTable>
</div>
</ElCol>
</ElRow>
<ElDialog
v-model="typeDialogVisible"
:title="typeDialogType === 'add' ? '新增字典类型' : '编辑字典类型'"
width="40%"
align-center
>
<ElForm ref="typeFormRef" :model="typeForm" :rules="typeRules" label-width="100px">
<ElFormItem label="字典分类" prop="category_id">
<ElSelect v-model="typeForm.category_id" placeholder="请选择字典分类" clearable>
<ElOption
v-for="item in DictTypeSelect"
:key="item.id"
:label="item.item_label"
:value="item.id"
/>
</ElSelect>
</ElFormItem>
<ElFormItem label="字典编码" prop="dict_code">
<ElInput v-model="typeForm.dict_code" />
</ElFormItem>
<ElFormItem label="字典名称" prop="dict_name">
<ElInput v-model="typeForm.dict_name" />
</ElFormItem>
<ElFormItem label="描述" prop="description">
<ElInput v-model="typeForm.description" type="textarea" :rows="2" />
</ElFormItem>
<ElFormItem label="状态" prop="status">
<ElSwitch v-model="typeForm.status" :active-value="1" :inactive-value="0" />
</ElFormItem>
</ElForm>
<template #footer>
<div class="dialog-footer">
<ElButton @click="typeDialogVisible = false">取消</ElButton>
<ElButton type="primary" @click="handleTypeSubmit(typeFormRef)">提交</ElButton>
</div>
</template>
</ElDialog>
<ElDialog
v-model="itemDialogVisible"
:title="itemDialogType === 'add' ? '新增字典项' : '编辑字典项'"
width="50%"
align-center
>
<ElForm ref="itemFormRef" :model="itemForm" :rules="itemRules" label-width="100px">
<ElFormItem label="所属类型">
<ElInput
:model-value="activeType ? `${activeType.dict_name}(${activeType.dict_code})` : ''"
disabled
/>
</ElFormItem>
<ElFormItem label="字典项名称" prop="item_label">
<ElInput v-model="itemForm.item_label" />
</ElFormItem>
<ElFormItem label="字典项值" prop="item_value">
<ElInput v-model="itemForm.item_value" type="textarea" :rows="3" />
</ElFormItem>
<ElFormItem label="排序" prop="sort">
<ElInputNumber v-model="itemForm.sort" :min="0" />
</ElFormItem>
<ElFormItem label="备注" prop="remark">
<ElInput v-model="itemForm.remark" />
</ElFormItem>
<ElFormItem label="状态" prop="status">
<ElSwitch v-model="itemForm.status" :active-value="1" :inactive-value="0" />
</ElFormItem>
</ElForm>
<template #footer>
<div class="dialog-footer">
<ElButton @click="itemDialogVisible = false">取消</ElButton>
<ElButton type="primary" @click="handleItemSubmit(itemFormRef)">提交</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 { systemService } from '@/api/systemApi'
import { commonService } from '@/api/commonApi'
import { useAuth } from '@/composables/useAuth'
const { hasAuth } = useAuth()
defineOptions({ name: 'Dictionary' })
const { width } = useWindowSize()
const isMobile = computed(() => width.value < 500)
// 左侧 类型
const typeLoading = ref(false)
const typeKeyword = ref<string>('')
const typeTableData = ref<any[]>([])
const typePagination = reactive({ currentPage: 1, pageSize: 10, total: 0 })
const activeType = ref<any | null>(null)
const typeFormRef = ref<FormInstance>()
const typeDialogVisible = ref(false)
const typeDialogType = ref<'add' | 'edit'>('add')
const typeForm = reactive<any>({ id: '', code: '', name: '', status: 1 })
const typeRules = reactive<FormRules>({
dict_code: [{ required: true, message: '请输入字典编码', trigger: 'blur' }],
dict_name: [{ required: true, message: '请输入字典名称', trigger: 'blur' }],
category_id: [{ required: true, message: '请选择字典分类', trigger: 'blur' }]
})
const DictTypeSelect = ref<any[]>([])
const getDictTypeSelect = async () => {
const records = await commonService.getDictTypeSelect()
DictTypeSelect.value = records.sysDictItem
}
const getTypeList = async () => {
typeLoading.value = true
try {
const { currentPage, pageSize } = typePagination
const { records, current, size, total } = await systemService.getDictTypeList({
current: currentPage,
size: pageSize,
keyword: typeKeyword.value
})
typeTableData.value = records
Object.assign(typePagination, { currentPage: current, pageSize: size, total })
if (!activeType.value && records && records.length) {
activeType.value = records[0]
await getItemList()
}
} finally {
typeLoading.value = false
}
}
const handleTypeRowClick = async (row: any) => {
activeType.value = row
itemPagination.currentPage = 1
await getItemList()
}
const handleTypeSizeChange = (val: number) => {
typePagination.pageSize = val
getTypeList()
}
const handleTypeCurrentChange = (val: number) => {
typePagination.currentPage = val
getTypeList()
}
const showTypeDialog = (type: 'add' | 'edit', row?: any) => {
getDictTypeSelect()
typeDialogVisible.value = true
typeDialogType.value = type
if (type === 'edit' && row) {
Object.assign(typeForm, {
id: row.id,
dict_code: row.dict_code,
dict_name: row.dict_name,
description: row.description,
category_id: row.category_id,
status: row.status ?? 1
})
} else {
Object.assign(typeForm, {
id: '',
dict_code: '',
dict_name: '',
description: '',
status: 1,
category_id: ''
})
}
}
const handleTypeDelete = (row: any) => {
ElMessageBox.confirm('确定删除该字典类型吗?删除后其字典项也可能被清理。', '删除确认', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'error'
}).then(async () => {
const { message } = await systemService.dictTypeDelete({ id: row.id })
ElMessage.success(message)
if (activeType.value && activeType.value.id === row.id) {
activeType.value = null
itemTableData.value = []
itemPagination.total = 0
}
getTypeList()
})
}
const handleTypeSubmit = async (formEl: FormInstance | undefined) => {
if (!formEl) return
await formEl.validate(async (valid) => {
if (!valid) return
if (typeDialogType.value === 'add') {
const { message } = await systemService.dictTypeAdd({
dict_code: typeForm.dict_code,
dict_name: typeForm.dict_name,
description: typeForm.description,
status: typeForm.status
})
ElMessage.success(message)
} else {
const { message } = await systemService.dictTypeEdit({
id: typeForm.id,
dict_code: typeForm.dict_code,
dict_name: typeForm.dict_name,
description: typeForm.description,
status: typeForm.status
})
ElMessage.success(message)
}
typeDialogVisible.value = false
getTypeList()
})
}
// 右侧 字典项
const itemLoading = ref(false)
const itemKeyword = ref<string>('')
const itemTableData = ref<any[]>([])
const itemTableRef = ref()
const itemPagination = reactive({ currentPage: 1, pageSize: 10, total: 0 })
const itemFormRef = ref<FormInstance>()
const itemDialogVisible = ref(false)
const itemDialogType = ref<'add' | 'edit'>('add')
const itemForm = reactive<any>({
id: '',
item_label: '',
item_value: '',
sort: 0,
status: 1,
remark: ''
})
const itemRules = reactive<FormRules>({
item_label: [{ required: true, message: '请输入字典项', trigger: 'blur' }],
item_value: [{ required: true, message: '请输入描述', trigger: 'blur' }]
})
const getItemList = async () => {
if (!activeType.value) return
itemLoading.value = true
try {
const { currentPage, pageSize } = itemPagination
const { records, current, size, total } = await systemService.getDictItemList({
current: currentPage,
size: pageSize,
keyword: itemKeyword.value,
id: activeType.value.id
})
itemTableData.value = records
Object.assign(itemPagination, { currentPage: current, pageSize: size, total })
} finally {
itemLoading.value = false
}
}
const handleItemSizeChange = (val: number) => {
itemPagination.pageSize = val
getItemList()
}
const handleItemCurrentChange = (val: number) => {
itemPagination.currentPage = val
getItemList()
}
const showItemDialog = (type: 'add' | 'edit', row?: any) => {
itemDialogVisible.value = true
itemDialogType.value = type
if (type === 'edit' && row) {
Object.assign(itemForm, {
id: row.id,
item_label: row.item_label,
item_value: row.item_value,
sort: row.sort ?? 0,
status: row.status ?? 1,
remark: row.remark || ''
})
} else {
Object.assign(itemForm, {
id: '',
item_label: '',
item_value: '',
sort: 0,
status: 1,
remark: ''
})
}
}
const handleItemDelete = (row: any) => {
ElMessageBox.confirm('确定删除该字典项吗?', '删除确认', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'error'
}).then(async () => {
const { message } = await systemService.dictItemDelete({ id: row.id })
ElMessage.success(message)
getItemList()
})
}
const handleItemSubmit = async (formEl: FormInstance | undefined) => {
if (!formEl || !activeType.value) return
await formEl.validate(async (valid) => {
if (!valid) return
if (itemDialogType.value === 'add') {
const { message } = await systemService.dictItemAdd({
dict_id: activeType.value.id,
item_label: itemForm.item_label,
item_value: itemForm.item_value,
sort: itemForm.sort,
status: itemForm.status,
remark: itemForm.remark
})
ElMessage.success(message)
} else {
const { message } = await systemService.dictItemEdit({
id: itemForm.id,
dict_id: activeType.value.id,
item_label: itemForm.item_label,
item_value: itemForm.item_value,
sort: itemForm.sort,
status: itemForm.status,
remark: itemForm.remark
})
ElMessage.success(message)
}
itemDialogVisible.value = false
getItemList()
})
}
onMounted(async () => {
await getTypeList()
})
</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;
}
}
</style>
+640
View File
@@ -0,0 +1,640 @@
<template>
<div class="page-content">
<div v-if="!isMobile" class="dict-layout">
<ElRow :gutter="16">
<ElCol :xs="24" :sm="10" :lg="10">
<div class="dict-type-card">
<div class="card-header">
<div style="display: flex; align-items: center">
<ElInput
v-model="brandKeyword"
placeholder="搜索品牌名称/编码"
clearable
@keyup.enter="getBrandList"
/>
<ElButton style="margin-left: 8px" @click="getBrandList" v-ripple>搜索</ElButton>
</div>
<ElButton
type="primary"
style="margin-left: 8px"
@click="showBrandDialog('add')"
v-if="hasAuth('brand@add')"
>新增品牌</ElButton
>
</div>
<el-table
:data="brandTableData"
v-loading="brandLoading"
height="calc(100vh - 280px)"
@row-click="handleBrandRowClick"
:highlight-current-row="true"
:row-key="'id'"
>
<el-table-column prop="brand_code" label="品牌编码" />
<el-table-column prop="brand_name" label="品牌名称" />
<el-table-column label="Logo" width="80">
<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="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="140" align="center">
<template #default="scope">
<el-button
link
size="small"
@click.stop="showBrandDialog('edit', scope.row)"
v-if="hasAuth('brand@update')"
>编辑</el-button
>
<el-button
link
type="danger"
size="small"
@click.stop="handleBrandDelete(scope.row)"
v-if="hasAuth('brand@destroy')"
>删除</el-button
>
</template>
</el-table-column>
<template #empty>
<el-empty description="暂无品牌" v-show="!brandLoading" />
</template>
</el-table>
<div class="type-pagination">
<el-pagination
background
layout="total, sizes, prev, pager, next"
:current-page="brandPagination.currentPage"
:page-size="brandPagination.pageSize"
:total="brandPagination.total"
:page-sizes="[10, 20, 30, 50]"
@current-change="handleBrandCurrentChange"
@size-change="handleBrandSizeChange"
/>
</div>
</div>
</ElCol>
<ElCol :xs="24" :sm="14" :lg="14">
<div class="dict-item-card">
<div class="card-header">
<div class="left">
<span v-if="activeBrand"
>当前品牌:{{ activeBrand.brand_name }}({{ activeBrand.brand_code }})</span
>
<span v-else class="muted">请选择左侧车辆品牌</span>
</div>
<div class="right">
<ElInput
v-model="seriesKeyword"
placeholder="搜索系列名称/编码"
clearable
style="width: 240px"
@keyup.enter="getSeriesList"
/>
<ElButton style="margin-left: 8px" @click="getSeriesList" v-ripple>搜索</ElButton>
<ElButton
type="primary"
style="margin-left: 8px"
@click="showSeriesDialog('add')"
:disabled="!activeBrand"
v-if="hasAuth('series@add')"
>新增系列</ElButton
>
</div>
</div>
<ArtTable
ref="seriesTableRef"
row-key="id"
:loading="seriesLoading"
:data="seriesTableData"
:currentPage="seriesPagination.currentPage"
:pageSize="seriesPagination.pageSize"
:total="seriesPagination.total"
@size-change="handleSeriesSizeChange"
@current-change="handleSeriesCurrentChange"
>
<template #default>
<ElTableColumn label="系列编码" prop="series_code" min-width="120" />
<ElTableColumn label="系列名称" prop="series_name" min-width="140" />
<ElTableColumn
label="描述"
prop="description"
min-width="160"
show-overflow-tooltip
/>
<!-- <ElTableColumn label="排序" prop="sort" width="80" /> -->
<ElTableColumn label="状态" prop="status">
<template #default="scope">
<ElTag :type="scope.row.status ? 'success' : 'info'">{{
scope.row.status ? '启用' : '停用'
}}</ElTag>
</template>
</ElTableColumn>
<ElTableColumn fixed="right" label="操作" min-width="140px" align="center">
<template #default="scope">
<ElRow>
<el-button
link
@click="showSeriesDialog('edit', scope.row)"
v-if="hasAuth('series@update')"
>编辑</el-button
>
<el-button
type="danger"
link
@click="handleSeriesDelete(scope.row)"
v-if="hasAuth('series@destroy')"
>删除</el-button
>
</ElRow>
</template>
</ElTableColumn>
</template>
</ArtTable>
</div>
</ElCol>
</ElRow>
<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="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="
activeBrand ? `${activeBrand.brand_name}(${activeBrand.brand_code})` : ''
"
disabled
/>
</ElFormItem>
<ElFormItem label="系列编码" prop="series_code">
<!-- <ElInput v-model="seriesForm.series_code" :disabled="seriesDialogType === 'edit'" /> -->
<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>
</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/vehicleApi'
import { useAuth } from '@/composables/useAuth'
import { useWindowSize } from '@vueuse/core'
import { useUploadHeaders } from '@/composables/useUploadHeaders'
const { uploadHeaders } = useUploadHeaders()
let actionImageUplode = `${import.meta.env.VITE_API_URL}/ticket/upload`
const { hasAuth } = useAuth()
defineOptions({ name: 'VehicleBrand' })
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: '',
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 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,
logo: row.logo,
description: row.description,
sort: row.sort ?? 0,
status: row.status ?? 1
})
} else {
Object.assign(brandForm, {
id: '',
brand_code: '',
brand_name: '',
logo: '',
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,
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_code: [{ required: true, message: '请输入系列编码', trigger: 'blur' }],
series_name: [{ required: true, message: '请输入系列名称', trigger: 'blur' }]
})
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()
})
}
const handleSeriesSubmit = async (formEl: FormInstance | undefined) => {
if (!formEl || !activeBrand.value) return
await formEl.validate(async (valid) => {
if (!valid) return
if (seriesDialogType.value === 'add') {
const { message } = await vehicleService.seriesAdd({
brand_id: activeBrand.value.id,
series_code: seriesForm.series_code,
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: activeBrand.value.id,
series_code: seriesForm.series_code,
series_name: seriesForm.series_name,
description: seriesForm.description,
sort: seriesForm.sort,
status: seriesForm.status
})
ElMessage.success(message)
}
seriesDialogVisible.value = false
getSeriesList()
})
}
// Logo上传处理
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
}
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;
}
</style>
+357
View File
@@ -0,0 +1,357 @@
<template>
<div class="color-page">
<div class="color-toolbar boxshadow">
<div class="color-search-group">
<el-form-item label="关键字:" label-width="70px">
<el-input
style="min-width: 120px"
v-model="keyword"
placeholder="请输入颜色编码或名称"
clearable
@keyup.enter="getColorList"
/>
</el-form-item>
<el-form-item label="类型:" label-width="50px">
<el-select v-model="typeFilter" placeholder="全部类型" clearable style="min-width: 120px">
<el-option label="外观颜色" :value="1" />
<el-option label="内饰颜色" :value="2" />
</el-select>
</el-form-item>
<el-form-item label="状态:" label-width="50px">
<el-select
v-model="statusFilter"
placeholder="全部状态"
clearable
style="min-width: 120px"
>
<el-option label="启用" :value="1" />
<el-option label="停用" :value="0" />
</el-select>
</el-form-item>
<el-button type="primary" @click="getColorList" v-ripple>搜索</el-button>
<el-button @click="resetSearch" v-ripple>重置</el-button>
</div>
</div>
<div class="boxshadow">
<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('color@add')" type="primary">
新增颜色
</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="color_code" min-width="120" />
<ElTableColumn label="颜色名称" prop="color_text" min-width="150" />
<ElTableColumn label="类型" prop="type" width="120">
<template #default="scope">
<ElTag :type="scope.row.type === 1 ? 'primary' : 'success'">
{{ scope.row.type === 1 ? '外观颜色' : '内饰颜色' }}
</ElTag>
</template>
</ElTableColumn>
<ElTableColumn label="排序" prop="sort" width="80" />
<ElTableColumn label="状态" prop="status" width="100">
<template #default="scope">
<ElTag :type="scope.row.status ? 'success' : 'info'">
{{ scope.row.status ? '启用' : '停用' }}
</ElTag>
</template>
</ElTableColumn>
<ElTableColumn label="创建时间" prop="created_at" min-width="160" />
<ElTableColumn fixed="right" label="操作" width="200px">
<template #default="scope">
<ElRow>
<el-button
link
@click="showDialog('edit', scope.row)"
v-if="hasAuth('color@update')"
>编辑</el-button
>
<el-button
type="danger"
link
@click="deleteColor(scope.row)"
v-if="hasAuth('color@destroy')"
>
删除
</el-button>
</ElRow>
</template>
</ElTableColumn>
</template>
</ArtTable>
</div>
<!-- 新增/编辑对话框 -->
<ElDialog
v-model="dialogVisible"
:title="dialogType === 'add' ? '新增颜色' : '编辑颜色'"
align-center
width="600px"
>
<ElForm ref="formRef" :model="form" :rules="rules" label-width="100px" class="form">
<ElFormItem label="颜色编码" prop="color_code">
<ElInput v-model="form.color_code" placeholder="请输入颜色编码" />
</ElFormItem>
<ElFormItem label="颜色名称" prop="color_text">
<ElInput v-model="form.color_text" placeholder="请输入颜色名称" />
</ElFormItem>
<ElFormItem label="类型" prop="type">
<el-select v-model="form.type" placeholder="请选择颜色类型" style="width: 100%">
<el-option label="外观颜色" :value="1" />
<el-option label="内饰颜色" :value="2" />
</el-select>
</ElFormItem>
<ElFormItem label="排序" prop="sort">
<ElInputNumber v-model="form.sort" :min="0" :max="999" style="width: 100%" />
</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>
</template>
<script setup lang="ts">
import { ElMessage, ElMessageBox } from 'element-plus'
import type { FormInstance, FormRules } from 'element-plus'
import { vehicleService } from '@/api/vehicleApi'
import { useAuth } from '@/composables/useAuth'
const { hasAuth } = useAuth()
defineOptions({ name: 'VehicleColor' })
// 响应式数据
const loading = ref(false)
const dialogVisible = ref(false)
const dialogType = ref('add')
const formRef = ref<FormInstance>()
const keyword = ref<string>('')
const typeFilter = ref<number | undefined>(undefined)
const statusFilter = ref<number | undefined>(undefined)
// 表单数据
const form = reactive<any>({
id: '',
color_code: '',
color_text: '',
type: 1,
status: 1,
sort: 0
})
// 表单验证规则
const rules = reactive<FormRules>({
color_code: [{ required: true, message: '请输入颜色编码', trigger: 'blur' }],
color_text: [{ required: true, message: '请输入颜色名称', trigger: 'blur' }],
type: [{ required: true, message: '请选择颜色类型', trigger: 'change' }]
})
// 分页数据
const pagination = reactive({
currentPage: 1,
pageSize: 10,
total: 0
})
// 表格数据
const tableData = ref<any[]>([])
const tableRef = ref()
// 生命周期
onMounted(() => {
getColorList()
})
// 监听搜索条件变化
watch([keyword, typeFilter, statusFilter], () => {
pagination.currentPage = 1
getColorList()
})
// 获取颜色列表
const getColorList = async () => {
loading.value = true
try {
const { currentPage, pageSize } = pagination
const params = {
current: currentPage,
size: pageSize,
keyword: keyword.value
}
const { records, current, size, total } = await vehicleService.getColorList(params)
tableData.value = records || []
Object.assign(pagination, { currentPage: current, pageSize: size, total })
} catch (error) {
console.error('获取颜色列表失败:', error)
ElMessage.error('获取颜色列表失败')
} finally {
loading.value = false
}
}
// 重置搜索条件
const resetSearch = () => {
keyword.value = ''
typeFilter.value = undefined
statusFilter.value = undefined
pagination.currentPage = 1
getColorList()
}
// 处理分页变化
const handleSizeChange = (newPageSize: number) => {
pagination.pageSize = newPageSize
getColorList()
}
const handleCurrentChange = (newCurrentPage: number) => {
pagination.currentPage = newCurrentPage
getColorList()
}
// 显示对话框
const showDialog = (type: string, row?: any) => {
dialogVisible.value = true
dialogType.value = type
if (type === 'edit' && row) {
// 编辑模式
Object.assign(form, {
id: row.id,
color_code: row.color_code,
color_text: row.color_text,
type: row.type,
status: row.status,
sort: row.sort || 0
})
} else {
// 新增模式
Object.assign(form, {
id: '',
color_code: '',
color_text: '',
type: 1,
status: 1,
sort: 0
})
}
}
// 删除颜色
const deleteColor = (row: any) => {
ElMessageBox.confirm('确定删除该颜色吗?', '删除确认', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'error'
}).then(async () => {
try {
const { message } = await vehicleService.colorDelete({ id: row.id })
ElMessage.success(message)
getColorList()
} catch (error) {
console.error('删除失败:', error)
ElMessage.error('删除失败')
}
})
}
// 提交表单
const handleSubmit = async (formEl: FormInstance | undefined) => {
if (!formEl) return
await formEl.validate(async (valid) => {
if (!valid) return
try {
if (dialogType.value === 'add') {
// 新增
const { message } = await vehicleService.colorAdd({
color_code: form.color_code,
color_text: form.color_text,
type: form.type,
status: form.status,
sort: form.sort
})
ElMessage.success(message)
} else {
// 编辑
const { message } = await vehicleService.colorEdit({
id: form.id,
color_code: form.color_code,
color_text: form.color_text,
type: form.type,
status: form.status,
sort: form.sort
})
ElMessage.success(message)
}
dialogVisible.value = false
getColorList()
} catch (error) {
console.error('操作失败:', error)
ElMessage.error('操作失败')
}
})
}
</script>
<style lang="scss" scoped>
:deep(.el-table__empty-block) {
height: 100% !important;
min-height: 200px;
display: flex;
align-items: center;
justify-content: center;
}
.color-page {
background: var(--art-main-bg-color);
min-height: 100vh;
}
.color-toolbar {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16px;
padding: 20px 20px;
background: var(--art-main-bg-color);
}
.color-search-group {
display: flex;
gap: 12px;
align-items: center;
}
.form {
padding: 20px 0;
}
.dialog-footer {
text-align: right;
}
.el-col2 {
margin-bottom: 16px;
}
</style>
+359
View File
@@ -0,0 +1,359 @@
<template>
<div class="modification-page">
<div class="modification-toolbar boxshadow">
<div class="modification-search-group">
<el-form-item label="关键字:" label-width="70px">
<el-input
style="min-width: 120px"
v-model="keyword"
placeholder="请输入关键字"
clearable
@keyup.enter="getModificationList"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="getModificationList" v-ripple>搜索</el-button>
</el-form-item>
</div>
</div>
<div class="boxshadow">
<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('modification@add')"
type="primary"
>
新增改装项目
</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="code" min-width="120" />
<ElTableColumn label="项目名称" prop="name" min-width="150" />
<ElTableColumn label="改装类型" prop="type" width="120">
<template #default="scope">
<ElTag :type="getTypeTagType(scope.row.type)">
{{ getTypeLabel(scope.row.type) }}
</ElTag>
</template>
</ElTableColumn>
<ElTableColumn label="排序" prop="sort" width="80" />
<ElTableColumn label="状态" prop="status" width="100">
<template #default="scope">
<ElTag :type="scope.row.status ? 'success' : 'info'">
{{ scope.row.status ? '启用' : '停用' }}
</ElTag>
</template>
</ElTableColumn>
<ElTableColumn label="创建时间" prop="created_at" min-width="160" />
<ElTableColumn fixed="right" label="操作" width="200px">
<template #default="scope">
<ElRow>
<el-button link @click="showDialog('edit', scope.row)">编辑</el-button>
<el-button
type="danger"
link
@click="deleteModification(scope.row)"
v-if="hasAuth('modification@destroy')"
>
删除
</el-button>
</ElRow>
</template>
</ElTableColumn>
</template>
</ArtTable>
</div>
<!-- 新增/编辑对话框 -->
<ElDialog
v-model="dialogVisible"
:title="dialogType === 'add' ? '新增改装项目' : '编辑改装项目'"
align-center
width="600px"
>
<ElForm ref="formRef" :model="form" :rules="rules" label-width="100px" class="form">
<ElFormItem label="项目编码" prop="code">
<ElInput v-model="form.code" placeholder="请输入改装项目编码" />
</ElFormItem>
<ElFormItem label="项目名称" prop="name">
<ElInput v-model="form.name" placeholder="请输入改装项目名称" />
</ElFormItem>
<ElFormItem label="改装类型" prop="type">
<el-select v-model="form.type" placeholder="请选择改装类型" style="width: 100%">
<el-option label="外观改装" :value="1" />
<el-option label="性能改装" :value="2" />
<el-option label="内饰改装" :value="3" />
<el-option label="电子设备" :value="4" />
<el-option label="其他" :value="5" />
</el-select>
</ElFormItem>
<ElFormItem label="排序" prop="sort">
<ElInputNumber v-model="form.sort" :min="0" :max="999" style="width: 100%" />
</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>
</template>
<script setup lang="ts">
import { ElMessage, ElMessageBox } from 'element-plus'
import type { FormInstance, FormRules } from 'element-plus'
import { vehicleService } from '@/api/vehicleApi'
import { useAuth } from '@/composables/useAuth'
const { hasAuth } = useAuth()
defineOptions({ name: 'VehicleModification' })
// 响应式数据
const loading = ref(false)
const dialogVisible = ref(false)
const dialogType = ref('add')
const formRef = ref<FormInstance>()
const keyword = ref<string>('')
const typeFilter = ref<number | undefined>(undefined)
const statusFilter = ref<number | undefined>(undefined)
// 表单数据
const form = reactive<any>({
id: '',
code: '',
name: '',
type: 1,
status: 1,
sort: 0
})
// 表单验证规则
const rules = reactive<FormRules>({
code: [{ required: true, message: '请输入改装项目编码', trigger: 'blur' }],
name: [{ required: true, message: '请输入改装项目名称', trigger: 'blur' }],
type: [{ required: true, message: '请选择改装类型', trigger: 'change' }]
})
// 分页数据
const pagination = reactive({
currentPage: 1,
pageSize: 10,
total: 0
})
// 表格数据
const tableData = ref<any[]>([])
const tableRef = ref()
// 生命周期
onMounted(() => {
getModificationList()
})
// 监听搜索条件变化
watch([keyword, typeFilter, statusFilter], () => {
pagination.currentPage = 1
getModificationList()
})
// 获取改装项目列表
const getModificationList = async () => {
loading.value = true
try {
const { currentPage, pageSize } = pagination
const params = {
current: currentPage,
size: pageSize,
keyword: keyword.value
}
const { records, current, size, total } = await vehicleService.getModificationList(params)
tableData.value = records || []
Object.assign(pagination, { currentPage: current, pageSize: size, total })
} catch (error) {
console.error('获取改装项目列表失败:', error)
ElMessage.error('获取改装项目列表失败')
} finally {
loading.value = false
}
}
// 处理分页变化
const handleSizeChange = (newPageSize: number) => {
pagination.pageSize = newPageSize
getModificationList()
}
const handleCurrentChange = (newCurrentPage: number) => {
pagination.currentPage = newCurrentPage
getModificationList()
}
// 显示对话框
const showDialog = (type: string, row?: any) => {
dialogVisible.value = true
dialogType.value = type
if (type === 'edit' && row) {
// 编辑模式
Object.assign(form, {
id: row.id,
code: row.code,
name: row.name,
type: row.type,
status: row.status,
sort: row.sort || 0
})
} else {
// 新增模式
Object.assign(form, {
id: '',
code: '',
name: '',
type: 1,
status: 1,
sort: 0
})
}
}
// 删除改装项目
const deleteModification = (row: any) => {
ElMessageBox.confirm('确定删除该改装项目吗?', '删除确认', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'error'
}).then(async () => {
try {
const { message } = await vehicleService.modificationDelete({ id: row.id })
ElMessage.success(message)
getModificationList()
} catch (error) {
console.error('删除失败:', error)
ElMessage.error('删除失败')
}
})
}
// 提交表单
const handleSubmit = async (formEl: FormInstance | undefined) => {
if (!formEl) return
await formEl.validate(async (valid) => {
if (!valid) return
try {
if (dialogType.value === 'add') {
// 新增
const { message } = await vehicleService.modificationAdd({
code: form.code,
name: form.name,
type: form.type,
status: form.status,
sort: form.sort
})
ElMessage.success(message)
} else {
// 编辑
const { message } = await vehicleService.modificationEdit({
id: form.id,
code: form.code,
name: form.name,
type: form.type,
status: form.status,
sort: form.sort
})
ElMessage.success(message)
}
dialogVisible.value = false
getModificationList()
} catch (error) {
console.error('操作失败:', error)
ElMessage.error('操作失败')
}
})
}
// 获取类型标签样式
const getTypeTagType = (type: number): 'primary' | 'success' | 'warning' | 'info' | 'danger' => {
const typeMap: Record<number, 'primary' | 'success' | 'warning' | 'info' | 'danger'> = {
1: 'primary', // 外观改装
2: 'success', // 性能改装
3: 'warning', // 内饰改装
4: 'info', // 电子设备
5: 'danger' // 其他
}
return typeMap[type] || 'info'
}
// 获取类型标签文本
const getTypeLabel = (type: number) => {
const typeMap: Record<number, string> = {
1: '外观改装',
2: '性能改装',
3: '内饰改装',
4: '电子设备',
5: '其他'
}
return typeMap[type] || '未知类型'
}
</script>
<style lang="scss" scoped>
:deep(.el-table__empty-block) {
height: 100% !important;
min-height: 200px;
display: flex;
align-items: center;
justify-content: center;
}
.modification-page {
background: var(--art-main-bg-color);
min-height: 100vh;
}
.modification-toolbar {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16px;
padding: 20px 20px;
background: var(--art-main-bg-color);
}
.modification-search-group {
display: flex;
gap: 12px;
align-items: center;
}
.form {
padding: 20px 0;
}
.dialog-footer {
text-align: right;
}
.el-col2 {
margin-bottom: 16px;
}
</style>
+351
View File
@@ -0,0 +1,351 @@
<template>
<div class="port-page">
<div class="port-toolbar boxshadow">
<div class="toolbar-left">
<ElButton @click="showDialog('add')" v-ripple v-if="hasAuth('port@add')" type="primary">创建备货单</ElButton>
</div>
<div class="toolbar-right">
<el-input
v-model="keyword"
placeholder="请输入客户名称"
clearable
style="width: 260px"
@keyup.enter="getPortList"
>
<template #suffix>
<el-icon class="clickable" @click="getPortList"><Search /></el-icon>
</template>
</el-input>
</div>
</div>
<div class="boxshadow">
<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="port_code" min-width="120" />
<ElTableColumn label="港口名称" prop="port_name" min-width="150" />
<ElTableColumn label="国家" prop="country_id" min-width="140">
<template #default="scope">
<span>{{ scope.row.country_name }}</span>
</template>
</ElTableColumn>
<!-- <ElTableColumn label="纬度" prop="lat" min-width="100" /> -->
<!-- <ElTableColumn label="经度" prop="lng" min-width="100" /> -->
<ElTableColumn label="排序" prop="sort" width="80" />
<ElTableColumn label="状态" prop="status" width="100">
<template #default="scope">
<ElTag :type="scope.row.status ? 'success' : 'info'">{{
scope.row.status ? '启用' : '停用'
}}</ElTag>
</template>
</ElTableColumn>
<ElTableColumn label="创建时间" prop="created_at" min-width="160" />
<ElTableColumn fixed="right" label="操作" width="200px">
<template #default="scope">
<ElRow>
<el-button link @click="showDialog('edit', scope.row)" v-if="hasAuth('port@update')"
>编辑</el-button
>
<el-button
type="danger"
link
@click="deletePort(scope.row)"
v-if="hasAuth('port@destroy')"
>删除</el-button
>
</ElRow>
</template>
</ElTableColumn>
</template>
</ArtTable>
</div>
<ElDialog
v-model="dialogVisible"
:title="dialogType === 'add' ? '新增港口' : '编辑港口'"
align-center
width="720px"
>
<ElForm ref="formRef" :model="form" :rules="rules" label-width="110px" class="form">
<ElFormItem label="港口代码" prop="port_code">
<ElInput v-model="form.port_code" placeholder="请输入港口代码" />
</ElFormItem>
<ElFormItem label="港口名称" prop="port_name">
<ElInput v-model="form.port_name" placeholder="请输入港口名称" />
</ElFormItem>
<ElFormItem label="国家" prop="country_id">
<el-select
v-model="form.country_id"
filterable
placeholder="请选择国家"
style="width: 100%"
>
<el-option
v-for="item in countryOptions"
:key="item.id"
:label="item.cn_addr"
:value="item.id"
/>
</el-select>
</ElFormItem>
<!-- <ElFormItem label="纬度" prop="lat">
<ElInput v-model="form.lat" placeholder="请输入纬度" />
</ElFormItem>
<ElFormItem label="经度" prop="lng">
<ElInput v-model="form.lng" placeholder="请输入经度" />
</ElFormItem> -->
<ElFormItem label="排序" prop="sort">
<ElInputNumber v-model="form.sort" :min="0" :max="9999" style="width: 100%" />
</ElFormItem>
<ElFormItem label="港口描述" prop="description">
<ElInput
v-model="form.description"
type="textarea"
:rows="3"
maxlength="300"
show-word-limit
placeholder="请输入港口描述"
/>
</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>
</template>
<script setup lang="ts">
import { ElMessage, ElMessageBox } from 'element-plus'
import type { FormInstance, FormRules } from 'element-plus'
import { vehicleService } from '@/api/vehicleApi'
import { commonService } from '@/api/commonApi'
import { useAuth } from '@/composables/useAuth'
const { hasAuth } = useAuth()
defineOptions({ name: 'VehiclePort' })
const loading = ref(false)
const dialogVisible = ref(false)
const dialogType = ref<'add' | 'edit'>('add')
const formRef = ref<FormInstance>()
const keyword = ref<string>('')
const countryFilter = ref<number | undefined>(undefined)
const statusFilter = ref<number | undefined>(undefined)
const countryOptions = ref<any[]>([])
const loadCountryOptions = async () => {
try {
const res = await commonService.getCountrySelect()
countryOptions.value = res || []
} catch (e) {
countryOptions.value = []
}
}
const form = reactive<any>({
id: '',
status: 1,
sort: 0,
port_code: '',
port_name: '',
lat: '',
lng: '',
description: '',
country_id: undefined
})
const rules = reactive<FormRules>({
port_code: [{ required: true, message: '请输入港口代码', trigger: 'blur' }],
port_name: [{ required: true, message: '请输入港口名称', trigger: 'blur' }],
country_id: [{ required: true, message: '请选择国家', trigger: 'change' }]
})
const pagination = reactive({
currentPage: 1,
pageSize: 10,
total: 0
})
const tableData = ref<any[]>([])
const tableRef = ref()
onMounted(() => {
loadCountryOptions()
getPortList()
})
watch([keyword, countryFilter, statusFilter], () => {
pagination.currentPage = 1
getPortList()
})
const getPortList = async () => {
loading.value = true
try {
const { currentPage, pageSize } = pagination
const params = {
current: currentPage,
size: pageSize,
keyword: keyword.value
}
const { records, current, size, total } = await vehicleService.getPortList(params)
tableData.value = records || []
Object.assign(pagination, { currentPage: current, pageSize: size, total })
} catch (error) {
console.error('获取港口列表失败:', error)
ElMessage.error('获取港口列表失败')
} finally {
loading.value = false
}
}
const handleSizeChange = (newPageSize: number) => {
pagination.pageSize = newPageSize
getPortList()
}
const handleCurrentChange = (newCurrentPage: number) => {
pagination.currentPage = newCurrentPage
getPortList()
}
const showDialog = (type: 'add' | 'edit', row?: any) => {
dialogVisible.value = true
dialogType.value = type
if (type === 'edit' && row) {
Object.assign(form, {
id: row.id,
status: row.status,
sort: row.sort ?? 0,
port_code: row.port_code,
port_name: row.port_name,
lat: row.lat,
lng: row.lng,
description: row.description,
country_id: row.country_id
})
} else {
Object.assign(form, {
id: '',
status: 1,
sort: 0,
port_code: '',
port_name: '',
lat: '',
lng: '',
description: '',
country_id: undefined
})
}
}
const deletePort = (row: any) => {
ElMessageBox.confirm('确定删除该港口吗?', '删除确认', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'error'
}).then(async () => {
try {
const { message } = await vehicleService.portDelete({ id: row.id })
ElMessage.success(message)
getPortList()
} catch (error) {
console.error('删除失败:', error)
ElMessage.error('删除失败')
}
})
}
const handleSubmit = async (formEl: FormInstance | undefined) => {
if (!formEl) return
await formEl.validate(async (valid) => {
if (!valid) return
try {
if (dialogType.value === 'add') {
const { message } = await vehicleService.portAdd({
status: form.status,
sort: form.sort,
port_code: form.port_code,
port_name: form.port_name,
lat: form.lat,
lng: form.lng,
description: form.description,
country_id: form.country_id
})
ElMessage.success(message)
} else {
const { message } = await vehicleService.portEdit({
id: form.id,
status: form.status,
sort: form.sort,
port_code: form.port_code,
port_name: form.port_name,
lat: form.lat,
lng: form.lng,
description: form.description,
country_id: form.country_id
})
ElMessage.success(message)
}
dialogVisible.value = false
getPortList()
} catch (error) {
console.error('操作失败:', error)
ElMessage.error('操作失败')
}
})
}
const getCountryLabel = (id: number | undefined) => {
if (!id) return '-'
const found = countryOptions.value.find((c: any) => c.id === id)
return found ? found.name : id
}
</script>
<style lang="scss" scoped>
:deep(.el-table__empty-block) {
height: 100% !important;
min-height: 200px;
display: flex;
align-items: center;
justify-content: center;
}
.port-page {
background: var(--art-main-bg-color);
min-height: 100vh;
}
.port-toolbar {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16px;
padding: 20px 20px;
background: var(--art-main-bg-color);
}
.port-search-group {
display: flex;
gap: 12px;
align-items: center;
}
.form {
padding: 20px 0;
}
.dialog-footer {
text-align: right;
}
.el-col2 {
margin-bottom: 16px;
}
</style>
File diff suppressed because it is too large Load Diff