Browse Source

✨ feat: 完善主页、页头页脚

Pchen. 8 months ago
parent
commit
6f6f681392

+ 2 - 2
index.html

@@ -2,9 +2,9 @@
 <html lang="en">
 <html lang="en">
   <head>
   <head>
     <meta charset="UTF-8" />
     <meta charset="UTF-8" />
-    <link rel="icon" type="image/svg+xml" href="/vite.svg" />
+    <link rel="icon" type="image/png" href="/icon.png" />
     <meta name="viewport" content="width=device-width, initial-scale=1.0" />
     <meta name="viewport" content="width=device-width, initial-scale=1.0" />
-    <title>Vite + Vue</title>
+    <title>Double_X 考勤</title>
   </head>
   </head>
   <body>
   <body>
     <div id="app"></div>
     <div id="app"></div>

BIN
public/icon.png


BIN
public/icon2.png


+ 0 - 1
public/vite.svg

@@ -1 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>

+ 26 - 23
src/App.vue

@@ -1,30 +1,33 @@
-<script setup>
-import HelloWorld from './components/HelloWorld.vue'
-</script>
-
 <template>
 <template>
-  <div>
-    <a href="https://vitejs.dev" target="_blank">
-      <img src="/vite.svg" class="logo" alt="Vite logo" />
-    </a>
-    <a href="https://vuejs.org/" target="_blank">
-      <img src="./assets/vue.svg" class="logo vue" alt="Vue logo" />
-    </a>
-  </div>
-  <HelloWorld msg="Vite + Vue" />
+  <router-view />
 </template>
 </template>
 
 
-<style scoped>
-.logo {
-  height: 6em;
-  padding: 1.5em;
-  will-change: filter;
-  transition: filter 300ms;
+<style>
+body {
+  padding: 0;
+  margin: 0;
+  background-color: #eee;
+  -webkit-font-smoothing: antialiased;
+}
+
+a {
+  text-decoration: none;
+  color: #000;
+  -webkit-tap-highlight-color: rgba(255, 255, 255, 0);
+  -moz-user-focus: none;
 }
 }
-.logo:hover {
-  filter: drop-shadow(0 0 2em #646cffaa);
+
+@keyframes fadeIn {
+  from {
+    opacity: 0;
+  }
+
+  to {
+    opacity: 1;
+  }
 }
 }
-.logo.vue:hover {
-  filter: drop-shadow(0 0 2em #42b883aa);
+
+.fade-in {
+  animation: fadeIn 0.7s ease-in-out forwards;
 }
 }
 </style>
 </style>

BIN
src/assets/img/background.jpg


+ 0 - 1
src/assets/vue.svg

@@ -1 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="37.07" height="36" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 198"><path fill="#41B883" d="M204.8 0H256L128 220.8L0 0h97.92L128 51.2L157.44 0h47.36Z"></path><path fill="#41B883" d="m0 0l128 220.8L256 0h-51.2L128 132.48L50.56 0H0Z"></path><path fill="#35495E" d="M50.56 0L128 133.12L204.8 0h-47.36L128 51.2L97.92 0H50.56Z"></path></svg>

+ 72 - 0
src/components/Footer.vue

@@ -0,0 +1,72 @@
+<template>
+    <div class="footer">
+
+        <div class="foot" @click="$router.push('/')">
+            <div class="icon">
+                <img src="/icon2.png" alt width="45px" />
+            </div>
+            <div class="text">
+                <div class="nsrh">Double_X 考勤系统</div>
+                <div class="dec">便捷智能的考勤解决方案</div>
+            </div>
+        </div>
+    </div>
+</template>
+
+<style scoped>
+.footer {
+    position: sticky;
+    display: flex;
+    justify-content: center;
+    height: 200px;
+    background-color: #337ecc;
+    color: #fff;
+    margin-top: 30px;
+    flex-direction: column;
+    justify-content: center;
+    align-items: center;
+}
+
+.footer .item .content p {
+    margin: 5px 0;
+    color: rgba(255, 255, 255, 0.7);
+    font-size: 0.9em;
+}
+
+.foot {
+    margin-top: 10px;
+    display: flex;
+    height: 80px;
+    align-items: center;
+    color: #fff;
+    border-radius: 10px;
+}
+
+.foot .icon {
+    margin-left: 10px;
+}
+
+.foot .text {
+    display: flex;
+    flex-direction: column;
+    margin-left: 10px;
+    flex: 1;
+}
+
+.foot .text .nsrh {
+    font-size: 1.2em;
+    font-weight: bold;
+}
+
+.foot .text .dec {
+    font-size: 13px;
+    margin-top: 6px;
+}
+
+
+@media only screen and (max-width: 768px) {
+    .footer {
+        height: 150px;
+    }
+}
+</style>

+ 214 - 0
src/components/Header.vue

@@ -0,0 +1,214 @@
+<template>
+    <div :class="['container', { 'scrolled': isScrolled }]"
+        :style="{ backgroundColor: headerBackgroundColor, color: fontColor }">
+        <div class="header">
+            <div class="left">
+                <img alt="Double_X 考勤" src="/icon.png" @click="$router.push('/')">
+            </div>
+
+            <div class="right">
+                <button class="menu-toggle" @click="toggleMenu">
+                    <el-icon :size="30">
+                        <Menu />
+                    </el-icon>
+                </button>
+
+                <div class="menu-container" :class="{ 'show-menu': isMenuOpen }">
+                    <div class="item" @click="$router.push('/')">
+                        <el-icon>
+                            <House />
+                        </el-icon>
+                        首页
+                    </div>
+                    <div class="item" @click="$router.push('/ClockIn')">
+                        <el-icon>
+                            <Edit />
+                        </el-icon>
+                        我的考勤
+                    </div>
+                    <div class="item" @click="$router.push('/MyOrder')">
+                        <el-icon>
+                            <Postcard />
+                        </el-icon>
+                        补卡申请
+                    </div>
+                    <div class="item" v-if="app.user === undefined" @click="$router.push('/login')">
+                        <el-icon>
+                            <User />
+                        </el-icon>
+                        用户登录
+                    </div>
+                    <div class="user" v-else @click="$router.push('/Mine')">
+                        <el-avatar :src="app.user.avatar" v-if="app.user.avatar" :size="25" />
+                        <div class="item">
+                            {{ app.user.username }}
+                        </div>
+                    </div>
+                </div>
+            </div>
+        </div>
+    </div>
+</template>
+
+<script setup>
+import { App } from '../app/app';
+import { ServerAPI } from '../app/lib/ServerAPI';
+import { useRouter} from 'vue-router';
+
+const app = reactive(App);
+
+const router = useRouter();
+
+const props = defineProps({
+    color: {
+        type: String,
+        default: 'black'
+    }
+});
+
+const isMenuOpen = ref(false);
+const isScrolled = ref(false);
+const headerBackgroundColor = ref('rgba(255, 255, 255, 0)');
+const fontColor = ref(props.color);
+
+function toggleMenu() {
+    isMenuOpen.value = !isMenuOpen.value;
+}
+
+function handleScroll() {
+    const scrollTop = window.pageYOffset || document.documentElement.scrollTop;
+    const maxScroll = 200; // 最大滚动距离
+    const opacity = Math.min(scrollTop / maxScroll, 1);
+    headerBackgroundColor.value = `rgba(255, 255, 255, ${opacity})`;
+    isScrolled.value = scrollTop > 0;
+    if (opacity > 0) {
+        fontColor.value = 'black';
+    } else {
+        fontColor.value = props.color;
+    }
+}
+
+onMounted(() => {
+    window.addEventListener('scroll', handleScroll);
+});
+
+onUnmounted(() => {
+    window.removeEventListener('scroll', handleScroll);
+});
+
+//修复登录过期问题
+const user = app.user;
+if (user != undefined) {
+    ServerAPI.checkLoginSession(user.uuid, user.session, (r) => {
+        if (r.code == 0) {
+            return;
+        }
+        //自动退出登录
+        ElMessage.error('登录已过期,请重新登录');
+        app.cleanUser();
+        router.push('/login');
+    })
+}
+</script>
+
+<style scoped>
+.container {
+    position: sticky;
+    top: 0;
+    width: 100%;
+    z-index: 100;
+}
+
+.header {
+    width: 80%;
+    margin: 0 auto;
+    border: none !important;
+    z-index: 1000;
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    padding: 10px 20px;
+    transition: background-color 0.3s ease;
+}
+
+.header.scrolled {
+    backdrop-filter: blur(10px);
+    /* 添加模糊效果 */
+}
+
+.header .left img {
+    width: 60px;
+}
+
+.header .right {
+    display: flex;
+    gap: 20px;
+}
+
+.header .right .menu-toggle {
+    display: none;
+    /* 默认隐藏按钮 */
+    cursor: pointer;
+    background: none;
+    border: none;
+    font-size: 1.4em;
+}
+
+.header .right .menu-container {
+    display: flex;
+    gap: 20px;
+}
+
+.header .right .item {
+    cursor: pointer;
+    font-size: 1.2em;
+}
+
+.header .right .user {
+    display: flex;
+}
+
+.header .right .user .item {
+    margin-left: 5px;
+}
+
+.header .right .item:hover {
+    color: #337ecc;
+}
+
+@media only screen and (max-width: 768px) {
+    .header {
+        width: 90%;
+        height: 40px;
+    }
+
+    .header .left img {
+        width: 50px
+    }
+
+    .header .right .menu-toggle {
+        display: block;
+        /* 在小屏幕下显示按钮 */
+    }
+
+    .header .right .menu-container {
+        display: none;
+        flex-direction: column;
+        position: absolute;
+        top: 100%;
+        right: 10px;
+        background-color: rgba(0, 0, 0, 0.5);
+        color: #fff;
+        border-radius: 5px;
+        padding: 10px;
+        box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
+        z-index: 999;
+        font-size: 0.8em;
+
+    }
+
+    .header .right .menu-container.show-menu {
+        display: flex;
+    }
+}
+</style>

+ 255 - 0
src/pages/Main/Main.vue

@@ -0,0 +1,255 @@
+<template>
+  <div class="header">
+    <Header :color="'aliceblue'" />
+  </div>
+  <div id="app">
+    <div id="section1" class="section background-image">
+      <div class="overlay"></div>
+      <div class="content">
+        <h1><span class="typing-animation">Double_X 考勤</span></h1>
+        <p class="fade-in">便捷智能的考勤解决方案</p>
+        <div class="buttons fade-in">
+          <div class="button" @click="$router.push('/ClockIn')">
+            <el-icon :size="35">
+              <Promotion />
+            </el-icon>
+            <span>我的考勤</span>
+          </div>
+          <div class="button" @click="$router.push('/ClockIn')">
+            <el-icon :size="35">
+              <List />
+            </el-icon>
+            <span>补卡申请</span>
+          </div>
+          <div class="button" @click="$router.push('/')">
+            <el-icon :size="35">
+              <PhoneFilled />
+            </el-icon>
+            <span>占个位置</span>
+          </div>
+          <div class="button" @click="$router.push('/')">
+            <el-icon :size="35">
+              <Lollipop />
+            </el-icon>
+            <span>占个位置</span>
+          </div>
+        </div>
+      </div>
+
+      <el-button icon="ArrowDown" circle @click="scrollToSection('section2')" class="floating-button" />
+    </div>
+
+    <div id="section2" class="section items">
+
+    </div>
+
+    <div id="section3" class="section items">
+
+    </div>
+
+
+
+  </div>
+
+
+  <el-backtop :right="70" :bottom="70" />
+  <Footer />
+
+</template>
+
+<script setup>
+import { onMounted, provide} from 'vue'
+import { useRoute } from 'vue-router'
+import Header from '../../components/Header.vue'
+import Footer from '../../components/Footer.vue'
+
+const route = useRoute();
+
+const scrollToSection = (sectionId) => {
+  const section = document.getElementById(sectionId);
+  if (section) {
+    section.scrollIntoView({ behavior: 'smooth' });
+  }
+};
+
+onMounted(async () => {
+  const page = route.params.page;
+  if (page) {
+    scrollToSection(`section${page}`);
+  }
+});
+
+provide('scrollToSection', scrollToSection);
+</script>
+
+<style scoped>
+.header {
+  position: fixed;
+  z-index: 100;
+  width: 100%;
+}
+
+#app {
+  overflow: hidden;
+}
+
+.section {
+  width: 100%;
+  min-height: 100vh;
+  scroll-snap-align: start;
+}
+
+.background-image {
+  position: relative;
+  background-image: url("../../assets/img/background.jpg");
+  background-size: cover;
+  background-position: center;
+  background-repeat: no-repeat;
+}
+
+.overlay {
+  position: absolute;
+  top: 0;
+  left: 0;
+  width: 100%;
+  height: 100%;
+  background: linear-gradient(320deg, rgba(50, 55, 62, 0) 0%, rgba(13, 27, 46, 0.45) 100%);
+}
+
+.content {
+  position: absolute;
+  top: 50%;
+  left: 50%;
+  transform: translate(-50%, -50%);
+  text-align: center;
+  width: 100%;
+}
+
+.background-image h1 {
+  color: aliceblue;
+  font-size: 4em;
+  margin: 0;
+}
+
+.background-image p {
+  color: aliceblue;
+  font-size: 1.5em;
+  margin: 0.5em 0 0;
+}
+
+.buttons {
+  display: flex;
+  justify-content: center;
+  gap: 50px;
+  margin: 0 auto;
+  margin-top: 70px;
+  width: 70%;
+}
+
+.icons {
+  margin-top: 70px;
+}
+
+.button {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  width: 33.3%;
+  min-width: 110px;
+  aspect-ratio: 1 / 1;
+  background-color: rgba(255, 255, 255, 0.55);
+  color: white;
+  border: none;
+  cursor: pointer;
+  border-radius: 10px;
+  font-size: 1.2em;
+}
+
+.button:hover {
+  color: #337ecc;
+}
+
+.button span {
+  margin-top: 10px;
+}
+
+.typing-animation {
+  overflow: hidden;
+  display: inline-block;
+  animation: typing 3s steps(40, end);
+  white-space: nowrap;
+  border-right: 2px solid transparent;
+  margin: 0;
+  padding: 0;
+}
+
+@keyframes typing {
+  from {
+    width: 0;
+  }
+
+  to {
+    width: 100%;
+  }
+}
+
+.items {
+  background-color: #f0f0f0;
+  padding-top: 80px;
+}
+
+.items .item {
+  text-align: center;
+}
+
+.floating-button {
+  position: absolute;
+  bottom: 30px;
+  left: 50%;
+  transform: translateX(-50%);
+  background-color: rgba(255, 255, 255, 0.4);
+  border-radius: 50%;
+  border: none;
+  padding: 10px;
+  transition: background-color 0.3s;
+}
+
+.floating-button:hover {
+  background-color: rgba(255, 255, 255, 0.8);
+}
+
+@media only screen and (max-width: 768px) {
+  .background-image h1 {
+    font-size: 2em;
+  }
+
+  .background-image p {
+    font-size: 0.9em;
+  }
+
+  .buttons {
+    display: grid;
+    grid-template-columns: repeat(2, 0fr);
+    gap: 25px;
+  }
+
+  .icons {
+    margin-top: 25px;
+    gap: 20px;
+  }
+
+  .buttons .button {
+    width: 110px;
+    height: 110px;
+    margin: 0 auto;
+    /* 按钮宽度占满父容器 */
+    max-width: 200px;
+    font-size: 0.9em;
+  }
+
+  .items {
+    padding-top: 50px;
+  }
+}
+</style>

+ 89 - 0
src/router/index.js

@@ -0,0 +1,89 @@
+import * as VueRouter from 'vue-router'
+
+const routes = [
+    {
+        path: "/",
+        name: "Main",
+        component: () => import('../pages/Main/Main.vue')
+    },
+    {
+        path: "/login",
+        name: "Login",
+        component: () => import('../pages/Login/Login.vue'),
+        meta: { title: '用户登录' },
+        children: [
+            {
+                name: "WXLoginStage2",
+                path: "Stage2",
+                component: () => import('../pages/Login/WXLoginStage2.vue'),
+                meta: { title: '企业微信登录' }
+            }
+        ]
+    },
+    {
+        path: "/Mine",
+        name: "Mine",
+        component: () => import('../pages/Mine/Mine.vue'),
+        meta: { title: '个人中心' }
+    },
+    {
+        path: "/ClockIn",
+        name: "ClockInList",
+        component: () => import('../pages/ClockIn/ClockInList.vue'),
+        meta: { title: '考勤列表' }
+    },
+    {
+        path: "/ClockIn/:id",
+        name: "ClockIn",
+        component: () => import('../pages/ClockIn/ClockIn.vue'),
+        meta: { title: '考勤打卡' }
+    },
+    {
+        path: "/ClockInManage",
+        name: "ClockInManage",
+        component: () => import('../pages/Admin/ClockIn/ClockInManage.vue'),
+        meta: { title: '考勤管理' }
+    },
+    {
+        path: "/ClockInManage/Edit/:id",
+        name: "EditAttendenceItem",
+        component: () => import('../pages/Admin/ClockIn/components/EditItem.vue'),
+        meta: { title: '编辑考勤项目' }
+    },
+    {
+        name: "UpdateInfo",
+        path: "/UpdateInfo",
+        component: () => import('../pages/Setup/UpdateInfoStage1.vue'),
+        meta: { title: '更新个人信息' },
+        children: [
+            {
+                name: "UpdateInfoStage2",
+                path: "Stage2",
+                component: () => import('../pages/Setup/UpdateInfoStage2.vue'),
+                meta: { title: '更新个人信息' }
+            }
+        ]
+    },
+    {
+        path: "/Admin",
+        name: "Admin",
+        component: () => import('../pages/Admin/Admin.vue'),
+        meta: { title: '网站管理' }
+    },
+
+]
+
+const router = VueRouter.createRouter({
+    history: VueRouter.createWebHashHistory(),
+    routes: routes
+});
+
+router.beforeEach((to, from, next) => {
+    if (!to.meta.title) 
+        document.title = 'Double_X 考勤'
+    else
+        document.title = to.meta.title
+    next();
+})
+
+export { router };

+ 0 - 79
src/style.css

@@ -1,79 +0,0 @@
-:root {
-  font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
-  line-height: 1.5;
-  font-weight: 400;
-
-  color-scheme: light dark;
-  color: rgba(255, 255, 255, 0.87);
-  background-color: #242424;
-
-  font-synthesis: none;
-  text-rendering: optimizeLegibility;
-  -webkit-font-smoothing: antialiased;
-  -moz-osx-font-smoothing: grayscale;
-}
-
-a {
-  font-weight: 500;
-  color: #646cff;
-  text-decoration: inherit;
-}
-a:hover {
-  color: #535bf2;
-}
-
-body {
-  margin: 0;
-  display: flex;
-  place-items: center;
-  min-width: 320px;
-  min-height: 100vh;
-}
-
-h1 {
-  font-size: 3.2em;
-  line-height: 1.1;
-}
-
-button {
-  border-radius: 8px;
-  border: 1px solid transparent;
-  padding: 0.6em 1.2em;
-  font-size: 1em;
-  font-weight: 500;
-  font-family: inherit;
-  background-color: #1a1a1a;
-  cursor: pointer;
-  transition: border-color 0.25s;
-}
-button:hover {
-  border-color: #646cff;
-}
-button:focus,
-button:focus-visible {
-  outline: 4px auto -webkit-focus-ring-color;
-}
-
-.card {
-  padding: 2em;
-}
-
-#app {
-  max-width: 1280px;
-  margin: 0 auto;
-  padding: 2rem;
-  text-align: center;
-}
-
-@media (prefers-color-scheme: light) {
-  :root {
-    color: #213547;
-    background-color: #ffffff;
-  }
-  a:hover {
-    color: #747bff;
-  }
-  button {
-    background-color: #f9f9f9;
-  }
-}