Sfoglia il codice sorgente

基本完成板块模块

枫叶秋林 2 anni fa
parent
commit
2fc650ea37

+ 13 - 0
components.d.ts

@@ -8,6 +8,7 @@ export {}
 declare module '@vue/runtime-core' {
   export interface GlobalComponents {
     Cod: typeof import('./src/components/cod/index.vue')['default']
+    ElAffix: typeof import('element-plus/es')['ElAffix']
     ElAvatar: typeof import('element-plus/es')['ElAvatar']
     ElBreadcrumb: typeof import('element-plus/es')['ElBreadcrumb']
     ElBreadcrumbItem: typeof import('element-plus/es')['ElBreadcrumbItem']
@@ -32,23 +33,35 @@ declare module '@vue/runtime-core' {
     ElImage: typeof import('element-plus/es')['ElImage']
     ElInput: typeof import('element-plus/es')['ElInput']
     ElLink: typeof import('element-plus/es')['ElLink']
+    ElLoading: typeof import('element-plus/es')['ElLoading']
     ElMain: typeof import('element-plus/es')['ElMain']
     ElMenu: typeof import('element-plus/es')['ElMenu']
     ElMenuItem: typeof import('element-plus/es')['ElMenuItem']
+    ElMenuItemGroup: typeof import('element-plus/es')['ElMenuItemGroup']
     ElOption: typeof import('element-plus/es')['ElOption']
     ElPageHeader: typeof import('element-plus/es')['ElPageHeader']
+    ElPagination: typeof import('element-plus/es')['ElPagination']
     ElPopover: typeof import('element-plus/es')['ElPopover']
+    ElProgress: typeof import('element-plus/es')['ElProgress']
+    ElRadioButton: typeof import('element-plus/es')['ElRadioButton']
+    ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup']
     ElRow: typeof import('element-plus/es')['ElRow']
     ElSelect: typeof import('element-plus/es')['ElSelect']
+    ElSkeleton: typeof import('element-plus/es')['ElSkeleton']
     ElSpace: typeof import('element-plus/es')['ElSpace']
+    ElSubMenu: typeof import('element-plus/es')['ElSubMenu']
     ElSwitch: typeof import('element-plus/es')['ElSwitch']
     ElTag: typeof import('element-plus/es')['ElTag']
     Hleft: typeof import('./src/components/header/hleft.vue')['default']
     Hright: typeof import('./src/components/header/hright.vue')['default']
     Hsearch: typeof import('./src/components/header/hsearch.vue')['default']
     Login: typeof import('./src/components/auth/login.vue')['default']
+    Platelist: typeof import('./src/components/plate/platelist.vue')['default']
+    PlateMenu: typeof import('./src/components/plate/plateMenu.vue')['default']
+    PlatePostItem: typeof import('./src/components/plate/platePostItem.vue')['default']
     Register: typeof import('./src/components/auth/register.vue')['default']
     RouterLink: typeof import('vue-router')['RouterLink']
     RouterView: typeof import('vue-router')['RouterView']
+    Userinfo: typeof import('./src/components/header/userinfo.vue')['default']
   }
 }

BIN
dump.rdb


+ 0 - 2
src/App.vue

@@ -1,6 +1,4 @@
 <script setup lang="ts">
-import router from './plugins/router'
-router.push('/home')
 </script>
 
 <template>

+ 18 - 10
src/components/auth/login.vue

@@ -4,6 +4,7 @@ import { reactive, ref } from 'vue'
 import service from '../../plugins/axios'
 import { token, userdata } from '../../plugins/pinia'
 import { encrypt } from '../../plugins/crypto'
+import { ElMessage, FormInstance } from 'element-plus'
 const form = reactive({
   name: '',
   paw: '',
@@ -11,7 +12,7 @@ const form = reactive({
 const rules = reactive({
   name: [
     { required: true, message: '请输入用户名', trigger: 'blur' },
-    { min: 6, max: 12, message: '长度在 6 到 12 个字符', trigger: 'blur' },
+    { min: 5, max: 16, message: '长度在 4 到 16 个字符', trigger: 'blur' },
   ],
   paw: [
     { required: true, message: '请输入密码', trigger: 'blur' },
@@ -19,19 +20,26 @@ const rules = reactive({
   ],
 })
 const formRef = ref()
-const bt_login = async (formEl: any) => {
-  formEl.validate(async (valid: boolean, e: any) => {
+const bt_login = async (formEl: FormInstance | undefined) => {
+  formEl?.validate(async (valid: boolean, e: any) => {
     if (valid) {
-      const userinfo = (await service.post('/auth/login', form)) as userdata
+      const userinfo = (await (
+        await service.post('/auth/login', form)
+      ).data) as userdata
       type userdata = {
-        id: number
-        name: string
-        email: string
-        createdAt: string
+        cod: number
+        msg: string
         token: string
       }
+      ElMessage({
+        message: userinfo.msg,
+        type: userinfo.cod === 200 ? 'success' : 'error',
+      })
+      //跳转到主页
+      if (userinfo.cod === 200) {
+        window.location.href = '/home'
+      }
       token().token = userinfo.token
-      userdata().data = encrypt(userinfo)
     }
   })
 }
@@ -75,7 +83,7 @@ const bt_login = async (formEl: any) => {
   <div>
     <el-button type="primary"
                style="width:98%;"
-               @click="bt_login(formRef.value)">
+               @click="bt_login(formRef)">
       登陆
     </el-button>
     <el-button type="text"

+ 32 - 2
src/components/header/hleft.vue

@@ -1,7 +1,9 @@
 <script setup lang="ts">
 import { useDark, useToggle } from '@vueuse/core'
 import { Sunny, Moon } from '@element-plus/icons-vue'
+import userinfo from './userinfo.vue'
 import router from '../../plugins/router'
+import { token } from '../../plugins/pinia'
 const isDark = useDark()
 const toggleDark = useToggle(isDark)
 const to_login = async () => {
@@ -12,16 +14,44 @@ const to_login = async () => {
 <template>
   <el-space wrap>
     <el-switch v-model="isDark"
-               class="mt-2 hidden-md-and-down"
+               class="mt-2 hidden-sm-and-down"
                size="large"
                style="margin-left: 24px"
                inline-prompt
                :active-icon="Moon"
                :inactive-icon="Sunny" />
-    <el-button @click="to_login">登陆/注册</el-button>
+    <el-button @click="to_login"
+               v-show="!token().token">登陆/注册</el-button>
+    <el-popover :width="200"
+                :show-arrow="false">
+      <template #reference>
+        <div>
+          <el-avatar id="login_avatar"
+                     v-show="token().token"
+                     src="https://cube.elemecdn.com/0/88/03b0d39583f48206768a7534e55bcpng.png" />
+        </div>
+      </template>
+      <Suspense>
+        <template #default>
+          <userinfo />
+        </template>
+      </Suspense>
+    </el-popover>
+
   </el-space>
 
 </template>
 
 <style scoped>
+.login_avatar {
+  margin-left: 24px;
+
+  /*  */
+}
+.container {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  height: 100%;
+}
 </style>

+ 17 - 6
src/components/header/hright.vue

@@ -25,16 +25,14 @@ const drawer = ref(false)
     </el-icon>
   </el-button>
   <el-drawer v-model="drawer"
-             direction="ltr">
-    <template #header="{titleId, titleClass}">
-      <span :id="titleId"
-            :class="titleClass">设置</span>
-    </template>
-
+             direction="ltr"
+             :with-header="false"
+             size="50%">
     <template #default>
       <el-image style=" min-width: 100px; height: 50px"
                 :src="url"
                 fit="fit" />
+      <el-divider>设置</el-divider>
       <el-form label-width="80px"
                label-position="">
         <el-form-item label="夜间模式">
@@ -45,6 +43,19 @@ const drawer = ref(false)
                      :inactive-icon="Sunny" />
         </el-form-item>
       </el-form>
+      <!-- 分割线 -->
+      <el-divider>菜单</el-divider>
+      <el-menu>
+        <el-menu-item index="1">
+          首页
+        </el-menu-item>
+        <el-menu-item index="2">
+          帖子
+        </el-menu-item>
+        <el-menu-item index="3">
+          热点
+        </el-menu-item>
+      </el-menu>
 
     </template>
 

+ 43 - 4
src/components/header/hsearch.vue

@@ -1,25 +1,64 @@
 
 
 <script setup lang="ts">
-import { ref } from 'vue'
+import { ref, h } from 'vue'
 import { Search } from '@element-plus/icons-vue'
+import { ElDivider } from 'element-plus'
+import router from '../../plugins/router'
+
 const select = ref('帖子')
 const input3 = ref('')
+
+const spacer = h(ElDivider, { direction: 'vertical' })
+const to_home = () => {
+  router.push('/home')
+}
 </script>
 
 <template>
+  <el-space :size="10"
+            :spacer="spacer"
+            class="hidden-sm-and-down">
+    <div>
+      <el-button text
+                 @click="to_home"> 首页</el-button>
+    </div>
+    <div>
+      <el-button text> 帖子</el-button>
+    </div>
+    <div>
+      <el-button text> 热点</el-button>
+    </div>
+    <div>
+      <el-input v-model="input3"
+                placeholder="请输入搜索内容"
+                class="input-with-select">
+        <template #prepend>
+          <el-select v-model="select"
+                     style="width: 70px">
+            <el-option label="帖子"
+                       value="1" />
+            <el-option label="用户"
+                       value="2" />
+          </el-select>
+        </template>
+        <template #append>
+          <el-button :icon="Search" />
+        </template>
+      </el-input>
+    </div>
+  </el-space>
   <el-input v-model="input3"
             placeholder="请输入搜索内容"
-            class="input-with-select">
+            class="input-with-select hidden-md-and-up">
     <template #prepend>
       <el-select v-model="select"
-                 style="width: 80px">
+                 style="width: 70px">
         <el-option label="帖子"
                    value="1" />
         <el-option label="用户"
                    value="2" />
       </el-select>
-
     </template>
     <template #append>
       <el-button :icon="Search" />

+ 98 - 0
src/components/header/userinfo.vue

@@ -0,0 +1,98 @@
+<script setup lang="ts">
+import { token } from '../../plugins/pinia'
+import service from '../../plugins/axios'
+import { reactive, ref } from 'vue'
+const info = ref()
+const data = await service({
+  url: '/userinfo',
+  method: 'get',
+  headers: {
+    Authorization: 'Bearer ' + token().token,
+  },
+})
+if (data.data.cod === 200) {
+  info.value = data.data.data
+}
+//隐藏多余字符串
+function hideStr(str: string) {
+  return str.replace(/^(.{4}).*$/, '$1...')
+}
+const outlogin = () => {
+  token().token = ''
+}
+</script>
+
+<template>
+  <el-row>
+    <el-space wrap>
+      <el-col :span="10">
+        <el-avatar src="https://cube.elemecdn.com/0/88/03b0d39583f48206768a7534e55bcpng.png" />
+      </el-col>
+      <el-col :span="16">
+        <el-row>
+          <el-col :span="24">
+            <el-button type="text">{{hideStr(info.username)}}</el-button>
+          </el-col>
+          <el-col :span="24">
+            <el-button type="text">个人主页</el-button>
+          </el-col>
+        </el-row>
+      </el-col>
+    </el-space>
+    <el-col :span="24">
+
+      <el-space wrap>
+        <div>
+          <el-button type="text">[用户]lv.{{info.user.level}}</el-button>
+        </div>
+        <div>
+          <el-button type="text">{{info.user.exp}}/max?</el-button>
+        </div>
+      </el-space>
+
+    </el-col>
+    <el-divider></el-divider>
+    <el-col :span="8">
+      <el-row>
+        <el-col :span="24">
+          <div style="text-align: center;">枫币</div>
+        </el-col>
+        <el-col :span="24">
+          <div style="text-align: center;">0</div>
+        </el-col>
+      </el-row>
+    </el-col>
+    <el-col :span="8">
+      <el-row>
+        <el-col :span="24">
+          <div style="text-align: center;">发帖数</div>
+        </el-col>
+        <el-col :span="24">
+          <div style="text-align: center;">0</div>
+        </el-col>
+      </el-row>
+    </el-col>
+    <el-col :span="8">
+      <el-row>
+        <el-col :span="24">
+          <div style="text-align: center;">回帖数</div>
+        </el-col>
+        <el-col :span="24">
+          <div style="text-align: center;">0</div>
+        </el-col>
+      </el-row>
+    </el-col>
+  </el-row>
+  <el-row>
+    <el-col :span="24">
+      <div style="float:right;">
+        <el-button type="text"
+                   @click="outlogin">退出</el-button>
+      </div>
+
+    </el-col>
+  </el-row>
+</template>
+
+<style scoped lang="scss">
+</style>

+ 100 - 0
src/components/plate/plateMenu.vue

@@ -0,0 +1,100 @@
+<script setup lang="ts">
+import { onMounted, ref } from 'vue'
+import { ArrowUp } from '@element-plus/icons-vue'
+import service from '../../plugins/axios'
+// import router from '../../plugins/router'
+import { useRouter } from 'vue-router'
+const isCollapse = ref(true)
+const isitem = ref(true)
+const data = (await service.get('/plate/getplate')).data
+
+data.sort((a: any, b: any) => {
+  return a.id - b.id
+})
+const router = useRouter()
+async function gotoPlate(id: number) {
+  window.location.href = `/platelist/${id}`
+  //路由跳转
+  // await router.push({
+  //   path: `/platelist/${id}`,
+  // })
+  // // router.push(`/platelist/${id}`)
+}
+
+onMounted(() => {
+  isitem.value = false
+  const bt = document.getElementById('el-icon-arrow-up')
+  const menu_plates = document.querySelectorAll('.menu-plate')
+  let mpheight = 0
+  for (let menuPlate of menu_plates) {
+    mpheight += menuPlate.offsetHeight
+  }
+  bt!.style.top = `${(mpheight - bt!.offsetHeight) / 2}px`
+  document
+    .querySelectorAll('.el-menu--collapse .el-menu-item')
+    .forEach((item, index) => {
+      item.style.width = '0px'
+      item.style.padding = '0px'
+    })
+  bt?.addEventListener('click', () => {
+    bt.style.left = isCollapse.value ? '-12px' : '186px'
+    bt.style.transition = 'left 0.4s'
+    //获取旋转角度
+    const plateIcon = document.getElementById('plateIcon')
+    if (plateIcon) {
+      isitem.value = !isitem.value
+      plateIcon.style.transform = isCollapse.value
+        ? 'rotate(90deg)'
+        : 'rotate(270deg)'
+      plateIcon.style.transition = 'width 1s'
+    }
+  })
+})
+</script>
+
+<template>
+  <el-button type="primary"
+             id="el-icon-arrow-up"
+             size="small"
+             circle
+             @click="isCollapse = !isCollapse">
+    <el-icon id="plateIcon">
+      <ArrowUp />
+    </el-icon>
+  </el-button>
+  <!-- menu-trigger -->
+  <el-menu default-active="2"
+           class="menu-plate"
+           :collapse="isCollapse"
+           trigger="click">
+    <el-menu-item v-for="(item,i) in data"
+                  :key="i"
+                  @click="gotoPlate(item.id)"
+                  :index='"platelist/"+item.id'>
+      <template #title>{{ item.name }}</template>
+    </el-menu-item>
+
+  </el-menu>
+</template>
+
+<style scoped >
+#el-icon-arrow-up {
+  position: absolute;
+  z-index: 1000;
+  top: 10px;
+  left: -12px;
+  font-size: 16px;
+}
+#plateIcon {
+  /*旋转90度  */
+  transform: rotate(90deg);
+}
+
+.menu-plate:not(.el-menu--collapse) {
+  width: 200px;
+}
+.el-menu--collapse {
+  width: 0px;
+  overflow: hidden;
+}
+</style>

+ 100 - 0
src/components/plate/platePostItem.vue

@@ -0,0 +1,100 @@
+<script setup lang="ts">
+import service from '../../plugins/axios'
+import { defineProps } from 'vue'
+
+const dp = defineProps({
+  id: {
+    type: String,
+    required: true,
+  },
+  title: {
+    type: String,
+    required: true,
+  },
+  content: {
+    type: String,
+    required: true,
+  },
+  authorid: {
+    type: String,
+    required: true,
+  },
+  time: {
+    type: String,
+    required: true,
+  },
+  numberOfReplies: {
+    type: String,
+    required: true,
+  },
+})
+//格式化时间几天前
+function formatDate(date: any) {
+  const d = new Date(date)
+  const now = Date.now()
+  const diff = (now - d.getTime()) / 1000
+  if (diff < 30) {
+    return '刚刚'
+  } else if (diff < 3600) {
+    // less 1 hour
+    return Math.ceil(diff / 60) + '分钟前'
+  } else if (diff < 3600 * 24) {
+    return Math.ceil(diff / 3600) + '小时前'
+  } else if (diff < 3600 * 24 * 2) {
+    return '1天前'
+  }
+  return Math.ceil(diff / (3600 * 24)) + '天前'
+}
+
+const userinfo = await (
+  await service.get(`userinfo/getuser?id=${dp.authorid}`)
+).data
+console.log(JSON.stringify(userinfo))
+</script>
+
+<template>
+  <el-card>
+    <el-row>
+      <el-col :span="4">
+        <el-avatar :src="userinfo.data.user.avatar? userinfo.data.user.avatar : 'https://avatars.githubusercontent.com/u/25154432?v=4'"
+                   size="large" />
+      </el-col>
+      <el-col :span="20">
+        <span class="title">{{dp.title}}</span>
+        <p class="content">{{dp.content}}</p>
+        <!-- 左边 -->
+        <div type="text"
+             style="float: left">
+          <el-space wrap>
+            <el-tag type="primary">{{ userinfo.data.username }}</el-tag>
+            <el-tag type="info">{{ formatDate(dp.time) }}</el-tag>
+            <el-tag type="success">回复{{ dp.numberOfReplies?numberOfReplies:0 }}</el-tag>
+            <el-tag type="warning">浏览量~开发中</el-tag>
+          </el-space>
+        </div>
+        <el-button type="text"
+                   style="float: right"
+                   @click="() => $router.push(`/${data.id}`)">
+          去看看
+        </el-button>
+      </el-col>
+    </el-row>
+  </el-card>
+</template>
+
+<style scoped>
+.title {
+  font-size: 14px;
+  color: #909399;
+  margin-bottom: 6px;
+}
+.content {
+  font-size: 14px;
+  color: #606266;
+}
+.title .content {
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+}
+</style>

+ 4 - 0
src/plugins/axios.ts

@@ -22,6 +22,10 @@ service.interceptors.response.use(
         ElMessage.error(data.message);
         break;
       case 401:
+        //主页跳转不需要判断
+        if (router.currentRoute.value.path === "/home") {
+          return Promise.reject(error);
+        }
         ElMessage.error("token失效,请重新登录");
         router.push("/auth");
         break;

+ 7 - 0
src/plugins/router.ts

@@ -6,11 +6,18 @@ const router = createRouter({
   routes: [
     {
       path: "/",
+      redirect: "/home",
       component: () => import("../layouts/home.vue"),
       children: [
         {
           path: "/home",
           component: () => import("../views/home/index.vue"),
+          children: [
+            {
+              path: "/platelist/:id",
+              component: () => import("../views/plate/platelist.vue"),
+            },
+          ],
         },
         {
           path: "/auth",

+ 38 - 12
src/views/home/index.vue

@@ -1,21 +1,47 @@
 <script setup lang="ts">
-import { onMounted, ref, VNodeRef } from 'vue'
-import { encrypt, decrypt } from '../../plugins/crypto'
-
-import service from '../../plugins/axios'
-const successcod = async (success: string) => {
-  const res = await service.post('cod/verifycod', {
-    cod: success,
-  })
-
-  console.log(res.data)
-}
+import plateMenu from '../../components/plate/plateMenu.vue'
+import plateList from '../../views/plate/platelist.vue'
 </script>
 
 <template>
-  主页
+
+  <div id="home">
+    <el-affix :offset="120"
+              target="#home"
+              id="plate">
+      <Suspense>
+        <template #default>
+          <plateMenu />
+        </template>
+        <template #fallback>
+          <el-skeleton />
+        </template>
+      </Suspense>
+    </el-affix>
+    <Suspense>
+      <template #default>
+        <router-view />
+      </template>
+      <template #fallback>
+        <el-skeleton />
+      </template>
+    </Suspense>
+
+  </div>
+
 </template>
 
 <style scoped>
+#home {
+  width: 100%;
+  height: 3000px;
+  background-color: #f5f5f5;
+}
+#plate {
+  position: fixed;
+  top: 120px;
+  left: 0;
+  z-index: 1000;
+}
 </style>
 

+ 4 - 2
src/views/homeHeader/index.vue

@@ -13,11 +13,13 @@ import hsearch from '../../components/header/hsearch.vue'
             :md="3"
             :lg="3"
             :xl="2">
+      <div style="height: 10px;"
+           class="hidden-sm-and-down"></div>
       <hright />
     </el-col>
     <el-col :xs="15"
             :sm="17"
-            :md="17"
+            :md="15"
             :lg="17"
             :xl="18">
       <div style="height: 10px;"
@@ -26,7 +28,7 @@ import hsearch from '../../components/header/hsearch.vue'
     </el-col>
     <el-col :xs="6"
             :sm="4"
-            :md="3"
+            :md="5"
             :lg="4"
             :xl="4">
       <div style="height: 10px;"

+ 40 - 0
src/views/plate/platelist.vue

@@ -0,0 +1,40 @@
+<script setup lang="ts">
+import { ref } from 'vue'
+import { useRoute } from 'vue-router'
+import service from '../../plugins/axios'
+import platePostItem from '../../components/plate/platePostItem.vue'
+let id = useRoute().params.id as any
+
+const data = ref(
+  await (
+    await service.get(`/post/platelist?plateid=${id}&page=${1}&limit=${5}`)
+  ).data
+)
+const page = async (v: number) => {
+  data.value = await (
+    await service.get(`/post/platelist?plateid=${id}&page=${v}&limit=${5}`)
+  ).data
+}
+</script>
+
+<template>
+  <div v-for="(item,i) of data.data"
+       :key="i"
+       style="margin: 10px 0;">
+    <platePostItem :id="item.id"
+                   :title="item.title"
+                   :content="item.content"
+                   :authorid="item.authorId"
+                   :time="item.updatedAt"
+                   :numberOfReplies="item.comments" />
+  </div>
+  <el-divider />
+  <el-pagination layout="prev, pager, next"
+                 :page-count="data.totalPage"
+                 @current-change="page"
+                 id="page" />
+
+</template>
+
+<style scoped >
+</style>