Compare commits
	
		
			3 Commits
		
	
	
		
			5bcd94a3d0
			...
			e4702b877f
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					e4702b877f | ||
| 
						 | 
					26eff2fcb3 | ||
| 
						 | 
					3f80854bbc | 
@ -22,7 +22,8 @@
 | 
			
		||||
    "three": "^0.172.0",
 | 
			
		||||
    "vue": "^3.3.0",
 | 
			
		||||
    "vue-i18n": "^9.1.9",
 | 
			
		||||
    "vue-router": "^4.5.0"
 | 
			
		||||
    "vue-router": "^4.5.0",
 | 
			
		||||
    "register-service-worker": "^1.7.2"
 | 
			
		||||
  },
 | 
			
		||||
  "devDependencies": {
 | 
			
		||||
    "@rushstack/eslint-patch": "^1.6.1",
 | 
			
		||||
@ -44,6 +45,8 @@
 | 
			
		||||
    "sass-loader": "^13.3.3",
 | 
			
		||||
    "typescript": "^4.9.4",
 | 
			
		||||
    "vite": "4.1.4",
 | 
			
		||||
    "vue-tsc": "^1.0.24"
 | 
			
		||||
    "vite-plugin-pwa": "^0.17.5",
 | 
			
		||||
    "vue-tsc": "^1.0.24",
 | 
			
		||||
    "workbox-window": "^7.0.0"
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										19
									
								
								src/main.ts
									
									
									
									
									
								
							
							
						
						
									
										19
									
								
								src/main.ts
									
									
									
									
									
								
							@ -3,6 +3,25 @@ import App from './App.vue'
 | 
			
		||||
import 'ant-design-vue/dist/reset.css';
 | 
			
		||||
import Antd from 'ant-design-vue';
 | 
			
		||||
import router from './router/router';
 | 
			
		||||
import { registerSW } from 'virtual:pwa-register'
 | 
			
		||||
import { isPwa, showPwaInstallPrompt } from './utils/pwa';
 | 
			
		||||
 | 
			
		||||
const updateSW = registerSW({
 | 
			
		||||
    onNeedRefresh() {
 | 
			
		||||
        // 当发现新版本时,可以在这里提示用户
 | 
			
		||||
        console.log('发现新版本')
 | 
			
		||||
    },
 | 
			
		||||
    onOfflineReady() {
 | 
			
		||||
        // 当离线功能准备就绪时
 | 
			
		||||
        console.log('应用已经可以离线使用')
 | 
			
		||||
        if (isPwa()) {
 | 
			
		||||
            console.log('PWA 模式')
 | 
			
		||||
        } else {
 | 
			
		||||
            // showPwaInstallPrompt()
 | 
			
		||||
        }
 | 
			
		||||
    },
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
const app = createApp(App);
 | 
			
		||||
app.use(Antd);
 | 
			
		||||
app.use(router);
 | 
			
		||||
 | 
			
		||||
@ -2,23 +2,108 @@
 | 
			
		||||
  <div>
 | 
			
		||||
    <video
 | 
			
		||||
      id="video"
 | 
			
		||||
      src="/static/test.mp4"
 | 
			
		||||
      :src="mediaSource"
 | 
			
		||||
      style="display: none"
 | 
			
		||||
      loop
 | 
			
		||||
      crossorigin="anonymous"
 | 
			
		||||
    ></video>
 | 
			
		||||
    <canvas id="canvas"></canvas>
 | 
			
		||||
    <button @click="handleClick">播放</button>
 | 
			
		||||
    <div class="play-button" v-show="!isPlaying" @click="handleClick">
 | 
			
		||||
      <i class="play-icon"></i>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="upload-container">
 | 
			
		||||
      <input
 | 
			
		||||
        type="file"
 | 
			
		||||
        ref="fileInput"
 | 
			
		||||
        @change="handleFileUpload"
 | 
			
		||||
        accept="image/*,video/*"
 | 
			
		||||
        style="display: none"
 | 
			
		||||
      />
 | 
			
		||||
      <button class="upload-btn" @click="triggerFileUpload">
 | 
			
		||||
        上传图片/视频
 | 
			
		||||
      </button>
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup lang="ts">
 | 
			
		||||
import * as THREE from "three";
 | 
			
		||||
import { onMounted, onUnmounted } from "vue";
 | 
			
		||||
import { onMounted, onUnmounted, ref } from "vue";
 | 
			
		||||
 | 
			
		||||
const isPlaying = ref(false);
 | 
			
		||||
const fileInput = ref<HTMLInputElement | null>(null);
 | 
			
		||||
const mediaSource = ref("/static/test.mp4");
 | 
			
		||||
const isVideo = ref(true);
 | 
			
		||||
const texture = ref<THREE.VideoTexture | THREE.Texture | null>(null);
 | 
			
		||||
 | 
			
		||||
const triggerFileUpload = () => {
 | 
			
		||||
  fileInput.value?.click();
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const handleClick = () => {
 | 
			
		||||
  const video = document.getElementById("video") as HTMLVideoElement;
 | 
			
		||||
  video.play();
 | 
			
		||||
  if (isVideo.value) {
 | 
			
		||||
    const video = document.getElementById("video") as HTMLVideoElement;
 | 
			
		||||
    video.play();
 | 
			
		||||
    isPlaying.value = true;
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const createImageTexture = (url: string): Promise<THREE.Texture> => {
 | 
			
		||||
  return new Promise((resolve) => {
 | 
			
		||||
    const loader = new THREE.TextureLoader();
 | 
			
		||||
    loader.load(url, (texture) => {
 | 
			
		||||
      texture.minFilter = THREE.LinearFilter;
 | 
			
		||||
      texture.magFilter = THREE.LinearFilter;
 | 
			
		||||
      resolve(texture);
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const handleFileUpload = async (event: Event) => {
 | 
			
		||||
  const input = event.target as HTMLInputElement;
 | 
			
		||||
  const file = input.files?.[0];
 | 
			
		||||
 | 
			
		||||
  if (file) {
 | 
			
		||||
    const url = URL.createObjectURL(file);
 | 
			
		||||
    const video = document.getElementById("video") as HTMLVideoElement;
 | 
			
		||||
 | 
			
		||||
    // 先暂停当前播放
 | 
			
		||||
    video.pause();
 | 
			
		||||
    isPlaying.value = false;
 | 
			
		||||
 | 
			
		||||
    if (file.type.startsWith("video/")) {
 | 
			
		||||
      isVideo.value = true;
 | 
			
		||||
      // 更新视频源
 | 
			
		||||
      mediaSource.value = url;
 | 
			
		||||
 | 
			
		||||
      // 如果是视频,等待加载后再播放
 | 
			
		||||
      video.addEventListener(
 | 
			
		||||
        "loadeddata",
 | 
			
		||||
        async () => {
 | 
			
		||||
          try {
 | 
			
		||||
            texture.value = new THREE.VideoTexture(video);
 | 
			
		||||
            texture.value.minFilter = THREE.LinearFilter;
 | 
			
		||||
            texture.value.magFilter = THREE.LinearFilter;
 | 
			
		||||
            texture.value.format = THREE.RGBFormat;
 | 
			
		||||
            await video.play();
 | 
			
		||||
            isPlaying.value = true;
 | 
			
		||||
          } catch (err) {
 | 
			
		||||
            console.error("视频播放失败:", err);
 | 
			
		||||
            isPlaying.value = false;
 | 
			
		||||
          }
 | 
			
		||||
        },
 | 
			
		||||
        { once: true }
 | 
			
		||||
      );
 | 
			
		||||
    } else if (file.type.startsWith("image/")) {
 | 
			
		||||
      isVideo.value = false;
 | 
			
		||||
      mediaSource.value = "";
 | 
			
		||||
      // 创建图片纹理
 | 
			
		||||
      texture.value = await createImageTexture(url);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // 重置文件输入,允许重复上传相同文件
 | 
			
		||||
    input.value = "";
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// 创建场景和相机
 | 
			
		||||
@ -36,21 +121,30 @@ onMounted(async () => {
 | 
			
		||||
    canvas: document.querySelector("#canvas"),
 | 
			
		||||
  });
 | 
			
		||||
  renderer.setSize(window.innerWidth, window.innerHeight);
 | 
			
		||||
 | 
			
		||||
  // 获取视频元素
 | 
			
		||||
  const video = document.getElementById("video") as HTMLVideoElement;
 | 
			
		||||
 | 
			
		||||
  // 创建视频纹理
 | 
			
		||||
  const videoTexture = new THREE.VideoTexture(video);
 | 
			
		||||
  videoTexture.minFilter = THREE.LinearFilter;
 | 
			
		||||
  videoTexture.magFilter = THREE.LinearFilter;
 | 
			
		||||
  videoTexture.format = THREE.RGBFormat;
 | 
			
		||||
  // 创建初始视频纹理
 | 
			
		||||
  texture.value = new THREE.VideoTexture(video);
 | 
			
		||||
  texture.value.minFilter = THREE.LinearFilter;
 | 
			
		||||
  texture.value.magFilter = THREE.LinearFilter;
 | 
			
		||||
  texture.value.format = THREE.RGBFormat;
 | 
			
		||||
 | 
			
		||||
  // 播放视频
 | 
			
		||||
  try {
 | 
			
		||||
    await video.play();
 | 
			
		||||
  } catch (err) {
 | 
			
		||||
    console.error("视频播放失败:", err);
 | 
			
		||||
  }
 | 
			
		||||
  // 等待视频加载完成后播放
 | 
			
		||||
  video.addEventListener(
 | 
			
		||||
    "loadeddata",
 | 
			
		||||
    async () => {
 | 
			
		||||
      try {
 | 
			
		||||
        await video.play();
 | 
			
		||||
        isPlaying.value = true;
 | 
			
		||||
      } catch (err) {
 | 
			
		||||
        console.error("视频播放失败:", err);
 | 
			
		||||
        isPlaying.value = false;
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    { once: true }
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  // 创建平面几何体
 | 
			
		||||
  const geometry = new THREE.PlaneGeometry(10, 10, 200, 200);
 | 
			
		||||
@ -76,7 +170,7 @@ onMounted(async () => {
 | 
			
		||||
              vec3 pos = position;
 | 
			
		||||
              
 | 
			
		||||
              // 基于位置和时间生成随机值
 | 
			
		||||
              float rand = random(pos.xy) * 2.0 - 1.0;
 | 
			
		||||
              float rand = random(pos.xy) * 1.01 - 0.01;
 | 
			
		||||
              vRandom = rand;
 | 
			
		||||
              
 | 
			
		||||
              // 计算打散效果
 | 
			
		||||
@ -114,29 +208,49 @@ onMounted(async () => {
 | 
			
		||||
          varying float vRandom;
 | 
			
		||||
          uniform float time;
 | 
			
		||||
 | 
			
		||||
          // 颜色调整辅助函数
 | 
			
		||||
          vec3 adjustColor(vec3 color) {
 | 
			
		||||
              // 增加对比度
 | 
			
		||||
              vec3 contrast = (color - 0.5) * 1.4 + 0.5;
 | 
			
		||||
              
 | 
			
		||||
              // 计算亮度
 | 
			
		||||
              float luminance = dot(contrast, vec3(0.299, 0.587, 0.114));
 | 
			
		||||
              
 | 
			
		||||
              // 增加饱和度
 | 
			
		||||
              vec3 saturated = mix(vec3(luminance), contrast, 1.3);
 | 
			
		||||
              
 | 
			
		||||
              // 稍微压暗高光
 | 
			
		||||
              saturated = pow(saturated, vec3(1.1));
 | 
			
		||||
              
 | 
			
		||||
              return saturated;
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          void main() {
 | 
			
		||||
              // 减小马赛克大小
 | 
			
		||||
              // 马赛克大小
 | 
			
		||||
              float mosaicSize = 1000.0;
 | 
			
		||||
              vec2 mosaicUv = floor(vUv * mosaicSize) / mosaicSize;
 | 
			
		||||
              
 | 
			
		||||
              // 减小抖动效果
 | 
			
		||||
              float jitter = sin(time * 2.0 + vRandom * 6.28) * 0.000001;
 | 
			
		||||
              float jitter = sin(time * 2.0 + vRandom * 1.28) * 0.001;
 | 
			
		||||
              mosaicUv += vec2(jitter);
 | 
			
		||||
 | 
			
		||||
              vec4 texColor = texture2D(mainTexture, mosaicUv);
 | 
			
		||||
              
 | 
			
		||||
              // 调整颜色
 | 
			
		||||
              vec3 adjustedColor = adjustColor(texColor.rgb);
 | 
			
		||||
              
 | 
			
		||||
              // 调整扭曲和淡出效果
 | 
			
		||||
              float distortionFactor = 1.0 - vDist * 0.05;
 | 
			
		||||
              float alpha = 0.9 + 0.1 * sin(time * 2.0 + vRandom * 6.28);
 | 
			
		||||
              float distortionFactor = 1.0 - vDist * 0.005;
 | 
			
		||||
              float alpha = 0.99 + 0.01 * sin(time * 2.0 + vRandom * 6.28);
 | 
			
		||||
              
 | 
			
		||||
              // 边缘淡出效果
 | 
			
		||||
              float edgeFade = 1.0 - length(gl_PointCoord - vec2(0.5)) * 2.0;
 | 
			
		||||
              edgeFade = smoothstep(0.0, 0.5, edgeFade);
 | 
			
		||||
 | 
			
		||||
              gl_FragColor = vec4(texColor.rgb * distortionFactor, texColor.a * alpha * edgeFade);
 | 
			
		||||
              gl_FragColor = vec4(adjustedColor, texColor.a * alpha * edgeFade);
 | 
			
		||||
          }`,
 | 
			
		||||
    uniforms: {
 | 
			
		||||
      mainTexture: { value: videoTexture },
 | 
			
		||||
      mainTexture: { value: texture.value },
 | 
			
		||||
      mouse: { value: new THREE.Vector2(0, 0) },
 | 
			
		||||
      radius: { value: 0.5 },
 | 
			
		||||
      time: { value: 0 },
 | 
			
		||||
@ -166,8 +280,11 @@ onMounted(async () => {
 | 
			
		||||
  function animate() {
 | 
			
		||||
    requestAnimationFrame(animate);
 | 
			
		||||
 | 
			
		||||
    if (video.readyState === video.HAVE_ENOUGH_DATA) {
 | 
			
		||||
      videoTexture.needsUpdate = true;
 | 
			
		||||
    if (texture.value) {
 | 
			
		||||
      if (isVideo.value && video.readyState === video.HAVE_ENOUGH_DATA) {
 | 
			
		||||
        (texture.value as THREE.VideoTexture).needsUpdate = true;
 | 
			
		||||
      }
 | 
			
		||||
      material.uniforms.mainTexture.value = texture.value;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    material.uniforms.time.value += 0.016;
 | 
			
		||||
@ -185,6 +302,16 @@ onMounted(async () => {
 | 
			
		||||
 | 
			
		||||
    renderer.setSize(width, height);
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  // 监听视频暂停事件
 | 
			
		||||
  video.addEventListener("pause", () => {
 | 
			
		||||
    isPlaying.value = false;
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  // 监听视频播放事件
 | 
			
		||||
  video.addEventListener("play", () => {
 | 
			
		||||
    isPlaying.value = true;
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
// 组件卸载时清理
 | 
			
		||||
@ -193,6 +320,17 @@ onUnmounted(() => {
 | 
			
		||||
  video.pause();
 | 
			
		||||
  video.src = "";
 | 
			
		||||
  video.load();
 | 
			
		||||
 | 
			
		||||
  // 清理创建的对象URL
 | 
			
		||||
  if (mediaSource.value.startsWith("blob:")) {
 | 
			
		||||
    URL.revokeObjectURL(mediaSource.value);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // 清理纹理
 | 
			
		||||
  if (texture.value) {
 | 
			
		||||
    texture.value.dispose();
 | 
			
		||||
    texture.value = null;
 | 
			
		||||
  }
 | 
			
		||||
});
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
@ -202,4 +340,58 @@ canvas {
 | 
			
		||||
  height: 100vh;
 | 
			
		||||
  display: block;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.play-button {
 | 
			
		||||
  position: fixed;
 | 
			
		||||
  top: 50%;
 | 
			
		||||
  left: 50%;
 | 
			
		||||
  transform: translate(-50%, -50%);
 | 
			
		||||
  width: 80px;
 | 
			
		||||
  height: 80px;
 | 
			
		||||
  background: rgba(0, 0, 0, 0.6);
 | 
			
		||||
  border-radius: 50%;
 | 
			
		||||
  cursor: pointer;
 | 
			
		||||
  display: flex;
 | 
			
		||||
  align-items: center;
 | 
			
		||||
  justify-content: center;
 | 
			
		||||
  transition: all 0.3s;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.play-button:hover {
 | 
			
		||||
  background: rgba(0, 0, 0, 0.8);
 | 
			
		||||
  transform: translate(-50%, -50%) scale(1.1);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.play-icon {
 | 
			
		||||
  width: 0;
 | 
			
		||||
  height: 0;
 | 
			
		||||
  border-style: solid;
 | 
			
		||||
  border-width: 20px 0 20px 30px;
 | 
			
		||||
  border-color: transparent transparent transparent #ffffff;
 | 
			
		||||
  margin-left: 8px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.upload-container {
 | 
			
		||||
  position: fixed;
 | 
			
		||||
  bottom: 20px;
 | 
			
		||||
  left: 50%;
 | 
			
		||||
  transform: translateX(-50%);
 | 
			
		||||
  z-index: 1000;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.upload-btn {
 | 
			
		||||
  background: rgba(0, 0, 0, 0.6);
 | 
			
		||||
  color: white;
 | 
			
		||||
  border: none;
 | 
			
		||||
  padding: 10px 20px;
 | 
			
		||||
  border-radius: 20px;
 | 
			
		||||
  cursor: pointer;
 | 
			
		||||
  font-size: 14px;
 | 
			
		||||
  transition: all 0.3s;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.upload-btn:hover {
 | 
			
		||||
  background: rgba(0, 0, 0, 0.8);
 | 
			
		||||
  transform: scale(1.05);
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
 | 
			
		||||
@ -2,13 +2,43 @@ import { createRouter, createWebHashHistory } from "vue-router";
 | 
			
		||||
import Index from "../pages/file/index.vue";
 | 
			
		||||
import Voice from "../pages/voice/webrtcVoice.vue";
 | 
			
		||||
import PointShader from "../pages/pointShader/pointShader.vue";
 | 
			
		||||
import { disablePwaPrompt, enablePwaPrompt } from '../utils/pwa';
 | 
			
		||||
 | 
			
		||||
const router = createRouter({
 | 
			
		||||
    history: createWebHashHistory(),
 | 
			
		||||
    routes: [
 | 
			
		||||
        { path: "/", component: Index },
 | 
			
		||||
        { path: "/voice", component: Voice },
 | 
			
		||||
        { path: "/pointShader", component: PointShader },
 | 
			
		||||
        {
 | 
			
		||||
            path: "/",
 | 
			
		||||
            component: Index,
 | 
			
		||||
            meta: {
 | 
			
		||||
                showPwaPrompt: true
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            path: "/voice",
 | 
			
		||||
            component: Voice,
 | 
			
		||||
            meta: {
 | 
			
		||||
                showPwaPrompt: false
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            path: "/pointShader",
 | 
			
		||||
            component: PointShader,
 | 
			
		||||
            meta: {
 | 
			
		||||
                showPwaPrompt: false
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
    ],
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
// 路由守卫控制 PWA 提示
 | 
			
		||||
router.beforeEach((to, from, next) => {
 | 
			
		||||
    if (to.meta.showPwaPrompt) {
 | 
			
		||||
        enablePwaPrompt();
 | 
			
		||||
    } else {
 | 
			
		||||
        disablePwaPrompt();
 | 
			
		||||
    }
 | 
			
		||||
    next();
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
export default router;
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										11
									
								
								src/types/virtual-pwa-register.d.ts
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								src/types/virtual-pwa-register.d.ts
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@ -0,0 +1,11 @@
 | 
			
		||||
declare module 'virtual:pwa-register' {
 | 
			
		||||
    export interface RegisterSWOptions {
 | 
			
		||||
        immediate?: boolean
 | 
			
		||||
        onNeedRefresh?: () => void
 | 
			
		||||
        onOfflineReady?: () => void
 | 
			
		||||
        onRegistered?: (registration: ServiceWorkerRegistration | undefined) => void
 | 
			
		||||
        onRegisterError?: (error: any) => void
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    export function registerSW(options?: RegisterSWOptions): (reloadPage?: boolean) => Promise<void>
 | 
			
		||||
} 
 | 
			
		||||
							
								
								
									
										46
									
								
								src/utils/pwa.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								src/utils/pwa.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,46 @@
 | 
			
		||||
// PWA 安装状态
 | 
			
		||||
let deferredPrompt: any = null;
 | 
			
		||||
 | 
			
		||||
// 控制是否显示安装提示
 | 
			
		||||
let showInstallPrompt = true;
 | 
			
		||||
 | 
			
		||||
// 监听 beforeinstallprompt 事件
 | 
			
		||||
window.addEventListener('beforeinstallprompt', (e) => {
 | 
			
		||||
    if (!showInstallPrompt) {
 | 
			
		||||
        e.preventDefault();
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
    deferredPrompt = e;
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
// 检查是否为 PWA 模式
 | 
			
		||||
export const isPwa = () => {
 | 
			
		||||
    return window.matchMedia('(display-mode: standalone)').matches ||
 | 
			
		||||
        (window.navigator as any).standalone ||
 | 
			
		||||
        document.referrer.includes('android-app://');
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// 禁用 PWA 安装提示
 | 
			
		||||
export const disablePwaPrompt = () => {
 | 
			
		||||
    showInstallPrompt = false;
 | 
			
		||||
    if (deferredPrompt) {
 | 
			
		||||
        deferredPrompt = null;
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// 启用 PWA 安装提示
 | 
			
		||||
export const enablePwaPrompt = () => {
 | 
			
		||||
    showInstallPrompt = true;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// 手动触发 PWA 安装提示
 | 
			
		||||
export const showPwaInstallPrompt = async () => {
 | 
			
		||||
    if (!deferredPrompt) {
 | 
			
		||||
        console.log('No PWA installation prompt available');
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
    deferredPrompt.prompt();
 | 
			
		||||
    const { outcome } = await deferredPrompt.userChoice;
 | 
			
		||||
    deferredPrompt = null;
 | 
			
		||||
    return outcome;
 | 
			
		||||
}; 
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								static/icon256.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								static/icon256.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 5.2 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								static/icon64.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								static/icon64.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 1.1 KiB  | 
@ -1,13 +1,43 @@
 | 
			
		||||
import { defineConfig } from 'vite'
 | 
			
		||||
import basicSsl from '@vitejs/plugin-basic-ssl'
 | 
			
		||||
import vue from '@vitejs/plugin-vue'
 | 
			
		||||
import { VitePWA } from 'vite-plugin-pwa'
 | 
			
		||||
// https://vitejs.dev/config/
 | 
			
		||||
export default defineConfig({
 | 
			
		||||
  build: {
 | 
			
		||||
    // 开发阶段启用源码映射:https://uniapp.dcloud.net.cn/tutorial/migration-to-vue3.html#需主动开启-sourcemap
 | 
			
		||||
    sourcemap: process.env.NODE_ENV === 'development',
 | 
			
		||||
  },
 | 
			
		||||
  plugins: [vue(), basicSsl()],
 | 
			
		||||
  plugins: [
 | 
			
		||||
    vue(),
 | 
			
		||||
    basicSsl(),
 | 
			
		||||
    VitePWA({
 | 
			
		||||
      registerType: 'autoUpdate',
 | 
			
		||||
      manifest: {
 | 
			
		||||
        name: 'P2P Explorer',
 | 
			
		||||
        short_name: 'P2P Explorer',
 | 
			
		||||
        description: 'P2P文件传输与浏览工具',
 | 
			
		||||
        theme_color: '#f5f5f5',//米色
 | 
			
		||||
        background_color: '#f5f5f5',
 | 
			
		||||
        icons: [
 | 
			
		||||
          {
 | 
			
		||||
            src: '/static/icon64.png',
 | 
			
		||||
            sizes: '64x64',
 | 
			
		||||
            type: 'image/png'
 | 
			
		||||
          },
 | 
			
		||||
          {
 | 
			
		||||
            src: '/static/icon256.png',
 | 
			
		||||
            sizes: '256x256',
 | 
			
		||||
            type: 'image/png'
 | 
			
		||||
          }
 | 
			
		||||
        ]
 | 
			
		||||
      },
 | 
			
		||||
      workbox: {
 | 
			
		||||
        globPatterns: ['**/*.{js,css,html,ico,png,svg,webp}'],
 | 
			
		||||
        runtimeCaching: []
 | 
			
		||||
      }
 | 
			
		||||
    })
 | 
			
		||||
  ],
 | 
			
		||||
  server: {
 | 
			
		||||
    host: '0.0.0.0',
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user