887 lines
24 KiB
Vue
887 lines
24 KiB
Vue
<script setup>
|
||
import { ref, reactive, onMounted, onUnmounted, computed } from 'vue'
|
||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||
import { Plus, PictureFilled, Delete } from '@element-plus/icons-vue'
|
||
import * as echarts from 'echarts'
|
||
import { sortArrayByField, reverseArray } from '@/utils/sort'
|
||
import { getSpeciesList, addSpecies, updateSpecies, getSpeciesStatistics, updateSpeciesStatus } from '@/api/monitor/species'
|
||
import { ElImageViewer } from 'element-plus'
|
||
|
||
// 查询参数
|
||
const queryParams = reactive({
|
||
page: 1,
|
||
page_size: 10,
|
||
species_code: '',
|
||
chinese_name: '',
|
||
category: undefined,
|
||
protection_level: undefined,
|
||
status: undefined
|
||
})
|
||
|
||
// 数据列表
|
||
const loading = ref(false)
|
||
const speciesList = ref([])
|
||
const pagination = reactive({
|
||
total: 0,
|
||
page: 1,
|
||
pageSize: 10
|
||
})
|
||
|
||
// 物种类别选项
|
||
const categoryOptions = [
|
||
{ label: '鸟类', value: 'bird' },
|
||
{ label: '哺乳类', value: 'mammal' },
|
||
{ label: '鱼类', value: 'fish' },
|
||
{ label: '两栖类', value: 'amphibian' },
|
||
{ label: '爬行类', value: 'reptile' },
|
||
{ label: '昆虫类', value: 'insect' },
|
||
{ label: '植物', value: 'plant' }
|
||
]
|
||
|
||
// 保护等级选项
|
||
const protectionLevelOptions = [
|
||
{ label: '国家一级', value: 'national_first' },
|
||
{ label: '国家二级', value: 'national_second' },
|
||
{ label: '省级', value: 'provincial' },
|
||
{ label: '普通', value: 'normal' }
|
||
]
|
||
|
||
// 弹窗显示控制
|
||
const dialogVisible = ref(false)
|
||
const dialogType = ref('add') // 'add' 或 'edit'
|
||
const dialogTitle = ref('添加物种')
|
||
|
||
// 表单数据
|
||
const formRef = ref()
|
||
const formData = ref({
|
||
chinese_name: '',
|
||
latin_name: '',
|
||
category: '',
|
||
protection_level: 'national_first',
|
||
characteristics: '',
|
||
habits: '',
|
||
distribution: '',
|
||
image_urls: [],
|
||
images: [],
|
||
fileList: [],
|
||
status: 1
|
||
})
|
||
|
||
// 表单校验规则
|
||
const rules = {
|
||
chinese_name: [
|
||
{ required: true, message: '请输入中文名称', trigger: 'blur' },
|
||
{ max: 100, message: '长度不能超过100个字符', trigger: 'blur' }
|
||
],
|
||
latin_name: [
|
||
{ required: true, message: '请输入拉丁名称', trigger: 'blur' }
|
||
],
|
||
category: [
|
||
{ required: true, message: '请选择物种类别', trigger: 'change' }
|
||
],
|
||
protection_level: [
|
||
{ required: true, message: '请选择保护等级', trigger: 'change' }
|
||
],
|
||
characteristics: [
|
||
{ required: true, message: '请输入特征描述', trigger: 'blur' }
|
||
],
|
||
habits: [
|
||
{ required: true, message: '请输入生活习性', trigger: 'blur' }
|
||
],
|
||
distribution: [
|
||
{ required: true, message: '请输入分布区域', trigger: 'blur' }
|
||
]
|
||
}
|
||
|
||
// 添加统计信息的响应式数据
|
||
const statistics = ref({
|
||
categories: {},
|
||
protection_levels: {}
|
||
})
|
||
|
||
// 图表实例
|
||
const protectionChartRef = ref(null)
|
||
let protectionChart = null
|
||
|
||
// 基础URL
|
||
const baseUrl = computed(() => import.meta.env.VITE_API_BASE_URL || '')
|
||
|
||
// 获取完整的图片URL
|
||
const getFullImageUrl = (url) => {
|
||
if (!url) return ''
|
||
if (url.startsWith('http')) return url
|
||
if (url.startsWith('data:')) return url
|
||
if (url.startsWith('blob:')) return url
|
||
|
||
// 移除URL开头的斜杠,避免重复
|
||
const cleanUrl = url.startsWith('/') ? url.slice(1) : url
|
||
// 确保不会重复添加 uploads 路径
|
||
if (cleanUrl.startsWith('uploads/')) {
|
||
return `${baseUrl.value}/${cleanUrl}`
|
||
}
|
||
return `${baseUrl.value}/uploads/${cleanUrl}`
|
||
}
|
||
|
||
// 初始化图表
|
||
const initCharts = () => {
|
||
if (protectionChartRef.value) {
|
||
protectionChart = echarts.init(protectionChartRef.value)
|
||
}
|
||
}
|
||
|
||
// 更新图表数据
|
||
const updateCharts = () => {
|
||
// 保护等级图表数据
|
||
const protectionData = Object.entries(statistics.value.protection_levels)
|
||
.filter(([_, count]) => count > 0)
|
||
.map(([level, count]) => ({
|
||
name: protectionLevelOptions.find(item => item.value === level)?.label || level,
|
||
value: count
|
||
}))
|
||
.sort((a, b) => b.value - a.value)
|
||
|
||
// 设置保护等级图表
|
||
protectionChart?.setOption({
|
||
title: {
|
||
text: '保护等级统计',
|
||
left: 'center'
|
||
},
|
||
tooltip: {
|
||
trigger: 'axis',
|
||
axisPointer: {
|
||
type: 'shadow'
|
||
},
|
||
formatter: '{b}: {c}种'
|
||
},
|
||
grid: {
|
||
left: '3%',
|
||
right: '4%',
|
||
bottom: '10%',
|
||
containLabel: true
|
||
},
|
||
xAxis: {
|
||
type: 'category',
|
||
data: protectionData.map(item => item.name),
|
||
axisLabel: {
|
||
interval: 0,
|
||
rotate: 30
|
||
}
|
||
},
|
||
yAxis: {
|
||
type: 'value',
|
||
name: '物种数量',
|
||
minInterval: 1
|
||
},
|
||
series: [
|
||
{
|
||
name: '物种数量',
|
||
type: 'bar',
|
||
barWidth: '40%',
|
||
data: protectionData.map(item => ({
|
||
value: item.value,
|
||
itemStyle: {
|
||
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||
{ offset: 0, color: '#83bff6' },
|
||
{ offset: 0.5, color: '#409EFF' },
|
||
{ offset: 1, color: '#2c76c5' }
|
||
])
|
||
}
|
||
})),
|
||
label: {
|
||
show: true,
|
||
position: 'top',
|
||
formatter: '{c}种'
|
||
}
|
||
}
|
||
]
|
||
})
|
||
}
|
||
|
||
// 监听窗口大小变化
|
||
const handleResize = () => {
|
||
protectionChart?.resize()
|
||
}
|
||
|
||
// 获取列表数据
|
||
const getList = async () => {
|
||
loading.value = true
|
||
try {
|
||
// 处理查询参数,移除中文名称,因为我们将在前端过滤
|
||
const params = {
|
||
...queryParams,
|
||
category: queryParams.category || undefined,
|
||
protection_level: queryParams.protection_level || undefined,
|
||
status: queryParams.status === '' ? undefined : queryParams.status
|
||
}
|
||
|
||
// 移除所有 undefined 的参数和中文名称参数
|
||
delete params.chinese_name
|
||
Object.keys(params).forEach(key =>
|
||
params[key] === undefined && delete params[key]
|
||
)
|
||
|
||
const res = await getSpeciesList(params)
|
||
if (res.success && res.data) {
|
||
// 确保数据是数组
|
||
let list = Array.isArray(res.data.list) ? [...res.data.list] : []
|
||
|
||
// 如果有中文名称搜索条件,在前端进行过滤
|
||
const searchText = queryParams.chinese_name?.trim().toLowerCase()
|
||
if (searchText) {
|
||
list = list.filter(item =>
|
||
item.chinese_name?.toLowerCase().includes(searchText)
|
||
)
|
||
}
|
||
|
||
// 反转数组顺序
|
||
speciesList.value = reverseArray(list)
|
||
|
||
// 更新分页信息
|
||
if (res.data.pagination) {
|
||
// 使用过滤后的数据长度作为总数
|
||
pagination.total = searchText ? list.length : Number(res.data.pagination.total) || 0
|
||
pagination.page = Number(res.data.pagination.current) || 1
|
||
pagination.pageSize = Number(res.data.pagination.page_size) || 10
|
||
}
|
||
} else {
|
||
speciesList.value = []
|
||
ElMessage.error(res.message || '获取数据失败')
|
||
}
|
||
} catch (error) {
|
||
console.error('获取物种列表失败:', error)
|
||
speciesList.value = []
|
||
ElMessage.error('获取物种列表失败')
|
||
} finally {
|
||
loading.value = false
|
||
}
|
||
}
|
||
|
||
// 重置查询
|
||
const resetQuery = () => {
|
||
queryParams.species_code = ''
|
||
queryParams.chinese_name = ''
|
||
queryParams.category = undefined
|
||
queryParams.protection_level = undefined
|
||
queryParams.status = undefined
|
||
getList()
|
||
}
|
||
|
||
// 处理添加/编辑
|
||
const handleAddOrEdit = (type, row) => {
|
||
dialogType.value = type
|
||
dialogTitle.value = type === 'add' ? '添加物种' : '编辑物种'
|
||
dialogVisible.value = true
|
||
|
||
if (type === 'edit' && row) {
|
||
formData.value = { ...row }
|
||
// 为已有图片创建文件列表
|
||
const fileList = []
|
||
if (row.image_urls && Array.isArray(row.image_urls)) {
|
||
row.image_urls.forEach((url, index) => {
|
||
const fullUrl = getFullImageUrl(url)
|
||
fileList.push({
|
||
uid: `-${index}`, // 添加唯一标识
|
||
name: url.split('/').pop(),
|
||
url: fullUrl,
|
||
status: 'success',
|
||
response: { url: fullUrl }
|
||
})
|
||
})
|
||
}
|
||
formData.value.fileList = fileList
|
||
formData.value.image_urls = row.image_urls || []
|
||
formData.value.images = []
|
||
} else {
|
||
formData.value = {
|
||
chinese_name: '',
|
||
latin_name: '',
|
||
category: '',
|
||
protection_level: 'national_first',
|
||
characteristics: '',
|
||
habits: '',
|
||
distribution: '',
|
||
image_urls: [],
|
||
images: [],
|
||
fileList: [],
|
||
status: 1
|
||
}
|
||
}
|
||
}
|
||
|
||
// 处理文件上传
|
||
const handleFileUpload = (uploadFile) => {
|
||
const file = uploadFile.raw || uploadFile
|
||
if (!file) return
|
||
|
||
// 验证文件类型
|
||
const isImage = file.type.startsWith('image/')
|
||
if (!isImage) {
|
||
ElMessage.error('只能上传图片文件!')
|
||
return false
|
||
}
|
||
|
||
// 验证文件大小(5MB)
|
||
const isLt5M = file.size / 1024 / 1024 < 5
|
||
if (!isLt5M) {
|
||
ElMessage.error('图片大小不能超过 5MB!')
|
||
return false
|
||
}
|
||
|
||
// 添加到图片列表
|
||
if (!formData.value.images.some(f => f.uid === file.uid)) {
|
||
formData.value.images.push(file)
|
||
}
|
||
return true
|
||
}
|
||
|
||
// 处理文件移除
|
||
const handleFileRemove = (uploadFile) => {
|
||
// 如果是新上传的文件,从images中移除
|
||
const imageIndex = formData.value.images.findIndex(file => file.uid === uploadFile.uid)
|
||
if (imageIndex !== -1) {
|
||
formData.value.images.splice(imageIndex, 1)
|
||
}
|
||
|
||
// 如果是已有的图片,从image_urls中移除
|
||
if (uploadFile.url) {
|
||
const urlIndex = formData.value.image_urls.findIndex(url => getFullImageUrl(url) === uploadFile.url)
|
||
if (urlIndex !== -1) {
|
||
formData.value.image_urls.splice(urlIndex, 1)
|
||
}
|
||
}
|
||
}
|
||
|
||
// 提交表单
|
||
const submitForm = async () => {
|
||
if (!formRef.value) return
|
||
|
||
await formRef.value.validate(async (valid) => {
|
||
if (valid) {
|
||
try {
|
||
// 创建 FormData 对象
|
||
const formDataObj = new FormData()
|
||
|
||
// 添加基本字段
|
||
formDataObj.append('chinese_name', formData.value.chinese_name)
|
||
formDataObj.append('latin_name', formData.value.latin_name)
|
||
formDataObj.append('category', formData.value.category)
|
||
formDataObj.append('protection_level', formData.value.protection_level)
|
||
formDataObj.append('characteristics', formData.value.characteristics)
|
||
formDataObj.append('habits', formData.value.habits)
|
||
formDataObj.append('distribution', formData.value.distribution)
|
||
formDataObj.append('status', formData.value.status)
|
||
|
||
// 添加已有的图片URL
|
||
formData.value.image_urls.forEach(url => {
|
||
formDataObj.append('existing_image_urls', url)
|
||
})
|
||
|
||
// 添加新上传的图片文件
|
||
formData.value.images.forEach((file) => {
|
||
formDataObj.append('image_urls', file)
|
||
})
|
||
|
||
// 调用API
|
||
const api = dialogType.value === 'add' ? addSpecies : (id) => updateSpecies(formData.value.id, formDataObj)
|
||
await api(formDataObj)
|
||
|
||
ElMessage.success(dialogType.value === 'add' ? '添加成功' : '修改成功')
|
||
dialogVisible.value = false
|
||
getList()
|
||
} catch (error) {
|
||
console.error('提交失败:', error)
|
||
ElMessage.error('提交失败')
|
||
}
|
||
}
|
||
})
|
||
}
|
||
|
||
// 获取统计信息
|
||
const getStatistics = async () => {
|
||
try {
|
||
const res = await getSpeciesStatistics()
|
||
statistics.value = res.data
|
||
// 更新图表数据
|
||
updateCharts()
|
||
} catch (error) {
|
||
console.error('获取统计信息失败:', error)
|
||
}
|
||
}
|
||
|
||
// 处理状态更新
|
||
const handleStatusChange = async (row) => {
|
||
const newStatus = row.status === 1 ? 0 : 1
|
||
try {
|
||
await updateSpeciesStatus(row.id, { status: newStatus })
|
||
ElMessage.success('状态更新成功')
|
||
getList()
|
||
} catch (error) {
|
||
console.error('状态更新失败:', error)
|
||
ElMessage.error('状态更新失败')
|
||
}
|
||
}
|
||
|
||
// 处理页码改变
|
||
const handleCurrentChange = (val) => {
|
||
queryParams.page = Number(val)
|
||
getList()
|
||
}
|
||
|
||
// 处理每页条数改变
|
||
const handleSizeChange = (val) => {
|
||
queryParams.page_size = Number(val)
|
||
queryParams.page = 1
|
||
getList()
|
||
}
|
||
|
||
// 添加排序处理函数
|
||
const handleSortChange = ({ prop, order }) => {
|
||
if (prop && order) {
|
||
const sortOrder = order === 'ascending' ? 'asc' : 'desc'
|
||
const sortedList = sortArrayByField(speciesList.value, prop, sortOrder)
|
||
speciesList.value = sortedList
|
||
}
|
||
}
|
||
|
||
onMounted(() => {
|
||
getList()
|
||
// 初始化图表
|
||
initCharts()
|
||
getStatistics()
|
||
window.addEventListener('resize', handleResize)
|
||
})
|
||
|
||
onUnmounted(() => {
|
||
// 销毁图表实例
|
||
protectionChart?.dispose()
|
||
window.removeEventListener('resize', handleResize)
|
||
})
|
||
</script>
|
||
|
||
<template>
|
||
<div class="app-container">
|
||
<!-- 统计信息展示 -->
|
||
<el-row :gutter="20" class="statistics-container">
|
||
<el-col :span="24">
|
||
<el-card>
|
||
<div ref="protectionChartRef" style="height: 400px"></div>
|
||
</el-card>
|
||
</el-col>
|
||
</el-row>
|
||
|
||
<!-- 搜索区域 -->
|
||
<el-card class="search-container">
|
||
<el-form :model="queryParams" ref="queryForm" :inline="true">
|
||
<el-form-item label="中文名称" prop="chinese_name">
|
||
<el-input
|
||
v-model="queryParams.chinese_name"
|
||
placeholder="请输入中文名称"
|
||
clearable
|
||
style="width: 200px"
|
||
/>
|
||
</el-form-item>
|
||
<el-form-item label="物种类别" prop="category">
|
||
<el-select
|
||
v-model="queryParams.category"
|
||
placeholder="请选择物种类别"
|
||
clearable
|
||
style="width: 200px"
|
||
>
|
||
<el-option
|
||
v-for="item in categoryOptions"
|
||
:key="item.value"
|
||
:label="item.label"
|
||
:value="item.value"
|
||
/>
|
||
</el-select>
|
||
</el-form-item>
|
||
<el-form-item label="保护等级" prop="protection_level">
|
||
<el-select
|
||
v-model="queryParams.protection_level"
|
||
placeholder="请选择保护等级"
|
||
clearable
|
||
style="width: 200px"
|
||
>
|
||
<el-option
|
||
v-for="item in protectionLevelOptions"
|
||
:key="item.value"
|
||
:label="item.label"
|
||
:value="item.value"
|
||
/>
|
||
</el-select>
|
||
</el-form-item>
|
||
<el-form-item>
|
||
<el-button type="primary" @click="getList">查询</el-button>
|
||
<el-button @click="resetQuery">重置</el-button>
|
||
</el-form-item>
|
||
</el-form>
|
||
</el-card>
|
||
|
||
<!-- 操作按钮区域 -->
|
||
<el-card class="table-container">
|
||
<template #header>
|
||
<el-button type="primary" @click="handleAddOrEdit('add')">添加物种</el-button>
|
||
</template>
|
||
|
||
<!-- 表格区域 -->
|
||
<el-table
|
||
v-loading="loading"
|
||
:data="speciesList"
|
||
border
|
||
style="width: 100%"
|
||
row-key="id"
|
||
>
|
||
<el-table-column type="index" label="序号" width="60" align="center" />
|
||
<el-table-column prop="species_code" label="物种编号" width="120" align="center" />
|
||
<el-table-column prop="chinese_name" label="中文名称" width="150" show-overflow-tooltip align="center"/>
|
||
<el-table-column prop="latin_name" label="拉丁名称" width="180" show-overflow-tooltip align="center"/>
|
||
<el-table-column prop="category" label="物种类别" width="100" align="center">
|
||
<template #default="{ row }">
|
||
{{ categoryOptions.find(item => item.value === row.category)?.label || '-' }}
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column prop="protection_level" label="保护等级" width="120" align="center">
|
||
<template #default="{ row }">
|
||
{{ protectionLevelOptions.find(item => item.value === row.protection_level)?.label || '-' }}
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column prop="characteristics" label="特征描述" show-overflow-tooltip align="center"/>
|
||
<el-table-column label="图片" width="100" align="center">
|
||
<template #default="{ row }">
|
||
<el-image
|
||
v-if="row.image_urls && row.image_urls.length > 0"
|
||
:src="getFullImageUrl(row.image_urls[0])"
|
||
:preview-src-list="row.image_urls.map(url => getFullImageUrl(url))"
|
||
preview-teleported
|
||
:initial-index="0"
|
||
fit="cover"
|
||
class="species-image"
|
||
loading="lazy"
|
||
:hide-on-click-modal="false"
|
||
crossorigin="anonymous"
|
||
referrerpolicy="no-referrer"
|
||
>
|
||
<template #error>
|
||
<div class="image-error">
|
||
<el-icon><picture-filled /></el-icon>
|
||
</div>
|
||
</template>
|
||
</el-image>
|
||
<div v-else class="no-image">
|
||
<el-icon><picture-filled /></el-icon>
|
||
</div>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="状态" width="80" align="center">
|
||
<template #default="{ row }">
|
||
<el-tag :type="row.status === 1 ? 'success' : 'info'">
|
||
{{ row.status === 1 ? '启用' : '禁用' }}
|
||
</el-tag>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="操作" width="120" fixed="right" align="center">
|
||
<template #default="{ row }">
|
||
<el-button type="primary" link @click="handleAddOrEdit('edit', row)">编辑</el-button>
|
||
</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
|
||
<!-- 分页 -->
|
||
<div class="pagination-container">
|
||
<el-pagination
|
||
v-model:current-page="pagination.page"
|
||
v-model:page-size="pagination.pageSize"
|
||
:page-sizes="[10, 20, 50, 100]"
|
||
:total="pagination.total"
|
||
:background="true"
|
||
layout="total, sizes, prev, pager, next, jumper"
|
||
@size-change="handleSizeChange"
|
||
@current-change="handleCurrentChange"
|
||
/>
|
||
</div>
|
||
</el-card>
|
||
|
||
<!-- 添加/编辑弹窗 -->
|
||
<el-dialog
|
||
v-model="dialogVisible"
|
||
:title="dialogTitle"
|
||
width="700px"
|
||
append-to-body
|
||
>
|
||
<el-form
|
||
ref="formRef"
|
||
:model="formData"
|
||
:rules="rules"
|
||
label-width="100px"
|
||
>
|
||
<el-form-item label="中文名称" prop="chinese_name">
|
||
<el-input v-model="formData.chinese_name" placeholder="请输入中文名称" />
|
||
</el-form-item>
|
||
<el-form-item label="拉丁名称" prop="latin_name">
|
||
<el-input v-model="formData.latin_name" placeholder="请输入拉丁名称" />
|
||
</el-form-item>
|
||
<el-form-item label="物种类别" prop="category">
|
||
<el-select v-model="formData.category" placeholder="请选择物种类别" style="width: 100%">
|
||
<el-option
|
||
v-for="item in categoryOptions"
|
||
:key="item.value"
|
||
:label="item.label"
|
||
:value="item.value"
|
||
/>
|
||
</el-select>
|
||
</el-form-item>
|
||
<el-form-item label="保护等级" prop="protection_level">
|
||
<el-select v-model="formData.protection_level" placeholder="请选择保护等级" style="width: 100%">
|
||
<el-option
|
||
v-for="item in protectionLevelOptions"
|
||
:key="item.value"
|
||
:label="item.label"
|
||
:value="item.value"
|
||
/>
|
||
</el-select>
|
||
</el-form-item>
|
||
<el-form-item label="特征描述" prop="characteristics">
|
||
<el-input
|
||
v-model="formData.characteristics"
|
||
type="textarea"
|
||
:rows="3"
|
||
placeholder="请输入特征描述"
|
||
/>
|
||
</el-form-item>
|
||
<el-form-item label="生活习性" prop="habits">
|
||
<el-input
|
||
v-model="formData.habits"
|
||
type="textarea"
|
||
:rows="3"
|
||
placeholder="请输入生活习性"
|
||
/>
|
||
</el-form-item>
|
||
<el-form-item label="分布区域" prop="distribution">
|
||
<el-input
|
||
v-model="formData.distribution"
|
||
type="textarea"
|
||
:rows="3"
|
||
placeholder="请输入分布区域"
|
||
/>
|
||
</el-form-item>
|
||
<el-form-item label="图片" prop="image_urls">
|
||
<el-upload
|
||
action="#"
|
||
list-type="picture-card"
|
||
:auto-upload="false"
|
||
:on-change="handleFileUpload"
|
||
:on-remove="handleFileRemove"
|
||
:file-list="formData.fileList"
|
||
accept=".jpg,.jpeg,.png"
|
||
:multiple="true"
|
||
:before-upload="() => false"
|
||
:http-request="() => {}"
|
||
>
|
||
<el-icon><Plus /></el-icon>
|
||
<template #file="{ file }">
|
||
<div class="upload-file-card">
|
||
<img
|
||
:src="file.url"
|
||
class="upload-file-image"
|
||
crossorigin="anonymous"
|
||
referrerpolicy="no-referrer"
|
||
/>
|
||
<span class="upload-file-actions">
|
||
<el-icon @click.stop="handleFileRemove(file)"><Delete /></el-icon>
|
||
</span>
|
||
</div>
|
||
</template>
|
||
</el-upload>
|
||
</el-form-item>
|
||
<el-form-item label="状态" prop="status">
|
||
<el-radio-group v-model="formData.status">
|
||
<el-radio :value="1">启用</el-radio>
|
||
<el-radio :value="0">禁用</el-radio>
|
||
</el-radio-group>
|
||
</el-form-item>
|
||
</el-form>
|
||
<template #footer>
|
||
<div class="dialog-footer">
|
||
<el-button @click="dialogVisible = false">取 消</el-button>
|
||
<el-button type="primary" @click="submitForm">确 定</el-button>
|
||
</div>
|
||
</template>
|
||
</el-dialog>
|
||
</div>
|
||
</template>
|
||
|
||
<style lang="scss" scoped>
|
||
.app-container {
|
||
padding: 20px;
|
||
|
||
.search-container {
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
.table-container {
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
.pagination-container {
|
||
margin-top: 20px;
|
||
display: flex;
|
||
justify-content: flex-end;
|
||
}
|
||
|
||
:deep(.el-card__header) {
|
||
padding: 10px 20px;
|
||
}
|
||
|
||
.statistics-container {
|
||
margin-bottom: 20px;
|
||
|
||
.card-header {
|
||
font-weight: bold;
|
||
}
|
||
|
||
.statistics-content {
|
||
.statistics-item {
|
||
display: flex;
|
||
align-items: center;
|
||
margin-bottom: 10px;
|
||
|
||
span {
|
||
margin-right: 10px;
|
||
}
|
||
|
||
&:last-child {
|
||
margin-bottom: 0;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
.species-image {
|
||
width: 60px;
|
||
height: 60px;
|
||
border-radius: 4px;
|
||
cursor: pointer;
|
||
object-fit: cover;
|
||
transition: all 0.3s;
|
||
border: 1px solid #e4e7ed;
|
||
|
||
&:hover {
|
||
transform: scale(1.05);
|
||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
|
||
}
|
||
}
|
||
|
||
.image-error,
|
||
.no-image {
|
||
width: 60px;
|
||
height: 60px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
background-color: #f5f7fa;
|
||
border-radius: 4px;
|
||
border: 1px dashed #dcdfe6;
|
||
color: #909399;
|
||
|
||
.el-icon {
|
||
font-size: 20px;
|
||
}
|
||
}
|
||
|
||
.image-preview {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 10px;
|
||
margin-top: 10px;
|
||
|
||
.preview-image {
|
||
width: 100px;
|
||
height: 100px;
|
||
border-radius: 4px;
|
||
object-fit: cover;
|
||
cursor: pointer;
|
||
transition: all 0.3s;
|
||
border: 1px solid #e4e7ed;
|
||
|
||
&:hover {
|
||
transform: scale(1.05);
|
||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
|
||
}
|
||
}
|
||
}
|
||
|
||
.upload-file-card {
|
||
position: relative;
|
||
width: 100%;
|
||
height: 100%;
|
||
|
||
.upload-file-image {
|
||
width: 100%;
|
||
height: 100%;
|
||
object-fit: cover;
|
||
}
|
||
|
||
.upload-file-actions {
|
||
position: absolute;
|
||
top: 0;
|
||
right: 0;
|
||
padding: 4px;
|
||
background-color: rgba(0, 0, 0, 0.5);
|
||
border-radius: 0 4px 0 4px;
|
||
opacity: 0;
|
||
transition: opacity 0.3s;
|
||
|
||
.el-icon {
|
||
color: #fff;
|
||
font-size: 16px;
|
||
cursor: pointer;
|
||
|
||
&:hover {
|
||
color: #f56c6c;
|
||
}
|
||
}
|
||
}
|
||
|
||
&:hover {
|
||
.upload-file-actions {
|
||
opacity: 1;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
.dialog-footer {
|
||
text-align: right;
|
||
padding-top: 20px;
|
||
}
|
||
|
||
:deep(.el-image-viewer__wrapper) {
|
||
.el-image-viewer__close {
|
||
color: #fff;
|
||
font-size: 30px;
|
||
|
||
&:hover {
|
||
color: #409EFF;
|
||
}
|
||
}
|
||
|
||
.el-image-viewer__actions {
|
||
opacity: 1;
|
||
background-color: rgba(0, 0, 0, 0.7);
|
||
}
|
||
|
||
.el-image-viewer__prev,
|
||
.el-image-viewer__next {
|
||
font-size: 36px;
|
||
color: #fff;
|
||
|
||
&:hover {
|
||
color: #409EFF;
|
||
}
|
||
}
|
||
|
||
.el-image-viewer__mask {
|
||
background-color: rgba(0, 0, 0, 0.8);
|
||
}
|
||
}
|
||
</style> |