feat(frontend): 为前端添加完整移动端响应式适配

- 新增响应式工具函数与全局移动端样式,统一适配≤768px屏幕
- 添加移动端底部导航组件,替代桌面端侧边栏菜单
- 为经销商、客户、报备列表页新增移动端卡片视图替换表格布局
- 调整所有页面的表单、弹窗与布局适配移动端尺寸
- 优化移动端viewport配置,适配全面屏安全区域
- 修复密码哈希工具,在非安全上下文下回退使用js-sha256库
- 更新依赖包,新增@vueuse/core与js-sha256
This commit is contained in:
andy committed 2026-08-03 15:35:45 +08:00
1 parent 4c6970c32d
commit fd89c31640
16 files changed
+613 -62

No files matched your search

+104 -10
View File
@@ -17,7 +17,7 @@
</template>
<!-- 查询表单 -->
<el-form :inline="true" :model="queryForm" class="query-form">
<el-form :inline="!isMobile" :model="queryForm" class="query-form">
<el-form-item label="学校名称">
<el-input v-model="queryForm.customerName" placeholder="请输入学校名称" clearable />
</el-form-item>
@@ -40,7 +40,7 @@
</el-form>
<!-- 数据表格 -->
<el-table :data="tableData" border stripe v-loading="loading">
<el-table v-if="!isMobile" :data="tableData" border stripe v-loading="loading">
<el-table-column prop="dealerName" label="经销商" v-if="userStore.isAdmin"/>
<el-table-column prop="schoolName" label="学校名称" />
<el-table-column prop="product" label="所属产品" show-overflow-tooltip />
@@ -102,6 +102,36 @@
</el-table-column>
</el-table>
<!-- 移动端卡片视图 -->
<div v-if="isMobile" class="mobile-card-list" v-loading="loading">
<div v-for="row in tableData" :key="row.id" class="mobile-card">
<div class="card-top">
<span class="card-title">{{ row.schoolName }}</span>
<el-tag v-if="row.status === 0" type="warning" size="small">待审核</el-tag>
<el-tag v-else-if="row.status === 1" type="success" size="small">已通过</el-tag>
<el-tag v-else-if="row.status === 2" type="danger" size="small">已驳回</el-tag>
<el-tag v-else-if="row.status === 3" type="info" size="small">已失效</el-tag>
<el-tag v-else-if="row.status === 4" type="warning" size="small">已作废</el-tag>
</div>
<div v-if="userStore.isAdmin" class="card-row"><span class="label">经销商</span><span class="val">{{ row.dealerName || '-' }}</span></div>
<div class="card-row"><span class="label">所属产品</span><span class="val">{{ row.product || '-' }}</span></div>
<div class="card-row"><span class="label">项目类型</span><span class="val">{{ row.projectType || '-' }}</span></div>
<div class="card-row"><span class="label">负责人</span><span class="val">{{ row.contactPerson || '-' }}</span></div>
<div class="card-row"><span class="label">负责人电话</span><span class="val">{{ row.contactPhone || '-' }}</span></div>
<div class="card-row"><span class="label">报备说明</span><span class="val">{{ row.description || '-' }}</span></div>
<div class="card-row"><span class="label">保护期截止</span><span class="val">{{ formatDate(row.protectEndDate) }}</span></div>
<div class="card-row"><span class="label">创建时间</span><span class="val">{{ row.createdAt }}</span></div>
<div class="card-actions">
<el-button link type="primary" @click="handleView(row)">查看</el-button>
<el-button v-if="row.status === 1" link type="info" @click="handleShowProgress(row)">进展</el-button>
<el-button v-if="userStore.isAdmin && row.status === 0" link type="success" @click="handleAudit(row)">审核</el-button>
<el-button v-if="userStore.isAdmin && row.status === 1" link type="warning" @click="handleEdit(row)">编辑</el-button>
<el-button v-if="!userStore.isAdmin && row.status === 0" link type="danger" @click="handleWithdraw(row)">撤回</el-button>
</div>
</div>
<el-empty v-if="!loading && tableData.length === 0" description="暂无报备数据" />
</div>
<!-- 分页 -->
<el-config-provider :locale="zhCn">
<el-pagination
@@ -109,18 +139,18 @@
v-model:page-size="queryForm.size"
:total="total"
:page-sizes="[10, 20, 50, 100]"
layout="total, sizes, prev, pager, next, jumper"
:layout="isMobile ? 'prev, pager, next' : 'total, sizes, prev, pager, next, jumper'"
prev-text="上一页"
next-text="下一页"
@size-change="fetchData"
@current-change="fetchData"
style="margin-top: 20px; justify-content: flex-end"
:style="{ marginTop: '20px', justifyContent: isMobile ? 'center' : 'flex-end' }"
/>
</el-config-provider>
</el-card>
<!-- 提交报备对话框 -->
<el-dialog v-model="dialogVisible" title="提交报备" width="650px" @close="handleDialogClose">
<el-dialog v-model="dialogVisible" title="提交报备" :width="isMobile ? '92%' : '650px'" @close="handleDialogClose">
<el-form ref="formRef" :model="formData" :rules="rules" label-width="100px">
<el-form-item label="学校" prop="schoolName">
<el-autocomplete
@@ -183,7 +213,7 @@
</el-dialog>
<!-- 审核对话框 -->
<el-dialog v-model="auditDialogVisible" title="审核报备" width="600px">
<el-dialog v-model="auditDialogVisible" title="审核报备" :width="isMobile ? '92%' : '600px'">
<el-form :model="auditForm" label-width="100px">
<el-form-item label="审核结果">
<el-radio-group v-model="auditForm.approved">
@@ -207,7 +237,7 @@
</el-dialog>
<!-- 编辑报备对话框 -->
<el-dialog v-model="editDialogVisible" title="编辑报备" width="600px" @close="handleEditDialogClose">
<el-dialog v-model="editDialogVisible" title="编辑报备" :width="isMobile ? '92%' : '600px'" @close="handleEditDialogClose">
<el-form ref="editFormRef" :model="editFormData" :rules="editRules" label-width="100px">
<el-form-item label="学校名称">
<el-input :model-value="currentReport?.schoolName" disabled />
@@ -270,8 +300,8 @@
</el-dialog>
<!-- 报备详情对话框 -->
<el-dialog v-model="detailDialogVisible" title="报备详情" width="700px">
<el-descriptions :column="2" border>
<el-dialog v-model="detailDialogVisible" title="报备详情" :width="isMobile ? '92%' : '700px'">
<el-descriptions :column="isMobile ? 1 : 2" border>
<el-descriptions-item label="经销商">
{{ currentReport?.dealerName || '-' }}
</el-descriptions-item>
@@ -330,7 +360,7 @@
</el-dialog>
<!-- 进展记录对话框 -->
<el-dialog v-model="progressDialogVisible" title="报备进展记录" width="1000px" @close="handleProgressDialogClose">
<el-dialog v-model="progressDialogVisible" title="报备进展记录" :width="isMobile ? '92%' : '1000px'" :fullscreen="isMobile" @close="handleProgressDialogClose">
<el-card>
<template #header>
<div class="card-header">
@@ -387,11 +417,13 @@ import { getProgressByReportId, createProgress, updateProgress } from '@/api/rep
import { searchSchoolByName } from '@/api/school'
import { getConfigValue } from '@/api/system'
import type { Report, ReportForm, ReportProgress, ReportProgressForm, ReportUpdateForm } from '@/types'
import { useResponsive } from '@/composables/useResponsive'
const route = useRoute()
const userStore = useUserStore()
const protectDays = ref<number>(90)
const { isMobile } = useResponsive()
const loading = ref(false)
const tableData = ref<Report[]>([])
@@ -792,4 +824,66 @@ onMounted(() => {
:deep(.el-descriptions__content) {
color: #333;
}
/* ===== 移动端卡片视图 ===== */
.mobile-card-list {
display: flex;
flex-direction: column;
gap: 10px;
}
.mobile-card {
background: #fff;
border: 1px solid #ebeef5;
border-radius: 8px;
padding: 12px;
}
.card-top {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 8px;
}
.card-title {
font-size: 15px;
font-weight: 600;
color: #303133;
}
.card-row {
display: flex;
align-items: flex-start;
font-size: 13px;
color: #606266;
line-height: 1.8;
}
.card-row .label {
width: 80px;
flex-shrink: 0;
color: #909399;
}
.card-row .val {
flex: 1;
word-break: break-all;
}
.card-actions {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin-top: 8px;
padding-top: 8px;
border-top: 1px solid #f0f0f0;
}
@media (max-width: 768px) {
.card-header {
flex-wrap: wrap;
gap: 10px;
}
}
</style>