Browse Source

feacture:访客概览静态模块制作

cmy 3 weeks ago
parent
commit
fdd020022a

+ 4 - 2
package-lock.json

@@ -18,6 +18,7 @@
 				"china-area-data": "^5.0.1",
 				"codemirror": "5.65.18",
 				"crypto-js": "4.2.0",
+				"dayjs": "^1.11.13",
 				"driver.js": "1.3.1",
 				"echarts": "5.5.1",
 				"element-plus": "2.8.7",
@@ -2692,8 +2693,9 @@
 		},
 		"node_modules/dayjs": {
 			"version": "1.11.13",
-			"resolved": "https://registry.npmmirror.com/dayjs/-/dayjs-1.11.13.tgz",
-			"integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg=="
+			"resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz",
+			"integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==",
+			"license": "MIT"
 		},
 		"node_modules/debug": {
 			"version": "4.4.0",

+ 0 - 4
src/layout/navMenu/horizontal.vue

@@ -53,10 +53,6 @@ const props = defineProps({
 	},
 });
 
-props.menuList.forEach((item) => {
-    console.log(JSON.stringify(item));
-})
-
 // 定义变量内容
 const elMenuHorizontalScrollRef = ref();
 const stores = useRoutesList();

+ 2 - 2
src/stores/themeConfig.ts

@@ -101,9 +101,9 @@ export const useThemeConfig = defineStore('themeConfig', {
 			// 是否开启色弱模式
 			isInvert: false,
 			// 是否开启水印
-			isWartermark: true,
+			isWartermark: false,
 			// 水印文案
-			wartermarkText: 'Pig',
+			wartermarkText: '',
 
 			/**
 			 * 其它设置

+ 0 - 121
src/views/home/current-user.vue

@@ -1,121 +0,0 @@
-<template>
-	<el-card class="h-full shadow-sm hover:shadow-md transition-shadow">
-		<div class="flex items-center justify-between">
-			<!-- 用户信息 -->
-			<div class="flex items-center gap-4">
-				<el-avatar 
-					:size="56" 
-					shape="circle" 
-					:src="baseURL + userData.avatar"
-					class="ring-1 ring-gray-100" 
-				/>
-				<div>
-					<h3 class="text-lg font-semibold text-gray-800 mb-2">{{ userData.name }}</h3>
-					<div class="flex items-center gap-2 text-sm">
-						<span class="px-3 py-1 bg-blue-50 text-blue-600 rounded-full">{{ userData?.dept?.name }}</span>
-						<span v-if="userData.postName" class="px-3 py-1 bg-green-50 text-green-600 rounded-full">{{ userData.postName }}</span>
-					</div>
-				</div>
-			</div>
-
-			<!-- 时间 -->
-			<div class="flex items-center gap-2 text-sm text-gray-500">
-				<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
-					<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"></path>
-				</svg>
-				<span>{{ parseTime(date) }}</span>
-			</div>
-		</div>
-	</el-card>
-</template>
-
-<script setup lang="ts" name="currentUser">
-import { useUserInfo } from '/@/stores/userInfo';
-import { getObj } from '/@/api/admin/user';
-
-const date = ref(new Date());
-
-const userData = ref({
-	postName: '',
-	name: '',
-	username: '',
-	userId: '',
-	avatar: '',
-	deptName: '',
-} as any);
-const loading = ref(false);
-
-setInterval(() => {
-	date.value = new Date();
-}, 1000);
-
-onMounted(() => {
-	const data = useUserInfo().userInfos;
-	initUserInfo(data.user.userId);
-});
-
-/**
- * 根据用户 ID 初始化用户信息。
- * @param {any} userId - 要查询的用户 ID。
- * @returns {Promise<void>} - 初始化用户信息的 Promise 实例。
- */
-const initUserInfo = async (userId: any): Promise<void> => {
-	try {
-		loading.value = true; // 显示加载状态
-
-		const res = await getObj(userId); // 执行查询操作
-		userData.value = res.data; // 将查询到的数据保存到 userData 变量中
-		userData.value.postName = res.data?.postList?.map((item: any) => item.postName).join(',') || ''; // 将 postList 中的 postName 合并成字符串并保存到 userData 变量中
-		// 文件上传增加后端前缀
-		userData.value.avatar = res.data.avatar;
-	} finally {
-		loading.value = false; // 结束加载状态
-	}
-};
-</script>
-
-<style scoped>
-.el-card {
-	border: 1px solid #e5e7eb;
-	border-radius: 12px;
-	transition: all 0.2s ease;
-}
-
-.el-card :deep(.el-card__body) {
-	padding: 24px;
-}
-
-/* 头像轻微悬停效果 */
-.el-avatar {
-	transition: transform 0.2s ease;
-}
-
-.el-avatar:hover {
-	transform: scale(1.05);
-}
-
-/* 标签悬停效果 */
-.px-3.py-1 {
-	transition: all 0.2s ease;
-	font-weight: 500;
-}
-
-.px-3.py-1:hover {
-	transform: translateY(-1px);
-	box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
-}
-
-/* 响应式适配 */
-@media (max-width: 640px) {
-	.flex.items-center.justify-between {
-		flex-direction: column;
-		align-items: flex-start;
-		gap: 16px;
-	}
-	
-	.el-card :deep(.el-card__body) {
-		padding: 16px;
-	}
-}
-</style>
-

+ 8 - 2
src/views/home/custom-panel.vue → src/views/home/echarts/custom-panel.vue

@@ -1,8 +1,8 @@
 <template>
-  <el-card>
+  <el-card shadow="hover">
     <div class="panel-top">
       <div class="panel-name">{{prop.title}}</div>
-      <div class="cursor-pointer"  @click="$emit('reload')">
+      <div class="cursor-pointer"  @click="handleRefresh">
         <svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
           <path d="M16.7167 6.66667C15.4895 4.19712 12.9411 2.5 9.99637 2.5C7.05158 2.5 4.56062 4.19712 3.33337 6.66667" stroke="#ADBFD7" stroke-linecap="round"/>
           <path d="M3.33337 3.33334V6.66667" stroke="#ADBFD7" stroke-linecap="round"/>
@@ -24,6 +24,12 @@ const prop = defineProps({
     default: ''
   }
 })
+
+const emit = defineEmits(['refresh']);
+
+const handleRefresh = () => {
+  emit('refresh');
+};
 </script>
 
 <style scoped lang="scss">

+ 26 - 15
src/views/home/traffic-sources.vue → src/views/home/echarts/traffic-sources.vue

@@ -3,7 +3,7 @@
   <div class="visitor-trend">
     <div ref="chartRef" style="width: 100%; height: 300px;"></div>
     <div style="position: absolute;top: 20px;right: 0;">
-      <el-select v-model="value" style="width: 84px;">
+      <el-select v-model="value" style="width: 84px;" @change="initChart">
         <el-option v-for="item in options" :key="item.value" :label="item.label" :value="item.value" />
       </el-select>
     </div>
@@ -41,6 +41,8 @@ const generateRandomData = () => {
 
 // 初始化图表
 const initChart = () => {
+  if (!chartRef.value) return;
+  
   if (chartInstance) {
     chartInstance.dispose();
   }
@@ -119,24 +121,33 @@ const initChart = () => {
   };
   
   chartInstance.setOption(option);
-  
-  // 响应式调整
-  window.addEventListener('resize', () => {
-    chartInstance.resize();
-  });
 };
 
-onMounted(() => {
+defineExpose({
+  initChart
+});
+
+onMounted(async () => {
   initChart();
 });
 
-onBeforeUnmount(() => {
-  if (chartInstance) {
-    window.removeEventListener('resize', () => {
-      chartInstance.resize();
-    });
-    chartInstance.dispose();
+</script>
+
+<style lang="scss">
+.visitor-trend {
+  position: relative;
+
+  /* 下拉框整体样式 */
+  .el-select__wrapper {
+    border: 1px solid rgba(22, 122, 240, 1);
   }
-});
 
-</script>
+  .el-select__selected-item {
+    color: rgba(22, 122, 240, 1); /* 文字颜色 */
+  }
+
+  .el-select__caret {
+    color: rgba(22, 122, 240, 1); /* 三角形颜色 */
+  }
+}
+</style>

+ 80 - 0
src/views/home/echarts/visitor-overview-item.vue

@@ -0,0 +1,80 @@
+<template>
+  <div class="item">
+    <span class="detail">
+      <slot></slot>
+    </span>
+    
+    <svg v-if="num > 0" width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
+      <mask id="mask0_1_312" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="0" y="0" width="12" height="12">
+        <rect width="12" height="12" fill="#D9D9D9" />
+      </mask>
+      <g mask="url(#mask0_1_312)">
+        <path
+          d="M4.94507 6.39557H6.52749V10.6485C6.52749 10.9215 6.23237 11.1428 5.86841 11.1428H5.60415C5.51759 11.1428 5.43189 11.13 5.35193 11.1052C5.27196 11.0804 5.19931 11.0439 5.13811 10.998C5.07691 10.9521 5.02836 10.8976 4.99524 10.8377C4.96212 10.7777 4.94507 10.7134 4.94507 10.6485V6.39557Z"
+          fill="#36BF6B" />
+        <path
+          d="M2.66265 5.42689L5.1842 1.15934C5.23836 1.06755 5.31401 0.991653 5.40402 0.938784C5.49403 0.885916 5.59546 0.857816 5.69875 0.85713C5.80204 0.856443 5.9038 0.883192 5.99445 0.934859C6.0851 0.986527 6.16167 1.06141 6.21694 1.15247L8.80647 5.42003C8.86485 5.51613 8.89742 5.62684 8.90079 5.74057C8.90416 5.85431 8.8782 5.9669 8.82562 6.06659C8.77304 6.16628 8.69578 6.2494 8.60189 6.30727C8.50801 6.36514 8.40096 6.39564 8.29192 6.39558H3.18085C3.07244 6.39569 2.96598 6.36559 2.87245 6.3084C2.77892 6.25121 2.70172 6.169 2.64882 6.07025C2.59592 5.97151 2.56925 5.85982 2.57155 5.74671C2.57385 5.63361 2.60505 5.5232 2.66192 5.42689H2.66265Z"
+          fill="#36BF6B" />
+      </g>
+    </svg>
+    <svg v-else-if="num < 0" width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
+      <mask id="mask0_1_319" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="0" y="0" width="12" height="12">
+        <rect width="12" height="12" transform="matrix(1 0 0 -1 0 12)" fill="#D9D9D9" />
+      </mask>
+      <g mask="url(#mask0_1_319)">
+        <path
+          d="M4.94507 5.60443H6.52749V1.35149C6.52749 1.07852 6.23237 0.85718 5.86841 0.85718H5.60415C5.51759 0.85718 5.43189 0.869966 5.35193 0.894807C5.27196 0.919648 5.19931 0.956059 5.13811 1.00196C5.07691 1.04786 5.02836 1.10235 4.99524 1.16232C4.96212 1.2223 4.94507 1.28657 4.94507 1.35149V5.60443Z"
+          fill="#E64242" />
+        <path
+          d="M2.66265 6.57311L5.1842 10.8407C5.23836 10.9324 5.31401 11.0083 5.40402 11.0612C5.49403 11.1141 5.59546 11.1422 5.69875 11.1429C5.80204 11.1436 5.9038 11.1168 5.99445 11.0651C6.0851 11.0135 6.16167 10.9386 6.21694 10.8475L8.80647 6.57997C8.86485 6.48387 8.89742 6.37316 8.90079 6.25943C8.90416 6.14569 8.8782 6.0331 8.82562 5.93341C8.77304 5.83372 8.69578 5.7506 8.60189 5.69273C8.50801 5.63486 8.40096 5.60436 8.29192 5.60442H3.18085C3.07244 5.60431 2.96598 5.63441 2.87245 5.6916C2.77892 5.74879 2.70172 5.831 2.64882 5.92975C2.59592 6.02849 2.56925 6.14018 2.57155 6.25329C2.57385 6.36639 2.60505 6.4768 2.66192 6.57311H2.66265Z"
+          fill="#E64242" />
+      </g>
+    </svg>
+    <svg v-else width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
+      <mask id="mask0_1_351" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="0" y="0" width="12" height="12">
+        <rect width="12" height="12" transform="matrix(1 0 0 -1 0 12)" fill="#D9D9D9" />
+      </mask>
+      <g mask="url(#mask0_1_351)">
+        <circle cx="6" cy="6" r="4" fill="#888888" />
+      </g>
+    </svg>
+    <span class="num" :class="{ red: num < 0, green: num > 0 }">{{ num }}%</span>
+  </div>
+</template>
+
+<script setup>
+import { defineProps } from 'vue'
+
+const props = defineProps({
+  num: {
+    type: Number,
+    default: 0
+  }
+})
+</script>
+
+<style scoped lang="scss">
+.item {
+  margin-bottom: 22px;
+
+  .num {
+    line-height: 17px;
+    font-size: 12px;
+    color: rgba(136, 136, 136, 1);
+  }
+
+  .red {
+    color: rgba(230, 66, 66, 1);
+  }
+
+  .green {
+    color: rgba(54, 191, 107, 1);
+  }
+
+  svg {
+    vertical-align: middle;
+    margin-left: 5px;
+    margin-right: 3px;
+  }
+}
+</style>

+ 106 - 0
src/views/home/echarts/visitor-overview.vue

@@ -0,0 +1,106 @@
+<template>
+  <div class="visitor-overview">
+    <div class="overview-list">
+      <Item :num="-100">
+        <span>
+          <i>0</i> 访问次数, <i>0</i> 独立访客
+        </span>
+      </Item>
+      <Item :num="100">
+        <span>
+          <i>0秒</i> 平均停留时间 
+        </span>
+      </Item>
+      <Item :num="0">
+        <span>
+          <i>0%</i> 跳出次数 (查看一个页面后就离开) 
+        </span>
+      </Item>
+      <Item :num="-100">
+        <span>
+          <i>0</i> 每个访客行为数 (查看页面、下载、离站链接和内部搜索) 
+        </span>
+      </Item>
+      <Item :num="100">
+        <span>
+          <i>0</i> 单次访问的最大活动量 
+        </span>
+      </Item>
+      <Item :num="0">
+        <span>
+          <i>0</i> 页面访问次数, <i>0</i> 唯一页面浏览量 
+        </span>
+      </Item>
+      <Item :num="100">
+        <span>
+          <i>0</i> 站内搜索数, <i>0</i> 关键词数量 
+        </span>
+      </Item>
+      <Item :num="-100">
+        <span>
+          <i>0</i> 下载次数, <i>0</i> 唯一下载次数 
+        </span>
+      </Item>
+      <Item :num="100">
+        <span>
+          <i>0</i> 离站链接数量, <i>0</i> 唯一离站链接数量
+        </span>
+      </Item>
+    </div>
+    <div style="position: absolute;top: 20px;right: 0;">
+      <el-select v-model="value" style="width: 84px;">
+        <el-option v-for="item in options" :key="item.value" :label="item.label" :value="item.value" />
+      </el-select>
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { defineAsyncComponent } from 'vue';
+const Item = defineAsyncComponent(() => import('./visitor-overview-item.vue'));
+
+const value = ref('7');
+const options = [
+  {
+    value: '7',
+    label: '7天',
+    selected: true,
+  },
+  {
+    value: '30',
+    label: '30天',
+  },
+]
+</script>
+<style lang="scss">
+.visitor-overview {
+  position: relative;
+  padding-top: 70px;
+  .overview-list {
+    span {
+      line-height: 20px;
+      font-size: 14px;
+      color: rgba(100, 100, 100, 1);
+    }
+    i {
+      font-family: Source Han Sans SC;
+      font-weight: 900;
+      font-style: normal;
+      color: rgba(18, 18, 18, 1);
+    }
+  }
+
+  /* 下拉框整体样式 */
+  .el-select__wrapper {
+    border: 1px solid rgba(22, 122, 240, 1);
+  }
+
+  .el-select__selected-item {
+    color: rgba(22, 122, 240, 1); /* 文字颜色 */
+  }
+
+  .el-select__caret {
+    color: rgba(22, 122, 240, 1); /* 三角形颜色 */
+  }
+}
+</style>

+ 27 - 13
src/views/home/visitor-trend.vue → src/views/home/echarts/visitor-trend.vue

@@ -2,7 +2,7 @@
   <div class="visitor-trend">
     <div ref="chartRef" style="width: 100%; height: 300px;"></div>
     <div style="position: absolute;top: 20px;right: 0;">
-      <el-select v-model="value" style="width: 84px;">
+      <el-select v-model="value" style="width: 84px;" @change="initChart">
         <el-option v-for="item in options" :key="item.value" :label="item.label" :value="item.value" />
       </el-select>
     </div>
@@ -10,7 +10,7 @@
 </template>
 
 <script setup>
-import { ref, onMounted, onBeforeUnmount } from 'vue';
+import { ref, onMounted } from 'vue';
 import * as echarts from 'echarts';
 import dayjs from 'dayjs';
 
@@ -49,7 +49,13 @@ const colorList = [
   'rgba(22, 122, 240, 1)'    // 跳出率
 ];
 
-onMounted(() => {
+const initChart = () => {
+  if (!chartRef.value) return;
+  
+  if (chartInstance) {
+    chartInstance.dispose();
+  }
+  
   chartInstance = echarts.init(chartRef.value);
 
   // 准备数据
@@ -60,13 +66,22 @@ onMounted(() => {
   const option = {
     tooltip: {
       trigger: 'axis',
-      backgroundColor: 'rgba(255, 255, 255, 0.5)',
+      backgroundColor: 'rgba(255, 255, 255, 0)',
       borderColor: 'rgba(204, 204, 204, 1)',
-      borderWidth: 1,
+      shadowColor: 'transparent', // 将阴影颜色设为透明
+      borderWidth: 0,
       borderRadius: 0,
+      padding: 0,
       formatter: function (params) {
+        let result = `<div style="
+          background-color: rgba(255, 255, 255, 0.5); 
+          border: 1px solid rgba(204, 204, 204, 1); 
+          border: 1px solid rgba(204, 204, 204, 1);
+          padding: 10px; 
+          backdrop-filter: blur(4px)
+        ">`;
         const date = params[0].axisValue;
-        let result = `<div style="font-weight: bold; font-size: 12px; color: rgba(18, 18, 18, 1); margin-bottom: 5px;">${dayjs(date).format('YYYY年MM月DD日')}&nbsp;${WEEK_DAYS[dayjs(date).day()]}</div>`;
+        result += `<div style="font-weight: bold; font-size: 12px; color: rgba(18, 18, 18, 1); margin-bottom: 5px;">${dayjs(date).format('YYYY年MM月DD日')}&nbsp;${WEEK_DAYS[dayjs(date).day()]}</div>`;
         params.forEach(item => {
           let value = item.value;
           // // 特殊处理不同指标的单位
@@ -77,7 +92,7 @@ onMounted(() => {
           // }
           result += `<div style="font-size: 12px; color: #666; margin-top: 2px;">${item.marker} <span style="font-weight: bold; color: #000000;">${value}</span> ${item.seriesName}</div>`;
         });
-        return result;
+        return result + `</div>`;
       },
     },
     legend: {
@@ -187,15 +202,14 @@ onMounted(() => {
   };
 
   chartInstance.setOption(option);
+}
 
-  // 响应式调整
-  const handleResize = () => chartInstance?.resize();
-  window.addEventListener('resize', handleResize);
+defineExpose({
+  initChart
 });
 
-onBeforeUnmount(() => {
-  window.removeEventListener('resize', handleResize);
-  chartInstance?.dispose();
+onMounted(async () => {
+  initChart();
 });
 </script>
 

+ 47 - 10
src/views/home/index.vue

@@ -4,18 +4,17 @@
       <userInfo />
     </el-col>
     <el-col :xs="24" :sm="24" :md="16" :lg="10" :xl="10">
-      <custom-panel :title="'访客趋势图'">
-        <visitor-trend />
+      <custom-panel :title="'访客趋势图'" @refresh="handlePanelRefresh('visitorTrend')">
+        <visitor-trend ref="visitorTrendRef" />
       </custom-panel>
     </el-col>
     <el-col :xs="24" :sm="24" :md="24" :lg="8" :xl="9">
-      <custom-panel :title="'引用域'">
-        <traffic-sources />
+      <custom-panel :title="'引用域'" @refresh="handlePanelRefresh('trafficSources')">
+        <traffic-sources ref="trafficSourcesRef" />
       </custom-panel>
     </el-col>
     <el-col :xs="24" :sm="24" :md="24" :lg="10" :xl="10">
       <custom-panel :title="'访客地图'">
-        <visitor-overview />
       </custom-panel>
     </el-col>
     <el-col :xs="24" :sm="24" :md="10" :lg="6" :xl="6">
@@ -32,12 +31,50 @@
 </template>
 
 <script setup lang="ts" name="home">
-import { defineAsyncComponent,ref } from 'vue';
+import { defineAsyncComponent } from 'vue';
+import { debounce } from 'lodash';
 
-const customPanel = defineAsyncComponent(() => import('./custom-panel.vue'));
+const customPanel = defineAsyncComponent(() => import('./echarts/custom-panel.vue'));
 const userInfo = defineAsyncComponent(() => import('./user-info.vue'));
-const visitorTrend = defineAsyncComponent(() => import('./visitor-trend.vue'));
-const trafficSources = defineAsyncComponent(() => import('./traffic-sources.vue'));
-const VisitorOverview = defineAsyncComponent(() => import('./visitor-overview.vue'));
 const keywordFrequency = defineAsyncComponent(() => import('./echarts/keyword-frequency.vue'));
+const visitorTrend = defineAsyncComponent(() => import('./echarts/visitor-trend.vue'));
+const trafficSources = defineAsyncComponent(() => import('./echarts/traffic-sources.vue'));
+const VisitorOverview = defineAsyncComponent(() => import('./echarts/visitor-overview.vue'));
+
+
+const visitorTrendRef = ref(null);
+const trafficSourcesRef = ref(null);
+
+const handlePanelRefresh = (value: string) => {
+  if (value == 'visitorTrend' && visitorTrendRef.value) {
+    visitorTrendRef.value.initChart();
+  }else if (value == 'trafficSources' && trafficSourcesRef.value) {
+    trafficSourcesRef.value.initChart();
+  }
+};
+
+const resize = debounce(() => {
+  if (visitorTrendRef.value) {
+    visitorTrendRef.value.initChart();
+  }
+  if (trafficSourcesRef.value) {
+    trafficSourcesRef.value.initChart();
+  }
+}, 200); // 200ms防抖间隔
+
+onMounted(() => {
+  window.addEventListener('resize', resize);
+});
+
+onBeforeUnmount(() => {
+  window.removeEventListener('resize', resize);
+})
+
+onActivated(() => {
+  window.addEventListener('resize', resize);
+})
+
+onDeactivated(() => {
+  window.removeEventListener('resize', resize);
+})
 </script>

+ 0 - 12
src/views/home/schedule.vue

@@ -1,12 +0,0 @@
-<template>
-  <el-calendar v-model="value"/>
-</template>
-
-<script setup lang="ts" name="systemSysSchedule">
-const value = ref(new Date())
-</script>
-<style>
-.el-calendar-table .el-calendar-day{
-  height: 40px;
-}
-</style>

+ 37 - 38
src/views/home/user-info.vue

@@ -1,17 +1,19 @@
 <template>
   <div class="user-info">
-    <el-card>
-      <div class="flex" style="height: 226px; ">
+    <el-card shadow="hover">
+      <div class="flex" style="height: 236px; ">
         <div class="avatar">
           <el-avatar :size="100" :src="user.avatar" @error="errorHandler">
-            <img :src="user.avatar" />
+            <img :src="user.avatar.includes('http') ? user.avatar : baseURL + user.avatar" class="upload-image" />
           </el-avatar>
         </div>
         <div class="detail">
           <div class="nickname">{{ user.nickname }}</div>
           <div class="username">@{{ user.username }}</div>
           <div class="email">{{ user.email }}</div>
-          <div class="postName">{{ user.postList[0].postName }}</div>
+          <div class="postName">
+            <span v-for="item in postName">{{ item }}</span>
+          </div>
           <div class="dept">
             <svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
               <g clip-path="url(#clip0_1_110)">
@@ -56,44 +58,36 @@
 </template>
 
 <script setup lang="ts">
+import {useUserInfo} from '/@/stores/userInfo';
+
+const { user } = useUserInfo().userInfos;
+console.log('user', user);
+
+
 const errorHandler = () => {
   return './src/assets/avatar.png'
 }
+
 const postName = computed(() => {
-  return user.postList?.forEach((item: any) => {
-    return item.postName
-  })?.join('|')
+  const list = user.postList?.map((item: any) => {
+    return item.postName;
+  })
+  return list;
 })
 
-const user = {
-  "userId": "1",
-  "username": "admin",
-  "avatar": "/admin/sys-file/s3demo/063a1ea1f6714226851de23f1329672b.png",
-  "dept": {
-    "deptId": "4",
-    "name": "销售部",
-  },
-  "postList": [
-    {
-      "postName": "CTO",
-    }
-  ],
-  "nickname": "管理员",
-  "name": "管理员",
-  "email": "[email protected]"
-}
 </script>
 <style scoped lang="scss">
 .user-info {
   .flex {
-    padding-top: 44px;
     display: flex;
     justify-content: center;
     box-sizing: border-box;
+    align-items: center;
+    margin-top: -10px;
     .avatar {
-      margin-top: 8px;
       width: 50%;
       padding-left: calc(50% - 110px);
+      height: 100px;
     }
 
     .detail {
@@ -119,19 +113,24 @@ const user = {
       }
 
       .postName {
-        display: inline-block;
-        font-size: 12px;
-        line-height: 14px;
-        color: rgba(136, 136, 136, 1);
         margin-top: 8px;
-        border: 1px solid rgba(22, 122, 240, 1);
-        line-height: 20px;
-        border-width: 1px;
-        border-radius: 4px;
-        font-family: Source Han Sans SC;
-        font-size: 11px;
-        color: rgba(22, 122, 240, 1);
-        padding: 0 4px;
+        display: flex;
+        flex-wrap: wrap;
+        span {
+          font-size: 12px;
+          line-height: 14px;
+          color: rgba(136, 136, 136, 1);
+          border: 1px solid rgba(22, 122, 240, 1);
+          line-height: 20px;
+          border-width: 1px;
+          border-radius: 4px;
+          font-family: Source Han Sans SC;
+          font-size: 11px;
+          color: rgba(22, 122, 240, 1);
+          padding: 0 4px;
+          margin-right: 2px;
+          margin-bottom: 2px;
+        }
       }
 
       .dept {

+ 0 - 8
src/views/home/visitor-overview.vue

@@ -1,8 +0,0 @@
-<template>
-  
-</template>
-
-<script setup lang="ts">
-</script>
-<style scoped lang="scss">
-</style>