修改自适应

This commit is contained in:
2026-08-19 08:53:50 +08:00
parent 0e797c39f4
commit ec5cf96d0b
14 changed files with 941 additions and 48 deletions
+65 -5
View File
@@ -2,7 +2,7 @@
<div class="erp-dashboard">
<!-- 第一行统计卡片 -->
<el-row :gutter="16" class="stat-row">
<el-col :span="4" v-for="(item, index) in firstRowStats" :key="'first-' + index">
<el-col :span="4" :xs="12" v-for="(item, index) in firstRowStats" :key="'first-' + index">
<div class="stat-card">
<div class="stat-header">
<span class="stat-label">{{ item.label }}</span>
@@ -18,7 +18,7 @@
<!-- 第二行统计卡片 -->
<el-row :gutter="16" class="stat-row" style="margin-top: 16px;">
<el-col :span="4" v-for="(item, index) in secondRowStats" :key="'second-' + index">
<el-col :span="4" :xs="12" v-for="(item, index) in secondRowStats" :key="'second-' + index">
<div class="stat-card">
<div class="stat-header">
<span class="stat-label">{{ item.label }}</span>
@@ -34,19 +34,19 @@
<!-- 图表区域 -->
<el-row :gutter="16" class="chart-row" style="margin-top: 16px;">
<el-col :span="8">
<el-col :span="8" :xs="24">
<div class="chart-card">
<div class="chart-title">销售统计</div>
<div ref="salesChartRef" class="chart-container"></div>
</div>
</el-col>
<el-col :span="8">
<el-col :span="8" :xs="24">
<div class="chart-card">
<div class="chart-title">零售统计</div>
<div ref="retailChartRef" class="chart-container"></div>
</div>
</el-col>
<el-col :span="8">
<el-col :span="8" :xs="24">
<div class="chart-card">
<div class="chart-title">采购统计</div>
<div ref="purchaseChartRef" class="chart-container"></div>
@@ -324,6 +324,66 @@ onUnmounted(() => {
.chart-container {
flex: 1;
min-height: 0;
width: 100%;
}
}
// 手机端适配:统计卡片每行 2 个,图表卡片每行 1 个
@media (max-width: 768px) {
.erp-dashboard {
overflow-x: hidden;
}
// el-row gutter 通过负 margin + col padding 实现,手机端缩小间距并防止横向溢出
.stat-row {
margin-left: -4px !important;
margin-right: -4px !important;
:deep(.el-col) {
padding-left: 4px !important;
padding-right: 4px !important;
margin-bottom: 8px;
&:nth-last-child(-n + 2) {
margin-bottom: 0;
}
}
}
.chart-row {
margin-left: 0 !important;
margin-right: 0 !important;
:deep(.el-col) {
padding-left: 0 !important;
padding-right: 0 !important;
margin-bottom: 12px;
&:last-child {
margin-bottom: 0;
}
}
}
// 一行两个统计卡片,缩小内边距与字号防止挤压
.stat-card {
padding: 10px 12px;
.stat-header {
margin-bottom: 8px;
.stat-label {
font-size: 12px;
}
}
.stat-value .number {
font-size: 16px;
}
}
.chart-card {
height: 280px;
}
}
</style>
+65 -5
View File
@@ -2,7 +2,7 @@
<div class="erp-dashboard">
<!-- 第一行统计卡片 -->
<el-row :gutter="16" class="stat-row">
<el-col :span="4" v-for="(item, index) in firstRowStats" :key="'first-' + index">
<el-col :span="4" :xs="12" v-for="(item, index) in firstRowStats" :key="'first-' + index">
<div class="stat-card">
<div class="stat-header">
<span class="stat-label">{{ item.label }}</span>
@@ -18,7 +18,7 @@
<!-- 第二行统计卡片 -->
<el-row :gutter="16" class="stat-row" style="margin-top: 16px;">
<el-col :span="4" v-for="(item, index) in secondRowStats" :key="'second-' + index">
<el-col :span="4" :xs="12" v-for="(item, index) in secondRowStats" :key="'second-' + index">
<div class="stat-card">
<div class="stat-header">
<span class="stat-label">{{ item.label }}</span>
@@ -34,19 +34,19 @@
<!-- 图表区域 -->
<el-row :gutter="16" class="chart-row" style="margin-top: 16px;">
<el-col :span="8">
<el-col :span="8" :xs="24">
<div class="chart-card">
<div class="chart-title">销售统计</div>
<div ref="salesChartRef" class="chart-container"></div>
</div>
</el-col>
<el-col :span="8">
<el-col :span="8" :xs="24">
<div class="chart-card">
<div class="chart-title">零售统计</div>
<div ref="retailChartRef" class="chart-container"></div>
</div>
</el-col>
<el-col :span="8">
<el-col :span="8" :xs="24">
<div class="chart-card">
<div class="chart-title">采购统计</div>
<div ref="purchaseChartRef" class="chart-container"></div>
@@ -324,6 +324,66 @@ onUnmounted(() => {
.chart-container {
flex: 1;
min-height: 0;
width: 100%;
}
}
// 手机端适配:统计卡片每行 2 个,图表卡片每行 1 个
@media (max-width: 768px) {
.erp-dashboard {
overflow-x: hidden;
}
// el-row gutter 通过负 margin + col padding 实现,手机端缩小间距并防止横向溢出
.stat-row {
margin-left: -4px !important;
margin-right: -4px !important;
:deep(.el-col) {
padding-left: 4px !important;
padding-right: 4px !important;
margin-bottom: 8px;
&:nth-last-child(-n + 2) {
margin-bottom: 0;
}
}
}
.chart-row {
margin-left: 0 !important;
margin-right: 0 !important;
:deep(.el-col) {
padding-left: 0 !important;
padding-right: 0 !important;
margin-bottom: 12px;
&:last-child {
margin-bottom: 0;
}
}
}
// 一行两个统计卡片,缩小内边距与字号防止挤压
.stat-card {
padding: 10px 12px;
.stat-header {
margin-bottom: 8px;
.stat-label {
font-size: 12px;
}
}
.stat-value .number {
font-size: 16px;
}
}
.chart-card {
height: 280px;
}
}
</style>
@@ -1,6 +1,6 @@
<template>
<el-dialog v-model="visible" title="报销详情" width="980px" destroy-on-close>
<el-descriptions v-if="detail" :column="3" border>
<el-dialog v-model="visible" title="报销详情" width="980px" class="reimburse-detail-dialog" destroy-on-close>
<el-descriptions v-if="detail" :column="isMobile ? 1 : 3" border>
<el-descriptions-item label="状态">
<el-tag :type="statusType(detail.status)">{{ statusLabel(detail.status) }}</el-tag>
</el-descriptions-item>
@@ -46,6 +46,7 @@
</template>
。识别结果可修改,确认后请保存。
</p>
<div class="table-scroll">
<el-table :data="amountCompare.details" border size="small" max-height="280">
<el-table-column prop="expense_type" label="费用类型" min-width="100" />
<el-table-column prop="name" label="发票文件" min-width="160" show-overflow-tooltip />
@@ -72,6 +73,7 @@
</template>
</el-table-column>
</el-table>
</div>
<div class="compare-actions">
<el-button v-if="isDraft" type="primary" size="small" :loading="amountSaving" @click="handleSaveReceiptAmounts">
保存发票金额
@@ -80,6 +82,7 @@
</div>
</div>
<div class="table-scroll">
<el-table :data="detail?.items || []" border size="small">
<el-table-column prop="expense_type" label="费用类型" min-width="120" />
<el-table-column prop="expense_date" label="发生日期" width="120" />
@@ -140,6 +143,7 @@
</el-table-column>
<template #empty><el-empty description="暂无费用明细" :image-size="60" /></template>
</el-table>
</div>
<input
ref="invoiceFileInputRef"
@@ -150,7 +154,7 @@
@change="handleInvoiceFileInputChange"
/>
<el-dialog v-model="receiptPreviewVisible" title="查看票据" width="980px" append-to-body destroy-on-close>
<el-dialog v-model="receiptPreviewVisible" title="查看票据" width="980px" class="receipt-preview-dialog" append-to-body destroy-on-close>
<div v-if="receiptList.length" class="receipt-preview">
<aside class="receipt-list">
<el-select
@@ -242,6 +246,7 @@
v-model="manualConfirmVisible"
title="手动确认"
width="420px"
class="manual-confirm-dialog"
append-to-body
destroy-on-close
>
@@ -280,7 +285,7 @@
</template>
<script setup>
import { computed, ref, watch } from 'vue'
import { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { uploadFile } from '@/api/file'
import { deleteReimbursementItem, submitReimbursement, updateReimbursementItem, updateReimbursementStatus } from '@/api/reimburse'
@@ -353,6 +358,18 @@ const amountSaving = ref(false)
const amountCompare = ref(null)
const pendingAmountCompare = ref(false)
const isMobile = ref(false)
const checkMobile = () => {
isMobile.value = window.innerWidth <= 768
}
onMounted(() => {
checkMobile()
window.addEventListener('resize', checkMobile)
})
onBeforeUnmount(() => {
window.removeEventListener('resize', checkMobile)
})
const compareSummary = computed(() => {
if (!amountCompare.value?.details) {
return {
@@ -1125,4 +1142,97 @@ const formatDateTime = value => value ? new Date(value).toLocaleString('zh-CN',
color: #67c23a;
font-weight: 600;
}
.table-scroll {
width: 100%;
}
@media (max-width: 768px) {
.section-header {
flex-wrap: wrap;
gap: 8px;
margin-top: 16px;
}
.section-actions {
flex-wrap: wrap;
}
.table-scroll {
overflow-x: auto;
-webkit-overflow-scrolling: touch;
:deep(.el-table) {
min-width: 640px;
}
}
.amount-compare-card {
padding: 12px;
}
.amount-compare-summary {
gap: 12px 20px;
}
.compare-item strong {
font-size: 16px;
}
.receipt-preview {
flex-direction: column;
min-height: 0;
gap: 12px;
}
.receipt-list {
flex: 0 0 auto;
max-height: 170px;
padding-right: 0;
padding-bottom: 10px;
border-right: 0;
border-bottom: 1px solid var(--el-border-color-light);
}
.receipt-thumb {
min-height: 56px;
}
.receipt-image {
max-height: 420px;
}
.receipt-pdf {
height: 420px;
}
.detail-toolbar {
flex-wrap: wrap;
gap: 10px;
}
}
</style>
<style lang="less">
/* 弹窗会 teleport 到 body,需非 scoped 样式覆盖宽度 */
@media (max-width: 768px) {
.reimburse-detail-dialog,
.receipt-preview-dialog,
.manual-confirm-dialog,
.reimburse-edit-dialog,
.reimburse-item-form-dialog,
.expense-type-dialog,
.expense-type-form-dialog {
width: 92% !important;
margin-top: 6vh;
.el-dialog__body {
padding: 16px;
}
}
.reimburse-item-form-dialog .item-form .el-form-item__label {
width: 70px !important;
}
}
</style>
@@ -1,5 +1,5 @@
<template>
<el-dialog v-model="visible" :title="title" width="720px" destroy-on-close>
<el-dialog v-model="visible" :title="title" width="720px" class="reimburse-edit-dialog" destroy-on-close>
<el-form ref="formRef" :model="form" :rules="formRules" label-width="90px">
<el-form-item label="申请日期" prop="apply_date">
<el-date-picker
@@ -3,6 +3,7 @@
v-model="visible"
title="报销类别管理"
width="720px"
class="expense-type-dialog"
append-to-body
destroy-on-close
@open="loadList"
@@ -12,6 +13,7 @@
<el-button type="primary" :icon="Plus" @click="openForm()">新增专有费项</el-button>
</div>
<div class="table-scroll">
<el-table v-loading="loading" :data="list" stripe max-height="420">
<el-table-column prop="name" label="费项名称" min-width="140" />
<el-table-column prop="sort_order" label="排序" width="80" align="center" />
@@ -40,11 +42,13 @@
</el-table-column>
<template #empty><el-empty description="暂无费项" /></template>
</el-table>
</div>
<el-dialog
v-model="formVisible"
:title="editingId ? '编辑专有费项' : '新增专有费项'"
width="420px"
class="expense-type-form-dialog"
append-to-body
destroy-on-close
>
@@ -193,4 +197,29 @@ const handleDelete = async row => {
color: #c0c4cc;
font-size: 13px;
}
.table-scroll {
width: 100%;
}
@media (max-width: 768px) {
.toolbar {
flex-direction: column;
align-items: stretch;
gap: 10px;
.el-button {
margin-left: 0;
}
}
.table-scroll {
overflow-x: auto;
-webkit-overflow-scrolling: touch;
:deep(.el-table) {
min-width: 520px;
}
}
}
</style>
@@ -3,6 +3,7 @@
v-model="visible"
:title="editingItemId ? '编辑费用明细' : '添加费用明细'"
width="860px"
class="reimburse-item-form-dialog"
append-to-body
destroy-on-close
>
@@ -34,7 +35,7 @@
<el-form :model="item" :rules="rules" label-width="80px" class="item-form">
<el-row :gutter="16">
<el-col :span="8">
<el-col :span="8" :xs="24">
<el-form-item label="费用类型" prop="expense_type">
<el-select v-model="item.expense_type" placeholder="请选择" style="width: 100%">
<el-option
@@ -46,7 +47,7 @@
</el-select>
</el-form-item>
</el-col>
<el-col :span="8">
<el-col :span="8" :xs="24">
<el-form-item label="发生日期" prop="expense_date">
<el-date-picker
v-model="item.expense_date"
@@ -56,7 +57,7 @@
/>
</el-form-item>
</el-col>
<el-col :span="8">
<el-col :span="8" :xs="24">
<el-form-item label="金额" prop="amount">
<el-input-number
v-model="item.amount"
@@ -250,4 +251,22 @@ watch(
margin-bottom: 0;
}
}
@media (max-width: 768px) {
.batch-toolbar {
flex-wrap: wrap;
gap: 8px;
}
.item-list {
max-height: none;
padding-right: 0;
}
.item-card {
:deep(.el-card__body) {
padding: 12px;
}
}
}
</style>
+85 -7
View File
@@ -30,7 +30,7 @@
v-model="filters.status"
clearable
placeholder="全部状态"
style="width: 150px"
class="status-select"
>
<el-option
v-for="item in statusOptions"
@@ -50,7 +50,8 @@
</div>
<div class="table-card">
<el-table v-loading="loading" :data="list" stripe>
<div class="table-scroll">
<el-table v-loading="loading" :data="list" stripe>
<el-table-column prop="status" label="状态" width="80">
<template #default="{ row }">
<el-tag :type="statusType(row.status)">{{
@@ -76,12 +77,17 @@
>
<template #default="{ row }">{{ row.description || "-" }}</template>
</el-table-column>
<el-table-column prop="create_time" label="创建时间" width="175">
<el-table-column
v-if="!isMobile"
prop="create_time"
label="创建时间"
width="175"
>
<template #default="{ row }">{{
formatDateTime(row.create_time)
}}</template>
</el-table-column>
<el-table-column label="操作" fixed="right" width="300">
<el-table-column label="操作" fixed="right" :width="isMobile ? 220 : 300">
<template #default="{ row }">
<el-button link type="primary" @click="openDetail(row.id)"
>详情</el-button
@@ -117,13 +123,14 @@
</template>
</el-table-column>
<template #empty><el-empty description="暂无报销记录" /></template>
</el-table>
</el-table>
</div>
<div class="pagination">
<el-pagination
:current-page="pagination.page"
:page-size="pagination.pageSize"
:total="pagination.total"
layout="total, sizes, prev, pager, next, jumper"
:layout="isMobile ? 'prev, pager, next' : 'total, sizes, prev, pager, next, jumper'"
@update:current-page="pagination.page = $event"
@update:page-size="pagination.pageSize = $event"
@current-change="loadList"
@@ -156,7 +163,7 @@
</template>
<script setup>
import { computed, onMounted, reactive, ref } from "vue";
import { computed, onBeforeUnmount, onMounted, reactive, ref } from "vue";
import { ElMessage, ElMessageBox } from "element-plus";
import { Plus, Refresh, Search, Setting } from "@element-plus/icons-vue";
import ReimburseEdit from "./components/edit.vue";
@@ -196,6 +203,11 @@ const records = ref([]);
const expenseTypeOptions = ref([]);
const exportingId = ref(0);
const isMobile = ref(false);
const checkMobile = () => {
isMobile.value = window.innerWidth <= 768;
};
const emptyForm = () => ({
apply_date: new Date().toISOString().slice(0, 10),
description: "",
@@ -360,9 +372,15 @@ const statusType = (value) =>
5: "success",
})[value] || "info";
onMounted(() => {
checkMobile();
window.addEventListener("resize", checkMobile);
loadExpenseTypes();
loadList();
});
onBeforeUnmount(() => {
window.removeEventListener("resize", checkMobile);
});
</script>
<style lang="less" scoped>
@@ -413,6 +431,14 @@ onMounted(() => {
margin-bottom: 0;
}
.status-select {
width: 150px;
}
.table-scroll {
width: 100%;
}
.pagination {
display: flex;
justify-content: flex-end;
@@ -425,12 +451,64 @@ onMounted(() => {
}
.page-header {
flex-direction: column;
align-items: flex-start;
gap: 12px;
}
.header-actions {
width: 100%;
flex-wrap: wrap;
.el-button {
margin-left: 0;
}
}
.page-header h2 {
font-size: 18px;
}
.filter-card,
.table-card {
padding: 12px;
}
.filter-card {
:deep(.el-form--inline .el-form-item) {
display: block;
width: 100%;
margin-right: 0;
margin-bottom: 10px;
&:last-child {
margin-bottom: 0;
}
}
:deep(.el-form-item__content) {
width: 100%;
flex-wrap: wrap;
}
:deep(.el-input),
.status-select {
width: 100%;
}
}
.table-scroll {
overflow-x: auto;
-webkit-overflow-scrolling: touch;
:deep(.el-table) {
min-width: 560px;
}
}
.pagination {
justify-content: center;
margin-top: 12px;
}
}
</style>