Ver Fonte

完成 发帖模块 增删改查
修复 BUG

枫叶秋林 há 2 anos atrás
pai
commit
9fa2b5b62e

+ 1 - 14
components.d.ts

@@ -10,22 +10,15 @@ declare module '@vue/runtime-core' {
     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']
     ElButton: typeof import('element-plus/es')['ElButton']
-    ElButtonGroup: typeof import('element-plus/es')['ElButtonGroup']
     ElCard: typeof import('element-plus/es')['ElCard']
     ElCarousel: typeof import('element-plus/es')['ElCarousel']
     ElCarouselItem: typeof import('element-plus/es')['ElCarouselItem']
     ElCheckbox: typeof import('element-plus/es')['ElCheckbox']
-    ElCod: typeof import('element-plus/es')['ElCod']
     ElCol: typeof import('element-plus/es')['ElCol']
     ElContainer: typeof import('element-plus/es')['ElContainer']
-    ElDescriptions: typeof import('element-plus/es')['ElDescriptions']
-    ElDescriptionsItem: typeof import('element-plus/es')['ElDescriptionsItem']
     ElDivider: typeof import('element-plus/es')['ElDivider']
     ElDrawer: typeof import('element-plus/es')['ElDrawer']
-    ElEmpty: typeof import('element-plus/es')['ElEmpty']
     ElFooter: typeof import('element-plus/es')['ElFooter']
     ElForm: typeof import('element-plus/es')['ElForm']
     ElFormItem: typeof import('element-plus/es')['ElFormItem']
@@ -38,19 +31,14 @@ declare module '@vue/runtime-core' {
     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']
+    ElPopconfirm: typeof import('element-plus/es')['ElPopconfirm']
     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']
     ElTable: typeof import('element-plus/es')['ElTable']
     ElTableColumn: typeof import('element-plus/es')['ElTableColumn']
@@ -62,7 +50,6 @@ declare module '@vue/runtime-core' {
     Hsearch: typeof import('./src/components/header/hsearch.vue')['default']
     Login: typeof import('./src/components/auth/login.vue')['default']
     NewPost: typeof import('./src/components/home/newPost.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']
     Redplate: typeof import('./src/components/home/redplate.vue')['default']

BIN
dump.rdb


+ 1 - 1
index.html

@@ -4,7 +4,7 @@
     <meta charset="UTF-8" />
     <link rel="icon" type="image/svg+xml" href="/vite.svg" />
     <meta name="viewport" content="width=device-width, initial-scale=1.0" />
-    <title>Vite + Vue + TS</title>
+    <title>枫酱论坛</title>
   </head>
   <body>
     <div id="app"></div>

Diff do ficheiro suprimidas por serem muito extensas
+ 3805 - 590
package-lock.json


+ 3 - 0
package.json

@@ -10,6 +10,7 @@
   },
   "dependencies": {
     "@element-plus/icons-vue": "^2.0.10",
+    "@kangc/v-md-editor": "^2.3.15",
     "@vueuse/core": "^9.5.0",
     "axios": "^1.2.0",
     "crypto-js": "^4.1.1",
@@ -24,11 +25,13 @@
   "devDependencies": {
     "@types/crypto-js": "^4.1.1",
     "@types/node": "^18.11.9",
+    "@types/prismjs": "^1.26.0",
     "@vitejs/plugin-vue": "^3.2.0",
     "typescript": "^4.6.4",
     "unplugin-auto-import": "^0.11.4",
     "unplugin-vue-components": "^0.22.9",
     "vite": "^3.2.3",
+    "vite-plugin-prismjs": "^0.0.8",
     "vue-tsc": "^1.0.9"
   }
 }

+ 2 - 1
src/components/auth/login.vue

@@ -61,7 +61,8 @@ const bt_login = async (formEl: FormInstance | undefined) => {
   <el-form ref="formRef"
            :model="form"
            :rules="rules"
-           label-position="top">
+           label-position="top"
+           hide-required-asterisk="false">
     <el-form-item label="用户名"
                   prop="name">
       <el-input v-model="form.name"

+ 1 - 0
src/components/auth/register.vue

@@ -74,6 +74,7 @@ const bt_resgister = async (formEl: FormInstance | undefined) => {
         .then((res) => {
           if (res.data.token) {
             token().token = res.data.token
+            window.location.href = '/home'
             ElMessage.success('注册成功')
           }
         })

+ 17 - 0
src/components/header/hleft.vue

@@ -4,6 +4,7 @@ import { Sunny, Moon } from '@element-plus/icons-vue'
 import userinfo from './userinfo.vue'
 import router from '../../plugins/router'
 import { token, Avatar } from '../../plugins/pinia'
+import { onMounted } from 'vue-demi'
 const isDark = useDark()
 const toggleDark = useToggle(isDark)
 const to_login = async () => {
@@ -11,6 +12,22 @@ const to_login = async () => {
   // await router.push('/auth?type=login')
   window.location.href = '/auth?type=login'
 }
+onMounted(() => {
+  const login_avatar = document.getElementById('login_avatar')
+  if (login_avatar) {
+    //鼠标进入头像旋转360度
+    login_avatar.addEventListener('mouseenter', () => {
+      login_avatar.style.transition = 'all 0.5s'
+      login_avatar.style.transform = 'rotate(360deg)'
+    })
+    //鼠标离开头像旋转回来
+    login_avatar.addEventListener('mouseleave', () => {
+      //一秒动画
+      login_avatar.style.transition = 'all 0.5s'
+      login_avatar.style.transform = 'rotate(0deg)'
+    })
+  }
+})
 </script>
 
 <template>

+ 18 - 3
src/components/header/hright.vue

@@ -3,11 +3,20 @@ import { Operation } from '@element-plus/icons-vue'
 import { ref } from 'vue'
 import { useDark, useToggle } from '@vueuse/core'
 import { Sunny, Moon, Close } from '@element-plus/icons-vue'
+import { ElMessage } from 'element-plus'
 
 const isDark = useDark()
 const toggleDark = useToggle(isDark)
 const url = ''
 const drawer = ref(false)
+const to_home = () => {
+  // router.push('/home')
+  window.location.href = '/home'
+}
+const to_post = () => {
+  // router.push('/platelist/0')
+  window.location.href = '/platelist/0'
+}
 </script>
 
 <template>
@@ -46,13 +55,19 @@ const drawer = ref(false)
       <!-- 分割线 -->
       <el-divider>菜单</el-divider>
       <el-menu>
-        <el-menu-item index="1">
+        <el-menu-item index="1"
+                      @click="to_home">
           首页
         </el-menu-item>
-        <el-menu-item index="2">
+        <el-menu-item index="2"
+                      @click="to_post">
           帖子
         </el-menu-item>
-        <el-menu-item index="3">
+        <el-menu-item index="3"
+                      @click="()=>{ElMessage({
+          message: '暂未开放',
+          type: 'warning'
+        })}">
           热点
         </el-menu-item>
       </el-menu>

+ 6 - 2
src/components/header/hsearch.vue

@@ -3,7 +3,7 @@
 <script setup lang="ts">
 import { ref, h } from 'vue'
 import { Search } from '@element-plus/icons-vue'
-import { ElDivider } from 'element-plus'
+import { ElDivider, ElMessage } from 'element-plus'
 import router from '../../plugins/router'
 
 const select = ref('帖子')
@@ -39,7 +39,11 @@ const search = () => {
                  @click="to_post"> 帖子</el-button>
     </div>
     <div>
-      <el-button text> 热点</el-button>
+      <el-button text
+                 @click="()=>{ElMessage({
+          message: '暂未开放',
+          type: 'warning'
+        })}"> 热点</el-button>
     </div>
     <div>
       <el-input v-model="input3"

+ 7 - 5
src/components/header/userinfo.vue

@@ -4,7 +4,7 @@ import service from '../../plugins/axios'
 import { Avatar, userdata } from '../../plugins/pinia'
 import { ref } from 'vue'
 import { API_URL } from '../../config'
-
+const count = ref()
 const info = ref()
 if (token().token) {
   const data = await service({
@@ -16,7 +16,9 @@ if (token().token) {
   })
   if (data.data.cod === 200) {
     info.value = data.data.data
-
+    count.value = await (
+      await service.get(`/userinfo/count?id=${data.data.data.auth_id}`)
+    ).data
     Avatar().URL = info.value.user.avatar
       ? API_URL + info.value.user.avatar
       : 'https://cube.elemecdn.com/0/88/03b0d39583f48206768a7534e55bcpng.png'
@@ -79,7 +81,7 @@ const outlogin = () => {
           <div style="text-align: center;">枫币</div>
         </el-col>
         <el-col :span="24">
-          <div style="text-align: center;">0</div>
+          <div style="text-align: center;">{{ count.data.mapleCoin}}</div>
         </el-col>
       </el-row>
     </el-col>
@@ -89,7 +91,7 @@ const outlogin = () => {
           <div style="text-align: center;">发帖数</div>
         </el-col>
         <el-col :span="24">
-          <div style="text-align: center;">0</div>
+          <div style="text-align: center;">{{count.data.post}}</div>
         </el-col>
       </el-row>
     </el-col>
@@ -99,7 +101,7 @@ const outlogin = () => {
           <div style="text-align: center;">回帖数</div>
         </el-col>
         <el-col :span="24">
-          <div style="text-align: center;">0</div>
+          <div style="text-align: center;">{{count.data.reply}}</div>
         </el-col>
       </el-row>
     </el-col>

+ 5 - 1
src/components/home/newPost.vue

@@ -22,11 +22,15 @@ const data = ref((await service.get(`/post/getNewPost?num=5`)).data)
 data.value.map((item: any) => {
   item.createdAt = formatDate(item.createdAt)
 })
+const tabclick = (row: any) => {
+  window.location.href = `/post/${row.id}`
+}
 </script>
 
 <template>
   <el-table :data="data"
-            style="width: 100%">
+            style="width: 100%"
+            @cell-click="tabclick">
     <el-table-column prop="title"
                      label="标题" />
     <el-table-column prop="createdAt"

+ 5 - 1
src/components/home/redplate.vue

@@ -1,11 +1,15 @@
 <script setup lang="ts">
 import service from '../../plugins/axios'
 const data = await (await service.get('/plate/getplatebycount')).data
+const tabclick = (row: any) => {
+  window.location.href = `/platelist/${row.id}`
+}
 </script>
 
 <template>
   <el-table :data="data"
-            style="width: 100%">
+            style="width: 100%"
+            @cell-click="tabclick">
     <el-table-column prop="name"
                      label="板块" />
     <el-table-column prop="count"

+ 2 - 2
src/components/plate/plateMenu.vue

@@ -24,7 +24,7 @@ async function gotoPlate(id: number) {
 onMounted(() => {
   isitem.value = false
   const bt = document.getElementById('el-icon-arrow-up')
-  const menu_plates = document.querySelectorAll('.menu-plate')
+  const menu_plates = document.querySelectorAll('.menu-plate') as any
   let mpheight = 0
   for (let menuPlate of menu_plates) {
     mpheight += menuPlate.offsetHeight
@@ -32,7 +32,7 @@ onMounted(() => {
   bt!.style.top = `${(mpheight - bt!.offsetHeight) / 2}px`
   document
     .querySelectorAll('.el-menu--collapse .el-menu-item')
-    .forEach((item, index) => {
+    .forEach((item: any, index) => {
       item.style.width = '0px'
       item.style.padding = '0px'
     })

+ 25 - 4
src/components/plate/platePostItem.vue

@@ -1,6 +1,6 @@
 <script setup lang="ts">
 import service from '../../plugins/axios'
-import { defineProps, ref } from 'vue'
+import { defineProps, ref, watch } from 'vue'
 import { API_URL } from '../../config'
 
 const dp = defineProps({
@@ -28,6 +28,10 @@ const dp = defineProps({
     type: String,
     required: true,
   },
+  views: {
+    type: String,
+    required: true,
+  },
 })
 //格式化时间几天前
 function formatDate(date: any) {
@@ -51,12 +55,29 @@ const userinfo = ref(
     await service.get(`userinfo/getuser?id=${dp.authorid}`)
   ).data
 )
+//监听数据变化
+watch(
+  dp,
+  async (newVal) => {
+    userinfo.value = await (
+      await service.get(`userinfo/getuser?id=${dp.authorid}`)
+    ).data
+    userinfo.value.data.user.avatar = API_URL + userinfo.value.data.user.avatar
+  },
+
+  { deep: true }
+)
+
 userinfo.value.data.user.avatar = API_URL + userinfo.value.data.user.avatar
+const to_post = () => {
+  window.location.href = `/post/${dp.id}`
+}
 </script>
 
 <template>
   <el-card>
     <el-row>
+
       <el-col :span="4">
         <div>
           <el-avatar :src="userinfo.data.user.avatar? userinfo.data.user.avatar : 'https://cube.elemecdn.com/0/88/03b0d39583f48206768a7534e55bcpng.png'"
@@ -72,15 +93,15 @@ userinfo.value.data.user.avatar = API_URL + userinfo.value.data.user.avatar
         <div type="text"
              style="float: left">
           <el-space wrap>
-            <el-tag type="primary">作者:{{ userinfo.data.user.nickname }}</el-tag>
+            <el-tag type="primary">作者:{{ userinfo.data.user.nickname?userinfo.data.user.nickname: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-tag type="warning">浏览量{{ dp.views }}</el-tag>
           </el-space>
         </div>
         <el-button type="text"
                    style="float: right"
-                   @click="() => $router.push(`/${data.id}`)">
+                   @click="to_post">
           去看看
         </el-button>
       </el-col>

+ 1 - 1
src/config.ts

@@ -1,5 +1,5 @@
 // 生产环境api地址
-const PROD_API_URL = "http://api.prod.com";
+const PROD_API_URL = "http://www.fyqlin.cn/";
 // 开发环境api地址
 const DEV_API_URL = "http://127.0.0.1:3000/";
 //判断当前环境

+ 139 - 0
src/layouts/post.vue

@@ -0,0 +1,139 @@
+
+
+<script setup lang="ts">
+import { onMounted, ref } from 'vue'
+import service from '../plugins/axios'
+import { useRoute, useRouter } from 'vue-router'
+import { ElMessage } from 'element-plus'
+import authorInformation from '../views/post/authorInformation.vue'
+import { useDark } from '@vueuse/core'
+import { watch } from 'vue'
+import { token } from '../plugins/pinia'
+const id = useRoute().params.id
+const data = await (await service.get(`/post?id=${id}`)).data
+const views = await (await service.get(`post/addview?id=${id}`)).data
+onMounted(() => {
+  const md = document.querySelector('.vuepress-markdown-body') as HTMLElement
+  // 每秒检测一次
+  const html = document.querySelector('html') as HTMLElement
+  //每秒检测一次
+  const timer = setInterval(() => {
+    if (md) {
+      if (html.className === 'dark') {
+        md.style.background = '#1d1e1f'
+        md.style.color = '#fff'
+      } else {
+        md.style.background = '#fff'
+        md.style.color = '#000'
+      }
+    }
+  }, 100)
+})
+async function IsMypost() {
+  if (token().token === '') {
+    return false
+  }
+  const res = await (
+    await service({
+      url: `/auth/islogin`,
+      method: 'get',
+      headers: {
+        Authorization: `Bearer ${token().token}`,
+      },
+    })
+  ).data
+  if (data.author.auth_id === res) {
+    return true
+  }
+  return false
+}
+const isMypost = ref(await IsMypost())
+
+if (data.cod === 400) {
+  window.location.href = '/home'
+}
+// 编辑
+const edit = () => {
+  window.location.href = `/posting?plate=${data.plateId}&postid=${data.id}`
+}
+const del = async () => {
+  await service({
+    url: `/post?postid=${id}`,
+    method: 'delete',
+    headers: {
+      Authorization: `Bearer ${token().token}`,
+    },
+  })
+  ElMessage({
+    message: '删除成功',
+    type: 'success',
+  })
+  window.location.href = `/home`
+}
+</script>
+
+<template>
+
+  <el-row gutter="10">
+    <el-col :xs="24"
+            :sm="24"
+            :md="6"
+            :lg="6"
+            :xl="6">
+      <Suspense>
+        <template #default>
+          <authorInformation :id="data.author.auth_id"
+                             :numberOfReplies="data.comment.length"
+                             :views="views.data" />
+        </template>
+        <template #fallback>
+          <el-skeleton />
+        </template>
+      </Suspense>
+    </el-col>
+    <el-col :xs="24"
+            :sm="24"
+            :md="18"
+            :lg="18"
+            :xl="18">
+      <el-card style="margin: 10px 0px;"
+               class="post-card">
+        <template #header>
+
+          <div class="card-header">
+            <span>{{ data.title }}</span>
+            <!-- 按钮组 -->
+            <div v-show="isMypost">
+              <el-button type="text"
+                         @click="edit">编辑</el-button>
+              <el-divider direction="vertical" />
+              <el-popconfirm title="确定要删除这篇帖子吗?"
+                             @confirm="del">
+                <template #reference>
+                  <el-button type="text">删除</el-button>
+                </template>
+              </el-popconfirm>
+            </div>
+          </div>
+        </template>
+        <v-md-editor :model-value="data.content"
+                     mode="preview"
+                     class="md"></v-md-editor>
+      </el-card>
+
+    </el-col>
+  </el-row>
+
+</template>
+
+<style scoped>
+.post-card {
+  min-height: 800px;
+}
+/* 右对齐 */
+.card-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+}
+</style>

+ 202 - 0
src/layouts/posting.vue

@@ -0,0 +1,202 @@
+<script setup lang="ts">
+import { FormInstance, FormRules, ElMessage } from 'element-plus'
+import { onMounted, reactive, ref } from 'vue'
+import { useRoute } from 'vue-router'
+import service from '../plugins/axios'
+import { token } from '../plugins/pinia'
+let { plate, postid } = useRoute().query
+const form = reactive({
+  platename: '',
+  title: '',
+  content: '',
+  cod: '',
+})
+const select = ref('')
+const platenames = await (await service.get(`/plate/getplate`)).data
+platenames.forEach((item: any) => {
+  if (item.id === Number(plate)) {
+    form.platename = `【${item.name}】`
+  }
+})
+let ressuccess = ''
+const codvisible = ref(false)
+const bt_disabled = ref(false)
+function codsuccess(success: string) {
+  codvisible.value = false
+  bt_disabled.value = true
+  ressuccess = success
+  form.cod = '✅验证通过'
+}
+if (postid) {
+  const post = await (await service.get(`/post?id=${postid}`)).data
+  form.title = post.title
+  form.content = post.content
+}
+const rules = reactive<FormRules>({
+  title: [
+    {
+      required: true,
+      message: '请输入标题',
+      trigger: 'blur',
+    },
+  ],
+  content: [
+    {
+      required: true,
+      message: '请输入内容',
+      trigger: 'blur',
+    },
+    {
+      validator: (rule, value, callback) => {
+        if (value.length < 10) {
+          callback(new Error('内容长度不能小于10个字符!'))
+        } else {
+          callback()
+        }
+      },
+      trigger: 'blur',
+    },
+  ],
+  cod: [{ required: true, message: '请点击按钮进行验证', trigger: 'change' }],
+})
+const formRef = ref<FormInstance>()
+const sub = (formEl: FormInstance | undefined) => {
+  formEl?.validate(async (valid, e) => {
+    if (valid) {
+      if (postid) {
+        const post = await (
+          await service({
+            url: `/post`,
+            method: 'put',
+            headers: {
+              Authorization: `Bearer ${token().token}`,
+            },
+            data: {
+              postid,
+              plateid: plate,
+              title: form.title,
+              content: form.content,
+            },
+          })
+        ).data
+        if (post.status) {
+          ElMessage({
+            message: post.msg,
+            type: 'error',
+            duration: 3 * 1000,
+          })
+        }
+        if (post.code === 200) {
+          window.location.href = `/post/${post.data.id}`
+        }
+        return
+      }
+      if (plate === '0') {
+        if (!select.value) {
+          ElMessage({
+            message: '请选择板块',
+            type: 'error',
+            duration: 3 * 1000,
+          })
+          return
+        }
+        plate = select.value
+      }
+
+      const data = (
+        await service({
+          url: '/post',
+          method: 'post',
+          headers: {
+            Authorization: `Bearer ${token().token}`,
+            code: ressuccess,
+          },
+          data: {
+            plateid: plate,
+            title: form.title,
+            content: form.content,
+            cod: ressuccess,
+          },
+        })
+      ).data
+      if (data.status) {
+        ElMessage({
+          message: data.msg,
+          type: 'error',
+          duration: 3 * 1000,
+        })
+      }
+      if (data.code === 200) {
+        window.location.href = `/post/${data.data.id}`
+      }
+    }
+  })
+}
+</script>
+
+<template>
+  <el-form ref="formRef"
+           label-position="top"
+           :model="form"
+           :rules="rules"
+           hide-required-asterisk="false">
+    <el-form-item prop="title">
+      <el-input v-model="form.title"
+                placeholder="请输入标题">
+        <template #prepend>
+          <span v-if="plate !== `0`">{{ form.platename }}</span>
+          <el-select v-else
+                     v-model="select"
+                     placeholder="请选择"
+                     style="width: 115px">
+            <el-option v-for="item in platenames"
+                       :key="item.id"
+                       :label="item.name"
+                       :value="item.id" />
+          </el-select>
+        </template>
+      </el-input>
+    </el-form-item>
+    <el-form-item prop="content">
+      <v-md-editor v-model="form.content"
+                   class="md"
+                   height="800px" />
+    </el-form-item>
+    <el-form-item label="验证码"
+                  prop="cod">
+      <el-input type="password"
+                v-model="form.cod"
+                style="display:none" />
+      <el-popover placement="top"
+                  :width="330"
+                  :visible="codvisible">
+        <template #reference>
+          <el-button :type="bt_disabled?'success':'info'"
+                     style="width: 100%;"
+                     :disabled="bt_disabled"
+                     @click="(codvisible = !codvisible)">{{bt_disabled?"✅验证通过":"点击进行验证"}}</el-button>
+        </template>
+        <Suspense>
+          <template #default>
+            <el-space>
+              <div class="center">
+                <cod @success="codsuccess" />
+              </div>
+            </el-space>
+          </template>
+          <template #fallback>
+            验证码获取中
+          </template>
+        </Suspense>
+      </el-popover>
+    </el-form-item>
+    <el-form-item>
+      <el-button type="primary"
+                 @click="sub(formRef)">提交</el-button>
+    </el-form-item>
+  </el-form>
+
+</template>
+
+<style scoped>
+</style>

+ 2 - 2
src/layouts/userinfo.vue

@@ -72,7 +72,7 @@ const logout = () => {
                  router="#default">
           <el-menu-item index="/user/userinfo">修改资料</el-menu-item>
           <el-menu-item index="/user/postlist/0?type=user">我的帖子</el-menu-item>
-          <el-menu-item index="3">账户安全</el-menu-item>
+          <el-menu-item index="/user/accountsecurity">账户安全</el-menu-item>
         </el-menu>
         <el-menu mode="horizontal"
                  class="hidden-md-and-up"
@@ -80,7 +80,7 @@ const logout = () => {
                  router="#default">
           <el-menu-item index="/user/userinfo">修改资料</el-menu-item>
           <el-menu-item index="/user/postlist/0?type=user">我的帖子</el-menu-item>
-          <el-menu-item index="3">账户安全</el-menu-item>
+          <el-menu-item index="/user/accountsecurity">账户安全</el-menu-item>
         </el-menu>
       </el-card>
 

+ 3 - 1
src/main.ts

@@ -3,12 +3,14 @@ import App from "./App.vue";
 import "element-plus/es/components/message/style/css";
 import "element-plus/theme-chalk/dark/css-vars.css";
 import "element-plus/theme-chalk/display.css";
-import router, { setupRouter } from "./plugins/router";
+import { setupRouter } from "./plugins/router";
 import { pinia } from "./plugins/pinia";
+import { setupEditor } from "./plugins/postmd";
 function bootstrap() {
   const app = createApp(App);
   setupRouter(app);
   app.use(pinia);
+  setupEditor(app);
   app.mount("#app");
 }
 bootstrap();

+ 2 - 1
src/plugins/axios.ts

@@ -1,9 +1,10 @@
 import axios from "axios";
 import { ElMessage } from "element-plus";
 import router from "./router";
+import { API_URL } from "../config";
 //axios封装
 const service = axios.create({
-  baseURL: "http://localhost:3000",
+  baseURL: API_URL,
   timeout: 5000,
 });
 //请求拦截器

+ 0 - 1
src/plugins/crypto.ts

@@ -24,6 +24,5 @@ export function decrypt(
     padding: pad.ZeroPadding,
   });
   let decString = enc.Utf8.stringify(decrypt).toString();
-  console.log("decString", decString);
   return decString;
 }

+ 12 - 0
src/plugins/postmd.ts

@@ -0,0 +1,12 @@
+import "@kangc/v-md-editor/lib/style/base-editor.css";
+import "@kangc/v-md-editor/lib/theme/style/vuepress.css";
+import VueMarkdownEditor from "@kangc/v-md-editor";
+import vuepressTheme from "@kangc/v-md-editor/lib/theme/vuepress.js";
+import createEmojiPlugin from "@kangc/v-md-editor/lib/plugins/emoji/index";
+import Prism from "prismjs";
+
+VueMarkdownEditor.use(vuepressTheme, { Prism });
+VueMarkdownEditor.use(createEmojiPlugin());
+export function setupEditor(app: any) {
+  app.use(VueMarkdownEditor);
+}

+ 14 - 0
src/plugins/router.ts

@@ -14,6 +14,11 @@ const router = createRouter({
           component: () => import("../views/home/index.vue"),
           children: [],
         },
+        {
+          path: "/posting",
+          component: () => import("../layouts/posting.vue"),
+          children: [],
+        },
         {
           path: "/auth",
           component: () => import("../layouts/auth.vue"),
@@ -22,6 +27,10 @@ const router = createRouter({
           path: "/platelist/:id",
           component: () => import("../views/plate/platelist.vue"),
         },
+        {
+          path: "/post/:id",
+          component: () => import("../layouts/post.vue"),
+        },
         {
           path: "/user",
           component: () => import("../layouts/userinfo.vue"),
@@ -34,6 +43,11 @@ const router = createRouter({
               path: "/user/postlist/:id",
               component: () => import("../views/plate/platelist.vue"),
             },
+            //accountsecurity
+            {
+              path: "/user/accountsecurity",
+              component: () => import("../views/userinfo/accountsecurity.vue"),
+            },
           ],
         },
       ],

+ 3 - 0
src/shims.d.ts

@@ -0,0 +1,3 @@
+declare module "@kangc/v-md-editor/lib/theme/vuepress.js";
+declare module "@kangc/v-md-editor";
+declare module "@kangc/v-md-editor/lib/plugins/emoji/index";

+ 24 - 1
src/views/plate/platelist.vue

@@ -4,6 +4,8 @@ import { useRoute } from 'vue-router'
 import service from '../../plugins/axios'
 import { token } from '../../plugins/pinia'
 import platePostItem from '../../components/plate/platePostItem.vue'
+import { ChatSquare } from '@element-plus/icons-vue'
+// <el-icon><ChatSquare /></el-icon>
 let id = useRoute().params.id as any
 id = id ? id : 0
 let type: 'search' | 'user' | '' = ''
@@ -53,30 +55,51 @@ const page = async (v: number) => {
     ).data
     return
   }
+
   data.value = await (
     await service.get(`/post/platelist?plateid=${id}&page=${v}&limit=${5}`)
   ).data
 }
+function bt_post() {
+  window.location.href = `/posting?plate=${id === 0 ? 1 : id}`
+}
 </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" />
+                   :numberOfReplies="item.comments"
+                   :views="item.views" />
   </div>
   <el-divider />
   <el-pagination layout="prev, pager, next"
                  :page-count="data.totalPage"
                  @current-change="page"
                  id="page" />
+  <el-affix position="bottom"
+            :offset="20"
+            :v-shou="id == 0">
+    <el-button type="primary"
+               @click="bt_post"
+               size="large"
+               :icon="ChatSquare"
+               circle></el-button>
+  </el-affix>
 
 </template>
 
 <style scoped >
+/* 右对齐 */
+.el-affix {
+  position: absolute;
+  right: 0;
+  bottom: 0;
+}
 </style>

+ 113 - 0
src/views/post/authorInformation.vue

@@ -0,0 +1,113 @@
+<script setup lang="ts">
+// 插槽
+import { ref } from 'vue'
+import service from '../../plugins/axios'
+import { API_URL } from '../../config'
+
+const dp = defineProps({
+  id: {
+    type: String,
+    required: true,
+  },
+  numberOfReplies: {
+    type: Number,
+    required: true,
+  },
+  views: {
+    type: Number,
+    required: true,
+  },
+})
+const to_gethub = () => {
+  window.location.href = `https://github.com/${data.data.user.github}`
+}
+const data = await (await service.get(`userinfo/getuser?id=${dp.id}`)).data
+// /userinfo/count?id=3
+const count = await (await service.get(`/userinfo/count?id=${dp.id}`)).data
+</script>
+
+<template>
+  <el-card style="margin: 10px 0px;"
+           class="post-card">
+    <template #header>
+      <el-button type="text"
+                 :href="data.data.user.github">
+        {{ data.data.user.nickname?data.data.user.nickname:data.data.username }}
+
+      </el-button>
+      <el-divider direction="vertical" />
+      回复:{{ numberOfReplies }}
+      <el-divider direction="vertical" />
+      浏览:{{ views }}
+    </template>
+
+    <div class="center">
+      <el-avatar :src="API_URL+data.data.user.avatar"
+                 :size="100" />
+    </div>
+    <div class="left">
+      <span class="personalSignature">{{ data.data.user.signature }}</span>
+    </div>
+    <el-button type="primary"
+               style="width: 100%;margin:  0px;"
+               @click="to_gethub">GitHub</el-button>
+    <!-- 用户组 -->
+    <div class="center">
+      <el-tag type="success">Lv.{{data.data.user.level}}</el-tag>
+    </div>
+    <el-row>
+      <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;">{{ count.data.mapleCoin}}</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;">{{count.data.post}}</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;">{{count.data.reply}}</div>
+          </el-col>
+        </el-row>
+      </el-col>
+    </el-row>
+  </el-card>
+</template>
+
+<style scoped >
+.center {
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  margin: 10px 0px;
+}
+/* 左对齐 */
+.left {
+  display: flex;
+  justify-content: flex-start;
+  align-items: center;
+  margin: 10px 0px;
+}
+
+/* 个性签名 */
+.personalSignature {
+  font-size: 14px;
+  color: #606266;
+}
+</style>

+ 146 - 0
src/views/userinfo/accountsecurity.vue

@@ -0,0 +1,146 @@
+<script setup lang="ts">
+import { reactive, ref } from 'vue'
+import service from '../../plugins/axios'
+import { token } from '../../plugins/pinia'
+import { ElMessage, FormInstance } from 'element-plus'
+import { API_URL } from '../../config'
+const form = reactive({
+  oldpassword: '',
+  newpassword: '',
+  newpassword2: '',
+  cod: '',
+})
+const codvisible = ref(false)
+const bt_disabled = ref(false)
+const formRef = ref()
+let ressuccess = ''
+function codsuccess(success: string) {
+  codvisible.value = false
+  bt_disabled.value = true
+  ressuccess = success
+  form.cod = '✅验证通过'
+}
+const rules = {
+  oldpassword: [
+    { required: true, message: '请输入原密码', trigger: 'blur' },
+    { min: 6, max: 20, message: '长度在 6 到 20 个字符', trigger: 'blur' },
+  ],
+  newpassword: [
+    { required: true, message: '请输入新密码', trigger: 'blur' },
+    { min: 6, max: 20, message: '长度在 6 到 20 个字符', trigger: 'blur' },
+  ],
+  newpassword2: [
+    { required: true, message: '请再次输入新密码', trigger: 'blur' },
+    { min: 6, max: 20, message: '长度在 6 到 20 个字符', trigger: 'blur' },
+    {
+      validator: (rule: any, value: any, callback: any) => {
+        if (value !== form.newpassword) {
+          callback(new Error('两次输入密码不一致!'))
+        } else {
+          callback()
+        }
+      },
+      trigger: 'blur',
+    },
+  ],
+  cod: [{ required: true, message: '请点击按钮进行验证', trigger: 'change' }],
+}
+const submit = async (formEl: FormInstance | undefined) => {
+  formEl?.validate(async (valid: boolean, e: any) => {
+    if (valid) {
+      const { oldpassword, newpassword, newpassword2 } = form
+      const res = (
+        await service({
+          method: 'put',
+          url: `/auth/paw`,
+          headers: {
+            Authorization: 'Bearer ' + token().token,
+            cod: ressuccess,
+          },
+          data: {
+            paw: oldpassword,
+            newpaw: newpassword,
+          },
+        })
+      ).data
+      if (res.cod === 200) {
+        ElMessage.success(res.msg)
+        token().token = ''
+        window.location.href = '/auth?type=login'
+      } else {
+        ElMessage.error(res.msg)
+      }
+    }
+  })
+}
+</script>
+
+<template>
+  <el-card style="margin-top: 10px;">
+    <template #header>修改密码</template>
+    <el-form ref="formRef"
+             :model="form"
+             label-width="120px"
+             :rules="rules"
+             hide-required-asterisk="false"
+             label-position="left">
+      <el-form-item label="验证码"
+                    prop="cod">
+        <el-input type="password"
+                  v-model="form.cod"
+                  style="display:none" />
+        <el-popover placement="bottom"
+                    :width="330"
+                    :visible="codvisible">
+          <template #reference>
+            <el-button :type="bt_disabled?'success':'info'"
+                       style="width: 100%;"
+                       :disabled="bt_disabled"
+                       @click="(codvisible = !codvisible)">{{bt_disabled?"✅验证通过":"点击进行验证"}}</el-button>
+          </template>
+          <Suspense>
+            <template #default>
+              <el-space>
+                <div class="center">
+                  <cod @success="codsuccess" />
+                </div>
+              </el-space>
+            </template>
+            <template #fallback>
+              验证码获取中
+            </template>
+          </Suspense>
+        </el-popover>
+      </el-form-item>
+      <el-form-item label="原密码"
+                    prop="oldpassword">
+        <el-input v-model="form.oldpassword"
+                  type="password"
+                  placeholder="请输入原密码"
+                  clearable />
+      </el-form-item>
+      <el-form-item label="新密码"
+                    prop="newpassword">
+        <el-input v-model="form.newpassword"
+                  type="password"
+                  placeholder="请输入新密码"
+                  clearable />
+      </el-form-item>
+      <el-form-item label="确认密码"
+                    prop="newpassword2">
+        <el-input v-model="form.newpassword2"
+                  type="password"
+                  placeholder="请再次输入新密码"
+                  clearable />
+      </el-form-item>
+      <el-form-item>
+        <el-button type="primary"
+                   @click="submit(formRef)">提交</el-button>
+      </el-form-item>
+    </el-form>
+  </el-card>
+
+</template>
+
+<style scoped >
+</style>

+ 7 - 0
vite.config.ts

@@ -5,7 +5,11 @@ import Components from "unplugin-vue-components/vite";
 import IconsResolver from "unplugin-icons/resolver";
 import Icons from "unplugin-icons/vite";
 import { ElementPlusResolver } from "unplugin-vue-components/resolvers";
+import { prismjsPlugin } from "vite-plugin-prismjs";
 export default defineConfig({
+  optimizeDeps: {
+    include: ["@kangc/v-md-editor/lib/theme/vuepress.js"],
+  },
   plugins: [
     vue(),
     AutoImport({
@@ -28,5 +32,8 @@ export default defineConfig({
     Icons({
       autoInstall: true,
     }),
+    prismjsPlugin({
+      languages: "all",
+    }),
   ],
 });

Diff do ficheiro suprimidas por serem muito extensas
+ 1080 - 6
yarn.lock


Alguns ficheiros não foram mostrados porque muitos ficheiros mudaram neste diff