Эх сурвалжийг харах

优化商品列表页面布局和功能
- 调整表格高度计算逻辑,实现全屏展示
- 优化导入按钮组件,提升用户体验
- 调整布局结构,提高页面响应性
-修复部分样式问题,优化整体视觉效果

WanGxC 7 сар өмнө
parent
commit
b25f2f6b1a

+ 0 - 2
src/components/ImportButton/index.vue

@@ -12,8 +12,6 @@ const { data } = BtnPermissionStore();
 
 const attrs = useAttrs() as any;
 
-console.log('attrs=> ', attrs);
-
 const props = defineProps<Partial<Omit<ButtonProps, 'disabled' | 'loading' | 'color'>>>();
 
 function hasPermission(permissions: string | string[]): boolean {

+ 2 - 3
src/layout/component/main.vue

@@ -4,7 +4,7 @@
 		<el-scrollbar ref="layoutMainScrollbarRef" class="layout-main-scroll layout-backtop-header-fixed"
 			wrap-class="layout-main-scroll" view-class="layout-main-scroll">
 			<LayoutParentView />
-      <!--<LayoutFooter v-if="isFooter" />-->
+      <LayoutFooter v-if="isFooter" />
 		</el-scrollbar>
 		<el-backtop :target="setBacktopClass" />
 	</el-main>
@@ -32,11 +32,10 @@ const { isTagsViewCurrenFull } = storeToRefs(storesTagsViewRoutes);
 
 // 设置 footer 显示/隐藏
 const isFooter = computed(() => {
-	return themeConfig.value.isFooter && !route.meta.isIframe;
+	return false;
 });
 // 设置 header 固定
 const isFixedHeader = computed(() => {
-  console.log('themeConfig.value.isFixedHeader;=> ', themeConfig.value.isFixedHeader);
 	return themeConfig.value.isFixedHeader;
 });
 // 设置 Backtop 回到顶部

+ 80 - 68
src/layout/routerView/parent.vue

@@ -1,21 +1,32 @@
 <template>
-	<div class="layout-parent">
-		<router-view v-slot="{ Component }">
-			<transition :name="setTransitionName" mode="out-in">
-				<keep-alive :include="getKeepAliveNames" v-if="showView">
-						<component :is="Component" :key="state.refreshRouterViewKey" class="w100" v-show="!isIframePage" />
-				</keep-alive>
-			</transition>
-		</router-view>
-		<transition :name="setTransitionName" mode="out-in">
-			<Iframes class="w100" v-show="isIframePage" :refreshKey="state.iframeRefreshKey" :name="setTransitionName"
-				:list="state.iframeList" />
-		</transition>
-	</div>
+  <div class="layout-parent justify-center">
+    <router-view v-slot="{ Component }">
+      <transition :name="setTransitionName" mode="out-in">
+        <keep-alive v-if="showView" :include="getKeepAliveNames">
+          <component :is="Component" v-show="!isIframePage" :key="state.refreshRouterViewKey" class="w100"/>
+        </keep-alive>
+      </transition>
+    </router-view>
+    <transition :name="setTransitionName" mode="out-in">
+      <Iframes v-show="isIframePage" :list="state.iframeList" :name="setTransitionName" :refreshKey="state.iframeRefreshKey"
+               class="w100"/>
+    </transition>
+  </div>
 </template>
 
-<script setup lang="ts" name="layoutParentView">
-import { defineAsyncComponent, computed, reactive, onBeforeMount, onUnmounted, nextTick, watch, onMounted, ref, provide } from 'vue';
+<script lang="ts" name="layoutParentView" setup>
+import {
+  defineAsyncComponent,
+  computed,
+  reactive,
+  onBeforeMount,
+  onUnmounted,
+  nextTick,
+  watch,
+  onMounted,
+  ref,
+  provide
+} from 'vue';
 import { useRoute, useRouter } from 'vue-router';
 import { storeToRefs } from 'pinia';
 import { useKeepALiveNames } from '/@/stores/keepAliveNames';
@@ -34,90 +45,91 @@ const storesThemeConfig = useThemeConfig();
 const { keepAliveNames, cachedViews } = storeToRefs(storesKeepAliveNames);
 const { themeConfig } = storeToRefs(storesThemeConfig);
 const state = reactive<ParentViewState>({
-	refreshRouterViewKey: '', // 非 iframe tagsview 右键菜单刷新时
-	iframeRefreshKey: '', // iframe tagsview 右键菜单刷新时
-	keepAliveNameList: [],
-	iframeList: [],
+  refreshRouterViewKey: '', // 非 iframe tagsview 右键菜单刷新时
+  iframeRefreshKey: '', // iframe tagsview 右键菜单刷新时
+  keepAliveNameList: [],
+  iframeList: []
 });
 
 //全局依赖刷新页面
-const showView = ref(true)
+const showView = ref(true);
 /**
  * 刷新页面
  */
-const refreshView = function () {
-	showView.value = false // 通过v-if移除router-view节点
-	nextTick(() => {
-		showView.value = true // DOM更新后再通过v-if添加router-view节点
-	})
-}
-provide('refreshView', refreshView)
+const refreshView = function() {
+  showView.value = false; // 通过v-if移除router-view节点
+  nextTick(() => {
+    showView.value = true; // DOM更新后再通过v-if添加router-view节点
+  });
+};
+provide('refreshView', refreshView);
 
 // 设置主界面切换动画
 const setTransitionName = computed(() => {
-	return themeConfig.value.animation;
+  return themeConfig.value.animation;
 });
 // 获取组件缓存列表(name值)
 const getKeepAliveNames = computed(() => {
   // console.log(cachedViews.value)
-	return themeConfig.value.isTagsview ? cachedViews.value : state.keepAliveNameList;
+  return themeConfig.value.isTagsview ? cachedViews.value : state.keepAliveNameList;
 });
 // 设置 iframe 显示/隐藏
 const isIframePage = computed(() => {
-	return route.meta.isIframe;
+  return route.meta.isIframe;
 });
 // 获取 iframe 组件列表(未进行渲染)
 const getIframeListRoutes = async () => {
-	router.getRoutes().forEach((v) => {
-		if (v.meta.isIframe) {
-			v.meta.isIframeOpen = false;
-			v.meta.loading = true;
-			state.iframeList.push({ ...v });
-		}
-	});
+  router.getRoutes().forEach((v) => {
+    if (v.meta.isIframe) {
+      v.meta.isIframeOpen = false;
+      v.meta.loading = true;
+      state.iframeList.push({ ...v });
+    }
+  });
 };
 // 页面加载前,处理缓存,页面刷新时路由缓存处理
 onBeforeMount(() => {
-	state.keepAliveNameList = keepAliveNames.value;
-	mittBus.on('onTagsViewRefreshRouterView', (fullPath: string) => {
-		state.keepAliveNameList = keepAliveNames.value.filter((name: string) => route.name !== name);
-		state.refreshRouterViewKey = '';
-		state.iframeRefreshKey = '';
-		nextTick(() => {
-			state.refreshRouterViewKey = fullPath;
-			state.iframeRefreshKey = fullPath;
-			state.keepAliveNameList = keepAliveNames.value;
-		});
-	});
+  state.keepAliveNameList = keepAliveNames.value;
+  mittBus.on('onTagsViewRefreshRouterView', (fullPath: string) => {
+    state.keepAliveNameList = keepAliveNames.value.filter((name: string) => route.name !== name);
+    state.refreshRouterViewKey = '';
+    state.iframeRefreshKey = '';
+    nextTick(() => {
+      state.refreshRouterViewKey = fullPath;
+      state.iframeRefreshKey = fullPath;
+      state.keepAliveNameList = keepAliveNames.value;
+    });
+  });
 });
 // 页面加载时
 onMounted(() => {
-	getIframeListRoutes();
-	// https://gitee.com/lyt-top/vue-next-admin/issues/I58U75
-	// https://gitee.com/lyt-top/vue-next-admin/issues/I59RXK
-	// https://gitee.com/lyt-top/vue-next-admin/pulls/40
-	nextTick(() => {
-		setTimeout(() => {
-			if (themeConfig.value.isCacheTagsView) {
-				let tagsViewArr: RouteItem[] = Session.get('tagsViewList') || [];
-				cachedViews.value = tagsViewArr.filter((item) => item.meta?.isKeepAlive).map((item) => item.name as string);
-			}
-		}, 0);
-	});
+  getIframeListRoutes();
+  // https://gitee.com/lyt-top/vue-next-admin/issues/I58U75
+  // https://gitee.com/lyt-top/vue-next-admin/issues/I59RXK
+  // https://gitee.com/lyt-top/vue-next-admin/pulls/40
+  nextTick(() => {
+    setTimeout(() => {
+      if (themeConfig.value.isCacheTagsView) {
+        let tagsViewArr: RouteItem[] = Session.get('tagsViewList') || [];
+        cachedViews.value = tagsViewArr.filter((item) => item.meta?.isKeepAlive).map((item) => item.name as string);
+      }
+    }, 0);
+  });
 });
 // 页面卸载时
 onUnmounted(() => {
-	mittBus.off('onTagsViewRefreshRouterView', () => { });
+  mittBus.off('onTagsViewRefreshRouterView', () => {
+  });
 });
 // 监听路由变化,防止 tagsView 多标签时,切换动画消失
 // https://toscode.gitee.com/lyt-top/vue-next-admin/pulls/38/files
 watch(
-	() => route.fullPath,
-	() => {
-		state.refreshRouterViewKey = decodeURI(route.fullPath);
-	},
-	{
-		immediate: true,
-	}
+    () => route.fullPath,
+    () => {
+      state.refreshRouterViewKey = decodeURI(route.fullPath);
+    },
+    {
+      immediate: true
+    }
 );
 </script>

+ 2 - 4
src/theme/app.scss

@@ -32,12 +32,12 @@ body,
 	width: 100%;
 	height: 100%;
 	color: rgba(0, 0, 0, 0.88);
+	font-size: 16px;
 	font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif;
 	font-weight: 400;
 	-webkit-font-smoothing: antialiased;
 	-webkit-tap-highlight-color: transparent;
 	background-color: var(--next-bg-main-color);
-	font-size: 14px;
 	overflow: hidden;
 	position: relative;
 	--vxe-ui-font-primary-color: #165DFF;
@@ -51,13 +51,11 @@ body,
 	.layout-pd {
 		padding: 15px !important;
 	}
-	.layout-screen-height {
-		height: calc(100vh - 300px);
-	}
 	.layout-flex {
 		display: flex;
 		flex-direction: column;
 		flex: 1;
+		height: 100%;
 	}
 	.layout-aside {
 		background: var(--next-bg-menuBar);

+ 47 - 0
src/utils/useTableHeight.ts

@@ -0,0 +1,47 @@
+/**
+ * 计算表格高度
+ * @param titleRef 标题容器
+ * @param queryRef 查询条件的容器
+ */
+export function useTableHeight(titleRef: Ref<HTMLElement | null>, queryRef: Ref<HTMLElement | null>) {
+  const computeTableHeight = ref({
+    titleHeight: 0,
+    queryHeight: 0,
+    headerHeight: 50,
+    dividerHeight: 32,
+    toolbarHeight: 51,
+    padding: 40,
+  });
+
+  const totalOtherHeight = ref(0);
+  const tableHeight = ref(0);
+
+  const computeHeight = () => {
+    const titleElement = unref(titleRef);
+    const queryElement = unref(queryRef);
+
+    computeTableHeight.value.titleHeight = titleElement ? titleElement.scrollHeight : 0;
+    computeTableHeight.value.queryHeight = queryElement ? queryElement.scrollHeight : 0;
+
+    totalOtherHeight.value =
+        computeTableHeight.value.titleHeight +
+        computeTableHeight.value.queryHeight +
+        computeTableHeight.value.headerHeight +
+        computeTableHeight.value.dividerHeight +
+        computeTableHeight.value.toolbarHeight +
+        computeTableHeight.value.padding;
+
+    tableHeight.value = window.innerHeight - totalOtherHeight.value;
+  };
+
+  onMounted(() => {
+    computeHeight(); // 组件挂载时计算一次高度
+    window.addEventListener('resize', computeHeight); // 监听窗口大小变化
+  });
+
+  onUnmounted(() => {
+    window.removeEventListener('resize', computeHeight); // 移除事件监听器
+  });
+
+  return { tableHeight: tableHeight as Ref<number> }; // 返回类型注释
+}

+ 0 - 0
src/views/product-list/colDefine.tsx → src/views/product-list/ColumnsTsx.tsx


+ 71 - 99
src/views/product-list/component/DataTable.vue

@@ -5,18 +5,17 @@
  * @Author: Cheney
  */
 
-import { ArrowDown, Download, Message, Money, Open, Operation, Refresh } from '@element-plus/icons-vue';
+import { Download, Message, Money, Open, Operation, Refresh } from '@element-plus/icons-vue';
 import { usePagination } from '/@/utils/usePagination';
 import { useTableData } from '/@/utils/useTableData';
 import * as api from '/@/views/product-list/api';
 import PermissionButton from '/@/components/PermissionButton/index.vue';
-import { productColumns } from '/@/views/product-list/colDefine';
+import { productColumns } from '/@/views/product-list/ColumnsTsx';
 import { useResponse } from '/@/utils/useResponse';
 import EditDrawer from '/@/views/product-list/component/EditDrawer.vue';
 import NoticeDialog from '/@/views/product-list/component/NoticeDialog.vue';
 import ImportButton from '/@/components/ImportButton/index.vue';
 import VerticalDivider from '/@/components/VerticalDivider/index.vue';
-import { Icon } from '@iconify/vue';
 
 
 const { tableOptions, handlePageChange } = usePagination(fetchList);
@@ -27,7 +26,7 @@ const gridOptions: any = reactive({
   round: true,
   stripe: true,
   currentRowHighLight: true,
-  height: 'auto',
+  height: '100%',
   toolbarConfig: {
     custom: true,
     slots: {
@@ -126,102 +125,79 @@ function downloadTemplate() {
 </script>
 
 <template>
-  <div class="layout-screen-height">
-    <vxe-grid ref="gridRef" v-bind="gridOptions"
-              @checkbox-change="selectChangeEvent"
-              @checkbox-all="selectAllChangeEvent">
-      <template #toolbar_buttons>
-        <div class="flex gap-2">
-          <PermissionButton :icon="Open" plain round type="primary" :disabled="!checkedList.size" @click="batchOpen">
-            批量开启
-          </PermissionButton>
-          <VerticalDivider class="px-1" style="margin-left: 7px;"/>
-          <div class="custom-el-input">
-            <el-select
-                v-model="value"
-                placeholder="Select"
-                style="width: 190px"
-            >
-              <template #prefix>
-                <div class="flex items-center">
-                  <el-button link type="success" style="margin-left: 0px; font-size: 14px"
-                             @click.stop="downloadTemplate">下载
-                  </el-button>
-                  <VerticalDivider />
-                </div>
-              </template>
-              <el-option
-                  label="商品通知模板"
-                  value="item1"
-              />
-              <el-option
-                  label="商品模板"
-                  value="item2"
-              />
-              <el-option
-                  label="指导价格模板"
-                  value="item3"
-              />
-            </el-select>
-          </div>
-          <ImportButton :icon="Message" text bg>
-            变更通知导入
-          </ImportButton>
-          <ImportButton  text bg>
-            <i class="bi bi-box-seam mr-3"></i>
-            商品导入
-          </ImportButton>
-          <ImportButton :icon="Money" text bg>
-            指导价格导入
-          </ImportButton>
+  <vxe-grid ref="gridRef" v-bind="gridOptions"
+            @checkbox-change="selectChangeEvent"
+            @checkbox-all="selectAllChangeEvent">
+    <template #toolbar_buttons>
+      <div class="flex gap-2">
+        <PermissionButton :disabled="!checkedList.size" :icon="Open" plain round type="primary" @click="batchOpen">
+          批量开启
+        </PermissionButton>
+        <VerticalDivider class="px-1" style="margin-left: 7px;" />
+        <div class="custom-el-input">
+          <el-select v-model="value" placeholder="Select" style="width: 190px">
+            <template #prefix>
+              <div class="flex items-center">
+                <el-button link style="margin-left: 0px; font-size: 14px" type="success"
+                           @click.stop="downloadTemplate">下载
+                </el-button>
+                <VerticalDivider />
+              </div>
+            </template>
+            <el-option label="商品通知模板" value="item1" />
+            <el-option label="商品模板" value="item2" />
+            <el-option label="指导价格模板" value="item3" />
+          </el-select>
         </div>
-      </template>
-      <!-- 工具栏右侧插槽 -->
-      <template #toolbar_tools>
-        <el-button circle class="toolbar-btn" @click="handleRefresh">
+        <ImportButton :icon="Message" bg text>变更通知导入</ImportButton>
+        <ImportButton bg text>
+          <i class="bi bi-box-seam mr-3"></i>
+          商品导入
+        </ImportButton>
+        <ImportButton :icon="Money" bg text>指导价格导入</ImportButton>
+      </div>
+    </template>
+    <template #toolbar_tools>
+      <el-button circle class="toolbar-btn" @click="handleRefresh">
+        <el-icon>
+          <Refresh />
+        </el-icon>
+      </el-button>
+      <el-button circle class="mr-3 toolbar-btn">
+        <el-icon>
+          <Download />
+        </el-icon>
+      </el-button>
+    </template>
+    <template #top>
+      <div class="mb-2"></div>
+    </template>
+    <template #pager>
+      <vxe-pager
+          v-model:currentPage="gridOptions.pagerConfig.page"
+          v-model:pageSize="gridOptions.pagerConfig.limit"
+          :total="gridOptions.pagerConfig.total"
+          class="mt-1.5"
+          @page-change="handlePageChange"
+      />
+    </template>
+    <template #operate="{ row }">
+      <div class="flex justify-between">
+        <PermissionButton circle plain type="warning" @click="handleEdit(row)">
           <el-icon>
-            <Refresh/>
+            <Operation />
           </el-icon>
-        </el-button>
-        <el-button circle class="mr-3 toolbar-btn">
+        </PermissionButton>
+        <PermissionButton circle plain type="info" @click="handleNotice(row)">
           <el-icon>
-            <Download/>
+            <Message />
           </el-icon>
-        </el-button>
-      </template>
-      <template #top>
-        <div class="mb-2"></div>
-      </template>
-      <!-- 分页插槽 -->
-      <template #pager>
-        <vxe-pager
-            v-model:currentPage="gridOptions.pagerConfig.page"
-            v-model:pageSize="gridOptions.pagerConfig.limit"
-            :total="gridOptions.pagerConfig.total"
-            @page-change="handlePageChange"
-        >
-        </vxe-pager>
-      </template>
-      <!-- 表格内容插槽 -->
-      <template #operate="{ row }">
-        <div class="flex justify-between">
-          <PermissionButton circle plain type="warning" @click="handleEdit(row)">
-            <el-icon>
-              <Operation/>
-            </el-icon>
-          </PermissionButton>
-          <PermissionButton circle plain type="info" @click="handleNotice(row)">
-            <el-icon>
-              <Message/>
-            </el-icon>
-          </PermissionButton>
-        </div>
-      </template>
-    </vxe-grid>
-
-    <EditDrawer v-if="editOpen" v-model="editOpen" :row-data="rowData"/>
-    <NoticeDialog v-if="dialogVisible" v-model="dialogVisible" :row-data="rowData"/>
-  </div>
+        </PermissionButton>
+      </div>
+    </template>
+  </vxe-grid>
+  <EditDrawer v-if="editOpen" v-model="editOpen" :row-data="rowData" />
+  <NoticeDialog v-if="dialogVisible" v-model="dialogVisible" :row-data="rowData" />
 </template>
 
 <style scoped>
@@ -234,8 +210,4 @@ function downloadTemplate() {
 :deep(.custom-el-input .el-select__wrapper) {
   border-radius: 20px;
 }
-
-/* .screen-height {
-  height: calc(100vh - 300px);
-} */
 </style>

+ 21 - 13
src/views/product-list/index.vue

@@ -8,8 +8,14 @@
 import VerticalDivider from '/@/components/VerticalDivider/index.vue';
 import { RefreshRight, Search } from '@element-plus/icons-vue';
 import DataTable from './component/DataTable.vue';
+import { useTableHeight } from '/@/utils/useTableHeight';
+import { useTemplateRef } from 'vue';
 
 
+const titleContainer: Ref<HTMLElement | null> = useTemplateRef('titleContainer');
+const queryContainer: Ref<HTMLElement | null> = useTemplateRef('queryContainer');
+
+const { tableHeight } = useTableHeight(titleContainer, queryContainer);
 const loading = ref(false);
 
 const formInline = reactive({
@@ -24,39 +30,40 @@ function onClick() {
     loading.value = false;
   }, 2000);
 }
+
 </script>
 
 <template>
-  <div class="p-5">
-    <el-card style="color: rgba(0, 0, 0, 0.88);">
-      <div class="text-xl font-semibold pb-7">商品列表</div>
+  <div class="p-5 flex-grow">
+    <el-card class="h-full" style="color: rgba(0, 0, 0, 0.88);">
+      <div ref="titleContainer" class="text-xl font-semibold pb-7">商品列表</div>
       <!-- 查询条件 -->
-      <div class="flex justify-between">
+      <div ref="queryContainer" class="flex justify-between">
         <div class="flex flex-1">
           <div class="w-full whitespace-nowrap">
             <el-row :gutter="20" style="margin-bottom: 16px;">
               <el-col :span="6">
                 <div class="flex items-center">
                   <span class="mr-2">国 家</span>
-                  <el-select v-model="formInline.date" clearable placeholder="请选择国家"/>
+                  <el-select v-model="formInline.date" clearable placeholder="请选择国家" />
                 </div>
               </el-col>
               <el-col :span="6">
                 <div class="flex items-center">
                   <span class="mr-2">品 牌</span>
-                  <el-select v-model="formInline.date" clearable placeholder="请选择品牌"/>
+                  <el-select v-model="formInline.date" clearable placeholder="请选择品牌" />
                 </div>
               </el-col>
               <el-col :span="6">
                 <div class="flex items-center">
                   <span class="mr-2">分 组</span>
-                  <el-select v-model="formInline.date" clearable placeholder="请选择分组"/>
+                  <el-select v-model="formInline.date" clearable placeholder="请选择分组" />
                 </div>
               </el-col>
               <el-col :span="6">
                 <div class="flex items-center">
                   <span class="mr-2">状 态</span>
-                  <el-select v-model="formInline.date" clearable placeholder="请选择状态"/>
+                  <el-select v-model="formInline.date" clearable placeholder="请选择状态" />
                 </div>
               </el-col>
             </el-row>
@@ -87,18 +94,19 @@ function onClick() {
           <el-button :icon="Search" :loading="loading" class="mb-4" type="primary" @click="onClick">
             查 询
           </el-button>
-          <el-button :icon="RefreshRight" style="width: 88px; color: #3c3c3c;" color="#ECECF1C9" >
+          <el-button :icon="RefreshRight" color="#ECECF1C9" style="width: 88px; color: #3c3c3c;">
             重 置
           </el-button>
         </div>
       </div>
-      <el-divider style="margin: 20px 0 12px 0"/>
-        
-      <DataTable></DataTable>
-
+      <el-divider ref="dividerContainer" style="margin: 20px 0 12px 0;" />
+      <div :style="{ height: tableHeight + 'px' }">
+        <DataTable />
+      </div>
     </el-card>
   </div>
 </template>
 
 <style scoped>
+
 </style>

+ 1 - 1
src/views/test/index.vue

@@ -12,7 +12,7 @@
 </script>
 
 <template>
-<div style="background-color: #3c3c3c;">
+<div class="flex-grow" style="background-color: #3c3c3c;">
   asd
 </div>
 </template>

+ 12 - 1
tailwind.config.js

@@ -6,7 +6,18 @@ module.exports = {
 			height: {
 				'screen/2': '50vh',
 			},
+			spacing: {
+				'5.5': '20px',  // 添加自定义间距值
+			},
 		},
 	},
-	plugins: [],
+	plugins: [
+		function({ addUtilities }) {
+			addUtilities({
+				'.border-none': {
+					border: 'none !important',
+				}
+			})
+		}
+	],
 };