Browse Source

feat(PostView): 添加浮动写作按钮及动画效果

- 在PostView页面右下角添加浮动写作按钮
- 实现按钮的显示/隐藏动画效果
- 添加随机提示文本功能
- 新增writeIcon图标资源
- 添加delay工具函数用于动画控制
- 隐藏编辑器菜单项(showInMenu设为false)
Sakulin 2 tháng trước cách đây
mục cha
commit
371b27d628
4 tập tin đã thay đổi với 117 bổ sung4 xóa
  1. 7 0
      src/assets/icons.ts
  2. 1 1
      src/router/index.ts
  3. 6 0
      src/utils/index.ts
  4. 103 3
      src/views/PostView.vue

+ 7 - 0
src/assets/icons.ts

@@ -46,3 +46,10 @@ export const accountIcon: Icon = {
     `
 }
 
+export const writeIcon: Icon = {
+  template: `
+  <svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg">
+    <path d="M659.655431 521.588015q23.970037-6.71161 46.022472-13.423221 19.17603-5.752809 39.310861-11.505618t33.558052-10.546816l-13.423221 50.816479q-5.752809 21.093633-10.546816 31.640449-9.588015 25.88764-22.531835 47.940075t-24.449438 38.35206q-13.423221 19.17603-27.805243 35.475655l-117.932584 35.475655 96.838951 17.258427q-19.17603 16.299625-41.228464 33.558052-19.17603 14.382022-43.625468 30.202247t-51.29588 29.243446-59.925094 13.902622-62.801498-4.314607q-34.516854-4.794007-69.033708-16.299625 10.546816-16.299625 23.011236-36.434457 10.546816-17.258427 25.40824-40.749064t31.161049-52.254682q46.022472-77.662921 89.168539-152.449438t77.662921-135.191011q39.310861-69.992509 75.745318-132.314607-45.06367 51.775281-94.921348 116.014981-43.146067 54.651685-95.88015 129.917603t-107.385768 164.434457q-11.505618 18.217228-25.88764 42.187266t-30.202247 50.816479-32.599251 55.131086-33.078652 55.131086q-38.35206 62.322097-78.621723 130.397004 0.958801-20.134831 7.670412-51.775281 5.752809-26.846442 19.17603-67.116105t38.35206-94.921348q16.299625-34.516854 24.928839-53.692884t13.423221-29.722846q4.794007-11.505618 7.670412-15.340824-4.794007-5.752809-1.917603-23.011236 1.917603-15.340824 11.026217-44.58427t31.161049-81.977528q22.052434-53.692884 58.007491-115.535581t81.018727-122.726592 97.797753-117.932584 107.865169-101.153558 110.262172-72.389513 106.906367-32.11985q0.958801 33.558052-6.71161 88.689139t-19.17603 117.932584-25.88764 127.520599-27.805243 117.453184z"/>
+  </svg>
+  `
+}

+ 1 - 1
src/router/index.ts

@@ -57,7 +57,7 @@ export const modules: BlogModule[] = [
     title: "编辑博文",
     routeUrl: "/editor",
     component: () => import('../views/PostEditView.vue'),
-    showInMenu: true
+    showInMenu: false
   }
 ];
 

+ 6 - 0
src/utils/index.ts

@@ -44,3 +44,9 @@ export function formateDateAccurateToDay(date: Date | string | number): string {
     return `${year} 年 ${month} 月 ${day} 日`
   }
 }
+
+export async function delay(ms: number) {
+  return new Promise((resolve) => {
+    setTimeout(resolve, ms);
+  });
+}

+ 103 - 3
src/views/PostView.vue

@@ -1,13 +1,19 @@
 <script setup lang="ts">
 import type { PostSketch } from '@/models'
-import { onMounted, ref } from 'vue'
+import { computed, onMounted, ref, watch } from 'vue'
 import { api } from '@/utils/axios'
 import PostCard from '@/components/PostCard.vue'
+import { useTokenStore } from '@/stores/auth.ts'
+import { delay } from '@/utils'
+import { writeIcon } from '@/assets/icons.ts'
+import { router } from '@/router'
 
 const data = ref<PostSketch[]>([])
 const pageSize = 10
 const isEnd = ref(false)
 
+const tokenStore = useTokenStore();
+
 function nextDatum() {
   if (isEnd.value) {
     return
@@ -27,11 +33,58 @@ function nextDatum() {
 
 onMounted(() => {
   nextDatum()
-})
+});
+
+const tokenAvailable = ref(false);
+
+const floatBtnElementAnimationStyle = ref({
+  display: "none",
+  transform: "scale(0)",
+  transition: "all .3s"
+});
+
+watch(() => tokenStore.available, async (newValue) => {
+  if (!tokenAvailable.value && newValue) {
+    tokenAvailable.value = true;
+    floatBtnElementAnimationStyle.value.display = "block";
+    floatBtnElementAnimationStyle.value.transform = "scale(0)";
+    await delay(20);
+    floatBtnElementAnimationStyle.value.transform = "scale(100%)";
+  } else if (tokenAvailable.value && !newValue) {
+    tokenAvailable.value = false;
+    floatBtnElementAnimationStyle.value.transform = "scale(0)";
+    await delay(320);
+    floatBtnElementAnimationStyle.value.display = "none";
+  }
+}, {immediate: true});
+
+const tips = [
+  "写点东西?", "有新鲜事?", "有新技术?", "有新发现?", "有新想法?"
+];
+
+const tipIndex = ref(0);
+
+const currentTip = computed(() => tips[tipIndex.value]);
+
+const changeTip = () => {
+  tipIndex.value = (tipIndex.value + 1) % tips.length;
+}
+
+function handleWriteClick() {
+  router.push("/editor")
+}
+
 </script>
 
 <template>
-  <div>
+  <div style="position: relative;">
+    <div class="float-btn" @click.stop="handleWriteClick" :style="floatBtnElementAnimationStyle" @mouseenter="changeTip">
+      <div class="float-btn-content">
+        <div class="float-btn-ico" v-html="writeIcon.template"/>
+        <div>{{ currentTip }}</div>
+      </div>
+
+    </div>
     <PostCard v-for="item of data" :key="item.id" :target="item" />
     <div
       class="post-item"
@@ -63,5 +116,52 @@ onMounted(() => {
   color: var(--text-color);
 }
 
+.float-btn {
+  position: fixed;
+  right: 48px;
+  bottom: 32px;
+  padding: 8px;
+  width: 32px;
+  height: 32px;
+  border-radius: 24px;
+  overflow: hidden;
+  fill: var(--text-color);
+  color: var(--text-color);
+  background-color: var(--secondary-background-color);
+  box-shadow: rgba(0, 0, 0, 0.4) 0 0 6px;
+  cursor: pointer;
+  z-index: 10;
+}
+
+.float-btn:hover {
+  fill: var(--secondary-background-color);
+  background-color: var(--text-color);
+  color: var(--secondary-background-color);
+  right: 32px;
+  bottom: 24px;
+  width: 150px;
+  height: 48px;
+  border-radius: 32px;
+}
+
+.float-btn-content {
+  width: 256px;
+  display: flex;
+  gap: 8px;
+  font-size: large;
+  font-weight: bold;
+  align-items: center;
+}
+
+.float-btn-ico {
+  width: 32px;
+  height: 32px;
+  transition: all .3s;
+}
+
+.float-btn:hover .float-btn-content .float-btn-ico {
+  width: 48px;
+  height: 48px;
+}
 
 </style>