From 07562200a704b8eaf9c1d080bf8bd12165a97647 Mon Sep 17 00:00:00 2001
From: zhoudw <zhoudw@infobird.com>
Date: Mon, 10 Jan 2022 10:58:45 +0800
Subject: [PATCH] 框架

---
 src/views/msg/msg.vue              |   24 
 src/assets/images/clock.svg        |    3 
 build/check-versions.js            |   54 
 src/store/mutationTypes.js         |   74 +
 config/dev.env.js                  |   11 
 src/views/voice/list.vue           |  258 ++++
 index.html                         |   16 
 src/store/getters.js               |    3 
 build/vue-loader.conf.js           |   22 
 src/components/HelloWorld.vue      |   34 
 src/sdk/makeAction.js              |   75 +
 config/index.js                    |   81 +
 src/store/index.js                 |   16 
 src/components/Tabbar.vue          |   28 
 src/sdk/cache.js                   |   21 
 src/router/index.js                |  109 +
 src/sdk/require.js                 |  169 ++
 README.md                          |   21 
 src/main.js                        |   20 
 src/store/actions.js               |   26 
 static/.gitkeep                    |    0 
 src/views/home.vue                 |   43 
 src/assets/styles/default.less     |  151 ++
 src/assets/images/service.png      |    0 
 .gitignore                         |   14 
 build/webpack.dev.conf.js          |   95 +
 config/prod.env.js                 |    4 
 build/logo.png                     |    0 
 src/mixins/test-mixin.js           |    7 
 src/views/iframe/iframe.vue        |  186 +++
 src/views/voice/list copy.vue      |  237 ++++
 .postcssrc.js                      |   23 
 build/build.js                     |   41 
 src/store/mutations.js             |   30 
 build/utils.js                     |  101 +
 src/views/vant.vue                 |  258 ++++
 src/assets/images/ai.png           |    0 
 src/vant.js                        |  102 +
 src/utils/utils.js                 |   32 
 src/assets/logo.png                |    0 
 src/store/state.js                 |    3 
 src/views/login/login copy.vue     |  263 ++++
 src/views/login/login.vue          |  344 +++++
 src/assets/images/scene_banner.png |    0 
 build/webpack.prod.conf.js         |  145 ++
 build/webpack.base.conf.js         |   88 +
 package.json                       |   71 +
 src/views/user/user.vue            |  120 ++
 src/App.vue                        |   46 
 49 files changed, 3,467 insertions(+), 2 deletions(-)

diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..ed9eac0
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,14 @@
+.DS_Store
+node_modules/
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+package-lock.json*
+
+# Editor directories and files
+.idea
+.vscode
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
diff --git a/.postcssrc.js b/.postcssrc.js
new file mode 100644
index 0000000..90daa4c
--- /dev/null
+++ b/.postcssrc.js
@@ -0,0 +1,23 @@
+// https://github.com/michael-ciniawsky/postcss-load-config
+
+module.exports = {
+  "plugins": {
+    "postcss-import": {},
+    "postcss-url": {},
+    "autoprefixer": {},
+    "postcss-px-to-viewport": {
+      unitToConvert: "px", // 要转化的单位
+      viewportWidth: 750, // UI设计稿的宽度
+      unitPrecision: 6, // 转换后的精度,即小数点位数
+      propList: ["*"], // 指定转换的css属性的单位,*代表全部css属性的单位都进行转换
+      viewportUnit: "vw", // 指定需要转换成的视窗单位,默认vw
+      fontViewportUnit: "vw", // 指定字体需要转换成的视窗单位,默认vw
+      selectorBlackList: ["wrap"], // 指定不转换为视窗单位的类名,
+      minPixelValue: 1, // 默认值1,小于或等于1px则不进行转换
+      mediaQuery: true, // 是否在媒体查询的css代码中也进行转换,默认false
+      replace: true, // 是否转换后直接更换属性值
+      exclude: [/node_modules/], // 设置忽略文件,用正则做目录名匹配
+      landscape: false // 是否处理横屏情况
+    }
+  }
+}
diff --git a/README.md b/README.md
index 7c17308..db3052a 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,21 @@
-## vue-app
+# mc-mobile
 
-教育培训vue版本app
+> 会神移动端
 
+## Build Setup
+
+``` bash
+# install dependencies
+npm install
+
+# serve with hot reload at localhost:8080
+npm run dev
+
+# build for production with minification
+npm run build
+
+# build for production and view the bundle analyzer report
+npm run build --report
+```
+
+For a detailed explanation on how things work, check out the [guide](http://vuejs-templates.github.io/webpack/) and [docs for vue-loader](http://vuejs.github.io/vue-loader).
diff --git a/build/build.js b/build/build.js
new file mode 100644
index 0000000..8f2ad8a
--- /dev/null
+++ b/build/build.js
@@ -0,0 +1,41 @@
+'use strict'
+require('./check-versions')()
+
+process.env.NODE_ENV = 'production'
+
+const ora = require('ora')
+const rm = require('rimraf')
+const path = require('path')
+const chalk = require('chalk')
+const webpack = require('webpack')
+const config = require('../config')
+const webpackConfig = require('./webpack.prod.conf')
+
+const spinner = ora('building for production...')
+spinner.start()
+
+rm(path.join(config.build.assetsRoot, config.build.assetsSubDirectory), err => {
+  if (err) throw err
+  webpack(webpackConfig, (err, stats) => {
+    spinner.stop()
+    if (err) throw err
+    process.stdout.write(stats.toString({
+      colors: true,
+      modules: false,
+      children: false, // If you are using ts-loader, setting this to true will make TypeScript errors show up during build.
+      chunks: false,
+      chunkModules: false
+    }) + '\n\n')
+
+    if (stats.hasErrors()) {
+      console.log(chalk.red('  Build failed with errors.\n'))
+      process.exit(1)
+    }
+
+    console.log(chalk.cyan('  Build complete.\n'))
+    console.log(chalk.yellow(
+      '  Tip: built files are meant to be served over an HTTP server.\n' +
+      '  Opening index.html over file:// won\'t work.\n'
+    ))
+  })
+})
diff --git a/build/check-versions.js b/build/check-versions.js
new file mode 100644
index 0000000..3ef972a
--- /dev/null
+++ b/build/check-versions.js
@@ -0,0 +1,54 @@
+'use strict'
+const chalk = require('chalk')
+const semver = require('semver')
+const packageConfig = require('../package.json')
+const shell = require('shelljs')
+
+function exec (cmd) {
+  return require('child_process').execSync(cmd).toString().trim()
+}
+
+const versionRequirements = [
+  {
+    name: 'node',
+    currentVersion: semver.clean(process.version),
+    versionRequirement: packageConfig.engines.node
+  }
+]
+
+if (shell.which('npm')) {
+  versionRequirements.push({
+    name: 'npm',
+    currentVersion: exec('npm --version'),
+    versionRequirement: packageConfig.engines.npm
+  })
+}
+
+module.exports = function () {
+  const warnings = []
+
+  for (let i = 0; i < versionRequirements.length; i++) {
+    const mod = versionRequirements[i]
+
+    if (!semver.satisfies(mod.currentVersion, mod.versionRequirement)) {
+      warnings.push(mod.name + ': ' +
+        chalk.red(mod.currentVersion) + ' should be ' +
+        chalk.green(mod.versionRequirement)
+      )
+    }
+  }
+
+  if (warnings.length) {
+    console.log('')
+    console.log(chalk.yellow('To use this template, you must update following to modules:'))
+    console.log()
+
+    for (let i = 0; i < warnings.length; i++) {
+      const warning = warnings[i]
+      console.log('  ' + warning)
+    }
+
+    console.log()
+    process.exit(1)
+  }
+}
diff --git a/build/logo.png b/build/logo.png
new file mode 100644
index 0000000..f3d2503
--- /dev/null
+++ b/build/logo.png
Binary files differ
diff --git a/build/utils.js b/build/utils.js
new file mode 100644
index 0000000..e534fb0
--- /dev/null
+++ b/build/utils.js
@@ -0,0 +1,101 @@
+'use strict'
+const path = require('path')
+const config = require('../config')
+const ExtractTextPlugin = require('extract-text-webpack-plugin')
+const packageConfig = require('../package.json')
+
+exports.assetsPath = function (_path) {
+  const assetsSubDirectory = process.env.NODE_ENV === 'production'
+    ? config.build.assetsSubDirectory
+    : config.dev.assetsSubDirectory
+
+  return path.posix.join(assetsSubDirectory, _path)
+}
+
+exports.cssLoaders = function (options) {
+  options = options || {}
+
+  const cssLoader = {
+    loader: 'css-loader',
+    options: {
+      sourceMap: options.sourceMap
+    }
+  }
+
+  const postcssLoader = {
+    loader: 'postcss-loader',
+    options: {
+      sourceMap: options.sourceMap
+    }
+  }
+
+  // generate loader string to be used with extract text plugin
+  function generateLoaders (loader, loaderOptions) {
+    const loaders = options.usePostCSS ? [cssLoader, postcssLoader] : [cssLoader]
+
+    if (loader) {
+      loaders.push({
+        loader: loader + '-loader',
+        options: Object.assign({}, loaderOptions, {
+          sourceMap: options.sourceMap
+        })
+      })
+    }
+
+    // Extract CSS when that option is specified
+    // (which is the case during production build)
+    if (options.extract) {
+      return ExtractTextPlugin.extract({
+        use: loaders,
+        fallback: 'vue-style-loader'
+      })
+    } else {
+      return ['vue-style-loader'].concat(loaders)
+    }
+  }
+
+  // https://vue-loader.vuejs.org/en/configurations/extract-css.html
+  return {
+    css: generateLoaders(),
+    postcss: generateLoaders(),
+    less: generateLoaders('less'),
+    sass: generateLoaders('sass', { indentedSyntax: true }),
+    scss: generateLoaders('sass'),
+    stylus: generateLoaders('stylus'),
+    styl: generateLoaders('stylus')
+  }
+}
+
+// Generate loaders for standalone style files (outside of .vue)
+exports.styleLoaders = function (options) {
+  const output = []
+  const loaders = exports.cssLoaders(options)
+
+  for (const extension in loaders) {
+    const loader = loaders[extension]
+    output.push({
+      test: new RegExp('\\.' + extension + '$'),
+      use: loader
+    })
+  }
+
+  return output
+}
+
+exports.createNotifierCallback = () => {
+  const notifier = require('node-notifier')
+
+  return (severity, errors) => {
+    if (severity !== 'error') return
+
+    const error = errors[0]
+    const filename = error.file && error.file.split('!').pop()
+
+    notifier.notify({
+      title: packageConfig.name,
+      message: severity + ': ' + error.name,
+      subtitle: filename || '',
+      icon: path.join(__dirname, 'logo.png')
+    })
+  }
+}
diff --git a/build/vue-loader.conf.js b/build/vue-loader.conf.js
new file mode 100644
index 0000000..33ed58b
--- /dev/null
+++ b/build/vue-loader.conf.js
@@ -0,0 +1,22 @@
+'use strict'
+const utils = require('./utils')
+const config = require('../config')
+const isProduction = process.env.NODE_ENV === 'production'
+const sourceMapEnabled = isProduction
+  ? config.build.productionSourceMap
+  : config.dev.cssSourceMap
+
+module.exports = {
+  loaders: utils.cssLoaders({
+    sourceMap: sourceMapEnabled,
+    extract: isProduction
+  }),
+  cssSourceMap: sourceMapEnabled,
+  cacheBusting: config.dev.cacheBusting,
+  transformToRequire: {
+    video: ['src', 'poster'],
+    source: 'src',
+    img: 'src',
+    image: 'xlink:href'
+  }
+}
diff --git a/build/webpack.base.conf.js b/build/webpack.base.conf.js
new file mode 100644
index 0000000..bf95aa9
--- /dev/null
+++ b/build/webpack.base.conf.js
@@ -0,0 +1,88 @@
+'use strict'
+const path = require('path')
+const utils = require('./utils')
+const config = require('../config')
+const vueLoaderConfig = require('./vue-loader.conf')
+var webpack = require('webpack');
+
+function resolve(dir) {
+    return path.join(__dirname, '..', dir)
+}
+
+
+
+module.exports = {
+    context: path.resolve(__dirname, '../'),
+    entry: {
+        app: './src/main.js'
+    },
+    output: {
+        path: config.build.assetsRoot,
+        filename: '[name].js',
+        publicPath: process.env.NODE_ENV === 'production' ?
+            config.build.assetsPublicPath : config.dev.assetsPublicPath
+    },
+    resolve: {
+        extensions: ['.js', '.vue', '.json'],
+        alias: {
+            'vue$': 'vue/dist/vue.esm.js',
+            '@': resolve('src'),
+        }
+    },
+    module: {
+        rules: [{
+                test: /\.vue$/,
+                loader: 'vue-loader',
+                options: vueLoaderConfig
+            },
+            {
+                test: /\.js$/,
+                loader: 'babel-loader',
+                include: [resolve('src'), resolve('test'), resolve('node_modules/webpack-dev-server/client')]
+            },
+            {
+                test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
+                loader: 'url-loader',
+                options: {
+                    limit: 10000,
+                    name: utils.assetsPath('img/[name].[hash:7].[ext]')
+                }
+            },
+            {
+                test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/,
+                loader: 'url-loader',
+                options: {
+                    limit: 10000,
+                    name: utils.assetsPath('media/[name].[hash:7].[ext]')
+                }
+            },
+            {
+                test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
+                loader: 'url-loader',
+                options: {
+                    limit: 10000,
+                    name: utils.assetsPath('fonts/[name].[hash:7].[ext]')
+                }
+            }
+        ]
+    },
+    node: {
+        // prevent webpack from injecting useless setImmediate polyfill because Vue
+        // source contains it (although only uses it if it's native).
+        setImmediate: false,
+        // prevent webpack from injecting mocks to Node native modules
+        // that does not make sense for the client
+        dgram: 'empty',
+        fs: 'empty',
+        net: 'empty',
+        tls: 'empty',
+        child_process: 'empty'
+    },
+    //在最后添加一个plugins配置
+    plugins: [
+        new webpack.ProvidePlugin({
+            $: "jquery",
+            jQuery: "jquery"
+        })
+    ],
+}
\ No newline at end of file
diff --git a/build/webpack.dev.conf.js b/build/webpack.dev.conf.js
new file mode 100644
index 0000000..070ae22
--- /dev/null
+++ b/build/webpack.dev.conf.js
@@ -0,0 +1,95 @@
+'use strict'
+const utils = require('./utils')
+const webpack = require('webpack')
+const config = require('../config')
+const merge = require('webpack-merge')
+const path = require('path')
+const baseWebpackConfig = require('./webpack.base.conf')
+const CopyWebpackPlugin = require('copy-webpack-plugin')
+const HtmlWebpackPlugin = require('html-webpack-plugin')
+const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin')
+const portfinder = require('portfinder')
+
+const HOST = process.env.HOST
+const PORT = process.env.PORT && Number(process.env.PORT)
+
+const devWebpackConfig = merge(baseWebpackConfig, {
+  module: {
+    rules: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap, usePostCSS: true })
+  },
+  // cheap-module-eval-source-map is faster for development
+  devtool: config.dev.devtool,
+
+  // these devServer options should be customized in /config/index.js
+  devServer: {
+    clientLogLevel: 'warning',
+    historyApiFallback: {
+      rewrites: [
+        { from: /.*/, to: path.posix.join(config.dev.assetsPublicPath, 'index.html') },
+      ],
+    },
+    hot: true,
+    contentBase: false, // since we use CopyWebpackPlugin.
+    compress: true,
+    host: HOST || config.dev.host,
+    port: PORT || config.dev.port,
+    open: config.dev.autoOpenBrowser,
+    overlay: config.dev.errorOverlay
+      ? { warnings: false, errors: true }
+      : false,
+    publicPath: config.dev.assetsPublicPath,
+    proxy: config.dev.proxyTable,
+    quiet: true, // necessary for FriendlyErrorsPlugin
+    watchOptions: {
+      poll: config.dev.poll,
+    }
+  },
+  plugins: [
+    new webpack.DefinePlugin({
+      'process.env': require('../config/dev.env')
+    }),
+    new webpack.HotModuleReplacementPlugin(),
+    new webpack.NamedModulesPlugin(), // HMR shows correct file names in console on update.
+    new webpack.NoEmitOnErrorsPlugin(),
+    // https://github.com/ampedandwired/html-webpack-plugin
+    new HtmlWebpackPlugin({
+      filename: 'index.html',
+      template: 'index.html',
+      inject: true
+    }),
+    // copy custom static assets
+    new CopyWebpackPlugin([
+      {
+        from: path.resolve(__dirname, '../static'),
+        to: config.dev.assetsSubDirectory,
+        ignore: ['.*']
+      }
+    ])
+  ]
+})
+
+module.exports = new Promise((resolve, reject) => {
+  portfinder.basePort = process.env.PORT || config.dev.port
+  portfinder.getPort((err, port) => {
+    if (err) {
+      reject(err)
+    } else {
+      // publish the new Port, necessary for e2e tests
+      process.env.PORT = port
+      // add port to devServer config
+      devWebpackConfig.devServer.port = port
+
+      // Add FriendlyErrorsPlugin
+      devWebpackConfig.plugins.push(new FriendlyErrorsPlugin({
+        compilationSuccessInfo: {
+          messages: [`Your application is running here: http://${devWebpackConfig.devServer.host}:${port}`],
+        },
+        onErrors: config.dev.notifyOnErrors
+        ? utils.createNotifierCallback()
+        : undefined
+      }))
+
+      resolve(devWebpackConfig)
+    }
+  })
+})
diff --git a/build/webpack.prod.conf.js b/build/webpack.prod.conf.js
new file mode 100644
index 0000000..d9f99f6
--- /dev/null
+++ b/build/webpack.prod.conf.js
@@ -0,0 +1,145 @@
+'use strict'
+const path = require('path')
+const utils = require('./utils')
+const webpack = require('webpack')
+const config = require('../config')
+const merge = require('webpack-merge')
+const baseWebpackConfig = require('./webpack.base.conf')
+const CopyWebpackPlugin = require('copy-webpack-plugin')
+const HtmlWebpackPlugin = require('html-webpack-plugin')
+const ExtractTextPlugin = require('extract-text-webpack-plugin')
+const OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin')
+const UglifyJsPlugin = require('uglifyjs-webpack-plugin')
+
+const env = require('../config/prod.env')
+
+const webpackConfig = merge(baseWebpackConfig, {
+  module: {
+    rules: utils.styleLoaders({
+      sourceMap: config.build.productionSourceMap,
+      extract: true,
+      usePostCSS: true
+    })
+  },
+  devtool: config.build.productionSourceMap ? config.build.devtool : false,
+  output: {
+    path: config.build.assetsRoot,
+    filename: utils.assetsPath('js/[name].[chunkhash].js'),
+    chunkFilename: utils.assetsPath('js/[id].[chunkhash].js')
+  },
+  plugins: [
+    // http://vuejs.github.io/vue-loader/en/workflow/production.html
+    new webpack.DefinePlugin({
+      'process.env': env
+    }),
+    new UglifyJsPlugin({
+      uglifyOptions: {
+        compress: {
+          warnings: false
+        }
+      },
+      sourceMap: config.build.productionSourceMap,
+      parallel: true
+    }),
+    // extract css into its own file
+    new ExtractTextPlugin({
+      filename: utils.assetsPath('css/[name].[contenthash].css'),
+      // Setting the following option to `false` will not extract CSS from codesplit chunks.
+      // Their CSS will instead be inserted dynamically with style-loader when the codesplit chunk has been loaded by webpack.
+      // It's currently set to `true` because we are seeing that sourcemaps are included in the codesplit bundle as well when it's `false`, 
+      // increasing file size: https://github.com/vuejs-templates/webpack/issues/1110
+      allChunks: true,
+    }),
+    // Compress extracted CSS. We are using this plugin so that possible
+    // duplicated CSS from different components can be deduped.
+    new OptimizeCSSPlugin({
+      cssProcessorOptions: config.build.productionSourceMap
+        ? { safe: true, map: { inline: false } }
+        : { safe: true }
+    }),
+    // generate dist index.html with correct asset hash for caching.
+    // you can customize output by editing /index.html
+    // see https://github.com/ampedandwired/html-webpack-plugin
+    new HtmlWebpackPlugin({
+      filename: config.build.index,
+      template: 'index.html',
+      inject: true,
+      minify: {
+        removeComments: true,
+        collapseWhitespace: true,
+        removeAttributeQuotes: true
+        // more options:
+        // https://github.com/kangax/html-minifier#options-quick-reference
+      },
+      // necessary to consistently work with multiple chunks via CommonsChunkPlugin
+      chunksSortMode: 'dependency'
+    }),
+    // keep module.id stable when vendor modules does not change
+    new webpack.HashedModuleIdsPlugin(),
+    // enable scope hoisting
+    new webpack.optimize.ModuleConcatenationPlugin(),
+    // split vendor js into its own file
+    new webpack.optimize.CommonsChunkPlugin({
+      name: 'vendor',
+      minChunks (module) {
+        // any required modules inside node_modules are extracted to vendor
+        return (
+          module.resource &&
+          /\.js$/.test(module.resource) &&
+          module.resource.indexOf(
+            path.join(__dirname, '../node_modules')
+          ) === 0
+        )
+      }
+    }),
+    // extract webpack runtime and module manifest to its own file in order to
+    // prevent vendor hash from being updated whenever app bundle is updated
+    new webpack.optimize.CommonsChunkPlugin({
+      name: 'manifest',
+      minChunks: Infinity
+    }),
+    // This instance extracts shared chunks from code splitted chunks and bundles them
+    // in a separate chunk, similar to the vendor chunk
+    // see: https://webpack.js.org/plugins/commons-chunk-plugin/#extra-async-commons-chunk
+    new webpack.optimize.CommonsChunkPlugin({
+      name: 'app',
+      async: 'vendor-async',
+      children: true,
+      minChunks: 3
+    }),
+
+    // copy custom static assets
+    new CopyWebpackPlugin([
+      {
+        from: path.resolve(__dirname, '../static'),
+        to: config.build.assetsSubDirectory,
+        ignore: ['.*']
+      }
+    ])
+  ]
+})
+
+if (config.build.productionGzip) {
+  const CompressionWebpackPlugin = require('compression-webpack-plugin')
+
+  webpackConfig.plugins.push(
+    new CompressionWebpackPlugin({
+      asset: '[path].gz[query]',
+      algorithm: 'gzip',
+      test: new RegExp(
+        '\\.(' +
+        config.build.productionGzipExtensions.join('|') +
+        ')$'
+      ),
+      threshold: 10240,
+      minRatio: 0.8
+    })
+  )
+}
+
+if (config.build.bundleAnalyzerReport) {
+  const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
+  webpackConfig.plugins.push(new BundleAnalyzerPlugin())
+}
+
+module.exports = webpackConfig
diff --git a/config/dev.env.js b/config/dev.env.js
new file mode 100644
index 0000000..9e81873
--- /dev/null
+++ b/config/dev.env.js
@@ -0,0 +1,11 @@
+'use strict'
+const merge = require('webpack-merge')
+const prodEnv = require('./prod.env')
+
+module.exports = merge(prodEnv, {
+    NODE_ENV: '"development"',
+    APP_SERVER_PORT: '"50057"',
+    INTERFACE_SERVER_PATH: '"https://testetsapp.51huishen.com/"',
+    ETS_APP_NEWRECORD_URL: '"app/voice/newrecord"',
+    WX_API_URL: '"http://res.wx.qq.com/open/js/jweixin-1.2.0.js"', // 企业微信js客户端sdk
+})
\ No newline at end of file
diff --git a/config/index.js b/config/index.js
new file mode 100644
index 0000000..a59b0b0
--- /dev/null
+++ b/config/index.js
@@ -0,0 +1,81 @@
+'use strict'
+// Template version: 1.3.1
+// see http://vuejs-templates.github.io/webpack for documentation.
+
+const path = require('path')
+const env = process.env.NODE_ENV === "production" ?
+    require("./prod.env") : process.env.NODE_ENV === 'test' ?
+    require("./test.env") : require("./dev.env");
+
+module.exports = {
+    dev: {
+
+        // Paths
+        assetsSubDirectory: 'static',
+        assetsPublicPath: '/',
+        proxyTable: {
+            '/app': {
+                target: env.INTERFACE_SERVER_PATH.slice(1, env.INTERFACE_SERVER_PATH.length - 1),
+                changeOrigin: true, // 允许跨域
+                secure: false,
+                pathRewrite: {
+                    '^/app': '/app'
+                }
+            },
+        },
+
+        // Various Dev Server settings
+        host: '127.0.0.1', // can be overwritten by process.env.HOST
+        port: 3001, // can be overwritten by process.env.PORT, if port is in use, a free one will be determined
+        autoOpenBrowser: false,
+        errorOverlay: true,
+        notifyOnErrors: true,
+        poll: false, // https://webpack.js.org/configuration/dev-server/#devserver-watchoptions-
+
+
+        /**
+         * Source Maps
+         */
+
+        // https://webpack.js.org/configuration/devtool/#development
+        devtool: 'cheap-module-eval-source-map',
+
+        // If you have problems debugging vue-files in devtools,
+        // set this to false - it *may* help
+        // https://vue-loader.vuejs.org/en/options.html#cachebusting
+        cacheBusting: true,
+
+        cssSourceMap: true
+    },
+
+    build: {
+        // Template for index.html
+        index: path.resolve(__dirname, '../dist/index.html'),
+
+        // Paths
+        assetsRoot: path.resolve(__dirname, '../dist'),
+        assetsSubDirectory: 'static',
+        assetsPublicPath: '/',
+
+        /**
+         * Source Maps
+         */
+
+        productionSourceMap: true,
+        // https://webpack.js.org/configuration/devtool/#production
+        devtool: '#source-map',
+
+        // Gzip off by default as many popular static hosts such as
+        // Surge or Netlify already gzip all static assets for you.
+        // Before setting to `true`, make sure to:
+        // npm install --save-dev compression-webpack-plugin
+        productionGzip: false,
+        productionGzipExtensions: ['js', 'css'],
+
+        // Run the build command with an extra argument to
+        // View the bundle analyzer report after build finishes:
+        // `npm run build --report`
+        // Set to `true` or `false` to always turn it on or off
+        bundleAnalyzerReport: process.env.npm_config_report
+    }
+}
\ No newline at end of file
diff --git a/config/prod.env.js b/config/prod.env.js
new file mode 100644
index 0000000..a6f9976
--- /dev/null
+++ b/config/prod.env.js
@@ -0,0 +1,4 @@
+'use strict'
+module.exports = {
+  NODE_ENV: '"production"'
+}
diff --git a/index.html b/index.html
new file mode 100644
index 0000000..e317095
--- /dev/null
+++ b/index.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+    <meta charset="utf-8">
+    <meta name="viewport" content="width=device-width,initial-scale=1.0">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, viewport-fit=cover" />
+    <title>智能对练</title>
+</head>
+
+<body>
+    <div id="app"></div>
+    <!-- built files will be auto injected -->
+</body>
+
+</html>
\ No newline at end of file
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..108f00d
--- /dev/null
+++ b/package.json
@@ -0,0 +1,71 @@
+{
+    "name": "mc-mobile",
+    "version": "1.0.0",
+    "description": "会神移动端",
+    "author": "yehong <yhjob910114@163.com>",
+    "private": true,
+    "scripts": {
+        "dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js",
+        "start": "npm run dev",
+        "build": "node build/build.js"
+    },
+    "dependencies": {
+        "axios": "^0.21.1",
+        "jquery": "^3.6.0",
+        "js-md5": "^0.7.3",
+        "vant": "^2.12.37",
+        "vue": "^2.5.2",
+        "vue-router": "^3.0.1",
+        "vuex": "^3.6.2"
+    },
+    "devDependencies": {
+        "autoprefixer": "^7.1.2",
+        "babel-core": "^6.22.1",
+        "babel-helper-vue-jsx-merge-props": "^2.0.3",
+        "babel-loader": "^7.1.1",
+        "babel-plugin-import": "^1.13.3",
+        "babel-plugin-syntax-jsx": "^6.18.0",
+        "babel-plugin-transform-runtime": "^6.22.0",
+        "babel-plugin-transform-vue-jsx": "^3.5.0",
+        "babel-preset-env": "^1.3.2",
+        "babel-preset-stage-2": "^6.22.0",
+        "chalk": "^2.0.1",
+        "copy-webpack-plugin": "^4.0.1",
+        "css-loader": "^0.28.0",
+        "extract-text-webpack-plugin": "^3.0.0",
+        "file-loader": "^1.1.4",
+        "friendly-errors-webpack-plugin": "^1.6.1",
+        "html-webpack-plugin": "^2.30.1",
+        "less": "^4.1.1",
+        "less-loader": "^5.0.0",
+        "node-notifier": "^5.1.2",
+        "optimize-css-assets-webpack-plugin": "^3.2.0",
+        "ora": "^1.2.0",
+        "portfinder": "^1.0.13",
+        "postcss-import": "^11.0.0",
+        "postcss-loader": "^2.0.8",
+        "postcss-px-to-viewport": "^1.1.1",
+        "postcss-url": "^7.2.1",
+        "rimraf": "^2.6.0",
+        "semver": "^5.3.0",
+        "shelljs": "^0.7.6",
+        "uglifyjs-webpack-plugin": "^1.1.1",
+        "url-loader": "^0.5.8",
+        "vue-loader": "^13.3.0",
+        "vue-style-loader": "^3.0.1",
+        "vue-template-compiler": "^2.5.2",
+        "webpack": "^3.6.0",
+        "webpack-bundle-analyzer": "^2.9.0",
+        "webpack-dev-server": "^2.9.1",
+        "webpack-merge": "^4.1.0"
+    },
+    "engines": {
+        "node": ">= 6.0.0",
+        "npm": ">= 3.0.0"
+    },
+    "browserslist": [
+        "> 1%",
+        "last 2 versions",
+        "not ie <= 8"
+    ]
+}
\ No newline at end of file
diff --git a/src/App.vue b/src/App.vue
new file mode 100644
index 0000000..20592e2
--- /dev/null
+++ b/src/App.vue
@@ -0,0 +1,46 @@
+<template>
+  <div id="app">
+    <router-view/>
+  </div>
+</template>
+<script>
+import { mapActions, mapState } from "vuex";
+export default {
+  name: 'App',
+  provide() {
+    return {
+      reload: this.reload
+    }
+  },
+  data() {
+    return {
+      isRouterAlive: true
+    }
+  },
+  computed: {
+    ...mapState({
+      token: "token",
+    }),
+  },
+  watch:{},
+  created() {
+    try {
+      let localToken = JSON.parse(localStorage.getItem("token"));
+      this.set_token(localToken);
+    } catch (error) {
+      console.info("用户未登录");
+    }
+  },
+  methods: {
+    ...mapActions({
+      set_token: "set_token",
+    }),
+    reload() {
+      this.isRouterAlive = false
+      this.$nextTick(function() {
+        this.isRouterAlive = true
+      })
+    }
+  },
+}
+</script>
diff --git a/src/assets/images/ai.png b/src/assets/images/ai.png
new file mode 100644
index 0000000..14ca861
--- /dev/null
+++ b/src/assets/images/ai.png
Binary files differ
diff --git a/src/assets/images/clock.svg b/src/assets/images/clock.svg
new file mode 100644
index 0000000..98cf3d9
--- /dev/null
+++ b/src/assets/images/clock.svg
@@ -0,0 +1,3 @@
+<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M6.99995 4.2002V7.0002L9.09995 9.1002M13.3 7.0002C13.3 7.82752 13.137 8.64675 12.8204 9.4111C12.5038 10.1755 12.0397 10.87 11.4547 11.455C10.8697 12.04 10.1752 12.504 9.41086 12.8206C8.64651 13.1372 7.82728 13.3002 6.99995 13.3002C6.17262 13.3002 5.3534 13.1372 4.58905 12.8206C3.82469 12.504 3.13019 12.04 2.54518 11.455C1.96017 10.87 1.49611 10.1755 1.17951 9.4111C0.862905 8.64675 0.699951 7.82752 0.699951 7.0002C0.699951 5.32933 1.3637 3.7269 2.54518 2.54542C3.72666 1.36394 5.32909 0.700195 6.99995 0.700195C8.67082 0.700195 10.2732 1.36394 11.4547 2.54542C12.6362 3.7269 13.3 5.32933 13.3 7.0002Z" stroke="#585D6B" stroke-linecap="round" stroke-linejoin="round"/>
+</svg>
diff --git a/src/assets/images/scene_banner.png b/src/assets/images/scene_banner.png
new file mode 100644
index 0000000..974b5bb
--- /dev/null
+++ b/src/assets/images/scene_banner.png
Binary files differ
diff --git a/src/assets/images/service.png b/src/assets/images/service.png
new file mode 100644
index 0000000..21fa048
--- /dev/null
+++ b/src/assets/images/service.png
Binary files differ
diff --git a/src/assets/logo.png b/src/assets/logo.png
new file mode 100644
index 0000000..f3d2503
--- /dev/null
+++ b/src/assets/logo.png
Binary files differ
diff --git a/src/assets/styles/default.less b/src/assets/styles/default.less
new file mode 100644
index 0000000..2f3b89b
--- /dev/null
+++ b/src/assets/styles/default.less
@@ -0,0 +1,151 @@
+@navbar_height: 46px;
+@tabs_wrap_height: 44px;
+
+// 颜色
+@primary_color: #1CA2FF;
+@info_color: #2672FF;
+@simple_color: #00B07A;
+@disabled_color: #AAAAAA;
+@error_color: #F84343;
+
+@price_color: #ff8616;
+
+@bg_color: #f4f4f4;
+
+// 主要字体
+@primary_font_color: #333333;
+@primary_font_weight: 600;
+
+@button_radius: 4px;
+
+@mask: rgba(51, 51, 51, .45);
+
+@sticky_shadow: 0px 3px 6px 0px rgba(51,51,51,0.10);
+
+html,
+body,
+#app {
+  height: 100%;
+  overflow: auto;
+  font-family: 'Avenir', Helvetica, Arial, sans-serif;
+  -webkit-font-smoothing: antialiased;
+  -moz-osx-font-smoothing: grayscale;
+  font-size: 16px;
+  background-color: #f8f8f8;
+}
+
+* {
+  margin: 0;
+  padding: 0;
+  -webkit-touch-callout: none;
+  -webkit-user-select: none;
+  -khtml-user-select: none;
+  -moz-user-select: none;
+  -ms-user-select: none;
+  user-select: none;
+  box-sizing: border-box;
+}
+
+input,
+textarea {
+  -webkit-user-select: auto;
+  -khtml-user-select: auto;
+  -moz-user-select: auto;
+  -ms-user-select: auto;
+  -o-user-select: auto;
+  user-select: auto;
+}
+
+// 弹窗
+.van-overlay {
+  background-color: @mask;
+}
+
+.van-dialog {
+  border-radius: 10px;
+}
+
+.van-dialog__content {
+  height: 100%;
+}
+
+.van-dialog__confirm {
+  color: @info_color;
+}
+
+.van-sticky {
+  box-shadow: @sticky_shadow;
+}
+
+// NavBar
+.van-nav-bar {
+  .van-icon {
+    color: @primary_font_color;
+  }
+}
+.van-nav-bar--fixed {
+  box-shadow: @sticky_shadow;
+}
+.van-nav-bar__title {
+  font-weight: @primary_font_weight;
+}
+.van-nav-bar__content {
+  height: @navbar_height;
+}
+
+// Tab
+.van-nav-bar+.van-tabs {
+  top: @navbar_height;
+
+  .van-sticky {
+    position: fixed;
+    top: @navbar_height;
+    left: 0;
+    right: 0;
+    z-index: 99;
+  }
+
+  .van-tabs__content {
+    margin-top: @tabs_wrap_height;
+  }
+}
+
+.van-tab {
+  font-weight: 400;
+}
+
+.van-tab--active {
+  color: @primary_color;
+  font-weight: 600;
+}
+
+.van-tabs__line {
+  background-color: @primary_color;
+  width: 20px;
+  bottom: 22px;
+  height: 2px;
+}
+
+// button
+.van-button {
+  border-radius: @button_radius;
+}
+.van-button--primary {
+  background-color: @primary_color !important;
+  border-color: @primary_color !important;
+}
+
+// field
+.van-field__error-message {
+  position: absolute;
+  bottom: 0;
+}
+
+.van-field--error .van-field__control::placeholder {
+  color: #cccccc !important;
+}
+
+.van-field__control{
+  color: #323233 !important;
+  font-weight: 500 !important;
+}
diff --git a/src/components/HelloWorld.vue b/src/components/HelloWorld.vue
new file mode 100644
index 0000000..4fbb342
--- /dev/null
+++ b/src/components/HelloWorld.vue
@@ -0,0 +1,34 @@
+<template>
+  <div class="hello">
+    <h1>{{ msg }}</h1>
+  </div>
+</template>
+
+<script>
+export default {
+  name: 'HelloWorld',
+  data () {
+    return {
+      msg: '全局组件在这里'
+    }
+  }
+}
+</script>
+
+<!-- Add "scoped" attribute to limit CSS to this component only -->
+<style scoped>
+h1, h2 {
+  font-weight: normal;
+}
+ul {
+  list-style-type: none;
+  padding: 0;
+}
+li {
+  display: inline-block;
+  margin: 0 10px;
+}
+a {
+  color: #42b983;
+}
+</style>
diff --git a/src/components/Tabbar.vue b/src/components/Tabbar.vue
new file mode 100644
index 0000000..a524e8b
--- /dev/null
+++ b/src/components/Tabbar.vue
@@ -0,0 +1,28 @@
+
+<template>
+  <div class="tabber">
+    <van-tabbar v-model="Tabactive">
+      <van-tabbar-item replace to="/list" icon="home-o">首页</van-tabbar-item>
+      <!-- <van-tabbar-item replace to="/msg" icon="chat-o">消息</van-tabbar-item> -->
+      <van-tabbar-item replace to="/user" icon="friends-o">我的</van-tabbar-item>
+    </van-tabbar>
+  </div>
+</template>
+<script>
+export default {
+  props:{
+    active: {
+      type: Number,
+      default: '0',
+    }
+  },
+  data() {
+    return {
+      Tabactive:this.active
+    };
+  },
+}
+</script>
+<style lang="less" scoped>
+
+</style>
diff --git a/src/main.js b/src/main.js
new file mode 100644
index 0000000..01be60c
--- /dev/null
+++ b/src/main.js
@@ -0,0 +1,20 @@
+import Vue from 'vue'
+import App from './App'
+import router from './router'
+import store from './store/index'
+
+// 公共样式
+require('@/assets/styles/default.less')
+
+// Vant组件
+require("@/vant")
+
+Vue.config.productionTip = false
+
+new Vue({
+    el: '#app',
+    router,
+    store,
+    components: { App },
+    template: '<App/>'
+})
\ No newline at end of file
diff --git a/src/mixins/test-mixin.js b/src/mixins/test-mixin.js
new file mode 100644
index 0000000..4222018
--- /dev/null
+++ b/src/mixins/test-mixin.js
@@ -0,0 +1,7 @@
+export default {
+  data() {
+    return {
+      text: "测试mixin"
+    }
+  }
+}
\ No newline at end of file
diff --git a/src/router/index.js b/src/router/index.js
new file mode 100644
index 0000000..d923d3a
--- /dev/null
+++ b/src/router/index.js
@@ -0,0 +1,109 @@
+import Vue from 'vue'
+import Router from 'vue-router'
+import Login from '@/views/login/login.vue'
+import List from '@/views/voice/list.vue'
+import User from '@/views/user/user.vue'
+import Msg from '@/views/msg/msg.vue'
+import Iframe from '@/views/iframe/iframe.vue'
+import Vant from '@/views/vant.vue'
+import store from '../store/index'
+import {
+    isEmpty,
+    getCookieValue
+} from '@/utils/utils.js'
+
+Vue.use(Router)
+
+let router = new Router({
+    mode: "history",
+    routes: [{
+            path: '/login',
+            name: 'login',
+            component: Login
+        },
+        {
+            path: '/list',
+            name: 'list',
+            meta: {
+                requiresAuth: true,
+
+            },
+            component: List
+        },
+        {
+            path: '/user',
+            name: 'user',
+            meta: {
+                requiresAuth: true,
+
+            },
+            component: User
+        },
+        {
+            path: '/msg',
+            name: 'msg',
+            meta: {
+                requiresAuth: true,
+
+            },
+            component: Msg
+        },
+        {
+            path: '/iframe',
+            name: 'iframe',
+            meta: {
+                requiresAuth: true,
+
+            },
+            component: Iframe
+        },
+        {
+            path: '/vant',
+            name: 'vant',
+            component: Vant
+        },
+        {
+            path: "*",
+            redirect: "login"
+        }
+    ]
+})
+router.beforeEach((to, from, next) => {
+    const token = JSON.parse(localStorage.getItem("token"));
+    const cookie = getCookieValue('ets-app');
+    const requiresAuth = to.matched.some(record => record.meta.requiresAuth)
+
+
+    // 页面title
+    if (to.meta.title) { //判断是否有标题
+        // console.log(to.meta.title)
+        document.title = to.meta.title
+    }
+
+    if (isEmpty(cookie)) {
+        localStorage.removeItem("token");
+        store._actions.set_token[0]({});
+    }
+
+    // 需要登录
+    if (requiresAuth) {
+        // 判断cookie是否过期
+        if (!isEmpty(cookie) && !isEmpty(token)) {
+            next();
+        } else {
+            // 无token
+            next({
+                replace: true,
+                path: '/login',
+                query: {
+                    redirect: to.path,
+                    ...to.query
+                }
+            });
+        }
+    } else {
+        // 无需登录
+        next();
+    }
+})
+export default router;
diff --git a/src/sdk/cache.js b/src/sdk/cache.js
new file mode 100644
index 0000000..639d08d
--- /dev/null
+++ b/src/sdk/cache.js
@@ -0,0 +1,21 @@
+function getParseItem(key) {
+  try {
+    const data = localStorage.getItem(`config_${key}`)
+    return JSON.parse(data)
+  } catch (e) {
+    return null
+  }
+}
+
+function setParseItem(key, value) {
+  if (key && value) {
+    localStorage.setItem(`config_${key}`, JSON.stringify(value))
+  }
+}
+
+const cacheUtil = {
+  getParseItem,
+  setParseItem
+}
+
+export default cacheUtil
\ No newline at end of file
diff --git a/src/sdk/makeAction.js b/src/sdk/makeAction.js
new file mode 100644
index 0000000..1c06e0d
--- /dev/null
+++ b/src/sdk/makeAction.js
@@ -0,0 +1,75 @@
+import {
+    request
+} from './require.js'
+import cacheUtil from './cache.js'
+
+/**
+ * 制造请求action
+ * @param method 请求方法: get(默认), post, put等
+ * @param type mutation类型
+ * @param url 请求url
+ * @param defaultQuery 默认参数
+ * @param useCache 是否使用缓存
+ * @param resolve 回调函数(可处理数据)
+ * @returns function
+ */
+function makeAction({
+        method = 'get',
+        type,
+        url,
+        defaultQuery,
+        useCache,
+        config = {}
+    },
+    resolve
+) {
+    /**
+     * @param query 请求上传数据
+     */
+    return ({
+        commit
+    }, {
+        query,
+        isCommit = true
+    } = {}) => {
+        if (config.header && config.header['Content-Type'] !== 'multipart/form-data' && !Array.isArray(query)) {
+            query = {
+                ...defaultQuery,
+                ...query
+            }
+        }
+
+        // get参数特殊处理
+        if (method === 'get' || method === 'delete') {
+            query = {
+                params: query
+            }
+        }
+
+        // 使用缓存
+        const cacheKey = `${method}_${url}_${JSON.stringify(query)}`
+        if (useCache) {
+            const resData = cacheUtil.getParseItem(cacheKey)
+            if (resData) {
+                resolve && resolve(resData)
+                type && isCommit && commit(type, resData.data)
+                return Promise.resolve(resData)
+            }
+        }
+        // 请求
+        return request[method](url, query, config).then(
+            (resData = {}) => {
+                // 使用缓存
+                if (useCache) {
+                    Object.keys(resData).length &&
+                        cacheUtil.setParseItem(cacheKey, resData)
+                }
+                resolve && resolve(resData)
+                type && isCommit && commit(type, resData.data)
+                return Promise.resolve(resData)
+            }
+        )
+    }
+}
+
+export default makeAction
\ No newline at end of file
diff --git a/src/sdk/require.js b/src/sdk/require.js
new file mode 100644
index 0000000..26d1173
--- /dev/null
+++ b/src/sdk/require.js
@@ -0,0 +1,169 @@
+import axios from 'axios'
+import store from '@/store/index.js'
+import md5 from 'js-md5';
+import router from '@/router';
+import { Toast } from 'vant';
+
+const qs = require('qs');
+
+const codeMessage = {
+    200: '成功',
+    201: '新建或修改成功。',
+    202: '进入后台排队。',
+    204: '删除数据成功。',
+    400: '请求错误',
+    401: '用户没有权限。',
+    403: '禁止访问',
+    // 404: '接口地址不存在。',
+    406: '请求的格式不可得。',
+    410: '资源被删除',
+    422: '创建对象时验证错误。',
+    500: '网络错误,请重试',
+    502: '网络错误,请重试',
+    503: '网络错误,请重试',
+    504: '网络错误,请重试'
+}
+
+// 默认配置
+axios.defaults.timeout = 120000
+axios.defaults.headers['Content-Type'] = 'application/x-www-form-urlencoded'
+
+const request = axios.create({
+    baseURL: process.env.PTH_API_ROUTER,
+    withCredentials: false
+})
+
+function mdSign(head, body) {
+    let arr = {};
+    // 对form提交进行特殊处理 file 字段不加入鉴权
+    if (head['Content-Type'] === 'multipart/form-data') {
+        arr = {};
+        for (const key of body.keys()) {
+            if (key !== 'file') arr[key] = body.get(key);
+        }
+    } else {
+        arr = body;
+    }
+    let sign = ''
+    const strArr = Object.entries(arr).map(v => {
+        let val = v[1];
+        if (typeof val == 'object') {
+            val = JSON.stringify(val);
+        }
+        return val != undefined ? `${v[0]}=${val}&` : ''
+    })
+    strArr.push(`time=${head.TIME}&`);
+    strArr.sort()
+    const str = strArr.join('');
+    sign = md5(str).toUpperCase();
+
+    return sign
+}
+
+/**
+ * 配置请求头
+ * @param {*} conf
+ */
+function confRequestHeader(conf = {}) {
+    conf.headers = conf.headers || {}
+    conf.headers.TIME = new Date().getTime();
+    conf.headers.TOKEN = store.state.token;
+    conf.headers.SIGN = mdSign(conf.headers, conf.data || {})
+
+    return conf
+}
+
+/**
+ * 响应拦截
+ * @param {*} res
+ */
+function resolve(res) {
+    let data = res.data;
+    if (typeof res.data == 'string') {
+        try {
+            data = JSON.parse(res.data)
+        } catch (error) {
+            return data
+        }
+    }
+    if (data.code == 0 || data.code == 200 || data.result == 0) {
+        return data
+    }
+    if (data.code === 1084 || data.code === 1083 || data.code === 1085) { // 用户未登录/1085在其他设备登录
+        localStorage.removeItem("token");
+        localStorage.removeItem("userInfo");
+        store.commit("set_token", {});
+        store.commit("set_user_info", {});
+        return Promise.reject(data);
+    }
+
+    if (!res.config.hideMsg) {
+        if (data.code === 701) {
+            Toast.fail("验证码错误或已过期")
+        } else {
+            Toast.fail(data.msg)
+        }
+    }
+    return Promise.reject(data)
+}
+
+/**
+ * 错误拦截,一般为网络错误
+ * @param {*} error
+ */
+function reject(error) {
+    let status
+    try {
+        status = error.response.status
+    } catch (err) {
+        console.error('=====> response error err: ', error, err);
+    }
+    if (
+        error.code === 'ECONNABORTED' &&
+        error.message.indexOf('timeout') !== -1 &&
+        error.config && !error.config.hideMsg
+    ) {
+        Toast.fail("请求超时")
+    }
+    if (error.config && !error.config.hideMsg) {
+        const errortext = codeMessage[status]
+        Toast.fail(errortext || '网络错误')
+    }
+    // 无权限
+    if (status === 401) {
+        router.replace({
+            path: 'login',
+            query: {
+                redirect: router.currentRoute.fullPath
+            }
+        })
+    }
+    return Promise.reject(error);
+}
+
+/**
+ * 请求拦截
+ * @param {*} conf
+ */
+function requistHold(conf) {
+    conf = confRequestHeader(conf);
+    if (conf.headers['Content-Type'] !== 'multipart/form-data') {
+        for (const key in conf.data) {
+            if (conf.data.hasOwnProperty(key)) {
+                const val = conf.data[key];
+                if (typeof val === 'object') {
+                    conf.data[key] = JSON.stringify(val);
+                }
+            }
+        }
+        conf.data = qs.stringify(conf.data)
+    }
+    return conf;
+}
+
+request.interceptors.request.use(requistHold)
+request.interceptors.response.use(resolve, reject)
+
+export {
+    request
+}
\ No newline at end of file
diff --git a/src/store/actions.js b/src/store/actions.js
new file mode 100644
index 0000000..c1050ba
--- /dev/null
+++ b/src/store/actions.js
@@ -0,0 +1,26 @@
+import mutationTypes from '@/store/mutationTypes.js'
+import makeAction from '@/sdk/makeAction.js'
+
+const actions = {
+    set_token({ commit }, obj) {
+        commit('SET_TOKEN', obj);
+    },
+    set_user_info({ commit }, obj) {
+        commit('SET_USER_INFO', obj);
+    },
+    login_out({ commit }) {
+        commit('LOGIN_OUT');
+    },
+    set_cookie({ commit }) {
+        commit('SET_COOKIE');
+    },
+};
+for (const key in mutationTypes) {
+    actions[key] = makeAction({
+        method: mutationTypes[key].method || 'post',
+        url: mutationTypes[key].url,
+        config: mutationTypes[key].config || {}
+    })
+}
+
+export default actions
\ No newline at end of file
diff --git a/src/store/getters.js b/src/store/getters.js
new file mode 100644
index 0000000..336788b
--- /dev/null
+++ b/src/store/getters.js
@@ -0,0 +1,3 @@
+export default {
+    token: state => state.token,
+}
\ No newline at end of file
diff --git a/src/store/index.js b/src/store/index.js
new file mode 100644
index 0000000..a9509fa
--- /dev/null
+++ b/src/store/index.js
@@ -0,0 +1,16 @@
+import Vue from 'vue'
+import Vuex from 'vuex'
+
+import actions from '@/store/actions.js'
+import getters from '@/store/getters.js'
+import mutations from '@/store/mutations.js'
+import state from '@/store/state.js'
+
+Vue.use(Vuex)
+
+export default new Vuex.Store({
+  state,
+  mutations,
+  getters,
+  actions
+})
\ No newline at end of file
diff --git a/src/store/mutationTypes.js b/src/store/mutationTypes.js
new file mode 100644
index 0000000..dc43697
--- /dev/null
+++ b/src/store/mutationTypes.js
@@ -0,0 +1,74 @@
+export default {
+    // 发送验证码
+    sendSms: {
+        method: 'post',
+        url: "/app/loginApi/signInSendSms",
+        config: {
+            hideLoading: true,
+            hideMsg: true
+        }
+    },
+    // 验证码登录
+    codeLogin: {
+        method: 'post',
+        url: "/app/loginApi/codeSignin",
+        config: {
+            hideLoading: true,
+            hideMsg: true
+        }
+    },
+    // 密码登录
+    pwdLogin: {
+        method: 'post',
+        url: "/app/loginApi/login",
+        config: {
+            hideLoading: true,
+            hideMsg: true
+        }
+    },
+    // 获取企业信息列表
+    getCompList: {
+        method: 'post',
+        url: "/app/loginApi/getCompList",
+        config: {
+            hideLoading: true,
+            hideMsg: true
+        }
+    },
+    // 获取企业信息列表
+    newgetList: {
+        method: 'post',
+        url: "/app/voice/newgetList",
+        config: {
+            hideLoading: true,
+            hideMsg: true
+        }
+    },
+    // 获取用户信息
+    getUserInfo: {
+        method: 'post',
+        url: "/app/voice/getUser",
+        config: {
+            hideLoading: true,
+            hideMsg: true
+        }
+    },
+    // 微信鉴权
+    clientconfig: {
+        method: 'post',
+        url: "/app/voice/clientconfig",
+        config: {
+            hideLoading: true,
+            hideMsg: true
+        }
+    },
+    // 退出登录
+    loginout: {
+        method: 'post',
+        url: "/app/loginApi/signOut",
+        config: {
+            hideLoading: true,
+            hideMsg: true
+        }
+    },
+}
\ No newline at end of file
diff --git a/src/store/mutations.js b/src/store/mutations.js
new file mode 100644
index 0000000..0f154b2
--- /dev/null
+++ b/src/store/mutations.js
@@ -0,0 +1,30 @@
+import mutationTypes from '@/store/mutationTypes.js'
+import { isEmpty, setCookieValue } from '@/utils/utils.js'
+
+const mutations = {
+    SET_TOKEN(state, obj) {
+        obj = Object.assign({}, {
+                token: "",
+            }, obj)
+            // if(isEmpty(obj.token) || isEmpty(obj.uid)){
+            //   return false;
+            // }
+        state.token = obj.token;
+        // 判断localstorage是否token且相等,不一致则更新
+        localStorage.setItem('token', JSON.stringify(obj))
+    },
+    LOGIN_OUT(state) {
+        state.token = '';
+        localStorage.removeItem('userInfo');
+        localStorage.removeItem('token');
+    },
+    SET_COOKIE() {
+        let d = new Date();
+        let time = 1000 * (60 * 60 * 24 * 6); //毫秒
+        d.setTime(d.getTime() + time);
+        let expires = d.toGMTString();
+        let val = "ets-app=etsappcookie; expires=" + expires + ";path=/";
+        setCookieValue(val)
+    },
+};
+export default mutations
\ No newline at end of file
diff --git a/src/store/state.js b/src/store/state.js
new file mode 100644
index 0000000..7040834
--- /dev/null
+++ b/src/store/state.js
@@ -0,0 +1,3 @@
+export default {
+    token: '',
+}
\ No newline at end of file
diff --git a/src/utils/utils.js b/src/utils/utils.js
new file mode 100644
index 0000000..e1efda7
--- /dev/null
+++ b/src/utils/utils.js
@@ -0,0 +1,32 @@
+/* 判空
+ *null, undefined, {}, [],"" 这五类都判定为空
+ */
+export function isEmpty(a) {
+    if (typeof a === 'string') { // 检验空字符串
+        const reg = /^\s+|\s+$/g
+        if (a.replace(reg, '') === '') {
+            return true
+        } else {
+            return false
+        }
+    } else {
+        if (a === 'null') return true // 检验字符串类型的null
+        if (a === 'undefined') return true // 检验字符串类型的 undefined
+        if (!a && a !== 0 && a !== '') return true // 检验 undefined 和 null
+        if (Object.prototype.toString.call(a) === '[object Array]' && a.length === 0) return true // 检验空数组
+        if (Object.prototype.toString(a) === '[object Object]' && Object.keys(a).length === 0) return true // 检验空对象
+    }
+    return false
+}
+
+// 获取cookie
+export function getCookieValue(name) {
+    let result = document.cookie.match("(^|[^;]+)\\s*" + name + "\\s*=\\s*([^;]+)")
+    return result ? result.pop() : ""
+}
+
+// 设置cookie
+// val = ets-app=token;path=/;expires=Fri, 31 Dec 9999 23:59:59 GMT
+export function setCookieValue(val) {
+    return document.cookie = val;
+}
\ No newline at end of file
diff --git a/src/vant.js b/src/vant.js
new file mode 100644
index 0000000..f588a46
--- /dev/null
+++ b/src/vant.js
@@ -0,0 +1,102 @@
+import Vue from 'vue';
+
+import {
+    Tabbar,
+    TabbarItem,
+    Tabs,
+    Tab,
+    ActionSheet,
+    Sticky,
+    Form,
+    Button,
+    Cell,
+    CellGroup,
+    Image as VanImage,
+    Col,
+    Row,
+    Field,
+    Cascader,
+    Checkbox,
+    CheckboxGroup,
+    RadioGroup,
+    Radio,
+    Rate,
+    Search,
+    Switch,
+    DropdownMenu,
+    DropdownItem,
+    List,
+    PullRefresh,
+    Badge,
+    Empty,
+    Skeleton,
+    Grid,
+    GridItem,
+    NavBar,
+    Icon,
+    DatetimePicker,
+    Stepper,
+    NumberKeyboard,
+
+    Overlay,
+    Area,
+    Toast,
+    Dialog,
+    Popup,
+    Loading,
+    ImagePreview,
+    Swipe,
+    SwipeItem,
+    Lazyload,
+    Picker
+} from 'vant';
+
+Vue.use(Toast);
+Vue.use(Dialog);
+Vue.use(ImagePreview);
+Vue.use(Lazyload);
+Vue.use(Picker);
+Vue.use(Overlay);
+
+Vue.component(Tabbar.name, Tabbar);
+Vue.component(TabbarItem.name, TabbarItem);
+Vue.component(Tabs.name, Tabs);
+Vue.component(Tab.name, Tab);
+Vue.component(ActionSheet.name, ActionSheet);
+Vue.component(Sticky.name, Sticky);
+
+Vue.component(Button.name, Button);
+Vue.component(Form.name, Form);
+Vue.component(Field.name, Field);
+Vue.component(Cell.name, Cell);
+Vue.component(CellGroup.name, CellGroup);
+Vue.component(VanImage.name, VanImage);
+Vue.component(Col.name, Col);
+Vue.component(Row.name, Row);
+Vue.component(Cascader.name, Cascader);
+Vue.component(Checkbox.name, Checkbox);
+Vue.component(CheckboxGroup.name, CheckboxGroup);
+Vue.component(RadioGroup.name, RadioGroup);
+Vue.component(Radio.name, Radio);
+Vue.component(Rate.name, Rate);
+Vue.component(Search.name, Search);
+Vue.component(Switch.name, Switch);
+Vue.component(DropdownItem.name, DropdownItem);
+Vue.component(DropdownMenu.name, DropdownMenu);
+Vue.component(List.name, List);
+Vue.component(PullRefresh.name, PullRefresh);
+Vue.component(Badge.name, Badge);
+Vue.component(Empty.name, Empty);
+Vue.component(Skeleton.name, Skeleton);
+Vue.component(Popup.name, Popup);
+Vue.component(Grid.name, Grid);
+Vue.component(GridItem.name, GridItem);
+Vue.component(NavBar.name, NavBar);
+Vue.component(Icon.name, Icon);
+Vue.component(DatetimePicker.name, DatetimePicker);
+Vue.component(Stepper.name, Stepper);
+Vue.component(NumberKeyboard.name, NumberKeyboard);
+Vue.component(Area.name, Area);
+Vue.component(Loading.name, Loading);
+Vue.component(Swipe.name, Swipe);
+Vue.component(SwipeItem.name, SwipeItem);
\ No newline at end of file
diff --git a/src/views/home.vue b/src/views/home.vue
new file mode 100644
index 0000000..cbc7a8e
--- /dev/null
+++ b/src/views/home.vue
@@ -0,0 +1,43 @@
+<template>
+  <div>
+    <mt-button type="default">default</mt-button>
+    <mt-button type="primary">primary</mt-button>
+    <mt-button id="btn" type="danger">danger</mt-button>
+    <a
+      href="http://mint-ui.github.io/#!/zh-cn"
+      target="_blank"
+      rel="noopener noreferrer"
+      >UI库:Mint UI:</a
+    ><br />
+    <a href="http://mint-ui.github.io/#!/zh-cn" target="_blank" rel="noopener noreferrer"
+      >postcss-px-to-viewport</a
+    >
+    <p class="test_postcss">在这里写页面</p>
+    <p>{{ text }}</p>
+    <t-component></t-component>
+  </div>
+</template>
+<script>
+import TComponent from "@/components/HelloWorld";
+import tmixin from "@/mixins/test-mixin";
+import $ from 'jquery'
+
+export default {
+  components: { TComponent },
+  mixins: [tmixin],
+    created(){
+     //已经能打印出来了
+    console.log($("#btn").text())
+  }
+};
+</script>
+
+<style lang="less" scoped>
+a {
+  text-decoration: underline;
+}
+.test_postcss {
+  font-size: 20px;
+  padding: 50px;
+}
+</style>
diff --git a/src/views/iframe/iframe.vue b/src/views/iframe/iframe.vue
new file mode 100644
index 0000000..0aba94b
--- /dev/null
+++ b/src/views/iframe/iframe.vue
@@ -0,0 +1,186 @@
+
+<template>
+  <div class="iframe">
+      <iframe
+        ref='iframe' allowfullscreen
+        :src="live_url"
+        frameborder="0"
+        width='100%'
+        height='100%'>
+      </iframe>
+  </div>
+</template>
+<script>
+import { mapActions, mapState } from "vuex";
+export default {
+  data(){
+    return{
+      live_url:"",
+      scenes_id:'',
+      scenes_name:'',
+      client_id:'',
+      jsload:false,//js是否加载成功
+      signature:'',//微信签名
+      timeStamp:'',//生成签名时间戳
+      nonceStr:'',//随机字符串
+      appId:'',//企业微信ID
+      access_token:'',
+      iframeWin:'',// iframe对象
+    };
+  },
+  computed: {
+    ...mapState({ token: "token" })
+  },
+  mounted(){
+    this.iframeWin = this.$refs.iframe.contentWindow;
+  },
+  created () {
+     let item = JSON.parse(localStorage.getItem("item"));
+     this.scenes_id = item.scenes_id;
+     this.scenes_name = item.scenes_name;
+     this.client_id = 'wechat_enterprise';
+     let url = process.env.INTERFACE_SERVER_PATH+process.env.ETS_APP_NEWRECORD_URL;
+     this.live_url = url + "?scenes_id="+this.scenes_id+"&scenes_name="+this.scenes_name+"&client_id="+this.client_id+"&sid="+this.token;
+     console.log(this.live_url);
+     this.getwsConfig();
+  },
+  methods:{
+    ...mapActions({
+        clientconfig: "clientconfig", // 鉴权
+    }),
+    getwsConfig(){
+      let _this = this;
+      let url = location.href;
+      _this.clientconfig({ query: {
+          client_id:_this.client_id,
+          url:url
+      } }).then((res) => {
+          console.log(res);
+          if(res.data.access_token){
+              _this.access_token = res.data.access_token;
+              _this.corpId = res.data.corpId;
+              _this.nonceStr = res.data.nonceStr;
+              _this.signature = res.data.signature;
+              _this.timeStamp = res.data.timeStamp;
+              _this.postMessageEvent();
+          }else{
+              _this.$toast.fail('企业微信鉴权失败');
+          }
+      }).catch(() => {
+          _this.$toast.fail('企业微信鉴权失败');
+      });;
+    },
+     postMessageEvent(){
+       window.addEventListener("message", function(e){
+            var data = e.data;
+            switch (data.action) {
+                case 'init': // 初始化
+                    wx.config({
+                        beta: true,// 必须这么写,否则wx.invoke调用形式的jsapi会有问题
+                        debug: false, // 开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印。
+                        appId: appId, // 必填,企业微信的corpID
+                        timestamp: timeStamp, // 必填,生成签名的时间戳
+                        nonceStr: nonceStr, // 必填,生成签名的随机串
+                        signature: signature,// 必填,签名,见 附录-JS-SDK使用权限签名算法
+                        jsApiList: [
+                                'startRecord',
+                                'stopRecord',
+                                'uploadVoice'
+                        ], //必填,传入需要使用的接口名称
+                        success: function (res) {
+                          sendMsg({
+                            action:'init',
+                            state:'ok',
+                            data:{}
+                          });
+                        }
+                    });
+                    wx.error(function(res){
+                        sendMsg({
+                          action:'init',
+                          state:'fail',
+                          data:{}
+                        });
+                    })
+                  break;
+                case 'start': // 开始录音
+                  wx.startRecord({
+                    success: function (res) {
+                      sendMsg({
+                        action:'start',
+                        state:'ok',
+                        data:{}
+                      });
+                    }
+                  });
+                  wx.error(function(res){
+                      sendMsg({
+                        action:'start',
+                        state:'fail',
+                        data:{}
+                      });
+                  })
+                  break;
+                case 'stop':
+                  wx.stopRecord({
+                    success: function (res) {
+                      var localId = res.localId;
+                      wx.uploadVoice({
+                          localId: localId, // 需要识别的音频的本地Id,由录音相关接口获得,音频时长不能超过60秒
+                          isShowProgressTips: 0, // 默认为1,显示进度提示
+                          success: function (ures) {
+                            let serverId = ures.serverId;
+                            sendMsg({
+                              action:'stop',
+                              state:'ok',
+                              data:{
+                                access_token:_this.access_token,
+                                serverId:serverId
+                              }
+                            });
+                          }
+                      });
+                    }
+                  });
+                  wx.error(function(res){
+                      sendMsg({
+                        action:'start',
+                        state:'fail',
+                        data:{}
+                      });
+                  })
+                  break;
+            }
+       })
+     },
+     sendMsg(msg){
+       this.iframeWin.postMessage(msg,'*');
+     },
+     loadWxjs(){
+        let _this = this;
+        let wx_js = process.env.WX_API_URL;
+        const isInclude = $("script[src='"+wx_js+"']").length; //引用次数
+        if(isInclude == 0){
+            const script = document.createElement('script');
+            script.type = 'text/javascript';
+            script.src = wx_js;
+            document.body.appendChild(script);
+            if(index == 0){
+              script.onload=function(){
+                _this.jsload = true;
+              }
+            }
+        }else{
+          _this.jsload = true;
+        }
+     }
+  }
+}
+</script>
+<style lang="less" scoped>
+.iframe{
+  position: relative;
+  width: 100%;
+  height: 100%;
+}
+</style>
diff --git a/src/views/login/login copy.vue b/src/views/login/login copy.vue
new file mode 100644
index 0000000..93efa05
--- /dev/null
+++ b/src/views/login/login copy.vue
@@ -0,0 +1,263 @@
+
+<template>
+  <div class="login-content">
+      <div class="login-body">
+        <van-form ref='form'>
+          <van-tabs v-model="active" @click="onClick">
+            <van-tab v-for="(item,index) in tabList" :title="item" :key="index">
+                <!-- <div v-if="index == 0">
+                    <van-form>
+                      <van-cell-group>
+                        <div class="from-li">
+                          <van-field
+                            v-model="phone"
+                            name="pattern"
+                            placeholder="用户手机号"
+                            :rules="rules.phone"
+                            maxlength="11"
+                            oninput="value=value.replace(/[^\d]/g,'')"
+                            @blur="phoneBlur()"
+                          />
+                        </div>
+                        <div class="from-li">
+                              <van-field
+                                readonly
+                                clickable
+                                is-link
+                                v-model="comp_name"
+                                placeholder="点击选择企业"
+                                @click="showComp = true"
+                                :rules="rules.comp_name"
+                              />
+                              <van-popup v-model="showComp" position="bottom">
+                                <van-picker
+                                  :defaultIndex="defaultIndex"
+                                  show-toolbar
+                                  :columns="compList"
+                                  value-key="compname"
+                                  @confirm="onConfirm"
+                                  @cancel="showComp = false"
+                                />
+                              </van-popup>
+                        </div>
+                        <div class="from-li">
+                          <van-field
+                            v-model="code"
+                            center
+                            clearable
+                            :rules="rules.code"
+                            maxlength="6"
+                            placeholder="请输入验证码">
+                            <div slot="button" >
+                              {{codeBtn}}
+                            </div>
+                          </van-field>
+                        </div>
+                      </van-cell-group>
+                    </van-form>
+                </div> -->
+                <div v-if="index == 1">
+                    <van-form>
+                      <van-cell-group>
+                        <div class="from-li">
+                          <van-field
+                            v-model="phone"
+                            name="pattern"
+                            placeholder="用户手机号"
+                            :rules="rules.phone"
+                            maxlength="11"
+                            oninput="value=value.replace(/[^\d]/g,'')"
+                            @blur="phoneBlur()"
+                          />
+                        </div>
+                        <div class="from-li">
+                              <van-field
+                                readonly
+                                clickable
+                                is-link
+                                v-model="comp_name"
+                                placeholder="点击选择企业"
+                                @click="showComp = true"
+                                :rules="rules.comp_name"
+                              />
+                              <van-popup v-model="showComp" position="bottom">
+                                <van-picker
+                                  :defaultIndex="defaultIndex"
+                                  show-toolbar
+                                  :columns="compList"
+                                  value-key="compname"
+                                  @confirm="onConfirm"
+                                  @cancel="showComp = false"
+                                />
+                              </van-popup>
+                        </div>
+                        <div class="from-li">
+                            <van-field
+                              v-model="password"
+                              name="validator"
+                              placeholder="密码"
+                              :rules="rules.password"
+                            />
+                        </div>
+                      </van-cell-group>
+                    </van-form>
+                </div>
+            </van-tab>
+          </van-tabs>
+          <div class="form-footer">
+            <van-button round block type="primary" native-type="submit" @click="submitForm">
+              登录
+            </van-button>
+          </div>
+        </van-form>
+      </div>
+  </div>
+</template>
+<script>
+import { mapActions, mapState } from "vuex";
+export default {
+  data() {
+    return {
+      phone:'',
+      password:'',
+      code:'',
+      comp_id:'',
+      tabList:['验证码登录','密码登录'],
+      active:0,
+      codeBtn:"获取验证码",
+      compList:[],
+      comp_name:'',
+      defaultIndex:'',
+      showComp:false,
+      rules:{
+        phone:[{
+          pattern:/^(13[0-9]|14[01456879]|15[0-35-9]|16[2567]|17[0-8]|18[0-9]|19[0-35-9])\d{8}$/,
+          message:'请输入正确的手机号',
+          trigger:'onBlur'
+        }],
+        comp_name:[{
+          required: true,
+          message:'请选择企业',
+          trigger:'onChange'
+        }],
+        code:[{
+          required: true,
+          message:'请输入验证码',
+          trigger:'onBlur'
+        },
+        {
+          pattern: /^\d{6}$/,
+          message:'请输入6位数字验证码',
+          trigger:'onBlur'
+        },
+        ],
+        password:[{
+          required: true,
+          message:'请输入密码',
+          trigger:'onBlur'
+        }],
+      }
+    };
+  },
+  created() {
+
+  },
+  methods: {
+    ...mapActions({
+      sendSmsApi: "sendSms", // 发送验证码
+      codeLoginApi: "codeLogin", // 验证码登录
+      pwdLoginApi: "pwdLogin",  // 账户密码登录
+      set_token: "set_token",   // 设置缓存
+      getCompList: "getCompList",   // 获取企业列表
+    }),
+    submitForm(){
+      let _this = this;
+      if(_this.active == 0){
+
+      }else{
+        // 全局表单验证
+        this.$refs.form.validate().then(() => {
+            this.$toast.success('提交成功')
+        }).catch(() => {
+            this.$toast.fail('提交失败')
+        })
+      }
+    },
+    onClick(index){
+      this.active = index;
+    },
+    phoneBlur(){
+      let _this = this;
+      if(_this.phone){
+
+        _this.getCompList({query:
+          {
+            ac_name:_this.phone,
+          }
+          }).then((res) =>{
+            if(res.code == 0){
+              _this.compList = res.result;
+            }
+          }).catch(err => {
+            _this.$toast('账号不存在');
+          });
+      }
+    },
+    onConfirm(item){
+      if(item){
+        this.comp_name = item.compname;
+        this.comp_id = item.comp_id;
+      }
+      this.showComp = false;
+    },
+  },
+};
+</script>
+<style lang="less" scoped>
+.login-content{
+  height: 100%;
+  width: 100%;
+  position: relative;
+  .login-body{
+    padding: 110px 42px 0 42px;
+    /deep/ .van-tabs__line{
+        background-color: #1CA2FF;
+    }
+    /deep/ .van-tabs__wrap{
+      .van-tab{
+        font-size: 16px;
+      }
+    }
+    .from-li{
+      position: relative;
+      margin-top: 88px;
+    }
+    /deep/ .van-cell{
+      padding: 0 0;
+      align-items: center;
+      border-bottom: 1px solid #ddd;
+      position:unset;
+    }
+    /deep/ .van-cell__value{
+      position:unset;
+    }
+    [class*=van-hairline]::after{
+      border:none;
+    }
+    /deep/ .van-field__body{
+      height: 50px;
+    }
+    /deep/ .van-field__error-message{
+      height: 20px;
+      top:50px;
+    }
+    .van-field--error{
+      border-bottom: 1px solid #ee0a24;
+    }
+    .form-footer{
+      margin-top: 100px;
+    }
+  }
+}
+
+</style>
diff --git a/src/views/login/login.vue b/src/views/login/login.vue
new file mode 100644
index 0000000..aee8b56
--- /dev/null
+++ b/src/views/login/login.vue
@@ -0,0 +1,344 @@
+
+<template>
+  <div class="login-content">
+      <div class="login-body">
+        <van-form ref='form' :key="formKey">
+          <van-tabs v-model="active" @click="onClick">
+            <van-tab v-for="(item,index) in tabList" :title="item" :key="index">
+                <div v-if="index == 0">
+                      <van-cell-group>
+                        <div class="from-li">
+                          <van-field
+                            v-model="phone"
+                            name="phone"
+                            placeholder="用户手机号"
+                            :rules="rules.phone"
+                            maxlength="11"
+                            oninput="value=value.replace(/[^\d]/g,'')"
+                            @blur="phoneBlur()"
+                          />
+                        </div>
+                        <div class="from-li">
+                              <van-field
+                                readonly
+                                clickable
+                                is-link
+                                v-model="comp_name"
+                                name="comp_name"
+                                placeholder="点击选择企业"
+                                @click="showComp = true"
+                                :rules="rules.comp_name"
+                              />
+                              <van-popup v-model="showComp" position="bottom">
+                                <van-picker
+                                  :defaultIndex="defaultIndex"
+                                  show-toolbar
+                                  :columns="compList"
+                                  value-key="compname"
+                                  @confirm="onConfirm"
+                                  @cancel="showComp = false"
+                                />
+                              </van-popup>
+                        </div>
+                        <div class="from-li">
+                          <van-field
+                            v-model="code"
+                            name="code"
+                            center
+                            clearable
+                            :rules="rules.code"
+                            maxlength="6"
+                            placeholder="请输入验证码">
+                            <div v-bind:class="{ codeBtn: smsDisabled }" slot="button" @click="getCode()" :disabled="smsDisabled">
+                              {{codeBtn}}
+                            </div>
+                          </van-field>
+                        </div>
+                      </van-cell-group>
+                </div>
+                <div v-if="index == 1">
+                      <van-cell-group>
+                        <div class="from-li">
+                          <van-field
+                            v-model="phone"
+                            name="phone"
+                            placeholder="用户手机号"
+                            :rules="rules.phone"
+                            maxlength="11"
+                            oninput="value=value.replace(/[^\d]/g,'')"
+                            @blur="phoneBlur()"
+                          />
+                        </div>
+                        <div class="from-li">
+                              <van-field
+                                readonly
+                                clickable
+                                is-link
+                                v-model="comp_name"
+                                name="comp_name"
+                                placeholder="点击选择企业"
+                                @click="showComp = true"
+                                :rules="rules.comp_name"
+                              />
+                              <van-popup v-model="showComp" position="bottom">
+                                <van-picker
+                                  :defaultIndex="defaultIndex"
+                                  show-toolbar
+                                  :columns="compList"
+                                  value-key="compname"
+                                  @confirm="onConfirm"
+                                  @cancel="showComp = false"
+                                />
+                              </van-popup>
+                        </div>
+                        <div class="from-li">
+                            <van-field
+                              v-model="password"
+                              name="password"
+                              placeholder="密码"
+                              :rules="rules.password"
+                              type="password"
+                            />
+                        </div>
+                      </van-cell-group>
+                </div>
+            </van-tab>
+          </van-tabs>
+          <div class="form-footer">
+          <van-button type="primary" round block @click="submit">提交</van-button>
+          </div>
+        </van-form>
+      </div>
+  </div>
+</template>
+<script>
+import { mapActions, mapState } from "vuex";
+export default {
+  data() {
+    return {
+      phone:'',
+      password:'',
+      code:'',
+      comp_id:'',
+      tabList:['验证码登录','密码登录'],
+      active:0,
+      codeBtn:"获取验证码",
+      compList:[],
+      comp_name:'',
+      defaultIndex:'',
+      showComp:false,
+      formKey:1,
+      smsTimer:false,
+      smsDisabled:false,//可以点击
+      rules:{
+        phone:[{
+          pattern:/^(13[0-9]|14[01456879]|15[0-35-9]|16[2567]|17[0-8]|18[0-9]|19[0-35-9])\d{8}$/,
+          message:'请输入正确的手机号',
+          trigger:'onBlur'
+        }],
+        comp_name:[{
+          required: true,
+          message:'请选择企业',
+          trigger:'onChange'
+        }],
+        code:[{
+          required: true,
+          message:'请输入验证码',
+          trigger:'onBlur'
+        },
+        {
+          pattern: /^\d{4}$/,
+          message:'请输入4位数字验证码',
+          trigger:'onBlur'
+        },
+        ],
+        password:[{
+          required: true,
+          message:'请输入密码',
+          trigger:'onBlur'
+        }],
+      }
+    };
+  },
+  created() {
+    if(this.token){
+      window.location.href = "/list";
+    }
+  },
+  computed: {
+    ...mapState({ token: "token" }),
+  },
+  methods: {
+    ...mapActions({
+      sendSmsApi: "sendSms", // 发送验证码
+      codeLoginApi: "codeLogin", // 验证码登录
+      pwdLoginApi: "pwdLogin",  // 账户密码登录
+      set_token: "set_token",   // 设置缓存
+      set_cookie:'set_cookie',  // 设置缓存
+      getCompList: "getCompList",   // 获取企业列表
+    }),
+    submit(){
+      let _this = this;
+      if(_this.active == 0){
+        // 全局表单验证
+        _this.$refs.form.validate().then(() => {
+          _this.codeLoginApi({query:
+            {
+              ac_name:_this.phone,
+              code:_this.code,
+              comp_id:_this.comp_id,
+              comp_name:_this.comp_name,
+            }
+            }).then((res) =>{
+              if(res.code == 0){
+                _this.$toast.success('登录成功');
+                let obj = { token: res.result.sid };
+                _this.set_token(obj);
+                _this.set_cookie();
+                _this.$router.push({ path: "/list" });
+              }
+            }).catch(err => {
+              _this.$toast.fail(err.msg)
+            });
+        }).catch(() => {})
+      }else{
+        // 全局表单验证
+        _this.$refs.form.validate().then(() => {
+          _this.pwdLoginApi({query:
+            {
+              ac_name:_this.phone,
+              password:_this.password,
+              comp_id:_this.comp_id,
+              comp_name:_this.comp_name,
+            }
+            }).then((res) =>{
+              console.log(res,'成功')
+              if(res.code == 200){
+                _this.$toast.success('登录成功');
+                let obj = { token: res.result.sid };
+                _this.set_token(obj);
+                _this.set_cookie();
+                _this.$router.push({ path: "/list" });
+              }
+            }).catch(err => {
+              console.log(err,'失败')
+              _this.$toast.fail(err.msg)
+            });
+        }).catch(() => {})
+      }
+    },
+    getCode(){
+      console.log(1);
+      let _this = this;
+      if(_this.smsDisabled){return;}
+      _this.$refs.form.validate('phone').then(() => {
+          _this.sendSmsApi({query:
+            {
+              ac_name:_this.phone,
+            }
+            }).then((res) =>{
+              if(res.code == 0){
+                _this.$toast.success('发送成功');
+                let time = 60;
+                _this.smsDisabled = true;
+                _this.smsTimer = setInterval(function(){
+                  time = time - 1;
+                  if(time > 0){
+                     _this.codeBtn = time + "秒后重新获取";
+                  }else{
+                    clearInterval(_this.smsTimer);
+                    _this.codeBtn = "重新获取验证码";
+                    _this.smsDisabled = false;
+                  }
+                },1000)
+              }
+            }).catch(err => {
+              _this.$toast.fail(err.msg)
+            });
+      }).catch(() => {})
+    },
+    onClick(index){
+      this.active = index;
+      this.formKey++;
+    },
+    phoneBlur(){
+      let _this = this;
+      if(_this.phone){
+        _this.getCompList({query:
+          {
+            ac_name:_this.phone,
+          }
+          }).then((res) =>{
+            if(res.code == 0){
+              _this.compList = res.result;
+            }
+          }).catch(err => {
+            _this.$toast('企业信息为空,请检查账号是否正确');
+            _this.compList = [];
+          });
+      }
+    },
+    onConfirm(item){
+      if(item){
+        this.comp_name = item.compname;
+        this.comp_id = item.comp_id;
+      }
+      this.showComp = false;
+    },
+  },
+};
+</script>
+<style lang="less" scoped>
+.login-content{
+  height: 100%;
+  width: 100%;
+  position: relative;
+  background: white;
+  .login-body{
+    padding: 110px 42px 0 42px;
+    /deep/ .van-tabs__line{
+        background-color: #1CA2FF;
+    }
+    /deep/ .van-tabs__wrap{
+      .van-tab{
+        font-size: 16px;
+      }
+    }
+    .from-li{
+      position: relative;
+      margin-top: 88px;
+    }
+    /deep/ .van-cell{
+      padding: 0 0;
+      align-items: center;
+      border-bottom: 1px solid #ddd;
+      position:unset;
+    }
+    /deep/ .van-field__button{
+      .codeBtn{
+            color: #999999;
+      }
+    }
+    /deep/ .van-cell__value{
+      position:unset;
+    }
+    [class*=van-hairline]::after{
+      border:none;
+    }
+    /deep/ .van-field__body{
+      height: 50px;
+    }
+    /deep/ .van-field__error-message{
+      height: 20px;
+      top:50px;
+    }
+    .van-field--error{
+      border-bottom: 1px solid #ee0a24;
+    }
+    .form-footer{
+      margin-top: 100px;
+    }
+  }
+}
+
+</style>
diff --git a/src/views/msg/msg.vue b/src/views/msg/msg.vue
new file mode 100644
index 0000000..14f2b0f
--- /dev/null
+++ b/src/views/msg/msg.vue
@@ -0,0 +1,24 @@
+<template>
+  <div class="msg">
+      暂无消息
+     <Tabbar :active="1"></Tabbar>
+  </div>
+</template>
+<script>
+import Tabbar from "@/components/Tabbar";
+export default {
+  components: { Tabbar },
+}
+</script>
+<style lang="less" scoped>
+.msg{
+  position: relative;
+  width: 100%;
+  height: 100%;
+  text-align: center;
+  background-color: white;
+  color: #818181;
+  font-size: 30px;
+  padding-top: 45%;
+}
+</style>
diff --git a/src/views/user/user.vue b/src/views/user/user.vue
new file mode 100644
index 0000000..f28c548
--- /dev/null
+++ b/src/views/user/user.vue
@@ -0,0 +1,120 @@
+<template>
+  <div class="user-content">
+    <div class="user-head">
+      <div class="head-left">
+          <van-image
+          lazy-load
+          :src="userLogo" rel="external nofollow"
+        />
+      </div>
+      <div class="head-right">
+        <p class="userName">{{userInfo.nickname}}</p>
+        <p class="userPhone">{{userInfo.ac_name}}</p>
+      </div>
+    </div>
+    <div class="user-body">
+        <div class="list-li" @click="loginoutEvent">退出登录</div>
+    </div>
+    <Tabbar :active="1"></Tabbar>
+  </div>
+</template>
+<script>
+import { mapActions, mapState } from "vuex";
+import Tabbar from "@/components/Tabbar";
+export default {
+  components: { Tabbar },
+  data() {
+    return {
+      userLogo:require('@/assets/images/service.png'),
+      userInfo:""
+    };
+  },
+  created () {
+    this.load();
+  },
+  computed: {
+    ...mapState({ token: "token" }),
+  },
+  methods: {
+    ...mapActions({
+        loginout: "loginout", // 发送验证码
+        getUserInfo:"getUserInfo", //获取用户信息
+        set_token:"set_token"
+    }),
+    loginoutEvent(){
+      let _this = this;
+      _this.$dialog
+        .confirm({ message: "退出登录确认" })
+        .then(() => {
+          _this.loginout({ query: {} }).then(() => {
+            localStorage.removeItem("token");
+            _this.set_token({});
+            location.href = "/";
+          });
+        })
+        .catch(() => {});
+    },
+    load(){
+      let _this = this;
+      _this.getUserInfo({query:
+      {
+        token:_this.token,
+      }
+      }).then((res) =>{
+        if(res.user_info){
+          _this.userInfo = res.user_info;
+          if(res.user_info.head_img){
+            _this.userLogo = res.user_info.head_img;
+          }
+        }else{
+
+        }
+      }).catch(err => {
+        console.log(err,'失败')
+        _this.$toast.fail(err.msg)
+      });
+    }
+  }
+}
+</script>
+<style lang="less" scoped>
+.user-content{
+  position: relative;
+  width: 100%;
+  height: 100%;
+  .user-head{
+    width: 750px;
+    height: 360px;
+    background: linear-gradient(180deg,#7ccdff, #4a90e2);
+    display: flex;
+    align-items: center;
+    padding-left: 51px;
+    .head-left{
+      width: 120px;
+      height: 120px;
+      border-radius: 100%;
+      margin-right: 28px;
+      /deep/ .van-image__img{
+         border-radius: 100%;
+       }
+    }
+    .head-right{
+      color: white;
+      font-size: 30px;
+      font-weight: 500;
+      .userName{
+        margin-bottom: 5px;
+      }
+    }
+  }
+  .list-li{
+    background-color: white;
+    height: 90px;
+    text-align: center;
+    line-height: 90px;
+    font-size: 30px;
+    color: #ff3512;
+    margin-top: 20px;
+  }
+}
+</style>
diff --git a/src/views/vant.vue b/src/views/vant.vue
new file mode 100644
index 0000000..dc4635a
--- /dev/null
+++ b/src/views/vant.vue
@@ -0,0 +1,258 @@
+<template>
+  <div>
+    <van-grid :column-num="10">
+    <van-grid-item
+      v-for="(item,index) in iconlist"
+      :key="index"
+      :icon="item"
+      :text="item"
+    />
+  </van-grid>
+  </div>
+</template>
+<script>
+export default {
+  data() {
+    return {
+        iconlist:[
+          'success',
+          'plus',
+          'cross',
+          'fail',
+          'arrow',
+          'arrow-left',
+          'arrow-up',
+          'arrow-down',
+          'location-o',
+          'like-o',
+          'star-o',
+          'phone-o',
+          'setting-o',
+          'fire-o',
+          'coupon-o',
+          'cart-o',
+          'shopping-cart-o',
+          'cart-circle-o',
+          'friends-o',
+          'comment-o',
+          'gem-o',
+          'gift-o',
+          'point-gift-o',
+          'send-gift-o',
+          'service-o',
+          'bag-o',
+          'todo-list-o',
+          'balance-list-o',
+          'close',
+          'clock-o',
+          'question-o',
+          'passed',
+          'add-o',
+          'gold-coin-o',
+          'info-o',
+          'play-circle-o',
+          'pause-circle-o',
+          'stop-circle-o',
+          'warning-o',
+          'phone-circle-o',
+          'music-o',
+          'smile-o',
+          'thumb-circle-o',
+          'comment-circle-o',
+          'browsing-history-o',
+          'underway-o',
+          'more-o',
+          'video-o',
+          'shop-o',
+          'shop-collect-o',
+          'chat-o',
+          'smile-comment-o',
+          'vip-card-o',
+          'award-o',
+          'diamond-o',
+          'volume-o',
+          'cluster-o',
+          'photo-o',
+          'gift-card-o',
+          'expand-o',
+          'medel-o',
+          'good-job-o',
+          'manager-o',
+          'label-o',
+          'bookmark-o',
+          'bill-o',
+          'hot-o',
+          'hot-sale-o',
+          'new-o',
+          'new-arrival-o',
+          'goods-collect-o',
+          'eye-o',
+          // without corresponding filled icon
+          'balance-o',
+          'refund-o',
+          'birthday-cake-o',
+          'user-o',
+          'orders-o',
+          'tv-o',
+          'envelop-o',
+          'flag-o',
+          'flower-o',
+          'filter-o',
+          'bar-chart-o',
+          'chart-trending-o',
+          'brush-o',
+          'bullhorn-o',
+          'hotel-o',
+          'cashier-o',
+          'newspaper-o',
+          'warn-o',
+          'notes-o',
+          'calender-o',
+          'bulb-o',
+          'user-circle-o',
+          'desktop-o',
+          'apps-o',
+          'home-o',
+          'share',
+          'search',
+          'points',
+          'edit',
+          'delete',
+          'qr',
+          'qr-invalid',
+          'closed-eye',
+          'wap-home',
+          'scan',
+          'free-postage',
+          'certificate',
+          'logistics',
+          'contact',
+          'cash-back-record',
+          'after-sale',
+          'exchange',
+          'upgrade',
+          'ellipsis',
+          'circle',
+          'description',
+          'records',
+          'sign',
+          'completed',
+          'failure',
+          'ecard-pay',
+          'peer-pay',
+          'balance-pay',
+          'credit-pay',
+          'debit-pay',
+          'cash-on-deliver',
+          'other-pay',
+          'tosend',
+          'pending-payment',
+          'paid',
+          'aim',
+          'discount',
+          'idcard',
+          'replay',
+          'shrink',
+          'location',
+          'like',
+          'star',
+          'phone',
+          'setting',
+          'fire',
+          'coupon',
+          'cart',
+          'shopping-cart',
+          'cart-circle',
+          'friends',
+          'comment',
+          'gem',
+          'gift',
+          'point-gift',
+          'send-gift',
+          'service',
+          'bag',
+          'todo-list',
+          'balance-list',
+          'clear',
+          'clock',
+          'question',
+          'checked',
+          'add',
+          'gold-coin',
+          'info',
+          'play-circle',
+          'pause-circle',
+          'stop-circle',
+          'warning',
+          'phone-circle',
+          'music',
+          'smile',
+          'thumb-circle',
+          'comment-circle',
+          'browsing-history',
+          'underway',
+          'more',
+          'video',
+          'shop',
+          'shop-collect',
+          'chat',
+          'smile-comment',
+          'vip-card',
+          'award',
+          'diamond',
+          'volume',
+          'cluster',
+          'photo',
+          'gift-card',
+          'expand',
+          'medel',
+          'good-job',
+          'manager',
+          'label',
+          'bookmark',
+          'bill',
+          'hot',
+          'hot-sale',
+          'new',
+          'new-arrival',
+          'goods-collect',
+          'eye',
+          // without corresponding outline icon
+          'alipay',
+          'wechat',
+          'photograph',
+          'youzan-shield',
+          'umbrella-circle',
+          'bell',
+          'printer',
+          'map-marked',
+          'card',
+          'add-square',
+          'live',
+          'lock',
+          'audio',
+          'graphic',
+          'column',
+          'invition',
+          'play',
+          'pause',
+          'stop',
+          'weapp-nav',
+          'ascending',
+          'descending',
+          'bars',
+          'wap-nav'
+        ]
+    }
+  }
+}
+</script>
+<style lang="less" scoped>
+
+.icon-list{
+    width: 80px;
+    text-align: center;
+    margin-right: 10px;
+    float: left;
+}
+</style>
diff --git a/src/views/voice/list copy.vue b/src/views/voice/list copy.vue
new file mode 100644
index 0000000..23c6337
--- /dev/null
+++ b/src/views/voice/list copy.vue
@@ -0,0 +1,237 @@
+
+<template>
+  <div class="list-content">
+    <div class="list-head">
+        <div class="list-search">
+           <van-search
+            v-model="value"
+            placeholder="请输入场景关键词"
+            @cancel="onCancel"
+          />
+        </div>
+        <div class="list-banner">
+          <van-image
+            lazy-load
+            :src="banner" rel="external nofollow"
+          />
+        </div>
+    </div>
+    <van-pull-refresh v-model="refreshing" @refresh="onRefresh">
+        <van-list
+          v-model="loading"
+          :finished="finished"
+          :immediate-check="false"
+          :offset="8"
+          finished-text="没有更多了"
+          @load="onLoad"
+        >
+          <van-cell v-for="(item,index) in list" :key="index" >
+            <div class="list-li">
+                <div class="list-left">
+                    <van-image
+                      lazy-load
+                      :src="banner" rel="external nofollow"
+                    />
+                </div>
+                <div class="list-right">
+                  <p class="r-title">场景名称场景景名称场景场景名称场景景名称场景</p>
+                  <div class="r-tip">
+                    <span>场景名称场景景名称场景场景名称场景景名称场景</span>
+                    <span>场景名称场景景名称场景场景名称场景景名称场景</span>
+                    <span>文字dsadasddasdas3</span>
+                  </div>
+                  <p class="r-time">
+                    <img src="../../assets/images/clock.svg">
+                    距离结束还有<span>50</span>天
+                  </p>
+                </div>
+            </div>
+          </van-cell>
+        </van-list>
+    </van-pull-refresh>
+    <Tabbar></Tabbar>
+  </div>
+</template>
+<script>
+import { mapActions, mapState } from "vuex";
+import Tabbar from "@/components/Tabbar";
+export default {
+  components: { Tabbar },
+  data() {
+    return {
+      banner: require('@/assets/images/banner.svg'),
+      value:'',
+      list: [],
+      loading: false,
+      finished: false,
+      refreshing: false,
+      page:1,
+      pageSize:8,
+      scenes_name:'',
+      scenes_type:'',
+      total:'',
+    };
+  },
+  computed: {
+    ...mapState({ token: "token" }),
+  },
+  created () {
+    this.getPageData();
+  },
+  methods: {
+    ...mapActions({
+        newgetList: "newgetList", // 发送验证码
+    }),
+    getPageData(){
+        let _this = this;
+        _this.newgetList({query:
+        {
+          token:_this.token,
+          page:_this.page,
+        }
+        }).then((res) =>{
+          _this.total = res.total;
+          if(res.list.length === 0){
+             _this.finished = true;
+             return;
+          }
+          if(res.list.length < _this.pageSize){
+             _this.finished = true;
+          }
+          if(_this.page == 1){
+            _this.list = res.list;
+            console.log(_this.page);
+          }else{
+            _this.list = _this.list.concat(res.list);
+            console.log(_this.page,'2');
+          }
+        }).catch(err => {
+          _this.$toast.fail(err.msg)
+        }).finally(()=>{
+          _this.loading = false;
+          _this.refreshing = false;
+        });
+    },
+    onLoad(){
+      this.page++;
+      this.getPageData();
+      console.log(3);
+    },
+    onCancel(){},
+    onRefresh() {
+      this.page = 1;
+      this.finished = false;
+      this.getPageData();
+    }
+  }
+}
+</script>
+<style lang="less" scoped>
+.list-content{
+  padding-bottom: 90px;
+}
+.list-search{
+    position: fixed;
+    top: 0;
+    left: 0;
+    z-index: 99;
+    width: 100%;
+    height: 88px;
+    .van-search{
+      padding: 8px 32px;
+    }
+}
+.list-banner{
+  height: 300px;
+  margin-top: 88px;
+  .van-image{
+    width: 100%;
+    height: 100%;
+  }
+}
+/deep/ .van-pull-refresh{
+  background-color: #f8f8f8;
+}
+/deep/ .van-list{
+    background-color: #f8f8f8;
+    .van-cell{
+      padding: 20px 0 20px 32px;
+      height: 200px;
+      margin-top: 20px;
+      .list-li{
+        display: flex;
+        height: 100%;
+        width: 100%;
+        .list-left{
+          width: 232px;
+          height: 160px;
+          .van-image{
+            width: 100%;
+            height: 100%;
+            .van-image__img{
+              object-fit:cover
+            }
+          }
+        }
+        .list-right{
+            width: 420px;
+            margin-left: 16px;
+            .r-title{
+              height: 48px;
+              line-height: 48px;
+              color: #000000;
+              font-size: 32px;
+              font-weight: 500;
+              white-space:nowrap;
+              overflow:hidden;
+              text-overflow:ellipsis;
+              margin-bottom: 12px;
+            }
+            .r-tip{
+              font-size: 0;
+              height: 36px;
+              line-height: 36px;
+              span{
+                    display: inline-block;
+                    font-size: 14px;
+                    color: #16BAA8;
+                    text-align: center;
+                    max-width: 134px;
+                    height: 36px;
+                    line-height: 36px;
+                    border: 1px solid #16BAA8;
+                    background: #ffffff;
+                    overflow: hidden;
+                    text-overflow: ellipsis;
+                    white-space: nowrap;
+                    margin-right: 8px;
+                    text-align: center;
+              }
+              span:last-child{
+                margin-right: 0;
+              }
+            }
+            .r-time{
+              margin-top: 20px;
+              height: 42px;
+              line-height: 42px;
+              img{
+                width: 28px;
+                height: 28px;
+                position: relative;
+                top:3px;
+              }
+              span{
+                color: #ff7b73;
+                display: inline-block;
+                margin: 0 4px;
+              }
+            }
+        }
+      }
+    }
+    .van-cell::after{
+      border-bottom:none;
+    }
+}
+</style>
diff --git a/src/views/voice/list.vue b/src/views/voice/list.vue
new file mode 100644
index 0000000..6ee3222
--- /dev/null
+++ b/src/views/voice/list.vue
@@ -0,0 +1,258 @@
+
+<template>
+  <div class="list-content">
+    <div class="list-head">
+        <div class="list-search">
+           <van-search
+            v-model="value"
+            placeholder="请输入场景关键词"
+            @cancel="onCancel"
+            @search="onSearch"
+          />
+        </div>
+        <div class="list-banner">
+          <van-image
+            lazy-load
+            :src="banner" rel="external nofollow"
+          />
+        </div>
+    </div>
+    <van-pull-refresh v-model="refreshing" @refresh="onRefresh">
+        <van-list
+          v-model="loading"
+          :finished="finished"
+          :immediate-check="false"
+          :offset="8"
+          finished-text="没有更多了"
+          @load="onLoad"
+        >
+          <van-cell v-for="(item,index) in list" :key="index" >
+            <div class="list-li" @click="onVoice(item)">
+                <div class="list-left">
+                    <van-image
+                      lazy-load
+                      :src="item.cover" rel="external nofollow"
+                    />
+                </div>
+                <div class="list-right">
+                  <p class="r-title">{{item.scenes_name}}</p>
+                  <div class="r-tip">
+                    <label v-for="(label,index) in item.label_names" :key="index">
+                      <span>
+                        {{label}}
+                      </span>
+                    </label>
+                  </div>
+                  <p class="r-time">
+                    <img src="../../assets/images/clock.svg">
+                    距离结束还有<span>{{item.scenes_day}}</span>天
+                  </p>
+                </div>
+            </div>
+          </van-cell>
+        </van-list>
+    </van-pull-refresh>
+    <Tabbar :active="0"></Tabbar>
+  </div>
+</template>
+<script>
+import { mapActions, mapState } from "vuex";
+import Tabbar from "@/components/Tabbar";
+export default {
+  components: { Tabbar },
+  data() {
+    return {
+      banner: require('@/assets/images/scene_banner.png'),
+      value:'',
+      list: [],
+      loading: false,
+      finished: false,
+      refreshing: false,
+      page:1,
+      pageSize:8,
+      scenes_name:'',
+      scenes_type:'',
+      total:'',
+      offset:100,
+    };
+  },
+  computed: {
+    ...mapState({ token: "token" }),
+  },
+  created () {
+    this.getPageData();
+  },
+  methods: {
+    ...mapActions({
+        newgetList: "newgetList", // 获取对练列表
+    }),
+    getPageData(){
+        let _this = this;
+        _this.newgetList({query:
+        {
+          token:_this.token,
+          page:_this.page,
+          scenes_name:_this.value
+        }
+        }).then((res) =>{
+          _this.total = res.total;
+          if(res.list.length === 0){
+             _this.finished = true;
+             return;
+          }
+          if(res.list.length < _this.pageSize){
+             _this.finished = true;
+          }else{
+            _this.finished = false;
+          }
+          if(_this.page == 1){
+            _this.list = res.list;
+          }else{
+            _this.list = _this.list.concat(res.list);
+          }
+        }).catch(err => {
+          _this.$toast.fail(err.msg)
+        }).finally(()=>{
+          _this.loading = false;
+          _this.refreshing = false;
+        });
+    },
+    onLoad(){
+       let _this = this;
+      setTimeout(function(){
+        _this.page++;
+        _this.getPageData();
+      },200)
+    },
+    onSearch(){
+      this.page = 1;
+      this.getPageData();
+    },
+    onCancel(){
+      this.value = '';
+    },
+    onRefresh() {
+      this.page = 1;
+      this.finished = true;
+      this.getPageData();
+    },
+    onVoice(item){
+      localStorage.setItem('item', JSON.stringify(item));
+      this.$router.push({
+        path: "/iframe",
+        query: {},
+      });
+    }
+  }
+}
+</script>
+<style lang="less" scoped>
+.list-content{
+  padding-bottom: 90px;
+}
+.list-search{
+    position: fixed;
+    top: 0;
+    left: 0;
+    z-index: 99;
+    width: 100%;
+    height: 88px;
+    .van-search{
+      padding: 8px 32px;
+    }
+}
+.list-banner{
+  height: 300px;
+  margin-top: 88px;
+  .van-image{
+    width: 100%;
+    height: 100%;
+  }
+}
+/deep/ .van-pull-refresh{
+  background-color: #f8f8f8;
+}
+/deep/ .van-list{
+    background-color: #f8f8f8;
+    .van-cell{
+      padding: 20px 0 20px 32px;
+      height: 200px;
+      margin-top: 20px;
+      .list-li{
+        display: flex;
+        height: 100%;
+        width: 100%;
+        .list-left{
+          width: 232px;
+          height: 160px;
+          .van-image{
+            width: 100%;
+            height: 100%;
+            .van-image__img{
+              object-fit:cover
+            }
+          }
+        }
+        .list-right{
+            width: 420px;
+            margin-left: 16px;
+            .r-title{
+              height: 48px;
+              line-height: 48px;
+              color: #000000;
+              font-size: 32px;
+              font-weight: 500;
+              white-space:nowrap;
+              overflow:hidden;
+              text-overflow:ellipsis;
+              margin-bottom: 12px;
+            }
+            .r-tip{
+              font-size: 0;
+              height: 36px;
+              line-height: 36px;
+                  label{
+                    display: inline-block;
+                    font-size: 14px;
+                    color: #16BAA8;
+                    text-align: center;
+                    max-width: 134px;
+                    height: 36px;
+                    line-height: 36px;
+                    border: 1px solid #16BAA8;
+                    background: #ffffff;
+                    overflow: hidden;
+                    text-overflow: ellipsis;
+                    white-space: nowrap;
+                    margin-right: 8px;
+                    text-align: center;
+                    padding: 0 5px;
+                  }
+                  label:last-child{
+                    margin-right: 0;
+                  }
+            }
+            .r-time{
+              margin-top: 20px;
+              height: 42px;
+              line-height: 42px;
+              img{
+                width: 28px;
+                height: 28px;
+                position: relative;
+                top:3px;
+              }
+              span{
+                color: #ff7b73;
+                display: inline-block;
+                margin: 0 4px;
+              }
+            }
+        }
+      }
+    }
+    .van-cell::after{
+      border-bottom:none;
+    }
+}
+</style>
diff --git a/static/.gitkeep b/static/.gitkeep
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/static/.gitkeep

--
Gitblit v1.8.0