当前位置: 首页 > news >正文

vue+webpack5(高级配置)

项目地址
基础配置可查看文档

    • 1、devtool 配置 (找到报错位置)
    • 2、优化打包速度
    • 3、oneOf 每个文件只被一个loader处理
    • 4、 include/exclude 处理某些文件或者排除某些文件
    • 5、 cache 缓存 (提升后面几次的打包速度)
    • 6、 多进程打包
    • 7、减少代码体积 Tree Shaking
    • 8、压缩图片
    • 9、多入口
    • 10、 多入口想提取公共模块,这个时候就用到了代码分割
    • 11、chunk统一命名
    • 12、 Preload和Prefetch
    • 13、配置缓存,文件发生变化时,只有main 和runtime.js会重新打包,其他文件不变
    • 14、 core-js
    • 15、PWA

总结

我们从 4 个角度对 webpack 和代码进行了优化:

  • 1.提升开发体验
    。使用 sourceMap让开发或上线时代码报错能有更加准确的错误提示。
  • 2.提升 webpack 提升打包构建速度
    。使用 HotmduleReplacement让开发时只重新编译打包更新变化了的代码,不变的代码使用缓存,从而使更新速度更快。
    。使用 one0f让资源文件一旦被某个 loader 处理了,就不会继续遍历了,打包速度更快。
    。使用 Include/Exclude排除或只检测某些文件,处理的文件更少,速度更快。
    。使用cache 对 eslint 和 babel 处理的结果进行缓存,让第二次打包速度更快。
    。使用 Thead 多进程处理 esint和 babel 任务,速度更快。(需要注意的是,进程启动通信都有开销的,要在比较多代码处理时使用才有效
    果)
  • 3.减少代码体积
    。使用 Tree shaking剔除了没有使用的多余代码,让代码体积更小。
    。使用 @babel/plugin-transform-runtime 插件对 babel 进行处理,让辅助代码从中引入,而不是每个文件都生成辅助代码,从而体积更小。。使用 image miniizer 对项目中图片进行压缩,体积更小,请求速度更快。(需要注意的是,如果项目中图片都是在线链接,那么就不需要了。本地项目静态图片才需要进行压缩。)
  • 4.优化代码运行性能
    。使用 code split 对代码进行分割成多个i5文件,从而便单个文件体积更小,并行加载is速度更快。并通过 import 动态导入语法进行按需加载,从而达到需要使用时才加载该资源,不用时不加载资源。
    使用 Preload/Prefetch,对代码进行提前加载,等未来需要使用时就能直接使用,从而用户体验更好。使用 Network cache能对输出资源文件进行更好的命名,将来好做缓存,从而用户体验更好。
    。便用 core-js 对js 进行兼容性处理,让我们代码能运行在低版本浏览器。
    。使用PA能让代码离线也能访问,从而提升用户体验。

1、devtool 配置 (找到报错位置)

SourceMap(源代码映射)是一个用来生成源代码与构建后代码一一映射的文件的方案。它会生成一个 xx.map 文件,里面包含源代码和构建后代码每一行、每一列的映射关系。当构建后代码出错了,会通过 xxx.map 文件,从构建后代码出错位置找到映射后源代码出错位置,从而让浏览器提示源代码文件出错位置,帮助我们更快的找到错误根源。
开发模式用

  devtool: 'cheap-module-source-map',
  • 优点:打包编译速度快,只包含行映射
  • 缺点:没有列映射
    生产模式用
devtool: 'source-map',
  • 优点:包含行/列映射
  • 缺点:打包编译速度更慢
    build: slowest rebuild: slowest

2、优化打包速度(HMR)

HMR (HotModuleReplacement),即热模块替换,是 webpack 的一个功能,它可以在运行时更新模块,而无需完全刷新整个页面。

  devServer: {
    ...
    hot:true, // 提供HMR功能,只更新某个模块,没有替换整个项目
  },

在模块中判断

if(module.hot){
  module.hot.accept('./xxx')
}

实际开发并不需要上述这样,直接配置loader,以vule为例 vue-loader,生产模式不需要

3、oneOf 每个文件只被一个loader处理

未配置时,一个loader解析之后,其他loader还要解析,配置oneOf之后,每个文件一个loader解析之后就不往下解析了

  module: {
    rules: [
      // oneOf 不支持 vue-loader ,要单独拿出来
      {
        test: /\.vue$/,
        loader: "vue-loader",
      },
      // 每个文件只有一个loader配置处理
      {
        oneOf: [
        {
          test: /\.(gif|png|jpe?g)$/i,
          type: "asset",
          parser: {
            dataUrlCondition: {
              // 小于10kb的图片转成base64,减少请求数量
              // 缺点:体积会大一点
              maxSize: 10 * 1024, // 小于10kb
            },
          },
          generator: {
            // 输出图片的名称
            filename: "imgs/[name].[contenthash:8][ext]",
          },
        },
       ]
      }
    ],
  },
  • 报错 [VueLoaderPlugin Error] vue-loader 15 currently does not support vue rules with oneof
    解决方案:将vue-loader 移到配置外面,单独拿出来

4、 include/exclude 处理某些文件或者排除某些文件

 module: {
   rules: [
   {
            test: /\.(js|ts|jsx|tsx)$/,
            include: path.resolve(__dirname, 'src'),
            // exclude: /node_modules/,
            // loader:"babel-loader",
            loader: 'esbuild-loader',
            generator: {
              target: 'es2015'
            }
          },
   ]
 }

include和exclude的只写一个即可

 plugins: [
  new ESLintPlugin({
      // 配置哪些目录需要检查
      context: path.resolve(__dirname, './src'),
      exclude: 'node_modules',// 不写 默认也有
    }),
 ]

5、 cache 缓存 (提升后面几次的打包速度)

对eslint检查和babel编译进行缓存,提升打包速度,cacheDirectory 是否开启缓存。

       {
            test: /\.js$/,
            exclude: /node_modules/,
            use: {
              loader: 'babel-loader',
              options: {
                presets: ['@babel/preset-env'],
                cacheDirectory: true, // 开启babel缓存
                cacheCompression: false,// 关闭缓存文件压缩
              },
            },
          },

eslint 开启缓存

    new ESLintPlugin({
      // 配置哪些目录需要检查
      context: path.resolve(__dirname, './src'),
      exclude: 'node_modules',// 不写 默认也有
      cache: true,// 开启缓存
      cacheLocation: path.resolve(__dirname, '../node_modules/.cache/eslintcache'),
    }),

6、 多进程打包

const os = require('os');
const threads = os.cpus().length;
  • 下载 thread-loader
npm install thread-loader --D

在loader配置多进程

  const TerserWebpackPlugin = require("terser-webpack-plugin");
module.exports = {
...
  module: {
    rules: [
       {
          test: /\.js$/,
          exclude: /node_modules/,
          use: [{
            loader: 'thread-loader',
            options: {
              workers: threads, // 开启几个子进程去完成
            }
          },
          {
            loader: 'babel-loader',
            options: {
              presets: ['@babel/preset-env'],
              cacheDirectory: true, // 开启babel缓存
              cacheCompression: false,// 关闭缓存文件压缩
            },
          },] 
        },
        ]}
     }

在eslint中开启多进程

  new ESLintPlugin({
      // 配置哪些目录需要检查
      context: path.resolve(__dirname, './src'),
       cache: true,// 开启缓存
       cacheLocation: path.resolve(__dirname, '../node_modules/.cache/eslintcache'),
       threads,
    }),

引入压缩的插件

const TerserWebpackPlugin = require("terser-webpack-plugin");

在plugins中配置

   new TerserWebpackPlugin({
      parallel: threads, // 开启多进程
      extractComments: false, // 移除注释
      terserOptions: {
        compress: {
          pure_funcs: ['console.log'], // 移除console.log
        },
      },
    }),

在optimization添加压缩配置

  // 用于处理压缩
  optimization: {
   ...
    minimizer: [
      // CssMinimizerPlugin、TerserWebpackPlugin 也可以放在plugins中
      new CssMinimizerPlugin(),
      new TerserWebpackPlugin({
        parallel: threads, // 开启多进程
        extractComments: false, // 移除注释
        terserOptions: {
          compress: {
            pure_funcs: ['console.log'], // 移除console.log
          },
        },
      }),
    ],
  },

7、减少代码体积 Tree Shaking

减少babel体积
禁用babel自动对每个文件的runtime注入,@babel/plugin-transform-runtime 优化代码转换的运行时行为,通过复用辅助函数和减少全局污染来提升代码质量

  • 下载
npm i @babel/plugin-transform-runtime --D 

在babel中配置

          {
            test: /\.js$/,
            exclude: /node_modules/,
            use: [{
              loader: 'thread-loader',
              options: {
                workers: threads, // 开启几个子进程去完成
              }
            },
            {
              loader: 'babel-loader',
              options: {
                presets: ['@babel/preset-env'],
                cacheDirectory: true, // 开启babel缓存
                cacheCompression: false,// 关闭缓存文件压缩
                plugins: ['@babel/plugin-transform-runtime'],
              },
            },]
          },

8、压缩图片

下载包

npm i image-minimizer-webpack-plugin --D

无损压缩(包下载不下来)

npm install imagemin-gifsicle  imagemin-jpegtran imagemin-optipng imagemin-svgo --D

有损压缩

npm install imagemin-gifsicle imagemin-jpegtran imagemin-pngquant imagemin-svgo --D

包不好下

  optimization: {
   ...
    minimizer: [
    ...
      new ImageMinimizerPlugin({
        minimizer: {
          implementation: ImageMinimizerPlugin.imageminGenerate,
          options: {
            plugins: [
              ['gifsicle', { interlaced: true }],
              ['jpegtran', { progressive: true }],
              ['optipng', { optimizationLevel: 5 }],
              [
                'svgo',
                {
                  plugins: [
                    'preset-default',
                    "prefixIds",
                    {
                      name: "sortAttrs",
                      params: {
                        xmlnsOrder: "alphabetical"
                      }
                    }

                  ]
                },
              ],
            ]
          }
        }
      })
    ],
  },

9、多入口

const HtmlWebpackPlugin=require("html-webpack-plugin");
module.exports ={

  // entry:'./src/main.js',// 只有一个入口文件,单入口
  entry:{ // 有多个入口文件,多入口
      app:"./src/app.js"
      main:"./src/main.js"
    }
    output:{
      path: path.resolve( dirname,"dist"),
      filename: "[name].js"// webpack命名方式,[name]以文件名自己命名filename 
  }
  plugins:[
    new HtmlWebpackPlugin({template:path.resolve( dirname,"public/index.html")})
  ],
  mode:"production"
}

10、 多入口想提取公共模块,这个时候就用到了代码分割

  optimization: {
    minimize: true, // 强制启用压缩
    // 对代码进行分割
    splitChunks: {
      chunks: 'all',
      // 以下是默认值
      // minsize:20000,//分割代码最小的大小
      // minRemainingsize:0,//类似于minsize,最后确保提取的文件大小不能为0// minChunks:1,//至少被引用的次数,满足条件才会代码分割
      // maxAsyncRequests:3e,/按需加载时并行加载的文件的最大数量
      // maxInitialRequests:30,//入口js文件最大并行请求数量
      // enforcesizeThreshold:50000,
      // 超过50kb一定会单独打包(此时会忽略minRemainingsize、maxAsyncRequests、maxInitialRegquests)
      //cacheGroups:{// 组,哪些模块要打包到一个组)
      // defaultVendors:{//组名
      // test:/[\V/]node_modules[\\/]/,//需要打包到一起的模块
      // priority:-10,//权重(越大越高)
      //  reuseExistingchunk:true,//如果当前 chunk 包含已从主 bundle 中拆分出的模块,则它将被重用,
      // 而不是生成新的模块
      // },
      // default:{ // 其他没有写的配置会使用上面的默认值
      // {
      //   minChunks:2,//这里的minChunks权重更大
      //   priority: -20,
      //   reuseExistingchunk: true,
      // }

    },
    // runtimeChunk: 'single',
    minimizer: [
      new CssMinimizerPlugin(),
    ]
  },

11、chunk统一命名

  output: {
    clean: true, // 清理 /dist 文件夹
    // filename: "js/main.js", // 打包后的文件名称
    filename: "js/[name].[contenthash:8].js", // 打包后的文件名称
    // 打包输出的其他文件名称
    chunkFilename: "js/[name].[contenthash:8].chunk.js",
    path: path.resolve(__dirname, "../dist"), // 打包后的目录
    
    // 图片 等字体通过type: asset处理资源命名方式
    assetModuleFilename: 'media/[name].[contenthash:8][ext][query]',
  },
  new MiniCssExtractPlugin({
      filename: "css/[name].[contenthash:8].css"
    }),

添加 移除loader中字体、媒体文件的命名规则

  // generator: {
            //   filename: "fonts/[name].[contenthash:8][ext][query]"
            // }

12、 Preload和Prefetch(兼容性差)

  • Preload :告诉浏览器立即加载资源。
  • Prefetch :告诉浏览器在空闲时才开始加载资源。
    它们共同点:
    。都只会加载资源,并不执行。
    。都有缓存。
    它们区别:
    Preload 加载优先级高,Prefetch 加载优先级低
    只能加载当前页面需要使用的资源, Prefetch 可以加载当前页面资源,也可以加载下一个页面需要使用的资源。reloa
    总结:
    。当前页面优先级高的资源用 Preload 加载。
    。下一个页面需要使用的资源用Prefetch 加载
    它们的问题:兼容性较差,
    。我们可以去 CanlUseB 网站查询 API 的兼容性问题Preload 相对于 Prefetch 兼容性好一点。

点击查看兼容

13、配置缓存,文件发生变化时,只有main 和runtime.js会重新打包,其他文件不变

runtimeChunk:{
  name:(entrypoint)=>runtime~${entrypoint.name}.js`
}

14、 core-js

过去我们使用 babel 对js 代码进行了兼容性处理,其中使用@babe/preset-env 智能预设来处理兼容性问题它能将 ES6 的一些语法进行编译转换,比如箭头函数、点点点运算符等。但是如果是 async 函数、promise 对象、数组的一些方法(includes)等,它没办法处理
所以此时我们js 代码仍然存在兼容性问题,一旦遇到低版本浏览器会直接报错。所以我们想要将js 兼容性问题彻底解决
core-js 是专门用来做ES6 以及以上API的 polyfill。
polyfi11 翻译过来叫做垫片/补丁。就是用社区上提供的一段代码,让我们在不兼容某些新特性的浏览器上,使用该新特性。
例如:Promise 就没有被打包

return new Promise(resolve => {
        resolve("成功");
      });

解决方案:

  • 通过在代码中按需引入core-js的Promise
  • 在babel.config.js中配置core-js
 module.exports = {
  presets: [
    '@babel/preset-env',
    {
      useBuiltIns: "usage", //按需加载自动引入
      corejs: 3,
    }
  ]
};

15、PWA

开发 Web App 项目,项目一旦处于网络离线情况,就没法访问了。我们希望给项目提供离线体验。
渐进式网络应用程序(progressiveweb application·PWA):是一种可以提供类似于 native app(原生应用程序) 体验的 Web App 的技术。
其中最重要的是,在 离线(offline)时应用程序能够继续运行功能。内部通过 Service Workers 技术实现的。

  • 下载
npm install workbox-webpack-plugin --save-dev

npm run build
现在你可以看到,生成了两个额外的文件:service-worker.js 和名称冗长的 precache-manifest.b5ca1c555e832d6fbf9462efd29d27eb.js。service-worker.js 是 Service Worker 文件,precache-manifest.b5ca1c555e832d6fbf9462efd29d27eb.js 是 service-worker.js 引用的文件,所以它也可以运行。你本地生成的文件可能会有所不同,但是应该会有一个 service-worker.js 文件。

  • 注册 Service Worker
 if ('serviceWorker' in navigator) {
   window.addEventListener('load', () => {
     navigator.serviceWorker.register('/service-worker.js').then(registration => {
       console.log('SW registered: ', registration);
     }).catch(registrationError => {
       console.log('SW registration failed: ', registrationError);
     });
   });
 }

启动会发现未成功,这个时候要安装server
npm i server -g
启动
sever dist
[具体可参考](https://www.webpackjs.com/guides/progressive-web-application/#adding-workbox)

完整配置代码

const chalk = require('chalk');
const path = require("path");
const os = require('os');
const threads = os.cpus().length;

const { VueLoaderPlugin } = require("vue-loader");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const ProgressBarPlugin = require('progress-bar-webpack-plugin');
const ESLintPlugin = require('eslint-webpack-plugin');
const { DefinePlugin } = require('webpack');
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
const TerserWebpackPlugin = require("terser-webpack-plugin");
const ImageMinimizerPlugin = require('image-minimizer-webpack-plugin');

// 用来处理获取的样式
function getStyleLoaders(pre) {
  return [MiniCssExtractPlugin.loader,
    "css-loader",
  {
    loader: "postcss-loader",
    options: {
      postcssOptions: {
        // plugins: [["autoprefixer"]],
        plugins: ['postcss-preset-env'],//能解决大多数兼容性问题
      },
    },
  }, pre].filter(Boolean);
}

module.exports = {
  mode: "production", // 开发模式
  entry: path.resolve(__dirname, "../src/main.js"), // 入口文件 相对路径
  // web server
  devtool: 'source-map',
  output: {
    clean: true, // 清理 /dist 文件夹
    // filename: "js/main.js", // 打包后的文件名称
    filename: "js/[name].[contenthash:8].js", // 打包后的文件名称
    path: path.resolve(__dirname, "../dist"), // 打包后的目录
    // 图片 等字体通过type: asset处理资源命名方式
    assetModuleFilename: 'media/[name].[contenthash:8][ext][query]',
  },
  cache: {
    type: 'filesystem',
    allowCollectingMemory: true,
    idleTimeout: 60000,
    compression: 'gzip',
  },
  // 用于处理压缩
  optimization: {
    splitChunks: {
      chunks: 'all',
    },
    runtimeChunk: 'single',
    minimizer: [
      // CssMinimizerPlugin、TerserWebpackPlugin 也可以放在plugins中
      new CssMinimizerPlugin(),
      new TerserWebpackPlugin({
        parallel: threads, // 开启多进程
        extractComments: false, // 移除注释
        terserOptions: {
          compress: {
            pure_funcs: ['console.log'], // 移除console.log
          },
        },
      }),
      new ImageMinimizerPlugin({
        minimizer: {
          implementation: ImageMinimizerPlugin.imageminGenerate,
          options: {
            plugins: [
              ['gifsicle', { interlaced: true }],
              ['jpegtran', { progressive: true }],
              ['optipng', { optimizationLevel: 5 }],
              [
                'svgo',
                {
                  plugins: [
                    'preset-default',
                    "prefixIds",
                    {
                      name: "sortAttrs",
                      params: {
                        xmlnsOrder: "alphabetical"
                      }
                    }

                  ]
                },
              ],
            ]
          }
        }
      })
    ],
  },
  plugins: [
    new ESLintPlugin({
      // 配置哪些目录需要检查
      context: path.resolve(__dirname, './src'),
      cache: true,// 开启缓存
      cacheLocation: path.resolve(__dirname, '../node_modules/.cache/eslintcache'),
      threads,// 开启多进程打包
    }),
    new ProgressBarPlugin({
      format: `  :msg [:bar] ${chalk.green.bold(':percent')} (:elapsed s)`
    }),
    new VueLoaderPlugin(),
    new HtmlWebpackPlugin({
      // 模版:以public/index.html为模板生成打包后的index.html
      template: path.resolve(__dirname, "../public/index.html"),
      BASE_URL: process.env.BASE_URL || '/'
    }),
    new MiniCssExtractPlugin({
      filename: "css/[name].[contenthash:8].css"
    }),
    new DefinePlugin({
      // window.ENV = 'production'
      ENV: JSON.stringify('production'),
      BASE_URL: '"../"' // 定义全局变量BASE_URL
    }),
    // new CssMinimizerPlugin(),
    // new TerserWebpackPlugin({
    //   parallel: threads, // 开启多进程
    //   extractComments: false, // 移除注释
    //   terserOptions: {
    //     compress: {
    //       pure_funcs: ['console.log'], // 移除console.log
    //     },
    //   },
    // }),
  ],
  // loader 加载器
  module: {
    rules: [
      // oneOf 不支持 vue-loader ,要单独拿出来
      {
        test: /\.vue$/,
        loader: "vue-loader",
      },
      // 每个文件只有一个loader配置处理
      {
        oneOf: [
          {
            test: /\.(gif|png|jpe?g)$/i,
            type: "asset",
            parser: {
              dataUrlCondition: {
                // 小于10kb的图片转成base64,减少请求数量
                // 缺点:体积会大一点
                maxSize: 10 * 1024, // 小于10kb
              },
            },
            generator: {
              // 输出图片的名称
              filename: "imgs/[name].[contenthash:8][ext]",
            },
          },
          {
            test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/, //媒体文件
            // 对文件原封不动的输出
            type: "asset/resource",
            // generator: {
            //   filename: "media/[name].[contenthash:8][ext]",
            // },
          },
          {
            test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/i, // 字体
            type: "asset/resource",
            // generator: {
            //   filename: "fonts/[name].[contenthash:8][ext]"
            // }
          },
          // 简单场景,不需要复杂转义时
          // {
          //   test: /\.(js|ts|jsx|tsx)$/,
          //   include: path.resolve(__dirname, 'src'),
          //   // loader:"babel-loader",
          //   loader: 'esbuild-loader',
          //   generator: {
          //     target: 'es2015'
          //   }
          // },
          // 复杂场景用
          {
            test: /\.js$/,
            exclude: /node_modules/,
            use: [{
              loader: 'thread-loader',
              options: {
                workers: threads, // 开启几个子进程去完成
              }
            },
            {
              loader: 'babel-loader',
              options: {
                presets: ['@babel/preset-env'],
                cacheDirectory: true, // 开启babel缓存
                cacheCompression: false,// 关闭缓存文件压缩
                plugins: ['@babel/plugin-transform-runtime'],
              },
            },]
          },
          {
            test: /\.css$/,
            // MiniCssExtractPlugin.loader 最终会将css提取到单独的文件
            use: getStyleLoaders(), // 从右向左解析原则
            // use: ["style-loader", "css-loader"]
          },
          {
            test: /\.less$/,
            use: getStyleLoaders("less-loader"), // 从右向左解析原则
            // use: ["style-loader", "css-loader", "less-loader"]
          },
          {
            test: /\.s[ac]ss$/,
            use: getStyleLoaders("sass-loader")
          },
          {
            test: /\.styl$/,
            use: getStyleLoaders("stylus-loader")
          },]
      }
    ],
  },
  // 配置模块如何解析
  resolve: {
    alias: {
      vue$: "vue/dist/vue.runtime.esm.js",    // 末尾添加 $,以表示精准匹配
      "@": path.resolve(__dirname, "../src"),
    },
    extensions: ["*", ".js", ".json", ".vue"],  // 尝试按顺序解析这些后缀名。如果有多个文件有相同的名字,但后缀名不同,webpack 会解析列在数组首位的后缀的文件 并跳过其余的后缀
  }
};

相关文章:

  • fluent_UDF学习笔记
  • 进程间通信——信号量
  • git 如何统计还尚未合并完成的文件
  • UE4学习笔记 FPS游戏制作31 显示计分板
  • flex和bison笔记
  • 2025最新“科研创新与智能化转型“暨AI智能体开发与大语言模型的本地化部署、优化技术实践
  • 【MySQL基础-14】MySQL的INSERT语句详解:高效数据插入的艺术
  • 数据特征的判断
  • 机器学习算法
  • mysql不能远程访问可能有哪些原因,及如何解决
  • ubuntu 创建新用户
  • 权值线段树算法讲解及例题
  • 性能测试理论基础-测试流程及方案设计要点
  • 内联函数/函数重载/函数参数缺省
  • 211 本硕研三,已拿 C++ 桌面应用研发 offer,计划转音视频或嵌入式如何规划学习路线?
  • 前端框架入门:Angular
  • Flutter中实现拍照识题的功能
  • Starrocks架构及如何选择
  • 60V单通道高精度线性恒流LED驱动器防60V反接SOD123封装
  • 智能物流调度:AI如何让快递更快更省?
  • 哪种“网红减肥法”比较靠谱?医学专家和运动专家共同解答
  • 在古老的意大利科莫歌剧院,廖昌永唱响16首中国艺术歌曲
  • 白玉兰奖征片综述丨动画的IP生命力
  • 外企聊营商|特雷通集团:税务服务“及时雨”
  • 人民日报钟声:通过平等对话协商解决分歧的重要一步
  • 四姑娘山一游客疑因高反身亡,镇卫生院:送到时已很严重