范文健康探索娱乐情感热点
投稿投诉
热点动态
科技财经
情感日志
励志美文
娱乐时尚
游戏搞笑
探索旅游
历史星座
健康养生
美丽育儿
范文作文
教案论文

vue的两种服务器端渲染方案

  作者:京东零售 姜欣
  关于服务器端渲染方案,之前只接触了基于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 必须匹配版本)npm init -y yarn add vue vue-server-renderer -S yarn add express -S 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 yarn add clean-webpack-plugin @babel/preset-env -D yarn add rimraf // 模拟linx的删除命令,在build时先删除dist yarn add webpack-dev-middleware webpack-hot-middleware -D yarn add chokidar  -D //监听变化 yarn add memory-fs -D yarn add nodemon -D ...实在太多,如有缺失可以在package.json中查找 另外:我现在用的"vue-loader": "^15.9.0"版本,之前用的是"vue-loader": "^17.0.1",报了一个styles的错(2)配置app.js,entry-client.js,entry-server.js,将官网参考中的示例代码拷贝至对应文件。
  app.js import Vue from "vue" import App from "./App.vue" import { createRouter } from "./router" import { createStore } from "./store" import { sync } from "vuex-router-sync"  // 导出一个工厂函数,用于创建新的 // 应用程序、router 和 store 实例 export function createApp () {     // 创建 router 和 store 实例     const router = createRouter()     const store = createStore()          sync(store, router)      const app = new Vue({         router,         store,         render: h => h(App)     })      return { app, router, store } }
  entry-client.js import Vue from "vue" import { createApp } from "./app"  Vue.mixin({     beforeMount () {         const { asyncData } = this.$options         if (asyncData) {             this.dataPromise = asyncData({                 store: this.$store,                 route: this.$route             })         }     } })  const { app, router, store } = createApp()  if (window.__INITIAL_STATE__) {     store.replaceState(window.__INITIAL_STATE__) }  router.onReady(() => {     // 在初始路由 resolve 后执行,     // 使用 `router.beforeResolve()`,以便确保所有异步组件都 resolve。     router.beforeResolve((to, from, next) => {         const matched = router.getMatchedComponents(to)         const prevMatched = router.getMatchedComponents(from)          // 找出两个匹配列表的差异组件         let diffed = false         const activated = matched.filter((c, i) => {             return diffed || (diffed = (prevMatched[i] !== c))         })          if (!activated.length) {             return next()         }          Promise.all(activated.map(c => {             if (c.asyncData) {                 return c.asyncData({ store, route: to })             }         })).then(() => {             next()         }).catch(next)     })      app.$mount("#app") })
  entry-server.js import { createApp } from "./app"  export default context => {     // 返回一个promise,服务器能够等待所有的内容在渲染前,已经准备就绪,     return new Promise((resolve, reject) => {         const { app, router, store } = createApp()                  router.push(context.url)          router.onReady(() => {             const matchedComponents = router.getMatchedComponents()             if (!matchedComponents.length) {                 return reject({ code: 404 })             }              // 对所有匹配的路由组件调用 `asyncData()`             Promise.all(matchedComponents.map(Component => {                 if (Component.asyncData) {                     return Component.asyncData({                         store,                         route: router.currentRoute                     })                 }             })).then(() => {                 context.state = store.state                  resolve(app)             }).catch(reject)         }, reject)     }) }(3)在根目录下创建server.js 文件
  其中一个非常重要的api:createBundleRenderer,这个api上面有一个方法renderToString将代码转化成html字符串,主要功能就是把用webpack把打包后的服务端代码渲染出来。具体了解可看官网bundle renderer指引。 // server.js const app = require("express")() const { createBundleRenderer } = require("vue-server-renderer") const fs = require("fs") const path = require("path") const resolve = file => path.resolve(__dirname, file)  const isProd = process.env.NODE_ENE === "production"  const createRenderer = (bundle, options) => {     return createBundleRenderer(bundle, Object.assign(options, {         basedir: resolve("./dist"),         runInNewContext: false,     })) }  let renderer, readyPromise const templatePath = resolve("./src/index.template.html") if (isProd) {     const bundle = require("./dist/vue-ssr-server-bundle.json")     const clientManifest = require("./dist/vue-ssr-client-manifest.json")     const template = fs.readFileSync(templatePath, "utf-8")      renderer = createRenderer(bundle, {         // 推荐         template, // (可选)页面模板         clientManifest // (可选)客户端构建 manifest     }) } else {     // 开发模式     readyPromise = require("./config/setup-dev-server")(app, templatePath, (bundle, options) => {         renderer = createRenderer(bundle, options)     }) }  const render = (req, res) => {     const context = {         title: "hello ssr with webpack",         meta: `                          `,         url: req.url     }     renderer.renderToString(context, (err, html) => {         if (err) {             if (err.code === 404) {                 res.status(404).end("Page not found")             } else {                 res.status(500).end("Internal Server Error")             }         } else {             res.end(html)         }     }) }  // 在服务器处理函数中…… app.get("*", isProd ? render : (req, res) => {     readyPromise.then(() => render(req, res)) })  app.listen(8080) // 监听的是8080端口(4)接下来是config配置
  在根目录新增config文件夹,然后新增四个配置文件:webpack.base.config,webpack.client.config,webpack.server.config,setup-dev-server(此方法是一个封装,为了配置个热加载,差点没搞明白,参考了好多)(官网传送门: 构建配置 )
  大部分官网有示例代码,但是要在基础上进行一些更改
  webpack.base.config // webpack.base.config const path = require("path") // 用来处理后缀为.vue的文件 const { VueLoaderPlugin } = require("vue-loader") const FriendlyErrorsWebpackPlugin = require("friendly-errors-webpack-plugin") // 定位到根目录 const resolve = (dir) => path.join(path.resolve(__dirname, "../"), dir)  // 打包时会先清除一下 // const { CleanWebpackPlugin } = require("clean-webpack-plugin")  const isProd = process.env.NODE_ENV === "production"  module.exports = {     mode: isProd ? "production" : "development",     output: {         path: resolve("dist"),         publicPath: "/dist/",         filename: "[name].[chunk-hash].js"     },     resolve: {         alias: {             "public": resolve("public")         }     },     module: {         noParse: /es6-promise.js$/,         rules: [             {                 test: /.vue$/,                 loader: "vue-loader",                 options: {                     compilerOptions: {                         preserveWhiteSpace: false                     }                 }             },             {                 test: /.js$/,                 loader: "babel-loader",                 exclude: /node_modules/             },             {                 test: /.(png|jpg|gif|svg)$/,                 loader: "url-loader",                 options: {                     limit: 10000,                     name: "[name].[ext]?[hash]"                 }             },             {                 test: /.s(a|c)ss?$/,                 use: ["vue-style-loader", "css-loader", "sass-loader"]             }         ]     },     performance: {         hints: false     },     plugins:[         new VueLoaderPlugin(),         // 编译后的友好提示,比如编译完成或者编译有错误         new FriendlyErrorsWebpackPlugin(),         // 打包时会先清除一下         // new CleanWebpackPlugin()     ] }
  webpack.client.config // webpack.client.config const {merge} = require("webpack-merge") const baseConfig = require("./webpack.base.config.js") const VueSSRClientPlugin = require("vue-server-renderer/client-plugin")  module.exports = merge(baseConfig, {     entry: {         app: "./src/entry-client.js"     },     optimization: {         // 重要信息:这将 webpack 运行时分离到一个引导 chunk 中,         // 以便可以在之后正确注入异步 chunk。         // 这也为你的 应用程序/vendor 代码提供了更好的缓存。         splitChunks: {             name: "manifest",             minChunks: Infinity         }     },     plugins: [         // 此插件在输出目录中         // 生成 `vue-ssr-client-manifest.json`。         new VueSSRClientPlugin()     ] })
  webpack.server.config // webpack.server.config const {merge} = require("webpack-merge") const nodeExternals = require("webpack-node-externals")  // webpack的基础配置,比如sass,less预编译等 const baseConfig = require("./webpack.base.config.js") const VueSSRServerPlugin = require("vue-server-renderer/server-plugin")  module.exports = merge(baseConfig, {     // 将 entry 指向应用程序的 server entry 文件     entry: "./src/entry-server.js",      target: "node",      // 对 bundle renderer 提供 source map 支持     devtool: "source-map",      // 此处告知 server bundle 使用 Node 风格导出模块(Node-style exports)     output: {         libraryTarget: "commonjs2"     },      // https://webpack.js.org/configuration/externals/#function     // https://github.com/liady/webpack-node-externals     // 外置化应用程序依赖模块。可以使服务器构建速度更快,     // 并生成较小的 bundle 文件。     externals: nodeExternals({         // 不要外置化 webpack 需要处理的依赖模块。         // 你可以在这里添加更多的文件类型。例如,未处理 *.vue 原始文件,         // 你还应该将修改 `global`(例如 polyfill)的依赖模块列入白名单         allowlist: /.css$/     }),      // 这是将服务器的整个输出     // 构建为单个 JSON 文件的插件。     // 默认文件名为 `vue-ssr-server-bundle.json`     plugins: [         new VueSSRServerPlugin()     ] })
  setup-dev-server:封装createRenderer方法 const webpack = require("webpack") const fs = require("fs") const path = require("path") const chokidar = require("chokidar") const middleware = require("webpack-dev-middleware") const HMR = require("webpack-hot-middleware") const MFS = require("memory-fs")  const clientConfig = require("./webpack.client.config") const serverConfig = require("./webpack.server.config")  const readFile = (fs, file) => {     try {         return fs.readFileSync(path.join(clientConfig.output.path, file), "utf8")     } catch (error) {      } }  const setupServer = (app, templatePath, cb) => {     let bundle     let clientManifest     let template     let ready     const readyPromise = new Promise(r => ready = r)      template = fs.readFileSync(templatePath, "utf8")     const update = () => {         if (bundle && clientManifest) {             // 通知 server 进行渲染             // 执行 createRenderer -> RenderToString             ready()             cb(bundle, {                 template,                 clientManifest             })         }     }     // webpack -> entry-server -> bundle     const mfs = new MFS();     const serverCompiler = webpack(serverConfig);      serverCompiler.outputFileSystem = mfs;     serverCompiler.watch({}, (err, stats) => {         if (err) throw err         // 之后读取输出:         stats = stats.toJson()         stats.errors.forEach(err => console.error(err))         stats.warnings.forEach(err => console.warn(err))         if (stats.errors.length) return         bundle = JSON.parse(readFile(mfs, "vue-ssr-server-bundle.json"))         update()     });      clientConfig.plugins.push(         new webpack.HotModuleReplacementPlugin()     )     clientConfig.entry.app = ["webpack-hot-middleware/client", clientConfig.entry.app]     clientConfig.output.filename = "[name].js"      const clientCompiler = webpack(clientConfig);      const devMiddleware = middleware(clientCompiler, {         noInfo: true, publicPath: clientConfig.output.publicPath, logLevel: "silent"     })     app.use(devMiddleware);      app.use(HMR(clientCompiler));      clientCompiler.hooks.done.tap("clientsBuild", stats => {         stats = stats.toJson()         stats.errors.forEach(err => console.error(err))         stats.warnings.forEach(err => console.warn(err))         if (stats.errors.length) return         clientManifest = JSON.parse(readFile(             devMiddleware.fileSystem,             "vue-ssr-client-manifest.json"         ))         update()     })      // fs -> templatePath -> template     chokidar.watch(templatePath).on("change", () => {         template = fs.readFileSync(templatePath, "utf8")         console.log("template is updated");         update()     })      return readyPromise }  module.exports = setupServer(5)配置搞完了接下来是代码渲染
  在src目录下,新增index.template.html文件,将官网中的例子(地址:使用一个页面模板 )复制,并进行一些更改            {{ title }}           {{{ meta }}}      (6)再搞个store和api模拟一下数据请求
  这里介绍一下一个很重要的东西asyncData 预取数据,预取数据是在vue挂载前,所以下文这里用了上下文来获取store而不是this asyncData: ({ store }) => { return store.dispatch("getDataAction") },
  在src下创建api文件夹,并在下面创建data.js文件 // data.js const getData = () => new Promise((resolve) => {     setTimeout(() => {         resolve([             {                 id: 1,                 item: "测试1"             },             {                 id: 2,                 item: "测试2"             },         ])     }, 1000) })  export {     getData }
  在src下创建store文件夹,并在下面创建index.js文件 // store.js import Vue from "vue" import Vuex from "vuex"  Vue.use(Vuex)  import { getData } from "../api/data"  export function createStore () {     return new Vuex.Store({         state: {             lists: []         },         actions: {             getDataAction ({ commit }) {                 return getData().then((res) => {                                        commit("setData", res)                 })             }         },         mutations: {             setData (state, data) {                 state.lists = data             }         }     }) }(7)编写组件,在src/components文件夹下写两个组件,在app.vue中引用一下,用上刚写的模拟数据
  Hello.vue     
  Hello1.vue     (8)配置路由并在app.vue使用路由
  router.js import Vue from "vue" import Router from "vue-router"  Vue.use(Router)  export function createRouter () {     return new Router({         mode: "history",         routes: [             {                 path: "/hello",                 component: () => import("./components/Hello.vue")             },             {                 path: "/hello1",                 component: () => import("./components/Hello1.vue")             },         ]     }) }
  app.vue     (9)根目录下创建一个.babelrc,进行配置{   "presets": [     [       "@babel/preset-env",       {         "modules": false       }     ]   ] }(10)改写package.json执行命令"dev": "nodemon server.js", "build": "rimraf dist && npm run build:client && npm run build:server", "build:client": "webpack --config config/webpack.client.config.js", "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.vue初始化虽然有cli,但是nuxt.js的cli更加完备 2.nuxt有更合理的工程化目录,vue过于简洁,比如一些component,api文件夹都是要手动创建的 3.路由配置:传统应用需要自己来配置,nuxt.js自动生成 4.没有统一配置,需手动创建。nuxt.js会生成nuxt.config.js 5.传统不易与管理底层框架逻辑(nuxt支持中间件管理,虽然我还没探索过这里)
  显而易见这个上手就快多了,也不需要安装一大堆依赖, 如果用了sass需要安装sass和sass-loader,反正我是用了 (1)创建一个项目 可选npm,npx,yarn,具体看官方文档npm init nuxt-app (2)pages下面创建几个文件
  nuxt是通过pages页面形成动态的路由,不用手动配置路由。比如在pages下面新增了个文件about.vue,那么这个页面对应的路由就是/about
  其实这个时候运行npm run dev 就可以看到简单的页面了 (3)模拟接口
  这里介绍一个插件,可以快速创建一个服务 npm i json-server
  安装完后,在根目录新增db.json文件,模拟几个接口 {   "post": [{"id": 1, "title": "json-server", "author": "jx"}],   "comments": [{"id": 1, "body": "some comment", "postId": 1}],   "profile": {"name": "typicode"} }
  运行命令json-server --watch db.json --port=8000(不加会端口冲突),就可以看到
  因为是get请求,可以直接点击访问可以看到mock的数据已经返回了
  (4)页面调用
  先配置一下axios,推荐使用nuxt.js封装的axios:"@nuxtjs/axios": "^5.13.6",然后再在nuxt.config.js文件中modules下面配置一下就可以使用了 modules: [  "@nuxtjs/axios"],
  随便找个接口调用一下 
  刷新下页面就可以看到效果了,这里注意$axios有两个get方法,一个$axios.get还会返回头部等信息,另一个$axios.$get只返回结果
  总结:
  从页面篇幅上应该也能看到哪个容易上手了,nuxt相对于插件来说限定了文件夹的结构,并通过此预定了一些功能,更好上手。预设了利用vue.js开发服务端渲染所需要的各种配置,并且提供了提供了静态站点,异步数据加载,中间件支持,布局支持等

纳什之后会是谁?四位主帅不稳,一人是冠军教头,一人摆烂也不会每年NBA常规赛期间,都会有主帅下课的事件出现。2223赛季第一个下课的是纳什,这是大家预料之中的事情。对于纳什来说,离开篮网也是一种解脱,毕竟最近一段时间事情太多,纳什肯定焦头烂最大胆的Airpods联名配饰,17岁潮牌主理人设计11月8日,来自中国香港的17岁艺术家Offgod与俄罗斯圣彼得堡创意人士GlebKostin一同打造了全新联名配饰AppleAirPodsMax的适用配件。概念图一出炉,就被众人折腾,就是对梦想的最大尊重原创心语人生一世,草木一秋,生命几十载,如若大梦一场。我们自逃不过命运轮回,但可明了,一切外物外事,都是命运布下的棋局,身不由己当局其中,但,心可跳脱其外,不为所动。心不自虐,天奈真正成熟的人小事不争,大事不怠,苦事不屈铁是越炼越硬,人要越挫越勇。作者洞见yebo复旦大学教授陈果,说过这样一段话你看我们社会上很多人,身强力壮,人高马大,有很多人甚至是白发苍苍,他生理上毫无疑问是成熟了。但如果你仔细21世纪阴盛阳衰,姑娘和老丈人考察一个男生人品后,需要勇于直追以前是阳盛阴弱,男生能够勇于去追女生,表现出男性的家庭背景,自信心胆识,和勇于克服困难的魄力与勇气。在21世纪很多女生有房有车有胆识有知识有背景却剩下了,有些女性已过半生,将来能够学会这八点,教你拥有城府人可以没有心机,但一定要有城府。城府不是教你变坏,而是让你更自如地保护自己。做好以下这八点教你拥有城府。1防止套话在与别人沟通的时候,一定要提防别人套你的话,尤其是不熟的人。不要大两性深夜男人四十,生活要有性,也要有爱文阿糖人到四十,一切仿佛都开始改变,对于别人而言,到了不惑的年纪,一切似乎变得通透起来,但是又少了许多年轻时的冲动,做事自然也会顾及很多。但是作为一个极具魅力的男性,中年的生活不应Mytheresa中国设计师特辑,发掘原创设计力量2022年11月,欧洲领先的奢侈品电商平台Mytheresa欣然宣布推出中国设计师特辑。该项目映射了Mytheresa对中国新生设计力量的支持与信任。Mytheresa的CEOMi一个人看不上你,会有这些生理反应,别自讨没趣文丨桃小菁春风十里不如你,小菁在这里,等着你!爱一个人是没有原因的,你只知道无论何时何地,无论心情好坏,你都希望和他在一起。讨厌一个人也是没有原因的,即使这个人身上有很多优点,你们中国最早的不婚主义者,后来怎样了?世道变了。但世道又没变。1hr20世纪初,西方自由平等的婚恋观传入中国。一些中国青年从包办婚姻解脱,追求婚姻自由。有人开始推崇不婚主义,提出了去家废婚的主张。中国的去家废婚思想,其一分钟了解WiFi72022年,WiFi7无疑成为了万众瞩目的新晋网红。W从WiFi6到WiFi6E,再到WiFi7,科技前进步的步伐越来越来,网络速度越来越快。WiFi7问世后,无疑是最快的WiFi
电脑跑分90万以上是什么体验,大概要多少钱?电脑跑分90万以上是什么体验,大概要多少钱?说实话这个跑分从某种意义上来说其实没有太大的参考价值,真正懂硬件的他们压根就不回去关注跑分,只要一看硬件型号就知道他性能如何了,真正来看蒋兴权宫鲁鸣崔万军等名帅纷纷出山,中国男篮是否有救了?老将出山,一个顶两个,用他们丰富的执教经验来为中国男篮发挥余热,可喜可贺!鉴于中国男篮状况,他们也救不了中国男篮。他们的出山,也暴露了中国篮球教练队伍在隐患,教练阶梯建没出现了问题联想到底为中国科技做了些什么?联想是块金字招牌,但是在改制后,被柳传志,杨元庆一帮人搞成了组装厂,打着科技的名头,从银行低息贷款来放,高利贷成了金融公司,演义界就不能用外籍演员,联想必须赶走洋高管等外籍人员。希中国女排世锦赛能夺冠吗?2018年世界女子排球锦标赛将于2018年9月29日10月20日在日本举行。分组情况如下A组日本荷兰阿根廷德国喀麦隆墨西哥B组中国意大利土耳其保加利亚加拿大古巴C组美国俄罗斯韩国泰中国联通市盈率这么高市净率不到1,有长线持有的价值吗?感谢邀请!中国联通在最近一段时间里面走势很强。主要是因为有5G的概念在里面。中国联通在去年确实表现不怎么样,市盈率也很高。但随着半年报的披露,中国联通现在的业绩也还算可以。市盈率方谁才是中国象棋第一人?谢谢棋友的问题,我是从小就喜欢下象棋,下了10多年的棋,现在天天象棋上业7的水平,对棋界的是非常了解,至于谁排第一,一个年代和一个年代的第一人不同。在50年代末到60年代初,应该来中国男足下一场将迎战沙特。大家认为中国男足赢球的几率有多大?只要有王刚,于大宝,张稀哲三员悍将出现在沙特的体育场,再加上只有中国教练才能带好中国队的教练,比赛结果就已经注定,但求别把深夜看球的球迷气死就行。中国男足下一场将迎战沙特,大家认为中国十大面条有陕西的吗?如果没有,是因为什么?面条,是我国三大主食之一。就喜欢吃面条的人数来讲,以省而论,陕西可以名列三甲之中,喜爱面条的程度,一点儿也不比河南,山西的人差!陕西调侃的十大怪之一,就是一天不吃面条,嘟嘟囔囔!就常用主流的企业邮箱有哪些,有规模的企业用什么邮箱办公好?目前腾讯网易都有企业邮箱的,免费的和收费的都有,免费的限制较大,安全稳定性以及邮件到达率无法保证,有规模的企业公办建议使用付费版企业邮箱,性价比高的企业邮箱可以看互易的,稳定安全,假如一家公司的年营业额是1000万,那么到手的利润是多少?一千万营业额,以餐饮火锅店举例。一年一千万日均3万,如果500桌,那就是60桌一天,如果翻台两次,那就是15个桌位。继续推算每个桌位平均面积8平方,那楼面就是120平方米。过道,厨开车单手打方向盘,是技术好还是为了耍酷?开门见山!作为一个实际驾龄超过15年的司机,可以负责任的告诉你单手握方向盘开车既不是因为技术好,也不是为了耍酷。这事儿要分左右手,用姜文的话讲就是头猪,它也得分左右蹄啊!所以思想不