Przeglądaj źródła

更新index.html文件以添加mobile-web-app-capable meta标签,增强移动端应用体验;在package.json中添加svg-sprite-loader依赖,更新rspack配置以支持SVG图标的加载;删除不再使用的音频文件(bgm.mp3和sound.wav);在global.d.ts中扩展RequireContext类型以支持动态导入;在首页组件中添加功能介绍区,提升页面布局的可读性和功能性。

xbx 1 tydzień temu
rodzic
commit
8821e7321c

+ 31 - 16
config/rspack.base.config.ts

@@ -83,7 +83,20 @@ export const baseConfig = {
         loader: 'ignore-loader',
       },
       {
-        test: /\.svg/,
+        test: /\.svg$/,
+        include: [path.resolve(__dirname, 'src/assets/svg')],
+        use: [
+          {
+            loader: 'svg-sprite-loader',
+            options: {
+              symbolId: 'icon-[name]',
+            },
+          },
+        ],
+      },
+      {
+        test: /\.svg$/,
+        exclude: [path.resolve(__dirname, 'src/assets/svg')],
         type: 'asset/resource',
       },
       {
@@ -96,7 +109,7 @@ export const baseConfig = {
         use: [
           isProd() ? rspack.CssExtractRspackPlugin.loader : 'style-loader',
           'css-loader',
-          'postcss-loader'
+          'postcss-loader',
         ],
       },
       // 处理SCSS/SASS文件
@@ -132,20 +145,22 @@ export const baseConfig = {
       meta: {
         description: '声音AI平台 - 专业的声音处理与分析工具',
         keywords: '声音AI,音频处理,AI平台,语音识别',
-        author: 'Sound AI Team'
+        author: 'Sound AI Team',
       },
-      minify: isProd() ? {
-        removeComments: true,
-        collapseWhitespace: true,
-        removeRedundantAttributes: true,
-        useShortDoctype: true,
-        removeEmptyAttributes: true,
-        removeStyleLinkTypeAttributes: true,
-        keepClosingSlash: true,
-        minifyJS: true,
-        minifyCSS: true,
-        minifyURLs: true,
-      } : false,
+      minify: isProd()
+        ? {
+            removeComments: true,
+            collapseWhitespace: true,
+            removeRedundantAttributes: true,
+            useShortDoctype: true,
+            removeEmptyAttributes: true,
+            removeStyleLinkTypeAttributes: true,
+            keepClosingSlash: true,
+            minifyJS: true,
+            minifyCSS: true,
+            minifyURLs: true,
+          }
+        : false,
     }),
 
     AutoImportPlugin({
@@ -213,4 +228,4 @@ export const baseConfig = {
   },
 };
 
-export default baseConfig;
+export default baseConfig;

+ 1 - 0
index.html

@@ -7,6 +7,7 @@
 		<meta name="renderer" content="webkit" />
 		<meta name="format-detection" content="telephone=no,email=no,address=no" />
 		<meta name="apple-mobile-web-app-capable" content="yes" />
+		<meta name="mobile-web-app-capable" content="yes" />
 		<meta name="apple-mobile-web-app-status-bar-style" content="black" />
 		<meta name="theme-color" content="#ffffff" />
 	</head>

+ 1 - 0
package.json

@@ -36,6 +36,7 @@
     "postcss-loader": "^8.1.1",
     "qs": "^6.14.0",
     "query-string": "^9.2.2",
+    "svg-sprite-loader": "^6.0.11",
     "tailwindcss": "^4.1.11",
     "vue": "^3.5.13",
     "vue-router": "^4.5.1"

Plik diff jest za duży
+ 1748 - 0
pnpm-lock.yaml


BIN
src/assets/bgm.mp3


BIN
src/assets/sound.wav


Plik diff jest za duży
+ 1 - 0
src/assets/svg/reader.svg


+ 176 - 0
src/components/Icon/Index.vue

@@ -0,0 +1,176 @@
+<script setup lang="ts">
+import { computed, defineProps, withDefaults, h, onMounted } from 'vue';
+import * as AntdIcons from '@ant-design/icons-vue';
+import { hasIcon } from './register';
+
+defineOptions({
+  name: 'SvgIcon'
+});
+
+// 定义组件属性类型
+interface Props {
+  /** 图标名称 */
+  name: string;
+  /** 图标类型: 'iconfont' | 'antd' */
+  type?: 'iconfont' | 'antd';
+  /** 图标尺寸 (支持CSS单位) */
+  size?: string | number;
+  /** 图标颜色 */
+  color?: string;
+  /** 旋转动画 */
+  spin?: boolean;
+  /** 旋转角度 */
+  rotationDegree?: number;
+  /** 自定义类名 */
+  className?: string;
+}
+
+// 设置默认属性值
+const props = withDefaults(defineProps<Props>(), {
+  type: 'iconfont',
+  size: '1em',
+  color: 'currentColor',
+  spin: false,
+  rotationDegree: 360,
+  className: ''
+});
+
+// 检查图标是否存在
+const iconExists = computed(() => {
+  if (props.type === 'iconfont') {
+    console.log(hasIcon(props.name), 'hasIcon(props.name)')
+    return hasIcon(props.name);
+  } else if (props.type === 'antd') {
+    return !!AntdIcons[props.name as keyof typeof AntdIcons];
+  }
+
+  console.log(props.name, 'props.name',props.type)
+  return false;
+});
+
+// 动态解析Ant Design图标
+const antdIcon = computed(() => {
+  if (props.type !== 'antd') return null;
+  
+  const iconComponent = AntdIcons[props.name as keyof typeof AntdIcons] as any;
+  
+  if (!iconComponent) {
+    console.warn(`Ant Design icon not found: ${props.name}`);
+  }
+  
+  return iconComponent || null;
+});
+
+// 处理尺寸值,确保有单位
+const formattedSize = computed(() => {
+  if (typeof props.size === 'number') return `${props.size}px`;
+  return props.size;
+});
+
+// 组合类名
+const iconClasses = computed(() => [
+  props.className,
+  props.type === 'iconfont' ? 'iconfont-svg' : 'antd-icon',
+  { 'icon-spin': props.spin }
+]);
+
+// 自定义样式,包括旋转角度
+const customStyle = computed(() => {
+  const style: Record<string, any> = {
+    '--rotation-degree': `${props.rotationDegree}deg`,
+  };
+  
+  if (props.type === 'iconfont') {
+    style.width = formattedSize.value;
+    style.height = formattedSize.value;
+    style.color = props.color;
+  }
+  
+  return style;
+});
+
+// 在开发环境下检查图标是否存在
+onMounted(() => {
+  if (process.env.NODE_ENV !== 'production' && !iconExists.value) {
+    console.warn(`Icon not found: ${props.name} (type: ${props.type})`);
+  }
+});
+</script>
+
+<template>
+  <!-- Iconfont SVG图标 -->
+   
+  <svg 
+    v-if="type === 'iconfont' && iconExists"
+    :class="iconClasses"
+    :style="customStyle"
+    aria-hidden="true"
+  >
+    <use :xlink:href="`#icon-${name}`" />
+  </svg>
+  
+  <!-- Ant Design图标 -->
+  <component 
+    v-else-if="type === 'antd' && antdIcon"
+    :is="antdIcon"
+    v-bind="{ 
+      spin, 
+      style: { 
+        color, 
+        fontSize: formattedSize,
+        '--rotation-degree': `${rotationDegree}deg`
+      } 
+    }"
+  />
+  
+  <!-- 图标未找到时的回退 -->
+  <span 
+    v-else
+    class="icon-fallback"
+    :style="{ fontSize: formattedSize }"
+  >
+    ?
+  </span>
+</template>
+
+<style scoped>
+.iconfont-svg {
+  display: inline-block;
+  vertical-align: -0.15em;
+  fill: currentColor;
+  overflow: hidden;
+  transition: transform 0.3s ease;
+}
+
+.iconfont-svg:hover, .antd-icon:hover {
+  animation: icon-spin 1s infinite linear;
+}
+
+.antd-icon {
+  display: inline-block;
+  line-height: 1;
+  vertical-align: -0.125em;
+  transition: transform 0.3s ease;
+}
+
+.icon-spin {
+  animation: icon-spin 1s infinite linear !important;
+}
+
+@keyframes icon-spin {
+  from { transform: rotate(0deg); }
+  to { transform: rotate(var(--rotation-degree, 360deg)); }
+}
+
+.icon-fallback {
+  display: inline-block;
+  width: 1em;
+  height: 1em;
+  line-height: 1;
+  text-align: center;
+  background-color: #f0f0f0;
+  border-radius: 2px;
+  font-weight: bold;
+  color: #999;
+}
+</style>

+ 45 - 0
src/components/Icon/register.ts

@@ -0,0 +1,45 @@
+// 存储已加载的图标名称
+const loadedIcons: string[] = [];
+
+// 导入所有SVG图标并返回图标名称列表
+const importAllSvg = () => {
+  try {
+   
+    const requireContext = (require as any).context('@/assets/svg', false, /\.svg$/);
+    
+    // 获取所有图标的路径
+    const iconPaths = requireContext.keys() as string[];
+    
+    // 导入所有图标
+    iconPaths.forEach(path => {
+      requireContext(path);
+      
+      // 从路径中提取图标名称 (例如: ./icon-name.svg -> icon-name)
+      const iconName = path.match(/\.\/(.*)\.svg$/)?.[1] || '';
+      if (iconName) {
+        loadedIcons.push(iconName);
+      }
+    });
+    console.log(`成功加载 ${loadedIcons.length} 个SVG图标`);
+    return loadedIcons;
+  } catch (error) {
+    console.error('SVG图标导入错误:', error);
+    return [];
+  }
+};
+
+// 执行导入
+const icons = importAllSvg();
+
+// 导出图标列表,可用于图标预览或自动补全
+export default icons;
+
+// 检查图标是否存在
+export const hasIcon = (name: string): boolean => {
+  return loadedIcons.includes(name);
+};
+
+// 获取所有图标名称
+export const getIconNames = (): string[] => {
+  return [...loadedIcons];
+};

+ 34 - 0
src/components/index.ts

@@ -0,0 +1,34 @@
+import { App } from 'vue';
+import SvgIcon from './Icon/Index.vue';
+
+// 导入SVG图标
+import './Icon/register';
+
+// 需要全局注册的组件列表
+const components = {
+  SvgIcon,
+  // 在这里添加更多需要全局注册的组件
+  
+};
+
+/**
+ * 批量注册全局组件
+ * @param app Vue应用实例
+ */
+export function registerGlobalComponents(app: App) {
+  // 注册所有组件
+  Object.entries(components).forEach(([name, component]) => {
+    app.component(name, component);
+  });
+  
+  console.log(`已全局注册 ${Object.keys(components).length} 个组件`);
+}
+
+// 导出所有组件,方便按需导入
+export {
+  SvgIcon,
+  // 在这里导出更多组件
+};
+
+// 默认导出注册函数
+export default registerGlobalComponents; 

+ 6 - 0
src/main.ts

@@ -5,6 +5,9 @@ import App from './App.vue';
 import router from './router';
 import store from './store';
 
+// 导入全局组件注册函数
+import registerGlobalComponents from './components';
+
 // 设置 Vue 功能标志
 // @ts-expect-error - 全局 Vue 选项 API 标志未在类型定义中声明
 window.__VUE_OPTIONS_API__ = true;
@@ -15,6 +18,9 @@ window.__VUE_PROD_HYDRATION_MISMATCH_DETAILS__ = process.env.NODE_ENV !== 'produ
 
 const app = createApp(App);
 
+// 注册全局组件
+registerGlobalComponents(app);
+
 app.use(router);
 app.use(store);
 app.mount('#app');

+ 11 - 1
src/types/global.d.ts

@@ -2,4 +2,14 @@ declare global {
   type Nullable<T> = T | null
 
   type RefElement = Nullable<HTMLElement>
-} 
+} 
+
+
+// 扩展require.context类型
+declare interface RequireContext {
+  keys(): string[];
+  (id: string): any;
+  <T>(id: string): T;
+  resolve(id: string): string;
+  id: string;
+}

+ 19 - 0
src/types/webpack.d.ts

@@ -0,0 +1,19 @@
+// 为Webpack/Rspack API提供类型定义
+declare namespace __WebpackModuleApi {
+  interface RequireContext {
+    keys(): string[];
+    (id: string): any;
+    <T>(id: string): T;
+    resolve(id: string): string;
+    /** 用于标识上下文模块的id */
+    id: string;
+  }
+}
+
+interface NodeRequire {
+  context(
+    directory: string,
+    useSubdirectories?: boolean,
+    regExp?: RegExp
+  ): __WebpackModuleApi.RequireContext;
+} 

+ 1 - 1
src/views/home/components/topAnime.vue

@@ -1,5 +1,5 @@
 <template>
-  <section class="h-[100vh] flex items-center w-full">
+  <section class="h-[100vh] flex items-center w-full relative">
     <Motion
       class="top-anime flex flex-col items-center justify-center text-center w-full h-full"
       as="div"

+ 59 - 15
src/views/home/index.vue

@@ -4,17 +4,29 @@
     <HomeNav />
     <!-- 视差背景 -->
     <ParallaxBackground />
-   
-     <!-- 第一屏动画-->
-     <TopAnime />
 
-     <!--功能介绍区-->
-     <section class="h-[100vh] flex items-center w-full">
-      222222
-     </section>
+    <!-- 第一屏动画-->
+    <TopAnime />
 
-      <!-- 第一个section - 主视觉区域 -->
-     <!--  <section class="min-h-screen flex items-center">
+    <!--功能介绍区-->
+    <section class="h-[100vh] flex items-center w-full relative" id="product">
+      <div class="py-20 container mx-auto px-6">
+        <h2 class="text-4xl font-bold text-white mb-10">我们的特色</h2>
+        <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-8">
+          <div
+            v-for="item in products"
+            :key="item.title"
+            class="group bg-white/90 rounded-xl p-6 shadow-lg hover:shadow-xl transition-all duration-300 border border-gray-100 hover:border-blue-200 product-card"
+          >
+          <div class="text-blue-500 text-3xl mb-4 transition-all duration-300 group-hover:text-blue-600 group-hover:scale-110"></div>
+            <SvgIcon name="reader" type="iconfont" />
+          </div>
+        </div>
+      </div>
+    </section>
+
+    <!-- 第一个section - 主视觉区域 -->
+    <!--  <section class="min-h-screen flex items-center">
         <div class="container mx-auto px-6">
           <div class="text-4xl font-bold text-white mb-4">声音AI平台</div>
           <div class="text-xl text-white/80 max-w-2xl mb-8">
@@ -28,8 +40,8 @@
         </div>
       </section> -->
 
-      <!-- 第二个section - 半透明过渡区 -->
-     <!--  <section
+    <!-- 第二个section - 半透明过渡区 -->
+    <!--  <section
         class="min-h-screen flex items-center bg-white/10 backdrop-blur-sm"
       >
         <div class="container mx-auto px-6">
@@ -60,8 +72,8 @@
         </div>
       </section> -->
 
-      <!-- 第三个section - 白底部分 -->
-     <!--  <section class="min-h-screen bg-white relative z-20 pt-24">
+    <!-- 第三个section - 白底部分 -->
+    <!--  <section class="min-h-screen bg-white relative z-20 pt-24">
         <div class="container mx-auto px-6">
           <div class="max-w-4xl">
             <div class="text-3xl font-bold text-gray-800 mb-6">
@@ -104,8 +116,7 @@
           </div>
         </div>
       </section> -->
-    </div>
-  
+  </div>
 </template>
 <script setup lang="ts">
 import HomeNav from './components/nav.vue';
@@ -113,6 +124,31 @@ import ParallaxBackground from './components/ParallaxBackground.vue';
 import TopAnime from './components/topAnime.vue';
 import { onMounted, onUnmounted } from 'vue';
 
+const products = [
+  {
+    title: '智能语音合成',
+    description: '提供自然流畅的语音合成服务,支持多种语言和音色选择。',
+    icon: 'fa-solid fa-robot',
+  },
+  {
+    title: '语音克隆',
+    description:
+      '只需少量语音样本,即可克隆出与原始声音高度相似的个性化语音模型。',
+    icon: 'fa-solid fa-microphone',
+  },
+  {
+    title: '语音编辑',
+    description:
+      '强大的语音编辑工具,可调整语速、音调、情感等参数,满足专业需求。',
+    icon: 'fa-solid fa-sliders',
+  },
+  {
+    title: '批量处理',
+    description: '支持大规模文本批量转换为语音,提高工作效率,节省时间成本。',
+    icon: 'fa-solid fa-bolt',
+  },
+];
+
 // 页面进入时隐藏滚动条
 onMounted(() => {
   // 保存原始滚动条样式
@@ -137,5 +173,13 @@ onMounted(() => {
   &::-webkit-scrollbar {
     display: none;
   }
+
+  #product {
+    .product-card {
+      &:hover {
+        transform: translateY(-10px) rotate(1deg) scale(1.02);
+      }
+    }
+  }
 }
 </style>