Element Plus进阶-第五篇:布局与响应式进阶

Element Plus进阶-第五篇:布局与响应式进阶

在前端开发中,构建高度响应式且美观的页面是提升用户体验的关键。Element Plus 提供了丰富的布局工具和灵活的样式机制,使开发者能够轻松应对各种复杂的页面布局和响应式需求。本文将深入探讨如何运用 Grid 布局构建复杂的页面结构,以及结合媒体查询和 CSS 变量实现 Element Plus 组件在不同设备上的自适应主题切换。

一、运用 Grid 布局构建高度响应式且复杂的页面结构

(一)Grid 布局基础回顾

Element Plus 的 Grid 布局基于 CSS Grid 规范,通过 el-rowel-col 组件实现行列布局。el-row 表示行容器,el-col 表示列容器,el-colspan 属性用于设置列的宽度,取值范围为 1 到 24,24 表示占满整行。

<template>
  <el-row :gutter="20">
    <el-col :span="8">
      <div class="grid-item">列 1</div>
    </el-col>
    <el-col :span="8">
      <div c
vue3前端页面<template> <!-- 权限检查:只有 SYSADMIN 能看到页面内容 --> <div v-if="form.usepermission === &#39;SYSADMIN&#39;"> <div style="width: 100%; overflow-x: auto; padding: 16px; box-sizing: border-box;"> <!-- 查询区域:添加Flex布局实现左右分布 --> <div class="search-area"> <el-form :inline="true" :model="queryParams" class="demo-form-inline"> <!-- 左侧区域:关键词、查询、重置 --> <div class="form-left"> <el-form-item label="关键词"> <el-input v-model="queryParams.keyword" placeholder="课程关键词/工号/姓名"></el-input> </el-form-item> <el-form-item> <el-button type="primary" @click="handleQuery" icon="el-icon-search">查询</el-button> </el-form-item> <el-form-item> <el-button @click="handleReset">重置</el-button> </el-form-item> </div> <!-- 右侧区域:审核按钮、下载按钮 --> <div class="form-right"> <el-form-item> <el-button type="primary" @click="handleParticipate"> {{ isAuditMode ? &#39;取消审核&#39; : &#39;参训审核&#39; }} </el-button> </el-form-item> <el-form-item> <el-button @click="handleDownload">DOWNLOAD <i class="el-icon-download"></i></el-button> </el-form-item> </div> </el-form> </div> <!-- 当前筛选条件显示 --> <div v-if="hasActiveFilters" class="active-filters"> <span class="filters-label">当前筛选:</span> <el-tag v-for="(value, column) in filters" :key="column" closable type="primary" @close="clearFilter(column)" class="filter-tag" > {{ getColumnLabel(column) }}: {{ value }} </el-tag> <el-button type="text" @click="clearAllFilters" class="clear-all">清除所有</el-button> </div> <!-- 表格区域 --> <div class="table-area"> <el-table ref="multipleTableRef" :data="filteredTableData" border style="width: 100%" @selection-change="handleSelectionChange" > <el-table-column type="selection" width="35"></el-table-column> <!-- 序号列 --> <el-table-column prop="id" label="序号" width="55"> <template #header> <div class="filter-header" :class="{ &#39;filter-active&#39;: filters.id }"> <span>序号</span> <el-dropdown trigger="click" @command="handleFilter"> <el-icon :class="{ &#39;filter-icon-active&#39;: filters.id }"><Filter /></el-icon> <template #dropdown> <el-dropdown-menu> <el-dropdown-item :command="{ column: &#39;id&#39;, value: &#39;all&#39; }" :class="{ &#39;dropdown-item-active&#39;: !filters.id }" > 全部 </el-dropdown-item> <el-dropdown-item v-for="id in getUniqueValues(&#39;id&#39;)" :key="id" :command="{ column: &#39;id&#39;, value: id }" :class="{ &#39;dropdown-item-active&#39;: filters.id === id }" > {{ id }} </el-dropdown-item> </el-dropdown-menu> </template> </el-dropdown> </div> </template> </el-table-column> <!-- 区分列 --> <el-table-column prop="category" label="区分" width="100"> <template #header> <div class="filter-header" :class="{ &#39;filter-active&#39;: filters.category }"> <span>区分</span> <el-dropdown trigger="click" @command="handleFilter"> <el-icon :class="{ &#39;filter-icon-active&#39;: filters.category }"><Filter /></el-icon> <template #dropdown> <el-dropdown-menu> <el-dropdown-item :command="{ column: &#39;category&#39;, value: &#39;all&#39; }" :class="{ &#39;dropdown-item-active&#39;: !filters.category }" > 全部 </el-dropdown-item> <el-dropdown-item v-for="category in getUniqueValues(&#39;category&#39;)" :key="category" :command="{ column: &#39;category&#39;, value: category }" :class="{ &#39;dropdown-item-active&#39;: filters.category === category }" > {{ category }} </el-dropdown-item> </el-dropdown-menu> </template> </el-dropdown> </div> </template> </el-table-column> <!-- 等级列 --> <el-table-column prop="level" label="等级" width="80"> <template #header> <div class="filter-header" :class="{ &#39;filter-active&#39;: filters.level }"> <span>等级</span> <el-dropdown trigger="click" @command="handleFilter"> <el-icon :class="{ &#39;filter-icon-active&#39;: filters.level }"><Filter /></el-icon> <template #dropdown> <el-dropdown-menu> <el-dropdown-item :command="{ column: &#39;level&#39;, value: &#39;all&#39; }" :class="{ &#39;dropdown-item-active&#39;: !filters.level }" > 全部 </el-dropdown-item> <el-dropdown-item v-for="level in getUniqueValues(&#39;level&#39;)" :key="level" :command="{ column: &#39;level&#39;, value: level }" :class="{ &#39;dropdown-item-active&#39;: filters.level === level }" > {{ level }} </el-dropdown-item> </el-dropdown-menu> </template> </el-dropdown> </div> </template> </el-table-column> <!-- 课程名称列 --> <el-table-column prop="coursename" label="课程名称" width="120"> <template #header> <div class="filter-header" :class="{ &#39;filter-active&#39;: filters.coursename }"> <span>课程名称</span> <el-dropdown trigger="click" @command="handleFilter"> <el-icon :class="{ &#39;filter-icon-active&#39;: filters.coursename }"><Filter /></el-icon> <template #dropdown> <el-dropdown-menu> <el-dropdown-item :command="{ column: &#39;coursename&#39;, value: &#39;all&#39; }" :class="{ &#39;dropdown-item-active&#39;: !filters.coursename }" > 全部 </el-dropdown-item> <el-dropdown-item v-for="course in getUniqueValues(&#39;coursename&#39;)" :key="course" :command="{ column: &#39;coursename&#39;, value: course }" :class="{ &#39;dropdown-item-active&#39;: filters.coursename === course }" > {{ course }} </el-dropdown-item> </el-dropdown-menu> </template> </el-dropdown> </div> </template> </el-table-column> <!-- 期数列 --> <el-table-column prop="period" label="期数" width="80"> <template #header> <div class="filter-header" :class="{ &#39;filter-active&#39;: filters.period }"> <span>期数</span> <el-dropdown trigger="click" @command="handleFilter"> <el-icon :class="{ &#39;filter-icon-active&#39;: filters.period }"><Filter /></el-icon> <template #dropdown> <el-dropdown-menu> <el-dropdown-item :command="{ column: &#39;period&#39;, value: &#39;all&#39; }" :class="{ &#39;dropdown-item-active&#39;: !filters.period }" > 全部 </el-dropdown-item> <el-dropdown-item v-for="period in getUniqueValues(&#39;period&#39;)" :key="period" :command="{ column: &#39;period&#39;, value: period }" :class="{ &#39;dropdown-item-active&#39;: filters.period === period }" > {{ period }} </el-dropdown-item> </el-dropdown-menu> </template> </el-dropdown> </div> </template> </el-table-column> <!-- 姓名列 --> <el-table-column prop="name" label="姓名" width="80"> <template #header> <div class="filter-header" :class="{ &#39;filter-active&#39;: filters.name }"> <span>姓名</span> <el-dropdown trigger="click" @command="handleFilter"> <el-icon :class="{ &#39;filter-icon-active&#39;: filters.name }"><Filter /></el-icon> <template #dropdown> <el-dropdown-menu> <el-dropdown-item :command="{ column: &#39;name&#39;, value: &#39;all&#39; }" :class="{ &#39;dropdown-item-active&#39;: !filters.name }" > 全部 </el-dropdown-item> <el-dropdown-item v-for="name in getUniqueValues(&#39;name&#39;)" :key="name" :command="{ column: &#39;name&#39;, value: name }" :class="{ &#39;dropdown-item-active&#39;: filters.name === name }" > {{ name }} </el-dropdown-item> </el-dropdown-menu> </template> </el-dropdown> </div> </template> </el-table-column> <!-- 工号列 --> <el-table-column prop="employeeid" label="工号" width="80"> <template #header> <div class="filter-header" :class="{ &#39;filter-active&#39;: filters.employeeid }"> <span>工号</span> <el-dropdown trigger="click" @command="handleFilter"> <el-icon :class="{ &#39;filter-icon-active&#39;: filters.employeeid }"><Filter /></el-icon> <template #dropdown> <el-dropdown-menu> <el-dropdown-item :command="{ column: &#39;employeeid&#39;, value: &#39;all&#39; }" :class="{ &#39;dropdown-item-active&#39;: !filters.employeeid }" > 全部 </el-dropdown-item> <el-dropdown-item v-for="empId in getUniqueValues(&#39;employeeid&#39;)" :key="empId" :command="{ column: &#39;employeeid&#39;, value: empId }" :class="{ &#39;dropdown-item-active&#39;: filters.employeeid === empId }" > {{ empId }} </el-dropdown-item> </el-dropdown-menu> </template> </el-dropdown> </div> </template> </el-table-column> <!-- Team列 --> <el-table-column prop="team" label="Team" width="80"> <template #header> <div class="filter-header" :class="{ &#39;filter-active&#39;: filters.team }"> <span>Team</span> <el-dropdown trigger="click" @command="handleFilter"> <el-icon :class="{ &#39;filter-icon-active&#39;: filters.team }"><Filter /></el-icon> <template #dropdown> <el-dropdown-menu> <el-dropdown-item :command="{ column: &#39;team&#39;, value: &#39;all&#39; }" :class="{ &#39;dropdown-item-active&#39;: !filters.team }" > 全部 </el-dropdown-item> <el-dropdown-item v-for="team in getUniqueValues(&#39;team&#39;)" :key="team" :command="{ column: &#39;team&#39;, value: team }" :class="{ &#39;dropdown-item-active&#39;: filters.team === team }" > {{ team }} </el-dropdown-item> </el-dropdown-menu> </template> </el-dropdown> </div> </template> </el-table-column> <!-- Group列 --> <el-table-column prop="grouptext" label="Group" width="100"> <template #header> <div class="filter-header" :class="{ &#39;filter-active&#39;: filters.grouptext }"> <span>Group</span> <el-dropdown trigger="click" @command="handleFilter"> <el-icon :class="{ &#39;filter-icon-active&#39;: filters.grouptext }"><Filter /></el-icon> <template #dropdown> <el-dropdown-menu> <el-dropdown-item :command="{ column: &#39;grouptext&#39;, value: &#39;all&#39; }" :class="{ &#39;dropdown-item-active&#39;: !filters.grouptext }" > 全部 </el-dropdown-item> <el-dropdown-item v-for="group in getUniqueValues(&#39;grouptext&#39;)" :key="group" :command="{ column: &#39;grouptext&#39;, value: group }" :class="{ &#39;dropdown-item-active&#39;: filters.grouptext === group }" > {{ group }} </el-dropdown-item> </el-dropdown-menu> </template> </el-dropdown> </div> </template> </el-table-column> <!-- Part列 --> <el-table-column prop="department" label="Part" width="110"> <template #header> <div class="filter-header" :class="{ &#39;filter-active&#39;: filters.department }"> <span>Part</span> <el-dropdown trigger="click" @command="handleFilter"> <el-icon :class="{ &#39;filter-icon-active&#39;: filters.department }"><Filter /></el-icon> <template #dropdown> <el-dropdown-menu> <el-dropdown-item :command="{ column: &#39;department&#39;, value: &#39;all&#39; }" :class="{ &#39;dropdown-item-active&#39;: !filters.department }" > 全部 </el-dropdown-item> <el-dropdown-item v-for="dept in getUniqueValues(&#39;department&#39;)" :key="dept" :command="{ column: &#39;department&#39;, value: dept }" :class="{ &#39;dropdown-item-active&#39;: filters.department === dept }" > {{ dept }} </el-dropdown-item> </el-dropdown-menu> </template> </el-dropdown> </div> </template> </el-table-column> <!-- 联系方式列 --> <el-table-column prop="contact" label="联系方式" width="90"> <template #header> <div class="filter-header" :class="{ &#39;filter-active&#39;: filters.contact }"> <span>联系方式</span> <el-dropdown trigger="click" @command="handleFilter"> <el-icon :class="{ &#39;filter-icon-active&#39;: filters.contact }"><Filter /></el-icon> <template #dropdown> <el-dropdown-menu> <el-dropdown-item :command="{ column: &#39;contact&#39;, value: &#39;all&#39; }" :class="{ &#39;dropdown-item-active&#39;: !filters.contact }" > 全部 </el-dropdown-item> <el-dropdown-item v-for="contact in getUniqueValues(&#39;contact&#39;)" :key="contact" :command="{ column: &#39;contact&#39;, value: contact }" :class="{ &#39;dropdown-item-active&#39;: filters.contact === contact }" > {{ contact }} </el-dropdown-item> </el-dropdown-menu> </template> </el-dropdown> </div> </template> </el-table-column> <!-- 参训现况列 --> <el-table-column prop="status" label="参训现况" width="120"> <template #header> <div class="filter-header" :class="{ &#39;filter-active&#39;: filters.status }"> <span>参训现况</span> <el-dropdown trigger="click" @command="handleFilter"> <el-icon :class="{ &#39;filter-icon-active&#39;: filters.status }"><Filter /></el-icon> <template #dropdown> <el-dropdown-menu> <el-dropdown-item :command="{ column: &#39;status&#39;, value: &#39;all&#39; }" :class="{ &#39;dropdown-item-active&#39;: !filters.status }" > 全部 </el-dropdown-item> <el-dropdown-item v-for="status in getUniqueValues(&#39;status&#39;)" :key="status" :command="{ column: &#39;status&#39;, value: status }" :class="{ &#39;dropdown-item-active&#39;: filters.status === status }" > {{ status }} </el-dropdown-item> </el-dropdown-menu> </template> </el-dropdown> </div> </template> <template #default="scope"> <el-select v-model="scope.row.status" placeholder="请选择" @change="handleStatusChange(scope.row)"> <el-option label="参加" value="参加"></el-option> <el-option label="未参加" value="未参加"></el-option> </el-select> </template> </el-table-column> <!-- 审核状态列 --> <el-table-column prop="auditstatus" label="审核状态" width="120"> <template #header> <div class="filter-header" :class="{ &#39;filter-active&#39;: filters.auditstatus }"> <span>审核状态</span> <el-dropdown trigger="click" @command="handleFilter"> <el-icon :class="{ &#39;filter-icon-active&#39;: filters.auditstatus }"><Filter /></el-icon> <template #dropdown> <el-dropdown-menu> <el-dropdown-item :command="{ column: &#39;auditstatus&#39;, value: &#39;all&#39; }" :class="{ &#39;dropdown-item-active&#39;: !filters.auditstatus }" > 全部 </el-dropdown-item> <el-dropdown-item v-for="auditStatus in getUniqueValues(&#39;auditstatus&#39;)" :key="auditStatus" :command="{ column: &#39;auditstatus&#39;, value: auditStatus }" :class="{ &#39;dropdown-item-active&#39;: filters.auditstatus === auditStatus }" > {{ auditStatus }} </el-dropdown-item> </el-dropdown-menu> </template> </el-dropdown> </div> </template> </el-table-column> </el-table> <!-- 审核操作按钮:仅isAuditMode为true时显示 --> <div v-if="isAuditMode" class="action-buttons" style="margin: 10px 0;"> <el-button type="primary" @click="handleApprove">审核通过</el-button> <el-button type="danger" @click="handleReject">审核不通过</el-button> </div> <div class="pagination"> <el-pagination @size-change="handleSizeChange" @current-change="handleCurrentChange" :current-page="queryParams.pageNum" :page-sizes="[10, 20, 50]" :page-size="queryParams.pageSize" layout="total, sizes, prev, pager, next, jumper" :total="filteredTotal"> </el-pagination> </div> </div> </div> </div> <!-- 无权限时的提示 --> <div v-else class="permission-denied"> <el-result icon="warning" title="权限不足" sub-title="您没有访问此页面的权限,请联系管理员。" > <template #extra> <el-button type="primary" @click="handleGoBack">返回</el-button> <el-button @click="handleContactAdmin">联系管理员</el-button> </template> </el-result> </div> </template> <script setup> import { ref, reactive, onMounted, computed } from &#39;vue&#39; import { useRouter } from &#39;vue-router&#39; import axios from &#39;axios&#39; import { ElMessage, ElMessageBox } from &#39;element-plus&#39; import { Filter } from &#39;@element-plus/icons-vue&#39; import { useUserStore } from &#39;@/stores/auth&#39; const router = useRouter() const userStore = useUserStore() // 用户权限信息 const form = ref({ usepermission: userStore.permission || &#39;&#39; }) console.log(&#39;我的权限:&#39;, form.value.usepermission) // 查询参数 const queryParams = reactive({ keyword: &#39;&#39;, pageNum: 1, pageSize: 10 }) // 表格数据 const tableData = ref([]) // 筛选条件 const filters = reactive({}) // 总条数 const total = ref(0) // 选中数据 const multipleSelection = ref([]) // 表格引用 const multipleTableRef = ref(null) // 核心状态变量:仅通过"参训审核/取消审核"按钮修改 const isAuditMode = ref(false) // 列名映射 const columnLabels = { id: &#39;序号&#39;, category: &#39;区分&#39;, level: &#39;等级&#39;, coursename: &#39;课程名称&#39;, period: &#39;期数&#39;, name: &#39;姓名&#39;, employeeid: &#39;工号&#39;, team: &#39;Team&#39;, grouptext: &#39;Group&#39;, department: &#39;Part&#39;, contact: &#39;联系方式&#39;, status: &#39;参训现况&#39;, auditstatus: &#39;审核状态&#39; } // 获取列标签 const getColumnLabel = (column) => { return columnLabels[column] || column } // 检查是否有活跃的筛选条件 const hasActiveFilters = computed(() => { return Object.keys(filters).length > 0 }) // 获取唯一值列表 const getUniqueValues = (column) => { const values = tableData.value.map(item => item[column]).filter(Boolean) return [...new Set(values)] } // 处理筛选 const handleFilter = (command) => { const { column, value } = command if (value === &#39;all&#39;) { // 删除该列的筛选条件 delete filters[column] } else { // 设置筛选条件 filters[column] = value } // 重置到第一页 queryParams.pageNum = 1 } // 清除单个筛选条件 const clearFilter = (column) => { delete filters[column] } // 清除所有筛选条件 const clearAllFilters = () => { Object.keys(filters).forEach(key => { delete filters[key] }) } // 筛选后的数据 const filteredTableData = computed(() => { let filtered = tableData.value // 应用所有筛选条件 Object.keys(filters).forEach(column => { filtered = filtered.filter(item => item[column] === filters[column]) }) return filtered }) // 筛选后的总条数 const filteredTotal = computed(() => filteredTableData.value.length) // 获取表格数据 const fetchData = async () => { // 如果没有权限,不执行数据获取 if (form.value.usepermission !== &#39;SYSADMIN&#39;) { return } try { const res = await axios.get(&#39;/api/training/list&#39;, { params: queryParams }) // 处理数据:接口返回的status为空时,默认设为"未参加" const formattedData = res.data.data.records.map(record => ({ ...record, status: record.status ?? &#39;未参加&#39; })) tableData.value = formattedData total.value = res.data.total // 重置筛选条件 Object.keys(filters).forEach(key => delete filters[key]) } catch (error) { console.error(error) ElMessage.error(&#39;获取数据失败&#39;) } } // 查询按钮点击事件 const handleQuery = () => { queryParams.pageNum = 1 fetchData() } // 重置按钮点击事件 const handleReset = () => { queryParams.keyword = &#39;&#39; queryParams.pageNum = 1 // 重置筛选条件 Object.keys(filters).forEach(key => delete filters[key]) fetchData() } // 多选框选中数据 const handleSelectionChange = (selection) => { multipleSelection.value = selection } // 分页大小变化 const handleSizeChange = (val) => { queryParams.pageSize = val fetchData() } // 当前页变化 const handleCurrentChange = (val) => { queryParams.pageNum = val fetchData() } // 参训审核/取消审核按钮点击事件 const handleParticipate = () => { isAuditMode.value = !isAuditMode.value } // 审核通过按钮点击事件 const handleApprove = () => { if (multipleSelection.value.length === 0) { ElMessage.warning(&#39;请先选择需要审核的记录&#39;) return } const hasUnparticipated = multipleSelection.value.some(row => row.status !== &#39;参加&#39;) if (hasUnparticipated) { ElMessage.warning(&#39;未参加培训,不能通过&#39;) return } batchUpdateStatus(&#39;通过&#39;) } // 审核不通过按钮点击事件 const handleReject = () => { if (multipleSelection.value.length === 0) { ElMessage.warning(&#39;请先选择需要审核的记录&#39;) return } batchUpdateStatus(&#39;不通过&#39;) } // 批量更新审核状态 const batchUpdateStatus = async (status) => { const selectedIds = multipleSelection.value.map(item => item.id) ElMessageBox.confirm(`确定要将选中记录审核为"${status}"吗?`, &#39;提示&#39;, { confirmButtonText: &#39;确定&#39;, cancelButtonText: &#39;取消&#39;, type: &#39;warning&#39; }).then(async () => { try { await axios.put(&#39;/api/training/batchStatus&#39;, { ids: selectedIds, status }) ElMessage.success(&#39;审核成功&#39;) fetchData() } catch (error) { console.error(error) ElMessage.error(&#39;审核失败&#39;) } }).catch(() => { // 用户取消操作 }) } // 审核状态变化(单行更新) const handleStatusChange = async (row) => { try { await axios.put(`/api/training/status/${row.id}`, { status: row.status }) ElMessage.success(&#39;更新成功&#39;) } catch (error) { console.error(error) ElMessage.error(&#39;更新失败&#39;) fetchData() } } // 下载按钮点击事件 const handleDownload = () => { window.open(&#39;/api/training/download&#39;, &#39;_blank&#39;) } // 返回上一页 const handleGoBack = () => { router.back() } // 联系管理员 const handleContactAdmin = () => { ElMessage.info(&#39;请联系系统管理员获取权限&#39;) } // 初始化数据 onMounted(() => { // 只有 SYSADMIN 权限才获取数据 if (form.value.usepermission === &#39;SYSADMIN&#39;) { fetchData() } else { console.log(&#39;当前用户无权限访问此页面,权限为:&#39;, form.value.usepermission) } }) </script> <style scoped> .search-area { margin-bottom: 10px; } .demo-form-inline { display: flex; justify-content: space-between; align-items: center; width: 100%; } .form-left .el-form-item { margin-right: 10px; } .form-right .el-form-item { margin-left: 10px; } .action-buttons { text-align: left; } .pagination { margin-top: 10px; text-align: right; } /* 当前筛选条件样式 */ .active-filters { margin-bottom: 15px; padding: 10px; background-color: #f8f9fa; border-radius: 4px; border-left: 4px solid #409EFF; } .filters-label { font-weight: bold; color: #606266; margin-right: 10px; } .filter-tag { margin-right: 8px; margin-bottom: 5px; } .clear-all { margin-left: 10px; color: #409EFF; } /* 筛选头部样式 */ .filter-header { display: flex; align-items: center; justify-content: space-between; cursor: pointer; padding: 8px 0; transition: all 0.3s ease; } .filter-header.filter-active { background-color: #ecf5ff; border-radius: 4px; padding: 8px 12px; margin: -8px -12px; } .filter-header .el-icon { color: #c0c4cc; font-size: 14px; transition: all 0.3s ease; } .filter-header:hover .el-icon { color: #409EFF; } .filter-icon-active { color: #409EFF !important; } /* 下拉菜单选中项样式 */ :deep(.dropdown-item-active) { background-color: #ecf5ff !important; color: #409EFF !important; } :deep(.dropdown-item-active:hover) { background-color: #d9ecff !important; } /* 无权限提示样式 */ .permission-denied { display: flex; justify-content: center; align-items: center; height: 60vh; padding: 20px; background-color: #f5f7fa; } .permission-denied .el-result { padding: 40px 30px; } </style>,请帮我生成一个完整的自适应浏览器的vue3 html
11-19
<template> <view class="sort-container"> <!-- 顶部导航:返回居左 + 标题居中(保持不变) --> <view class="sort-nav"> <text class="nav-btn back-btn" @click="navigateBack">返回</text> <view class="nav-title-wrapper"> <text class="nav-title">排序</text> </view> <view class="nav-placeholder"></view> </view> <!-- 核心修改:删除拖拽容器,仅保留网格展示 --> <view class="images-grid"> <!-- 网格容器:一排2张,间距10rpx --> <view class="grid-container"> <!-- 遍历图片列表生成网格项 --> <view class="grid-item" v-for="(img, idx) in sortedImages" :key="img.key"> <view class="image-item"> <image :src="img.path" class="sort-image" mode="cover"></image> <!-- 序号标签(索引绑定,自动排序) --> <view class="image-index">{{ idx + 1 }}</view> <!-- 删除按钮(阻止事件冒泡) --> <view class="delete-btn" @click.stop="deleteImage(idx)"> <image src="/static/lawyerletter/delete.svg" class="delete-icon"></image> </view> </view> </view> </view> </view> <!-- 底部操作栏:添加图片 + 确认排序(保持不变) --> <view class="bottom-action"> <text class="add-img-btn" @click="addImage"> <image src="/static/lawyerletter/add.svg" class="add-icon"></image> <text>添加图片</text> </text> <button class="confirm-btn" @click="confirmSort">确定</button> </view> </view> </template> <script setup> import { onLoad} from &#39;@dcloudio/uni-app&#39;; import { ref, onUnmounted , nextTick } from &#39;vue&#39;; // 1. 布局参数(适配网格展示) const GAP = 10; // 图片间距(水平/垂直) const PADDING = 20; // 容器内边距 // 2. 状态管理(删除拖拽相关状态) const sortedImages = ref([]); // 排序后的图片数组(仅含路径和唯一key) // 3. 页面加载:接收上级页面图片并初始化(删除拖拽坐标相关逻辑) onLoad((options) => { if (options.images) { try { const imgPaths = JSON.parse(decodeURIComponent(options.images)); // 初始化图片列表(仅保留路径和唯一key,删除x/y坐标) sortedImages.value = imgPaths.map((path, idx) => ({ path, key: `img_${idx}_${Date.now()}` // 唯一key确保列表渲染稳定 })); } catch (error) { console.error(&#39;解析图片路径失败:&#39;, error); sortedImages.value = []; } } }); // 4. 删除图片:删除后自动更新排序(删除拖拽相关对齐逻辑) const deleteImage = (idx) => { if (sortedImages.value.length <= 1) { uni.showToast({ title: &#39;至少保留1张图片&#39;, icon: &#39;none&#39; }); return; } uni.showModal({ title: &#39;删除图片&#39;, content: &#39;确定要删除这张图片吗?&#39;, success: (res) => { if (res.confirm) { sortedImages.value.splice(idx, 1); // 直接删除对应索引图片 // 序号随数组索引自动更新,无需额外处理 } } }); }; // 5. 添加图片:保持网格布局一致性(删除拖拽坐标相关逻辑) const addImage = () => { uni.showActionSheet({ itemList: [&#39;拍照&#39;, &#39;从相册选择&#39;], success: (res) => { const sourceType = res.tapIndex === 0 ? [&#39;camera&#39;] : [&#39;album&#39;]; uni.chooseImage({ count: 20 - sortedImages.value.length, // 最多补充到20张 sourceType, sizeType: [&#39;compressed&#39;], success: (res) => { if (res.tempFilePaths.length > 0) { // 新增图片添加到列表末尾(仅保留路径和key) const newImages = res.tempFilePaths.map((path, addIdx) => ({ path, key: `img_${sortedImages.value.length + addIdx}_${Date.now()}` })); sortedImages.value.push(...newImages); // 提示用户 uni.showToast({ title: `添加${res.tempFilePaths.length}张图片`, icon: &#39;success&#39; }); } }, fail: (err) => { if (err.errMsg.includes(&#39;deny&#39;)) { uni.showModal({ title: &#39;权限不足&#39;, content: `请开启${sourceType[0]}权限`, confirmText: &#39;去设置&#39;, success: (res) => res.confirm && uni.openSetting() }); } } }); } }); }; // 6. 确认排序:同步结果到上级页面(删除拖拽对齐逻辑) const confirmSort = () => { // 提取排序后的图片路径(直接按当前数组顺序) const sortedPaths = sortedImages.value.map(img => img.path); // 通知上级页面更新图片顺序 uni.$emit(&#39;imagesSorted&#39;, sortedPaths); // 返回上级页面 navigateBack(); }; // 7. 返回上级页面(保持不变) const navigateBack = () => { uni.navigateBack(); }; // 8. 页面卸载:清理事件监听(保持不变) onUnmounted(() => { uni.$off(&#39;imagesSorted&#39;); }); </script> <style scoped> /* 全局容器:100%高度,flex垂直布局(保持不变) */ .sort-container { height: 100vh; display: flex; flex-direction: column; background-color: #fff; overflow: hidden; } /* 1. 顶部导航样式(保持不变) */ .sort-nav { display: flex; align-items: center; height: 88rpx; padding: 0 24rpx; border-bottom: 1rpx solid #eee; background-color: #fff; position: sticky; top: 0; z-index: 10; } .nav-btn { padding: 10rpx 20rpx; border-radius: 8rpx; min-width: 100rpx; text-align: center; font-size: 28rpx; color: #333; cursor: pointer; } .nav-title-wrapper { flex: 1; display: flex; justify-content: center; align-items: center; } .nav-title { font-size: 32rpx; font-weight: bold; color: #333; } .nav-placeholder { min-width: 100rpx; } /* 2. 图片网格核心样式(删除拖拽相关样式,保留网格) */ .images-grid { flex: 1; padding: 20rpx; box-sizing: border-box; overflow-y: auto; /* 图片超出时垂直滚动 */ background-color: #fafafa; /* 滚动条美化(可选) */ &::-webkit-scrollbar { width: 6rpx; } &::-webkit-scrollbar-thumb { background-color: #ddd; border-radius: 3rpx; } } /* 网格容器:一排2列,间距10rpx */ .grid-container { display: grid; grid-template-columns: repeat(2, 1fr); /* 2列均分宽度 */ grid-gap: 10rpx; /* 行列间距10rpx */ } /* 网格项:固定宽高比(正方形) */ .grid-item { aspect-ratio: 1 / 1; /* 宽高比1:1,正方形图片 */ border-radius: 8rpx; overflow: hidden; box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.08); background-color: #fff; } /* 图片容器:相对定位承载子元素(保持不变) */ .image-item { width: 100%; height: 100%; position: relative; } /* 图片样式:填充容器,保持比例(保持不变) */ .sort-image { width: 100%; height: 100%; object-fit: cover; /* 避免图片拉伸变形 */ } /* 序号标签:左上角悬浮(保持不变) */ .image-index { position: absolute; top: 8rpx; left: 8rpx; background-color: rgba(0, 0, 0, 0.6); color: #fff; font-size: 26rpx; font-weight: bold; width: 38rpx; height: 38rpx; border-radius: 50%; display: flex; align-items: center; justify-content: center; z-index: 2; } /* 删除按钮:右下角悬浮(保持不变) */ .delete-btn { position: absolute; bottom: 8rpx; right: 8rpx; width: 38rpx; height: 38rpx; border-radius: 50%; background-color: rgba(0, 0, 0, 0.6); display: flex; align-items: center; justify-content: center; z-index: 2; transition: background-color 0.2s ease; cursor: pointer; } .delete-btn:active { background-color: rgba(231, 76, 60, 0.8); } .delete-icon { width: 24rpx; height: 24rpx; color: #fff; } /* 3. 底部操作栏样式(保持不变) */ .bottom-action { display: flex; align-items: center; justify-content: space-between; height: 132rpx; border-top: 1rpx solid #eee; padding: 0 24rpx; gap: 20rpx; background-color: #fff; position: sticky; bottom: 0; z-index: 10; } .add-img-btn { display: flex; align-items: center; justify-content: center; border-radius: 20rpx; width: 140rpx; font-size: 24rpx; color: #333; cursor: pointer; } .add-icon { width: 24rpx; height: 24rpx; margin-right: 8rpx; } .confirm-btn { background-color: #e74c3c !important; color: #fff !important; width: 400rpx !important; height: 70rpx !important; line-height: 70rpx !important; border-radius: 35rpx !important; font-size: 30rpx !important; font-weight: 500 !important; display: flex !important; align-items: center !important; justify-content: center !important; padding: 0 !important; margin: 0 !important; border: none !important; box-shadow: none !important; } .confirm-btn:active { opacity: 0.8; } </style> 根据这个带啊吗修改 将图片拖拽位置后自动排序
09-18
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序员勇哥

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值