Jelajahi Sumber

试图移植 vue-pure-thin

mrh 1 tahun lalu
induk
melakukan
ab543e658e

+ 1 - 1
.env.dev

@@ -1,3 +1,3 @@
 BASE_URL=swl-8l9.pages.dev
 # 必须要加上 http:// ,否则框架会识别为后缀名然后地址重叠错乱
-VITE_API_URLS=http://192.168.1.32:8600,http://192.168.1.32:8600
+VITE_API_URLS=http://192.168.1.32:8601,http://192.168.1.32:8600

+ 26 - 4
src/App.vue

@@ -1,11 +1,33 @@
 <script setup>
     import { ref } from 'vue'
-    
+    import Head from '@/views/home/head.vue'; 
 </script>
 <template>
-    <router-view />
+      <el-container >
+        <el-header id="Head">
+            <Head></Head>
+        </el-header>
+        
+        <el-main >
+            <router-view />
+        </el-main>
+      </el-container>
 </template>
 
-<style lang="css">
-    
+<style lang="css" scoped>
+#Head  {
+    border: 1px solid;
+    border-color: rgb(227, 227, 227);
+}
+
+#app .el-container {
+    height: 100vh; 
+    display: flex; 
+    flex-direction: column;
+}
+
+.el-main{
+    background-color: rgb(233,241,253);
+}
+
 </style>

+ 35 - 0
src/api/client.js

@@ -0,0 +1,35 @@
+import axios from 'axios'; 
+const token = localStorage.getItem('token');  
+
+function createApiClient(urls, maxRetries = 3) {  
+    let currentIndex = 0;  
+    let retries = maxRetries;  
+    
+    return {  
+      get: async (url, config = {}) => {  
+        if (retries === 0) {  
+          throw new Error('Max retries exceeded');  
+        }  
+    
+        const apiUrl = urls[currentIndex];  
+        currentIndex = (currentIndex + 1) % urls.length;  
+    
+        const instance = axios.create({  
+          baseURL: apiUrl,  
+          headers: { Authorization: `Bearer ${token}` },  
+          ...config  
+        });  
+    
+        try {  
+          const response = await instance.get(url);  
+          retries = maxRetries; // 如果请求成功,重置重试次数  
+          return response;  
+        } catch (error) {  
+          console.error(`Request to ${apiUrl} failed, ${retries} retries left...`, error);  
+          retries--;  
+          return await this.get(url, config); // 重试  
+        }  
+      }  
+    };  
+  }  
+export default createApiClient

+ 146 - 0
src/component/notice/data.ts

@@ -0,0 +1,146 @@
+export interface ListItem {
+  avatar: string;
+  title: string;
+  datetime: string;
+  type: string;
+  description: string;
+  status?: "" | "success" | "warning" | "info" | "danger";
+  extra?: string;
+}
+
+export interface TabItem {
+  key: string;
+  name: string;
+  list: ListItem[];
+}
+
+export const noticesData: TabItem[] = [
+  {
+    key: "1",
+    name: "通知",
+    list: [
+      {
+        avatar:
+          "https://gw.alipayobjects.com/zos/rmsportal/ThXAXghbEsBCCSDihZxY.png",
+        title: "你收到了 12 份新周报",
+        datetime: "一年前",
+        description: "",
+        type: "1"
+      },
+      {
+        avatar:
+          "https://gw.alipayobjects.com/zos/rmsportal/OKJXDXrmkNshAMvwtvhu.png",
+        title: "你推荐的 前端高手 已通过第三轮面试",
+        datetime: "一年前",
+        description: "",
+        type: "1"
+      },
+      {
+        avatar:
+          "https://gw.alipayobjects.com/zos/rmsportal/kISTdvpyTAhtGxpovNWd.png",
+        title: "这种模板可以区分多种通知类型",
+        datetime: "一年前",
+        description: "",
+        type: "1"
+      },
+      {
+        avatar:
+          "https://gw.alipayobjects.com/zos/rmsportal/GvqBnKhFgObvnSGkDsje.png",
+        title:
+          "展示标题内容超过一行后的处理方式,如果内容超过1行将自动截断并支持tooltip显示完整标题。",
+        datetime: "一年前",
+        description: "",
+        type: "1"
+      },
+      {
+        avatar:
+          "https://gw.alipayobjects.com/zos/rmsportal/GvqBnKhFgObvnSGkDsje.png",
+        title: "左侧图标用于区分不同的类型",
+        datetime: "一年前",
+        description: "",
+        type: "1"
+      },
+      {
+        avatar:
+          "https://gw.alipayobjects.com/zos/rmsportal/GvqBnKhFgObvnSGkDsje.png",
+        title: "左侧图标用于区分不同的类型",
+        datetime: "一年前",
+        description: "",
+        type: "1"
+      }
+    ]
+  },
+  {
+    key: "2",
+    name: "消息",
+    list: [
+      {
+        avatar:
+          "https://gw.alipayobjects.com/zos/rmsportal/fcHMVNCjPOsbUGdEduuv.jpeg",
+        title: "李白 评论了你",
+        description: "长风破浪会有时,直挂云帆济沧海",
+        datetime: "一年前",
+        type: "2"
+      },
+      {
+        avatar:
+          "https://gw.alipayobjects.com/zos/rmsportal/fcHMVNCjPOsbUGdEduuv.jpeg",
+        title: "李白 回复了你",
+        description: "行路难,行路难,多歧路,今安在。",
+        datetime: "一年前",
+        type: "2"
+      },
+      {
+        avatar:
+          "https://gw.alipayobjects.com/zos/rmsportal/fcHMVNCjPOsbUGdEduuv.jpeg",
+        title: "标题",
+        description:
+          "请将鼠标移动到此处,以便测试超长的消息在此处将如何处理。本例中设置的描述最大行数为2,超过2行的描述内容将被省略并且可以通过tooltip查看完整内容",
+        datetime: "一年前",
+        type: "2"
+      }
+    ]
+  },
+  {
+    key: "3",
+    name: "代办",
+    list: [
+      {
+        avatar: "",
+        title: "任务名称",
+        description: "任务需要在 2022-11-16 20:00 前启动",
+        datetime: "",
+        extra: "未开始",
+        status: "info",
+        type: "3"
+      },
+      {
+        avatar: "",
+        title: "第三方紧急代码变更",
+        description:
+          "一拳提交于 2022-11-16,需在 2022-11-18 前完成代码变更任务",
+        datetime: "",
+        extra: "马上到期",
+        status: "danger",
+        type: "3"
+      },
+      {
+        avatar: "",
+        title: "信息安全考试",
+        description: "指派小仙于 2022-12-12 前完成更新并发布",
+        datetime: "",
+        extra: "已耗时 8 天",
+        status: "warning",
+        type: "3"
+      },
+      {
+        avatar: "",
+        title: "vue-pure-admin 版本发布",
+        description: "vue-pure-admin 版本发布",
+        datetime: "",
+        extra: "进行中",
+        type: "3"
+      }
+    ]
+  }
+];

+ 88 - 0
src/component/notice/index.vue

@@ -0,0 +1,88 @@
+<script setup lang="ts">
+import { ref } from "vue";
+import { noticesData } from "./data";
+import NoticeList from "./noticeList.vue";
+import Bell from "@iconify-icons/ep/bell";
+
+const noticesNum = ref(0);
+const notices = ref(noticesData);
+const activeKey = ref(noticesData[0].key);
+
+notices.value.map(v => (noticesNum.value += v.list.length));
+</script>
+
+<template>
+  <el-dropdown trigger="click" placement="bottom-end">
+    <span class="dropdown-badge navbar-bg-hover select-none">
+      <el-badge :value="noticesNum" :max="99">
+        <span class="header-notice-icon">
+          <IconifyIconOffline :icon="Bell" />
+        </span>
+      </el-badge>
+    </span>
+    <template #dropdown>
+      <el-dropdown-menu>
+        <el-tabs
+          :stretch="true"
+          v-model="activeKey"
+          class="dropdown-tabs"
+          :style="{ width: notices.length === 0 ? '200px' : '330px' }"
+        >
+          <el-empty
+            v-if="notices.length === 0"
+            description="暂无消息"
+            :image-size="60"
+          />
+          <span v-else>
+            <template v-for="item in notices" :key="item.key">
+              <el-tab-pane
+                :label="`${item.name}(${item.list.length})`"
+                :name="`${item.key}`"
+              >
+                <el-scrollbar max-height="330px">
+                  <div class="noticeList-container">
+                    <NoticeList :list="item.list" />
+                  </div>
+                </el-scrollbar>
+              </el-tab-pane>
+            </template>
+          </span>
+        </el-tabs>
+      </el-dropdown-menu>
+    </template>
+  </el-dropdown>
+</template>
+
+<style lang="scss" scoped>
+.dropdown-badge {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  width: 40px;
+  height: 48px;
+  margin-right: 10px;
+  cursor: pointer;
+
+  .header-notice-icon {
+    font-size: 18px;
+  }
+}
+
+.dropdown-tabs {
+  .noticeList-container {
+    padding: 15px 24px 0;
+  }
+
+  :deep(.el-tabs__header) {
+    margin: 0;
+  }
+
+  :deep(.el-tabs__nav-wrap)::after {
+    height: 1px;
+  }
+
+  :deep(.el-tabs__nav-wrap) {
+    padding: 0 36px;
+  }
+}
+</style>

+ 177 - 0
src/component/notice/noticeItem.vue

@@ -0,0 +1,177 @@
+<script setup lang="ts">
+import { ListItem } from "./data";
+import { ref, PropType, nextTick } from "vue";
+import { useNav } from "@/layout/hooks/useNav";
+import { deviceDetection } from "@pureadmin/utils";
+
+const props = defineProps({
+  noticeItem: {
+    type: Object as PropType<ListItem>,
+    default: () => {}
+  }
+});
+
+const titleRef = ref(null);
+const titleTooltip = ref(false);
+const descriptionRef = ref(null);
+const descriptionTooltip = ref(false);
+const { tooltipEffect } = useNav();
+const isMobile = deviceDetection();
+
+function hoverTitle() {
+  nextTick(() => {
+    titleRef.value?.scrollWidth > titleRef.value?.clientWidth
+      ? (titleTooltip.value = true)
+      : (titleTooltip.value = false);
+  });
+}
+
+function hoverDescription(event, description) {
+  // currentWidth 为文本在页面中所占的宽度,创建标签,加入到页面,获取currentWidth ,最后在移除
+  const tempTag = document.createElement("span");
+  tempTag.innerText = description;
+  tempTag.className = "getDescriptionWidth";
+  document.querySelector("body").appendChild(tempTag);
+  const currentWidth = (
+    document.querySelector(".getDescriptionWidth") as HTMLSpanElement
+  ).offsetWidth;
+  document.querySelector(".getDescriptionWidth").remove();
+
+  // cellWidth为容器的宽度
+  const cellWidth = event.target.offsetWidth;
+
+  // 当文本宽度大于容器宽度两倍时,代表文本显示超过两行
+  currentWidth > 2 * cellWidth
+    ? (descriptionTooltip.value = true)
+    : (descriptionTooltip.value = false);
+}
+</script>
+
+<template>
+  <div
+    class="notice-container border-b-[1px] border-solid border-[#f0f0f0] dark:border-[#303030]"
+  >
+    <el-avatar
+      v-if="props.noticeItem.avatar"
+      :size="30"
+      :src="props.noticeItem.avatar"
+      class="notice-container-avatar"
+    />
+    <div class="notice-container-text">
+      <div class="notice-text-title text-[#000000d9] dark:text-white">
+        <el-tooltip
+          popper-class="notice-title-popper"
+          :effect="tooltipEffect"
+          :disabled="!titleTooltip"
+          :content="props.noticeItem.title"
+          placement="top-start"
+          :enterable="!isMobile"
+        >
+          <div
+            ref="titleRef"
+            class="notice-title-content"
+            @mouseover="hoverTitle"
+          >
+            {{ props.noticeItem.title }}
+          </div>
+        </el-tooltip>
+        <el-tag
+          v-if="props.noticeItem?.extra"
+          :type="props.noticeItem?.status"
+          size="small"
+          class="notice-title-extra"
+        >
+          {{ props.noticeItem?.extra }}
+        </el-tag>
+      </div>
+
+      <el-tooltip
+        popper-class="notice-title-popper"
+        :effect="tooltipEffect"
+        :disabled="!descriptionTooltip"
+        :content="props.noticeItem.description"
+        placement="top-start"
+      >
+        <div
+          ref="descriptionRef"
+          class="notice-text-description"
+          @mouseover="hoverDescription($event, props.noticeItem.description)"
+        >
+          {{ props.noticeItem.description }}
+        </div>
+      </el-tooltip>
+      <div class="notice-text-datetime text-[#00000073] dark:text-white">
+        {{ props.noticeItem.datetime }}
+      </div>
+    </div>
+  </div>
+</template>
+
+<style>
+.notice-title-popper {
+  max-width: 238px;
+}
+</style>
+<style scoped lang="scss">
+.notice-container {
+  display: flex;
+  align-items: flex-start;
+  justify-content: space-between;
+  padding: 12px 0;
+
+  // border-bottom: 1px solid #f0f0f0;
+
+  .notice-container-avatar {
+    margin-right: 16px;
+    background: #fff;
+  }
+
+  .notice-container-text {
+    display: flex;
+    flex: 1;
+    flex-direction: column;
+    justify-content: space-between;
+
+    .notice-text-title {
+      display: flex;
+      margin-bottom: 8px;
+      font-size: 14px;
+      font-weight: 400;
+      line-height: 1.5715;
+      cursor: pointer;
+
+      .notice-title-content {
+        flex: 1;
+        width: 200px;
+        overflow: hidden;
+        text-overflow: ellipsis;
+        white-space: nowrap;
+      }
+
+      .notice-title-extra {
+        float: right;
+        margin-top: -1.5px;
+        font-weight: 400;
+      }
+    }
+
+    .notice-text-description,
+    .notice-text-datetime {
+      font-size: 12px;
+      line-height: 1.5715;
+    }
+
+    .notice-text-description {
+      display: -webkit-box;
+      overflow: hidden;
+      text-overflow: ellipsis;
+      -webkit-line-clamp: 2;
+      -webkit-box-orient: vertical;
+    }
+
+    .notice-text-datetime {
+      margin-top: 4px;
+    }
+  }
+}
+</style>

+ 23 - 0
src/component/notice/noticeList.vue

@@ -0,0 +1,23 @@
+<script setup lang="ts">
+import { PropType } from "vue";
+import { ListItem } from "./data";
+import NoticeItem from "./noticeItem.vue";
+
+const props = defineProps({
+  list: {
+    type: Array as PropType<Array<ListItem>>,
+    default: () => []
+  }
+});
+</script>
+
+<template>
+  <div v-if="props.list.length">
+    <NoticeItem
+      v-for="(item, index) in props.list"
+      :noticeItem="item"
+      :key="index"
+    />
+  </div>
+  <el-empty v-else description="暂无数据" />
+</template>

+ 15 - 44
src/views/account/index.vue

@@ -1,60 +1,31 @@
 <template>  
-    <div>  
-      <h1>用户主页</h1>  
-      <div v-if="user">  
-        <h1>Welcome, {{ user.nickname }}!</h1>  
-        <img :src="user.avatar" alt="User Avatar" />  
-      </div>  
-    </div>  
+    <div >
+            <h1>用户主页</h1>  
+            <div v-if="user">  
+                <h1>Welcome, {{ user.nickname }}!</h1>  
+                <img :src="user.avatar" alt="User Avatar" />  
+            </div>  
+    </div>
+
 </template>  
 <script setup>  
 import { computed, ref, onMounted } from 'vue';  
 import { useStore } from 'vuex';  
+import Head from '@/views/home/head.vue';  
+import createApiClient from '@/api/client.js';  
 import axios from 'axios';  
   
 const API_URLS = import.meta.env.VITE_API_URLS.split(',');  
 const store = useStore();  
-const user = ref(null);  
-const token = localStorage.getItem('token');  
-  
-function createApiClient(urls, maxRetries = 3) {  
-  let currentIndex = 0;  
-  let retries = maxRetries;  
-  
-  return {  
-    get: async (url, config = {}) => {  
-      if (retries === 0) {  
-        throw new Error('Max retries exceeded');  
-      }  
-  
-      const apiUrl = urls[currentIndex];  
-      currentIndex = (currentIndex + 1) % urls.length;  
-  
-      const instance = axios.create({  
-        baseURL: apiUrl,  
-        headers: { Authorization: `Bearer ${token}` },  
-        ...config  
-      });  
-  
-      try {  
-        const response = await instance.get(url);  
-        retries = maxRetries; // 如果请求成功,重置重试次数  
-        return response;  
-      } catch (error) {  
-        console.error(`Request to ${apiUrl} failed, ${retries} retries left...`, error);  
-        retries--;  
-        return await this.get(url, config); // 重试  
-      }  
-    }  
-  };  
-}  
-  
+const user = ref(null)
+
+
 const apiClient = createApiClient(API_URLS);  
   
-const loadUserInfo = async () => {  
+const loadUserInfo = async () => {
   try {  
     const response = await apiClient.get('/user_info');  
-    user.value = response.data;  
+    user.value = response.data;
   } catch (error) {  
     console.error('Failed to load user info after all retries', error);  
     // 这里可以添加进一步的错误处理  

+ 13 - 0
src/views/account/sidebar.vue

@@ -0,0 +1,13 @@
+<template lang="">
+    <div>
+        
+    </div>
+</template>
+
+<script setup>
+   
+</script>
+
+<style lang="" scoped>
+   
+</style>

+ 2 - 19
src/views/home/index.vue

@@ -1,31 +1,14 @@
 <template lang="">
     <div id="div-home">
-      <el-container >
-        <el-header >
-            <Head></Head>
-        </el-header>
-        
-        <el-main >
-            <Main></Main>
-        </el-main>
-      </el-container>
+        <Main></Main>
     </div>
 </template>
 <script setup>
 import Main from './main.vue'; 
-import Head from './head.vue'; 
+
 
 </script>
 <style lang="css" scoped >
-#div-home .el-container {
-    height: 100vh; 
-    display: flex; 
-    flex-direction: column;
-}
-
-.el-main{
-    background-color: rgb(233,241,253);
-}
 
 /* .el-main .el-col {
     flex-grow: 1;

+ 1 - 1
src/views/home/verify.vue

@@ -1,6 +1,6 @@
 <template>  
     <div>  
-      <!-- 可以在这里添加一个加载指示器或其他UI元素 -->  
+      <!-- http://192.168.1.19:18888/verify?code=676a1101ea02bc5dTaUVtKg8c5enYaGqB4dT&state=&scopes=user_info,trial.whitelist -->  
     </div>  
 </template>  
 

+ 1 - 1
vite.config.js

@@ -13,7 +13,7 @@ export default defineConfig({
   },
   server: {  
     host: '0.0.0.0', // 监听所有地址,允许外部访问  
-    port: 18889,      // 指定端口号  
+    port: 18888,      // 指定端口号  
     // 其他配置项...  
   }, 
     define: {