Parcourir la source

pwa功能修复

wilhelm wong il y a 2 mois
Parent
commit
734f9bcaa3
8 fichiers modifiés avec 513 ajouts et 209 suppressions
  1. 1 1
      dev-dist/sw.js
  2. 216 208
      package-lock.json
  3. 1 0
      package.json
  4. 44 0
      src/components/layout/Header.vue
  5. 21 0
      src/main.ts
  6. 6 0
      src/services/api.ts
  7. 32 0
      src/store/user.ts
  8. 192 0
      src/style.css

+ 1 - 1
dev-dist/sw.js

@@ -79,7 +79,7 @@ define(['./workbox-f2cb1a81'], (function (workbox) { 'use strict';
    */
   workbox.precacheAndRoute([{
     "url": "index.html",
-    "revision": "0.5ihj3jmcn"
+    "revision": "0.mi2p0qfne9g"
   }], {});
   workbox.cleanupOutdatedCaches();
   workbox.registerRoute(new workbox.NavigationRoute(workbox.createHandlerBoundToURL("index.html"), {

Fichier diff supprimé car celui-ci est trop grand
+ 216 - 208
package-lock.json


+ 1 - 0
package.json

@@ -11,6 +11,7 @@
   "dependencies": {
     "@primevue/forms": "^4.3.9",
     "@types/axios": "^0.14.4",
+    "@types/vue-router": "^2.0.0",
     "@videojs/http-streaming": "^3.17.2",
     "@vueuse/core": "^13.9.0",
     "axios": "^1.12.2",

+ 44 - 0
src/components/layout/Header.vue

@@ -3,6 +3,7 @@ import { computed, ref } from "vue";
 import { useRouter, useRoute } from "vue-router";
 import { useUserStore } from "@/store/user";
 import { vipLevelToText, VipLevel } from "@/types/vip";
+import { useThemeStore } from "@/store/theme";
 
 const userStore = useUserStore();
 const router = useRouter();
@@ -49,6 +50,16 @@ const truncateUsername = (name: string, maxLength = 6) => {
   if (name.length <= maxLength) return name;
   return name.substring(0, maxLength) + "...";
 };
+
+// 主题
+const themeStore = useThemeStore();
+const isDark = computed(() => themeStore.theme === "dark");
+const switchTheme = () => themeStore.toggleTheme();
+const themeOptions = themeStore.AVAILABLE_THEMES;
+const changeTheme = (e: Event) => {
+  const target = e.target as HTMLSelectElement;
+  themeStore.setTheme(target.value as any);
+};
 </script>
 
 <template>
@@ -119,6 +130,39 @@ const truncateUsername = (name: string, maxLength = 6) => {
         </div>
 
         <div class="flex items-center gap-4 text-sm">
+          <!-- 主题选择器 - 仅未登录时显示 -->
+          <template v-if="!isLoggedIn">
+            <select
+              class="px-2 py-1 rounded-lg bg-white/5 border border-white/10 text-white/80 hover:text-white hover:bg-white/10 transition text-xs"
+              :value="themeStore.theme"
+              @change="changeTheme"
+              title="选择主题"
+            >
+              <option
+                v-for="t in themeOptions"
+                :key="t.key"
+                :value="t.key"
+                class="bg-surface text-white"
+              >
+                {{ t.label }}
+              </option>
+            </select>
+
+            <!-- 主题切换按钮 -->
+            <button
+              class="h-8 w-8 grid place-items-center rounded-lg bg-white/5 border border-white/10 text-white/80 hover:text-white hover:bg-white/10 transition"
+              @click="switchTheme"
+              :aria-label="isDark ? '切换为浅色主题' : '切换为深色主题'"
+              title="主题切换"
+            >
+              <svg v-if="isDark" viewBox="0 0 24 24" width="16" height="16" fill="currentColor">
+                <path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/>
+              </svg>
+              <svg v-else viewBox="0 0 24 24" width="16" height="16" fill="currentColor">
+                <path d="M6.76 4.84l-1.8-1.79-1.41 1.41 1.79 1.8 1.42-1.42zm10.45 10.45l1.79 1.8 1.41-1.41-1.8-1.79-1.4 1.4zM12 4V1h-1v3h1zm0 19v-3h-1v3h1zM4 13H1v-1h3v1zm19 0h-3v-1h3v1zM6.76 19.16l-1.42 1.42-1.79-1.8 1.41-1.41 1.8 1.79zM19.16 6.76l1.42-1.42-1.79-1.8-1.41 1.41 1.78 1.81z"/>
+              </svg>
+            </button>
+          </template>
           <template v-if="isLoggedIn">
             <button
               class="text-white/70 hover:text-brand transition"

+ 21 - 0
src/main.ts

@@ -9,6 +9,9 @@ import router from "./router";
 
 import { initPWA } from "./utils/pwa";
 import { initVConsole } from "@/utils/vconsole";
+import { useThemeStore } from "@/store/theme";
+import { useUserStore } from "@/store/user";
+import { getTeamTheme } from "@/services/api";
 
 const app = createApp(App);
 
@@ -18,4 +21,22 @@ app.use(createPinia());
 initVConsole();
 initPWA();
 
+// 初始化主题(从本地存储恢复并应用到 document)
+const themeStore = useThemeStore();
+themeStore.initTheme();
+
+// 如果用户已登录,获取团队主题并应用
+const userStore = useUserStore();
+if (userStore.token) {
+  getTeamTheme()
+    .then((themeData) => {
+      if (themeData?.themeColor) {
+        themeStore.setTheme(themeData.themeColor as any);
+      }
+    })
+    .catch((e) => {
+      console.error("启动时获取团队主题失败", e);
+    });
+}
+
 app.mount("#app");

+ 6 - 0
src/services/api.ts

@@ -223,6 +223,12 @@ export const resetPassword = async (password: string): Promise<any> => {
   return res.data;
 };
 
+// 获取团队主题颜色
+export const getTeamTheme = async (): Promise<{ themeColor: string }> => {
+  const res = await api.get("/teams/my-theme");
+  return res.data;
+};
+
 /**
  * ===================== 视频相关接口 =====================
  */

+ 32 - 0
src/store/user.ts

@@ -5,10 +5,12 @@ import {
   register as apiRegister,
   profile,
   newGuest,
+  getTeamTheme,
 } from "@/services/api";
 import { useStorage } from "@vueuse/core";
 import { VipLevel } from "@/types/vip";
 import { usePriceStore } from "@/store/price";
+import { useThemeStore } from "@/store/theme";
 
 // 主页浏览状态接口定义
 interface HomePageState {
@@ -103,6 +105,16 @@ export const useUserStore = defineStore("user", () => {
     } catch (e) {
       console.error("登录后加载价格失败", e);
     }
+    // 登录后获取并应用团队主题
+    try {
+      const themeData = await getTeamTheme();
+      const themeStore = useThemeStore();
+      if (themeData?.themeColor) {
+        themeStore.setTheme(themeData.themeColor as any);
+      }
+    } catch (e) {
+      console.error("登录后获取团队主题失败", e);
+    }
     return response;
   };
 
@@ -125,6 +137,16 @@ export const useUserStore = defineStore("user", () => {
     } catch (e) {
       console.error("注册后加载价格失败", e);
     }
+    // 注册后获取并应用团队主题
+    try {
+      const themeData = await getTeamTheme();
+      const themeStore = useThemeStore();
+      if (themeData?.themeColor) {
+        themeStore.setTheme(themeData.themeColor as any);
+      }
+    } catch (e) {
+      console.error("注册后获取团队主题失败", e);
+    }
     return response;
   };
 
@@ -157,6 +179,16 @@ export const useUserStore = defineStore("user", () => {
       } catch (e) {
         console.error("创建游客后加载价格失败", e);
       }
+      // 创建游客后获取并应用团队主题
+      try {
+        const themeData = await getTeamTheme();
+        const themeStore = useThemeStore();
+        if (themeData?.themeColor) {
+          themeStore.setTheme(themeData.themeColor as any);
+        }
+      } catch (e) {
+        console.error("创建游客后获取团队主题失败", e);
+      }
       return response;
     } catch (error) {
       console.error("创建游客账号失败", error);

+ 192 - 0
src/style.css

@@ -5,6 +5,75 @@
 :root {
   --color-brand: #10b981;
   --color-surface: #0f172a;
+  --color-text: #f1f5f9; /* 默认浅色文字 */
+}
+
+/* 主题变量:dark / light */
+[data-theme="dark"] {
+  --color-brand: #10b981;
+  --color-surface: #1e293b; /* 深灰蓝背景 */
+  --color-text: #f1f5f9; /* 浅色文字 */
+}
+
+[data-theme="light"] {
+  --color-brand: #059669; /* 略深一点的绿,便于浅色背景对比 */
+  --color-surface: #f8fafc; /* 浅色背景 */
+  --color-text: #1e293b; /* 深色文字 */
+}
+
+/* 扩展主题 */
+[data-theme="black"] {
+  --color-brand: #60a5fa; /* 亮蓝色品牌色,与黑色背景形成对比 */
+  --color-surface: #0a0a0a; /* 接近纯黑但稍微柔和 */
+  --color-text: #f1f5f9; /* 浅色文字 */
+}
+
+[data-theme="purple"] {
+  --color-brand: #a855f7; /* 亮紫色 */
+  --color-surface: #3b1f5e; /* 中等深度紫色背景 */
+  --color-text: #f1f5f9; /* 浅色文字 */
+}
+
+[data-theme="orange"] {
+  --color-brand: #fb923c; /* 亮橙色 */
+  --color-surface: #7c2d12; /* 中等深度橙色背景 */
+  --color-text: #f1f5f9; /* 浅色文字 */
+}
+
+[data-theme="green"] {
+  --color-brand: #22c55e; /* 亮绿色 */
+  --color-surface: #14532d; /* 中等深度绿色背景 */
+  --color-text: #f1f5f9; /* 浅色文字 */
+}
+
+[data-theme="galaxy"] {
+  --color-brand: #06b6d4; /* 青色/星空色 */
+  --color-surface: #1e1b4b; /* 深蓝紫星空背景 */
+  --color-text: #f1f5f9; /* 浅色文字 */
+}
+
+[data-theme="rose"] {
+  --color-brand: #f43f5e; /* 玫瑰粉红 */
+  --color-surface: #831843; /* 中等深度玫瑰色背景 */
+  --color-text: #f1f5f9; /* 浅色文字 */
+}
+
+[data-theme="blue"] {
+  --color-brand: #3b82f6; /* 亮蓝色 */
+  --color-surface: #1e3a8a; /* 中等深度蓝色背景 */
+  --color-text: #f1f5f9; /* 浅色文字 */
+}
+
+[data-theme="red"] {
+  --color-brand: #f87171; /* 亮红色 */
+  --color-surface: #7f1d1d; /* 中等深度红色背景 */
+  --color-text: #f1f5f9; /* 浅色文字 */
+}
+
+[data-theme="yellow"] {
+  --color-brand: #fbbf24; /* 亮黄色 */
+  --color-surface: #78350f; /* 中等深度黄棕色背景 */
+  --color-text: #f1f5f9; /* 浅色文字 */
 }
 
 html {
@@ -23,4 +92,127 @@ html {
   .bg-surface {
     background-color: var(--color-surface);
   }
+}
+
+/* 浅色主题下的文字颜色覆盖 */
+[data-theme="light"] {
+  /* 覆盖白色文字为深色 */
+  color: var(--color-text);
+}
+
+[data-theme="light"] .text-white,
+[data-theme="light"] .text-white\/90,
+[data-theme="light"] .text-white\/80,
+[data-theme="light"] .text-white\/70,
+[data-theme="light"] .text-white\/60,
+[data-theme="light"] .text-white\/50 {
+  color: var(--color-text) !important;
+}
+
+/* 浅色主题下的边框和背景透明度调整 */
+[data-theme="light"] .border-white\/5 {
+  border-color: rgba(30, 41, 59, 0.1) !important;
+}
+
+[data-theme="light"] .border-white\/10 {
+  border-color: rgba(30, 41, 59, 0.2) !important;
+}
+
+[data-theme="light"] .bg-white\/5 {
+  background-color: rgba(30, 41, 59, 0.05) !important;
+}
+
+[data-theme="light"] .bg-white\/10 {
+  background-color: rgba(30, 41, 59, 0.1) !important;
+}
+
+[data-theme="light"] .bg-surface\/70 {
+  background-color: rgba(248, 250, 252, 0.7) !important;
+}
+
+[data-theme="light"] .bg-surface\/90 {
+  background-color: rgba(248, 250, 252, 0.9) !important;
+}
+
+/* 浅色主题下的 placeholder 颜色 */
+[data-theme="light"] .placeholder-white\/50::placeholder {
+  color: rgba(30, 41, 59, 0.5) !important;
+}
+
+/* 浅色主题下的 hover 状态 */
+[data-theme="light"] .hover\:text-white:hover {
+  color: var(--color-text) !important;
+}
+
+[data-theme="light"] .hover\:text-white\/80:hover {
+  color: rgba(30, 41, 59, 0.8) !important;
+}
+
+[data-theme="light"] .hover\:bg-white\/10:hover {
+  background-color: rgba(30, 41, 59, 0.1) !important;
+}
+
+/* 浅色主题下的文本选择器颜色 */
+[data-theme="light"] .text-slate-100 {
+  color: var(--color-text) !important;
+}
+
+/* 浅色主题下的首页标签和排序按钮样式 */
+[data-theme="light"] .chip-small {
+  color: var(--color-text) !important;
+  border-color: rgba(30, 41, 59, 0.2) !important;
+  background-color: rgba(30, 41, 59, 0.05) !important;
+}
+
+[data-theme="light"] .chip-small:hover {
+  background-color: rgba(30, 41, 59, 0.1) !important;
+  color: var(--color-text) !important;
+}
+
+[data-theme="light"] .chip-small-active {
+  background-color: rgba(5, 150, 105, 0.15) !important;
+  border-color: rgba(5, 150, 105, 0.3) !important;
+  color: var(--color-brand) !important;
+}
+
+[data-theme="light"] .sort-chip {
+  color: var(--color-text) !important;
+  border-color: rgba(30, 41, 59, 0.2) !important;
+  background-color: rgba(30, 41, 59, 0.05) !important;
+}
+
+[data-theme="light"] .sort-chip:hover {
+  background-color: rgba(30, 41, 59, 0.1) !important;
+  color: var(--color-text) !important;
+}
+
+[data-theme="light"] .sort-chip-active {
+  background-color: rgba(5, 150, 105, 0.15) !important;
+  border-color: rgba(5, 150, 105, 0.3) !important;
+  color: var(--color-brand) !important;
+}
+
+/* 浅色主题下其他边框覆盖 */
+[data-theme="light"] .border-white\/20 {
+  border-color: rgba(30, 41, 59, 0.3) !important;
+}
+
+/* 浅色主题下视频卡片样式 */
+[data-theme="light"] .group.rounded-2xl {
+  border-color: rgba(30, 41, 59, 0.1) !important;
+  background-color: rgba(255, 255, 255, 0.5) !important;
+}
+
+[data-theme="light"] .group.rounded-2xl:hover {
+  background-color: rgba(255, 255, 255, 0.7) !important;
+}
+
+/* 浅色主题下视频卡片封面时间标签 - 保持白色文字和深色背景 */
+[data-theme="light"] .bg-black\/70 {
+  background-color: rgba(0, 0, 0, 0.7) !important;
+}
+
+/* 确保时间标签的文字在浅色主题下保持白色 */
+[data-theme="light"] .absolute.bottom-2.right-2.text-white {
+  color: white !important;
 }

Certains fichiers n'ont pas été affichés car il y a eu trop de fichiers modifiés dans ce diff