经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » JS/JS库/框架 » Vue.js » 查看文章
vue的两种服务器端渲染方案
来源:cnblogs  作者:京东云开发者  时间:2023/2/28 8:52:33  对本文有异议

作者:京东零售 姜欣

关于服务器端渲染方案,之前只接触了基于react的Next.js,最近业务开发vue用的比较多,所以调研了一下vue的服务器端渲染方案。 首先:长文预警,下文包括了两种方案的实践,没有耐心的小伙伴可以直接跳到方案标题下,down代码体验一下。

前置知识:

1、什么是服务器端渲染(ssr)?

简单来说就是用户第一次请求页面时,页面上的内容是通过服务器端渲染生成的,浏览器直接显示服务端返回的完整html就可以,加快首屏显示速度。

举个栗子:

当我们访问一个商品列表时,如果使用客户端渲染(csr),浏览器会加载空白的页面,然后下载js文件,通过js在客户端请求数据并渲染页面。如果使用服务器端渲染(ssr),在请求商品列表页面时,服务器会获取所需数据并将渲染后的HTML发送给浏览器,浏览器一步到位直接展示,而不用等待数据加载和渲染,提高用户的首屏体验。

2、服务器端渲染的优缺点

优点:

(1)更好的seo:抓取工具可以直接查看完全渲染的页面。现在比较常用的交互是页面初始展示 loading 菊花图,然后通过异步请求获取内容,但是但抓取工具并不会等待异步完成后再行抓取页面内容。

(2)内容到达更快:不用等待所有的 js 都完成下载并执行,所以用户会更快速地看到完整渲染的页面。

缺点:

(1)服务器渲染应用程序,需要处于 Node.js server 运行环境

(2)开发成本比较高

总结:

总得来说,决定是否使用服务器端渲染,取决于具体的业务场景和需求。对于具有大量静态内容的简单页面,客户端渲染更合适一些,因为它可以更快地加载页面。但是对于需要从服务器动态加载数据的复杂页面,服务器端渲染可能是一个更好的选择,因为他可以提高用户的首屏体验和搜索引擎优化。

下面进入正文

方案一:vue插件vue-server-render

git 示例demo地址

结论前置:不建议用,配置成本高

官网地址: https://v2.ssr.vuejs.org/zh/

首先要吐槽一下官网,按官网教程比较难搞,目录安排的不太合理,一顿操作项目都没起来...

并且官网示例的构建配置代码是webpack4的,现在初始化项目后基本安装的都是webpack5,有一些语法不同

(1)首先,先初始化一个npm项目,然后安装依赖得到一个基础项目 。(此处要注意vue-server-renderer 和 vue 必须匹配版本)

  1. npm init -y
  2. yarn add vue vue-server-renderer -S
  3. yarn add express -S
  4. yarn add webpack webpack-cli friendly-errors-webpack-plugin vue-loader babel-loader @babel/core url-loader file-loader vue-style-loader css-loader sass-loader sass webpack-merge webpack-node-externals -D
  5. yarn add clean-webpack-plugin @babel/preset-env -D
  6. yarn add rimraf // 模拟linx的删除命令,在build时先删除dist
  7. yarn add webpack-dev-middleware webpack-hot-middleware -D
  8. yarn add chokidar -D //监听变化
  9. yarn add memory-fs -D
  10. yarn add nodemon -D
  11. ...实在太多,如有缺失可以在package.json中查找
  12. 另外:我现在用的"vue-loader": "^15.9.0"版本,之前用的是"vue-loader": "^17.0.1",报了一个styles的错

(2)配置app.js,entry-client.js,entry-server.js,将官网参考中的示例代码(传送门: 构建配置 )拷贝至对应文件。

app.js

  1. import Vue from 'vue'
  2. import App from './App.vue'
  3. import { createRouter } from './router'
  4. import { createStore } from './store'
  5. import { sync } from 'vuex-router-sync'
  6. // 导出一个工厂函数,用于创建新的
  7. // 应用程序、router 和 store 实例
  8. export function createApp () {
  9. // 创建 router 和 store 实例
  10. const router = createRouter()
  11. const store = createStore()
  12. sync(store, router)
  13. const app = new Vue({
  14. router,
  15. store,
  16. render: h => h(App)
  17. })
  18. return { app, router, store }
  19. }

entry-client.js

  1. import Vue from 'vue'
  2. import { createApp } from './app'
  3. Vue.mixin({
  4. beforeMount () {
  5. const { asyncData } = this.$options
  6. if (asyncData) {
  7. this.dataPromise = asyncData({
  8. store: this.$store,
  9. route: this.$route
  10. })
  11. }
  12. }
  13. })
  14. const { app, router, store } = createApp()
  15. if (window.__INITIAL_STATE__) {
  16. store.replaceState(window.__INITIAL_STATE__)
  17. }
  18. router.onReady(() => {
  19. // 在初始路由 resolve 后执行,
  20. // 使用 `router.beforeResolve()`,以便确保所有异步组件都 resolve。
  21. router.beforeResolve((to, from, next) => {
  22. const matched = router.getMatchedComponents(to)
  23. const prevMatched = router.getMatchedComponents(from)
  24. // 找出两个匹配列表的差异组件
  25. let diffed = false
  26. const activated = matched.filter((c, i) => {
  27. return diffed || (diffed = (prevMatched[i] !== c))
  28. })
  29. if (!activated.length) {
  30. return next()
  31. }
  32. Promise.all(activated.map(c => {
  33. if (c.asyncData) {
  34. return c.asyncData({ store, route: to })
  35. }
  36. })).then(() => {
  37. next()
  38. }).catch(next)
  39. })
  40. app.$mount('#app')
  41. })

entry-server.js

  1. import { createApp } from './app'
  2. export default context => {
  3. // 返回一个promise,服务器能够等待所有的内容在渲染前,已经准备就绪,
  4. return new Promise((resolve, reject) => {
  5. const { app, router, store } = createApp()
  6. router.push(context.url)
  7. router.onReady(() => {
  8. const matchedComponents = router.getMatchedComponents()
  9. if (!matchedComponents.length) {
  10. return reject({ code: 404 })
  11. }
  12. // 对所有匹配的路由组件调用 `asyncData()`
  13. Promise.all(matchedComponents.map(Component => {
  14. if (Component.asyncData) {
  15. return Component.asyncData({
  16. store,
  17. route: router.currentRoute
  18. })
  19. }
  20. })).then(() => {
  21. context.state = store.state
  22. resolve(app)
  23. }).catch(reject)
  24. }, reject)
  25. })
  26. }

(3)在根目录下创建server.js 文件

其中一个非常重要的api:createBundleRenderer,这个api上面有一个方法renderToString将代码转化成html字符串,主要功能就是把用webpack把打包后的服务端代码渲染出来。具体了解可看官网bundle renderer指引(传送门: bundle renderer指引

  1. // server.js
  2. const app = require('express')()
  3. const { createBundleRenderer } = require('vue-server-renderer')
  4. const fs = require('fs')
  5. const path = require('path')
  6. const resolve = file => path.resolve(__dirname, file)
  7. const isProd = process.env.NODE_ENE === "production"
  8. const createRenderer = (bundle, options) => {
  9. return createBundleRenderer(bundle, Object.assign(options, {
  10. basedir: resolve('./dist'),
  11. runInNewContext: false,
  12. }))
  13. }
  14. let renderer, readyPromise
  15. const templatePath = resolve('./src/index.template.html')
  16. if (isProd) {
  17. const bundle = require('./dist/vue-ssr-server-bundle.json')
  18. const clientManifest = require('./dist/vue-ssr-client-manifest.json')
  19. const template = fs.readFileSync(templatePath, 'utf-8')
  20. renderer = createRenderer(bundle, {
  21. // 推荐
  22. template, // (可选)页面模板
  23. clientManifest // (可选)客户端构建 manifest
  24. })
  25. } else {
  26. // 开发模式
  27. readyPromise = require('./config/setup-dev-server')(app, templatePath, (bundle, options) => {
  28. renderer = createRenderer(bundle, options)
  29. })
  30. }
  31. const render = (req, res) => {
  32. const context = {
  33. title: 'hello ssr with webpack',
  34. meta: `
  35. <meta charset="UTF-8">
  36. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  37. <meta http-equiv="X-UA-Compatible" content="ie=edge">
  38. `,
  39. url: req.url
  40. }
  41. renderer.renderToString(context, (err, html) => {
  42. if (err) {
  43. if (err.code === 404) {
  44. res.status(404).end('Page not found')
  45. } else {
  46. res.status(500).end('Internal Server Error')
  47. }
  48. } else {
  49. res.end(html)
  50. }
  51. })
  52. }
  53. // 在服务器处理函数中……
  54. app.get('*', isProd ? render : (req, res) => {
  55. readyPromise.then(() => render(req, res))
  56. })
  57. app.listen(8080) // 监听的是8080端口

(4)接下来是config配置

在根目录新增config文件夹,然后新增四个配置文件:webpack.base.config,webpack.client.config,webpack.server.config,setup-dev-server(此方法是一个封装,为了配置个热加载,差点没搞明白,参考了好多)(官网传送门: 构建配置

大部分官网有示例代码,但是要在基础上进行一些更改

webpack.base.config

  1. // webpack.base.config
  2. const path = require('path')
  3. // 用来处理后缀为.vue的文件
  4. const { VueLoaderPlugin } = require('vue-loader')
  5. const FriendlyErrorsWebpackPlugin = require('friendly-errors-webpack-plugin')
  6. // 定位到根目录
  7. const resolve = (dir) => path.join(path.resolve(__dirname, "../"), dir)
  8. // 打包时会先清除一下
  9. // const { CleanWebpackPlugin } = require('clean-webpack-plugin')
  10. const isProd = process.env.NODE_ENV === "production"
  11. module.exports = {
  12. mode: isProd ? 'production' : 'development',
  13. output: {
  14. path: resolve('dist'),
  15. publicPath: '/dist/',
  16. filename: '[name].[chunk-hash].js'
  17. },
  18. resolve: {
  19. alias: {
  20. 'public': resolve('public')
  21. }
  22. },
  23. module: {
  24. noParse: /es6-promise.js$/,
  25. rules: [
  26. {
  27. test: /.vue$/,
  28. loader: 'vue-loader',
  29. options: {
  30. compilerOptions: {
  31. preserveWhiteSpace: false
  32. }
  33. }
  34. },
  35. {
  36. test: /.js$/,
  37. loader: 'babel-loader',
  38. exclude: /node_modules/
  39. },
  40. {
  41. test: /.(png|jpg|gif|svg)$/,
  42. loader: 'url-loader',
  43. options: {
  44. limit: 10000,
  45. name: '[name].[ext]?[hash]'
  46. }
  47. },
  48. {
  49. test: /.s(a|c)ss?$/,
  50. use: ['vue-style-loader', 'css-loader', 'sass-loader']
  51. }
  52. ]
  53. },
  54. performance: {
  55. hints: false
  56. },
  57. plugins:[
  58. new VueLoaderPlugin(),
  59. // 编译后的友好提示,比如编译完成或者编译有错误
  60. new FriendlyErrorsWebpackPlugin(),
  61. // 打包时会先清除一下
  62. // new CleanWebpackPlugin()
  63. ]
  64. }

webpack.client.config

  1. // webpack.client.config
  2. const {merge} = require('webpack-merge')
  3. const baseConfig = require('./webpack.base.config.js')
  4. const VueSSRClientPlugin = require('vue-server-renderer/client-plugin')
  5. module.exports = merge(baseConfig, {
  6. entry: {
  7. app: './src/entry-client.js'
  8. },
  9. optimization: {
  10. // 重要信息:这将 webpack 运行时分离到一个引导 chunk 中,
  11. // 以便可以在之后正确注入异步 chunk。
  12. // 这也为你的 应用程序/vendor 代码提供了更好的缓存。
  13. splitChunks: {
  14. name: "manifest",
  15. minChunks: Infinity
  16. }
  17. },
  18. plugins: [
  19. // 此插件在输出目录中
  20. // 生成 `vue-ssr-client-manifest.json`。
  21. new VueSSRClientPlugin()
  22. ]
  23. })

webpack.server.config

  1. // webpack.server.config
  2. const {merge} = require('webpack-merge')
  3. const nodeExternals = require('webpack-node-externals')
  4. // webpack的基础配置,比如sass,less预编译等
  5. const baseConfig = require('./webpack.base.config.js')
  6. const VueSSRServerPlugin = require('vue-server-renderer/server-plugin')
  7. module.exports = merge(baseConfig, {
  8. // 将 entry 指向应用程序的 server entry 文件
  9. entry: './src/entry-server.js',
  10. target: 'node',
  11. // 对 bundle renderer 提供 source map 支持
  12. devtool: 'source-map',
  13. // 此处告知 server bundle 使用 Node 风格导出模块(Node-style exports)
  14. output: {
  15. libraryTarget: 'commonjs2'
  16. },
  17. // https://webpack.js.org/configuration/externals/#function
  18. // https://github.com/liady/webpack-node-externals
  19. // 外置化应用程序依赖模块。可以使服务器构建速度更快,
  20. // 并生成较小的 bundle 文件。
  21. externals: nodeExternals({
  22. // 不要外置化 webpack 需要处理的依赖模块。
  23. // 你可以在这里添加更多的文件类型。例如,未处理 *.vue 原始文件,
  24. // 你还应该将修改 `global`(例如 polyfill)的依赖模块列入白名单
  25. allowlist: /.css$/
  26. }),
  27. // 这是将服务器的整个输出
  28. // 构建为单个 JSON 文件的插件。
  29. // 默认文件名为 `vue-ssr-server-bundle.json`
  30. plugins: [
  31. new VueSSRServerPlugin()
  32. ]
  33. })

setup-dev-server:封装createRenderer方法

  1. const webpack = require('webpack')
  2. const fs = require('fs')
  3. const path = require('path')
  4. const chokidar = require('chokidar')
  5. const middleware = require("webpack-dev-middleware")
  6. const HMR = require("webpack-hot-middleware")
  7. const MFS = require('memory-fs')
  8. const clientConfig = require('./webpack.client.config')
  9. const serverConfig = require('./webpack.server.config')
  10. const readFile = (fs, file) => {
  11. try {
  12. return fs.readFileSync(path.join(clientConfig.output.path, file), 'utf8')
  13. } catch (error) {
  14. }
  15. }
  16. const setupServer = (app, templatePath, cb) => {
  17. let bundle
  18. let clientManifest
  19. let template
  20. let ready
  21. const readyPromise = new Promise(r => ready = r)
  22. template = fs.readFileSync(templatePath, 'utf8')
  23. const update = () => {
  24. if (bundle && clientManifest) {
  25. // 通知 server 进行渲染
  26. // 执行 createRenderer -> RenderToString
  27. ready()
  28. cb(bundle, {
  29. template,
  30. clientManifest
  31. })
  32. }
  33. }
  34. // webpack -> entry-server -> bundle
  35. const mfs = new MFS();
  36. const serverCompiler = webpack(serverConfig);
  37. serverCompiler.outputFileSystem = mfs;
  38. serverCompiler.watch({}, (err, stats) => {
  39. if (err) throw err
  40. // 之后读取输出:
  41. stats = stats.toJson()
  42. stats.errors.forEach(err => console.error(err))
  43. stats.warnings.forEach(err => console.warn(err))
  44. if (stats.errors.length) return
  45. bundle = JSON.parse(readFile(mfs, 'vue-ssr-server-bundle.json'))
  46. update()
  47. });
  48. clientConfig.plugins.push(
  49. new webpack.HotModuleReplacementPlugin()
  50. )
  51. clientConfig.entry.app = ['webpack-hot-middleware/client', clientConfig.entry.app]
  52. clientConfig.output.filename = '[name].js'
  53. const clientCompiler = webpack(clientConfig);
  54. const devMiddleware = middleware(clientCompiler, {
  55. noInfo: true, publicPath: clientConfig.output.publicPath, logLevel: 'silent'
  56. })
  57. app.use(devMiddleware);
  58. app.use(HMR(clientCompiler));
  59. clientCompiler.hooks.done.tap('clientsBuild', stats => {
  60. stats = stats.toJson()
  61. stats.errors.forEach(err => console.error(err))
  62. stats.warnings.forEach(err => console.warn(err))
  63. if (stats.errors.length) return
  64. clientManifest = JSON.parse(readFile(
  65. devMiddleware.fileSystem,
  66. 'vue-ssr-client-manifest.json'
  67. ))
  68. update()
  69. })
  70. // fs -> templatePath -> template
  71. chokidar.watch(templatePath).on('change', () => {
  72. template = fs.readFileSync(templatePath, 'utf8')
  73. console.log('template is updated');
  74. update()
  75. })
  76. return readyPromise
  77. }
  78. module.exports = setupServer

(5)配置搞完了接下来是代码渲染

在src目录下,新增index.template.html文件,将官网中的例子(地址:使用一个页面模板 )复制,并进行一些更改

  1. <html>
  2. <head>
  3. <!-- 使用双花括号(double-mustache)进行 HTML 转义插值(HTML-escaped interpolation) -->
  4. <title>{{ title }}</title>
  5. <!-- 使用三花括号(triple-mustache)进行 HTML 不转义插值(non-HTML-escaped interpolation) -->
  6. {{{ meta }}}
  7. </head>
  8. <body>
  9. <!--这个是告诉我们在哪里插入正文的内容-->
  10. <!--vue-ssr-outlet-->
  11. </body>
  12. </html>

(6)再搞个store和api模拟一下数据请求

这里介绍一下一个很重要的东西asyncData 预取数据,预取数据是在vue挂载前,所以下文这里用了上下文来获取store而不是this

  1. asyncData: ({ store }) => { return store.dispatch('getDataAction') },

在src下创建api文件夹,并在下面创建data.js文件

  1. // data.js
  2. const getData = () => new Promise((resolve) => {
  3. setTimeout(() => {
  4. resolve([
  5. {
  6. id: 1,
  7. item: '测试1'
  8. },
  9. {
  10. id: 2,
  11. item: '测试2'
  12. },
  13. ])
  14. }, 1000)
  15. })
  16. export {
  17. getData
  18. }

在src下创建store文件夹,并在下面创建index.js文件

  1. // store.js
  2. import Vue from 'vue'
  3. import Vuex from 'vuex'
  4. Vue.use(Vuex)
  5. import { getData } from '../api/data'
  6. export function createStore () {
  7. return new Vuex.Store({
  8. state: {
  9. lists: []
  10. },
  11. actions: {
  12. getDataAction ({ commit }) {
  13. return getData().then((res) => {
  14. commit('setData', res)
  15. })
  16. }
  17. },
  18. mutations: {
  19. setData (state, data) {
  20. state.lists = data
  21. }
  22. }
  23. })
  24. }

(7)编写组件,在src/components文件夹下写两个组件,在app.vue中引用一下,用上刚写的模拟数据

Hello.vue

  1. <template>
  2. <div>
  3. 这里是测试页面一
  4. <p>{{item}}</p>
  5. <router-link to="/hello1">链接到测试页面二</router-link>
  6. </div>
  7. </template>
  8. <script>
  9. export default {
  10. asyncData: ({ store }) => {
  11. return store.dispatch('getDataAction')
  12. },
  13. computed: {
  14. item () {
  15. return this.$store.state.lists
  16. }
  17. }
  18. }
  19. </script>
  20. <style lang="scss" scoped>
  21. </style>

Hello1.vue

  1. <template>
  2. <div>这里是测试页面二{{item}}</div>
  3. </template>
  4. <script>
  5. export default {
  6. asyncData: ({ store }) => {
  7. return store.dispatch('getDataAction')
  8. },
  9. computed: {
  10. item () {
  11. return this.$store.state.lists
  12. }
  13. }
  14. }
  15. </script>
  16. <style lang="scss" scoped>
  17. </style>

(8)配置路由并在app.vue使用路由

router.js

  1. import Vue from 'vue'
  2. import Router from 'vue-router'
  3. Vue.use(Router)
  4. export function createRouter () {
  5. return new Router({
  6. mode: 'history',
  7. routes: [
  8. {
  9. path: '/hello',
  10. component: () => import('./components/Hello.vue')
  11. },
  12. {
  13. path: '/hello1',
  14. component: () => import('./components/Hello1.vue')
  15. },
  16. ]
  17. })
  18. }

app.vue

  1. <template>
  2. <div id="app">
  3. <router-view></router-view>
  4. </div>
  5. </template>
  6. <script>
  7. export default {
  8. name: 'App',
  9. }
  10. </script>
  11. <style lang="scss" scoped>
  12. </style>

(9)根目录下创建一个.babelrc,进行配置

  1. {
  2. "presets": [
  3. [
  4. "@babel/preset-env",
  5. {
  6. "modules": false
  7. }
  8. ]
  9. ]
  10. }

(10)改写package.json执行命令

  1. "dev": "nodemon server.js",
  2. "build": "rimraf dist && npm run build:client && npm run build:server",
  3. "build:client": "webpack --config config/webpack.client.config.js",
  4. "build:server": "webpack --config config/webpack.server.config.js"

大搞告成,执行一下dev命令,可以通过访问localhost:8080端口看到页面,记得带上路由哦~

执行build命令可看到,最后dist文件下共有三个文件:main.[chunk-hash].js,vue-ssr-client-manifest.json,vue-ssr-server-bundle.json

附上文件整体目录结构

方案二:基于vue的nuxt.js通用应用框架

git 示例demo地址

一对比,这个就显得丝滑多了~ 官网地址: nuxt.js

先对比一下两种方案的差别

  1. 1.vue初始化虽然有cli,但是nuxt.jscli更加完备
  2. 2.nuxt有更合理的工程化目录,vue过于简洁,比如一些componentapi文件夹都是要手动创建的
  3. 3.路由配置:传统应用需要自己来配置,nuxt.js自动生成
  4. 4.没有统一配置,需手动创建。nuxt.js会生成nuxt.config.js
  5. 5.传统不易与管理底层框架逻辑(nuxt支持中间件管理,虽然我还没探索过这里)

显而易见这个上手就快多了,也不需要安装一大堆依赖,如果用了sass需要安装sass和sass-loader,反正我是用了

(1)创建一个项目 可选npm,npx,yarn,具体看官方文档

  1. npm init nuxt-app <project-name>

(2)pages下面创建几个文件

nuxt是通过pages页面形成动态的路由,不用手动配置路由。比如在pages下面新增了个文件about.vue,那么这个页面对应的路由就是/about

其实这个时候运行npm run dev 就可以看到简单的页面了

(3)模拟接口

这里介绍一个插件,可以快速创建一个服务

  1. npm i json-server

安装完后,在根目录新增db.json文件,模拟几个接口

  1. {
  2. "post": [{"id": 1, "title": "json-server", "author": "jx"}],
  3. "comments": [{"id": 1, "body": "some comment", "postId": 1}],
  4. "profile": {"name": "typicode"}
  5. }

运行命令json-server --watch db.json --port=8000(不加会端口冲突),就可以看到

因为是get请求,可以直接点击访问可以看到mock的数据已经返回了

(4)页面调用

先配置一下axios,推荐使用nuxt.js封装的axios:"@nuxtjs/axios": "^5.13.6",然后再在nuxt.config.js文件中modules下面配置一下就可以使用了

  1. modules: [ '@nuxtjs/axios'],

随便找个接口调用一下

  1. <template>
  2. <div>
  3. <div>
  4. 这里是测试页面一
  5. </div>
  6. 接口返回数据:{{posts}}
  7. </div>
  8. </template>
  9. <script>
  10. export default {
  11. name: 'IndexPage',
  12. async asyncData({$axios}){
  13. const result = await $axios.get('http://localhost:8000/post')
  14. return{
  15. posts: result.data
  16. }
  17. }
  18. }
  19. </script>

刷新下页面就可以看到效果了,这里注意$axios有两个get方法,一个$axios.get还会返回头部等信息,另一个$axios.$get只返回结果

总结:

从页面篇幅上应该也能看到哪个容易上手了,nuxt相对于插件来说限定了文件夹的结构,并通过此预定了一些功能,更好上手。预设了利用vue.js开发服务端渲染所需要的各种配置,并且提供了提供了静态站点,异步数据加载,中间件支持,布局支持等

原文链接:https://www.cnblogs.com/Jcloud/p/17158858.html

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号