53e55b6ced38846062d6334df5111c8876cf2845..9860e221460a0a4ac1903dad2c97160d0eed0e63
2023-03-03 long
初始化
9860e2 对比 | 目录
2023-03-03 long
初始化
36e1de 对比 | 目录
62个文件已添加
1个文件已修改
40019 ■■■■■ 已修改文件
.babelrc 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.editorconfig 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.eslintignore 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.eslintrc.js 198 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.gitignore 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.postcssrc.js 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
README.md 33 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
build/build.js 41 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
build/check-versions.js 54 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
build/logo.png 补丁 | 查看 | 原始文档 | blame | 历史
build/utils.js 102 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
build/vue-loader.conf.js 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
build/webpack.base.conf.js 82 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
build/webpack.dev.conf.js 95 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
build/webpack.prod.conf.js 149 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
clear.html 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
config/dev.env.js 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
config/index.js 85 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
config/prod.env.js 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
config/test.env.js 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
index.html 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
jsconfig.json 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
package-lock.json 31110 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
package.json 82 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/App.vue 284 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/css/common.css 149 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/imgs/AI.png 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/imgs/mySelf.png 补丁 | 查看 | 原始文档 | blame | 历史
src/components/ChatList/1.vue 44 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/ChatList/chat_list.vue 315 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/ChatList/chat_list_mockData.js 79 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/ChatList/msg/imagePop.vue 101 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/ChatList/msg/textPop.vue 47 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/config/appid.js 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/config/index.js 87 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/filter/index.js 43 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/libs/axios.js 41 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main.js 42 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/mock.js 75 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/index.vue 76 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/router/index.js 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/store/index.js 59 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/chat/chat.js 50 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/districts.js 4599 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/fn.js 333 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/g_fn_install.js 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/jun_form_rules.js 169 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/jun_http.js 289 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/jun_httpEvent.js 376 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/jun_httpInstall.js 71 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/jun_httpStatus.js 31 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/jun_login.js 125 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/jun_upload.js 35 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/wxsign.js 92 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
static/.gitkeep 补丁 | 查看 | 原始文档 | blame | 历史
static/bgm/.gitkeep 补丁 | 查看 | 原始文档 | blame | 历史
static/imgs/headimg.jpg 补丁 | 查看 | 原始文档 | blame | 历史
test/e2e/custom-assertions/elementCount.js 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
test/e2e/nightwatch.conf.js 46 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
test/e2e/runner.js 48 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
test/e2e/specs/test.js 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
test/unit/.eslintrc 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
test/unit/specs/HelloWorld.spec.js 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.babelrc
New file
@@ -0,0 +1,17 @@
{
  "presets": [
    ["env", {
      "modules": false,
      "targets": {
        "browsers": ["> 1%", "last 2 versions", "not ie <= 8"]
      }
    }],
    "stage-2"
  ],
  "plugins": ["transform-vue-jsx", "transform-runtime"],
  "env": {
    "test": {
      "presets": ["env", "stage-2"]
    }
  }
}
.editorconfig
New file
@@ -0,0 +1,9 @@
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 2
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
.eslintignore
New file
@@ -0,0 +1,4 @@
build/*.js
src/assets
public
dist
.eslintrc.js
New file
@@ -0,0 +1,198 @@
module.exports = {
  root: true,
  parserOptions: {
    parser: 'babel-eslint',
    sourceType: 'module'
  },
  env: {
    browser: true,
    node: true,
    es6: true,
  },
  extends: ['plugin:vue/recommended', 'eslint:recommended'],
  // add your custom rules here
  //it is base on https://github.com/vuejs/eslint-config-vue
  rules: {
    "vue/max-attributes-per-line": [2, {
      "singleline": 10,
      "multiline": {
        "max": 1,
        "allowFirstLine": false
      }
    }],
    "vue/singleline-html-element-content-newline": "off",
    "vue/multiline-html-element-content-newline":"off",
    "vue/name-property-casing": ["error", "PascalCase"],
    "vue/no-v-html": "off",
    'accessor-pairs': 2,
    'arrow-spacing': [2, {
      'before': true,
      'after': true
    }],
    'block-spacing': [2, 'always'],
    'brace-style': [2, '1tbs', {
      'allowSingleLine': true
    }],
    'camelcase': [0, {
      'properties': 'always'
    }],
    'comma-dangle': [2, 'never'],
    'comma-spacing': [2, {
      'before': false,
      'after': true
    }],
    'comma-style': [2, 'last'],
    'constructor-super': 2,
    'curly': [2, 'multi-line'],
    'dot-location': [2, 'property'],
    'eol-last': 2,
    'eqeqeq': ["error", "always", {"null": "ignore"}],
    'generator-star-spacing': [2, {
      'before': true,
      'after': true
    }],
    'handle-callback-err': [2, '^(err|error)$'],
    'indent': [2, 2, {
      'SwitchCase': 1
    }],
    'jsx-quotes': [2, 'prefer-single'],
    'key-spacing': [2, {
      'beforeColon': false,
      'afterColon': true
    }],
    'keyword-spacing': [2, {
      'before': true,
      'after': true
    }],
    'new-cap': [2, {
      'newIsCap': true,
      'capIsNew': false
    }],
    'new-parens': 2,
    'no-array-constructor': 2,
    'no-caller': 2,
    'no-console': 'off',
    'no-class-assign': 2,
    'no-cond-assign': 2,
    'no-const-assign': 2,
    'no-control-regex': 0,
    'no-delete-var': 2,
    'no-dupe-args': 2,
    'no-dupe-class-members': 2,
    'no-dupe-keys': 2,
    'no-duplicate-case': 2,
    'no-empty-character-class': 2,
    'no-empty-pattern': 2,
    'no-eval': 2,
    'no-ex-assign': 2,
    'no-extend-native': 2,
    'no-extra-bind': 2,
    'no-extra-boolean-cast': 2,
    'no-extra-parens': [2, 'functions'],
    'no-fallthrough': 2,
    'no-floating-decimal': 2,
    'no-func-assign': 2,
    'no-implied-eval': 2,
    'no-inner-declarations': [2, 'functions'],
    'no-invalid-regexp': 2,
    'no-irregular-whitespace': 2,
    'no-iterator': 2,
    'no-label-var': 2,
    'no-labels': [2, {
      'allowLoop': false,
      'allowSwitch': false
    }],
    'no-lone-blocks': 2,
    'no-mixed-spaces-and-tabs': 2,
    'no-multi-spaces': 2,
    'no-multi-str': 2,
    'no-multiple-empty-lines': [2, {
      'max': 1
    }],
    'no-native-reassign': 2,
    'no-negated-in-lhs': 2,
    'no-new-object': 2,
    'no-new-require': 2,
    'no-new-symbol': 2,
    'no-new-wrappers': 2,
    'no-obj-calls': 2,
    'no-octal': 2,
    'no-octal-escape': 2,
    'no-path-concat': 2,
    'no-proto': 2,
    'no-redeclare': 2,
    'no-regex-spaces': 2,
    'no-return-assign': [2, 'except-parens'],
    'no-self-assign': 2,
    'no-self-compare': 2,
    'no-sequences': 2,
    'no-shadow-restricted-names': 2,
    'no-spaced-func': 2,
    'no-sparse-arrays': 2,
    'no-this-before-super': 2,
    'no-throw-literal': 2,
    'no-trailing-spaces': 2,
    'no-undef': 2,
    'no-undef-init': 2,
    'no-unexpected-multiline': 2,
    'no-unmodified-loop-condition': 2,
    'no-unneeded-ternary': [2, {
      'defaultAssignment': false
    }],
    'no-unreachable': 2,
    'no-unsafe-finally': 2,
    'no-unused-vars': [2, {
      'vars': 'all',
      'args': 'none'
    }],
    'no-useless-call': 2,
    'no-useless-computed-key': 2,
    'no-useless-constructor': 2,
    'no-useless-escape': 0,
    'no-whitespace-before-property': 2,
    'no-with': 2,
    'one-var': [2, {
      'initialized': 'never'
    }],
    'operator-linebreak': [2, 'after', {
      'overrides': {
        '?': 'before',
        ':': 'before'
      }
    }],
    'padded-blocks': [2, 'never'],
    'quotes': [2, 'single', {
      'avoidEscape': true,
      'allowTemplateLiterals': true
    }],
    'semi': [2, 'never'],
    'semi-spacing': [2, {
      'before': false,
      'after': true
    }],
    'space-before-blocks': [2, 'always'],
    'space-before-function-paren': [2, 'never'],
    'space-in-parens': [2, 'never'],
    'space-infix-ops': 2,
    'space-unary-ops': [2, {
      'words': true,
      'nonwords': false
    }],
    'spaced-comment': [2, 'always', {
      'markers': ['global', 'globals', 'eslint', 'eslint-disable', '*package', '!', ',']
    }],
    'template-curly-spacing': [2, 'never'],
    'use-isnan': 2,
    'valid-typeof': 2,
    'wrap-iife': [2, 'any'],
    'yield-star-spacing': [2, 'both'],
    'yoda': [2, 'never'],
    'prefer-const': 2,
    'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0,
    'object-curly-spacing': [2, 'always', {
      objectsInObjects: false
    }],
    'array-bracket-spacing': [2, 'never']
  }
}
.gitignore
New file
@@ -0,0 +1,17 @@
.DS_Store
node_modules/
/dist/
npm-debug.log*
yarn-debug.log*
yarn-error.log*
/test/unit/coverage/
/test/e2e/reports/
selenium-debug.log
# Editor directories and files
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
.postcssrc.js
New file
@@ -0,0 +1,30 @@
// https://github.com/michael-ciniawsky/postcss-load-config
module.exports = {
  "plugins": {
    "postcss-import": {},
    "postcss-url": {},
    "postcss-px-to-viewport-opt": {
      viewportWidth: 750,// 设计稿宽度
      viewportHeight: 1334,// 设计稿高度,可以不指定
      unitPrecision: 3,// px to vw无法整除时,保留几位小数
      viewportUnit: 'vw',// 转换成vw单位    
      selectorBlackList: ['.ignore', '.hairlines'],// 不转换的类名
      minPixelValue: 1,// 小于1px不转换
      mediaQuery: false, // 允许媒体查询中转换
      exclude: /(\/|\\)(node_modules)(\/|\\)/          // 排除node_modules文件中第三方css文件
    },
    "postcss-viewport-units": {},
    "cssnano": {
      // preset: "advanced",
      // autoprefixer: false,// 和cssnext同样具有autoprefixer,保留一个
      // "postcss-zindex": false
      "cssnano-preset-advanced": {
        "zindex": false,
        "autoprefixer": false
      }
    },
    // to edit target browsers: use "browserslist" field in package.json
    // "autoprefixer": {}
  }
}
README.md
@@ -1,4 +1,37 @@
<<<<<<< HEAD
# empty_wx_h5_vue_hx
> 火熊微信公众号H5vue起始源码
## 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
# run unit tests
npm run unit
# run e2e tests
npm run e2e
# run all tests
npm test
```
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).
=======
## ChatGPT_h5
ChatGPT的h5页面项目
>>>>>>> 53e55b6ced38846062d6334df5111c8876cf2845
build/build.js
New file
@@ -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'
    ))
  })
})
build/check-versions.js
New file
@@ -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)
  }
}
build/logo.png
build/utils.js
New file
@@ -0,0 +1,102 @@
'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',
        publicPath:'../../', // 修改,兼容css背景图
      })
    } 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')
    })
  }
}
build/vue-loader.conf.js
New file
@@ -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'
  }
}
build/webpack.base.conf.js
New file
@@ -0,0 +1,82 @@
'use strict'
const path = require('path')
const utils = require('./utils')
const config = require('../config')
const vueLoaderConfig = require('./vue-loader.conf')
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'
  }
}
build/webpack.dev.conf.js
New file
@@ -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)
    }
  })
})
build/webpack.prod.conf.js
New file
@@ -0,0 +1,149 @@
'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 = process.env.NODE_ENV === 'testing'
  ? require('../config/test.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: process.env.NODE_ENV === 'testing'
        ? 'index.html'
        : 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
clear.html
New file
@@ -0,0 +1,16 @@
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script>
        localStorage.clear()
        // TODO 直接进入长链
        // location.href = '';
    </script>
</head>
<body>
</body>
</html>
config/dev.env.js
New file
@@ -0,0 +1,7 @@
'use strict'
const merge = require('webpack-merge')
const prodEnv = require('./prod.env')
module.exports = merge(prodEnv, {
  NODE_ENV: '"development"'
})
config/index.js
New file
@@ -0,0 +1,85 @@
'use strict'
// Template version: 1.3.1
// see http://vuejs-templates.github.io/webpack for documentation.
const path = require('path')
module.exports = {
  dev: {
    // Paths
    assetsSubDirectory: 'static',
    assetsPublicPath: '/',
    // 反向代理
    proxyTable: {
      '/api_local': { // 本地
        target: 'http://192.168.31.69:8080', // 这个是你服务器开启的接口
        changeOrigin: true, // 是否跨域
        pathRewrite: {
          '^/api_local': ''
        }
      },
      '/api_test': { // 测试环境
        // target: 'https://test5.phiskin.com/',
        target: 'http://chatgpt.phiskin.com/', // 这个是你服务器开启的接口
        changeOrigin: true, // 是否跨域
        pathRewrite: {
          '^/api_test': ''
        }
      }
    },
    // Various Dev Server settings
    host: '0.0.0.0', // can be overwritten by process.env.HOST
    port: 8080, // 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: './',
    // assetsPublicPath: 'https://miji20.oss-cn-shenzhen.aliyuncs.com/miji/', // oss链接
    /**
     * Source Maps
     */
    productionSourceMap: false, // 打包时不生成map
    // 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
  }
}
config/prod.env.js
New file
@@ -0,0 +1,4 @@
'use strict'
module.exports = {
  NODE_ENV: '"production"'
}
config/test.env.js
New file
@@ -0,0 +1,7 @@
'use strict'
const merge = require('webpack-merge')
const devEnv = require('./dev.env')
module.exports = merge(devEnv, {
  NODE_ENV: '"testing"'
})
index.html
New file
@@ -0,0 +1,24 @@
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>ChatGPT</title>
    <meta name="viewport" content="width=device-width,initial-scale=1.0,user-scalable=no">
    <meta http-equiv="X-UA-Compatible" content="IE=Edge,chrome=1" />
    <!-- <link rel="stylesheet" href="./static/swiper/swiper.min.css"> -->
  </head>
  <body>
    <div id="app"></div>
    <!-- built files will be auto injected -->
    <!-- <script src="./static/swiper/swiper.min.js"></script> -->
    <script src="https://res.wx.qq.com/open/js/jweixin-1.6.0.js"></script>
    <script>
      (function () {
          var src = '//cdn.bootcss.com/eruda/1.4.2/eruda.min.js';
          if (!/eruda=true/.test(window.location) && localStorage.getItem('active-eruda') != 'true') return;
          document.write('<scr' + 'ipt src="' + src + '"></scr' + 'ipt>');
          document.write('<scr' + 'ipt>eruda.init();</scr' + 'ipt>');
      })();
    </script>
  </body>
</html>
jsconfig.json
New file
@@ -0,0 +1,9 @@
{
  "compilerOptions": {
    "baseUrl": "./",
    "paths": {
        "@/*": ["src/*"]
    }
  },
  "exclude": ["node_modules", "dist"]
}
package-lock.json
New file
Diff too large
package.json
New file
@@ -0,0 +1,82 @@
{
  "name": "empty_vue_h5",
  "version": "1.0.0",
  "description": "Vue H5 基础模板",
  "author": "children117cl <278950112@qq.com>",
  "private": true,
  "scripts": {
    "dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js --host 0.0.0.0",
    "start": "npm run dev",
    "e2e": "node test/e2e/runner.js",
    "test": "npm run unit && npm run e2e",
    "lint": "eslint --fix --ext .js,.vue src",
    "build": "node build/build.js"
  },
  "dependencies": {
    "axios": "^0.19.2",
    "clipboard": "^2.0.6",
    "moment": "^2.29.4",
    "vue": "^2.5.2",
    "vue-router": "^3.0.1",
    "vuex": "^3.5.1"
  },
  "devDependencies": {
    "autoprefixer": "^7.1.2",
    "babel-core": "^6.22.1",
    "babel-eslint": "10.1.0",
    "babel-helper-vue-jsx-merge-props": "^2.0.3",
    "babel-loader": "^7.1.1",
    "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",
    "babel-register": "^6.22.0",
    "chalk": "^2.0.1",
    "chromedriver": "^2.27.2",
    "copy-webpack-plugin": "^4.0.1",
    "cross-spawn": "^5.0.1",
    "css-loader": "^0.28.0",
    "cssnano": "^4.1.10",
    "cssnano-preset-advanced": "^4.0.7",
    "eslint": "6.7.2",
    "eslint-plugin-vue": "6.2.2",
    "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",
    "mockjs": "^1.1.0",
    "nightwatch": "^0.9.12",
    "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-opt": "^0.0.4",
    "postcss-url": "^7.2.1",
    "postcss-viewport-units": "^0.1.6",
    "rimraf": "^2.6.0",
    "selenium-server": "^3.0.1",
    "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"
  ]
}
src/App.vue
New file
@@ -0,0 +1,284 @@
<template>
  <div id="app">
    <transition name="fade" mode="out-in">
      <!-- 页面,isRouterAlive 刷新用 -->
      <router-view v-if="isRouterAlive" />
    </transition>
    <!-- Toast 提示 -->
    <transition name="fade">
      <div v-if="toastShow" class="toast-mask flex flex-center" :class="{'type-toast': toastType=='toast', 'type-loading': toastType=='loading'}">
        <!-- 提示 -->
        <div v-if="toastType=='toast'" class="toast" v-html="toastText" />
        <!-- loading -->
        <div v-if="toastType=='loading'" class="toast">
          <div class="ball-rotate"><div /></div>
        </div>
      </div>
    </transition>
  </div>
</template>
<script>
// 入口参数
// 这里注释入口参数
import Login from '@/utils/jun_login.js'
import wxsign from '@/utils/wxsign.js'
import Config from './config'
var timer = null
export default {
  name: 'App',
  // 安装组件
  components: {
  },
  data() {
    return {
      // 刷新用
      isRouterAlive: true,
      toastText: '',
      toastShow: false,
      toastType: 'toast'
    }
  },
  // 监听路由
  watch: {
    // $route(to, from){
    // }
  },
  // 子组件injection
  // -用于共享app methods
  provide() {
    return {
      reload: this.reload, // 刷新
      toast: this.toast, // toast
      // 修复input导致ios微信浏览器收起键盘时,不回滚
      inputBlur: () => {
        const u = navigator.userAgent
        const app = navigator.appVersion
        const isIOS = !!u.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/)
        if (isIOS) {
          setTimeout(() => {
            const scrollHeight = document.documentElement.scrollTop || document.body.scrollTop || 0
            window.scrollTo(0, Math.max(scrollHeight - 1, 0))
          }, 200)
        }
      },
      noop() {}
    }
  },
  mounted() {
    console.log('app amounted')
    // 避免刷新导致code重复使用
    const code = this.getQueryString('code') // 微信回调code
    const local_code = this.getLocalStorage('code') // 本地缓存code
    // 以下情况需要重定向到长链
    // 1. 链接带有code,但code已经用过
    // 2. 非cover入口
    // 3. 正式环境
    // 4. 非规则单页
    // 4. 非优惠券单页
    // console.dir(Config.createCodeUrl())
    if (Config.isWxLoginType) {
      if (code && local_code == code || (!code && !Config.ismock && !Config.istest)) {
        // 该微信code已使用,重新跳转长链
        Login.toLongUrl()
        return
      } else {
        this.removeLocalStorage('code')
      }
    }
    // 刷新固定首页
    if (location.hash && location.hash != '#/') {
      this.$router.replace({ name: 'root' })
    }
    // 暴露到全局,jun_httpEvent.js 调用
    window.appLoading = this.loading.bind(this)
    window.appHideLoading = this.hideLoading.bind(this)
    window.appToast = this.toast.bind(this)
    // this.$refs.audio.play()
    if (Config.isWxLoginType) this.wxinit()
  },
  methods: {
    // 微信初始化
    wxinit() {
      wxsign.main()
      // 测试用
      // let time = new Date('2020/10/11 00:00:00').getTime()
      // if ( Date.now() < time ) {
      //     alert('该活动未结束')
      // }
      wx.ready(() => {
        console.log('ready')
        const shareImg = sessionStorage.getItem('shareImg')
        const shareTitle = sessionStorage.getItem('shareTitle')
        const shareDesc = sessionStorage.getItem('shareDesc')
        // 分享链接
        let url = location.origin + location.pathname + '?cover=1'
        // 带channelNo
        const we_session = this.getLocalStorage('we_session')
        if (we_session && we_session.we_session) {
          url += '&channelNo=' + we_session.we_session
        }
        console.log(url)
        // 分享
        // wx.updateAppMessageShareData({
        //     title: shareTitle, // 分享标题
        //     desc: shareDesc, // 分享描述
        //     link: url, // 分享链接,该链接域名或路径必须与当前页面对应的公众号JS安全域名一致
        //     imgUrl: shareImg, // 分享图标
        //     success: function () {
        //         // 设置成功
        //     }
        // })
        // wx.updateTimelineShareData({
        //     title: shareTitle, // 分享标题
        //     link: url, // 分享链接,该链接域名或路径必须与当前页面对应的公众号JS安全域名一致
        //     imgUrl: shareImg, // 分享图标
        //     success: function () {
        //         // 设置成功
        //     }
        // })
        wx.onMenuShareTimeline({
          title: shareTitle, // 分享标题
          link: url, // 分享链接,该链接域名或路径必须与当前页面对应的公众号JS安全域名一致
          imgUrl: shareImg, // 分享图标
          success: function() {
            // 用户点击了分享后执行的回调函数
            // alert(123)
          }
        })
        wx.onMenuShareAppMessage({
          title: shareTitle, // 分享标题
          desc: shareDesc, // 分享描述
          link: url, // 分享链接,该链接域名或路径必须与当前页面对应的公众号JS安全域名一致
          imgUrl: shareImg, // 分享图标
          type: 'link', // 分享类型,music、video或link,不填默认为link
          // dataUrl: '', // 如果type是music或video,则要提供数据链接,默认为空
          success: function() {
            // 用户点击了分享后执行的回调函数
            // alert(123)
          }
        })
      })
    },
    // 刷新
    reload() {
      this.isRouterAlive = false
      this.$nextTick(() => {
        this.isRouterAlive = true
      })
    },
    // toast
    toast(e, type) {
      type = type || 'toast'
      this.toastText = e
      this.toastShow = true
      this.toastType = type
      if (type == 'toast') {
        clearTimeout(timer)
        timer = setTimeout(() => {
          this.hideToast()
        }, 2000)
      }
    },
    // 显示loading
    loading() {
      this.toast('', 'loading')
    },
    // 隐藏toast
    hideToast() {
      this.toastShow = false
    },
    // toast loading 收起
    hideLoading() {
      this.hideToast()
    }
  }
}
</script>
<style>
@import './assets/css/common.css';
.toast-mask{
    position: fixed;
    z-index: 2000;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    transition: all .5s;
}
.toast-mask.type-toast{
    pointer-events: none;
}
.toast{
    text-align: center;
    border-radius: 6px;
    color:#FFF;
    background: rgba(17, 17, 17, 0.9);
    line-height: 1.6;
    padding: 30px;
    max-width: 400px;
    font-size: 24px;
}
@keyframes ball-rotate {
  0% {transform: rotate(0deg) scale(1); }
  50% {transform: rotate(180deg) scale(0.6);}
  100% {transform: rotate(360deg) scale(1);}
}
.ball-rotate{position: relative;width: 200px;height: 140px;}
.ball-rotate > div {
    background-color: #fff;
    width: 30px;
    height: 30px;
    border-radius: 100%;
    margin: 4px;
    animation-fill-mode: both;
    position: absolute;
    top: 50%;
    left: 50%;
    margin: -16px 0 0 -16px;
}
.ball-rotate > div:first-child {
    animation: ball-rotate 1s 0s cubic-bezier(.7, -.13, .22, .86) infinite;
}
.ball-rotate > div:before, .ball-rotate > div:after {
    background-color: #fff;
    width: 30px;
    height: 30px;
    border-radius: 100%;
    margin: 4px;
    content: ""!important;
    position: absolute;
    opacity: .8;
}
.ball-rotate > div:before {top: 0px;left: -56px}
.ball-rotate > div:after {top: 0px;left: 50px}
</style>
src/assets/css/common.css
New file
@@ -0,0 +1,149 @@
/*@import "../js/app/toast/toast.css";*/
html{
    -webkit-tap-highlight-color: rgba(0,0,0,0);
    word-break: break-all;
    -webkit-text-size-adjust:none;
    text-size-adjust:none;
}
a,address,b,big,blockquote,body,center,cite,code,dd,del,div,dl,dt,em,fieldset,font,form,h1,h2,h3,h4,h5,h6,html,i,iframe,img,ins,label,legend,li,ol,p,pre,small,span,strong,u,ul,var {
    margin: 0;
    padding: 0
}
html,input,textarea,select{font-family: -apple-system-font, Helvetica Neue, Helvetica, STHeiTi, Microsoft Yahei, sans-serif;}
body{
    font: 12px/1.5 arial,Microsoft YaHei,"\u5b8b\u4f53",sans-serif;
    -webkit-font-smoothing: antialiased;
    color: #333;
    background: #f2f2f2;
}
a {color: #333;text-decoration: none;}
ol,ul{list-style: none;}
.flex {
  display: box;
  display: -webkit-box;      /* OLD - iOS 6-, Safari 3.1-6 */
  display: -moz-box;         /* OLD - Firefox 19- (buggy but mostly works) */
  display: -ms-flexbox;      /* TWEENER - IE 10 */
  display: -webkit-flex;     /* NEW - Chrome */
  display: -moz-flex;
  display: -ms-flex;
  display: -o-flex;
  display: flex;             /* NEW, Spec - Opera 12.1, Firefox 20+ */
}
.flex-1 {
    -webkit-box-flex: 1;
    flex: 1;
    -webkit-flex: 1;
}
.w1{width: 1px;}.h1{height: 1px;}
.flex-center {
    justify-content: center;
    -webkit-justify-content: center;
    align-items: center;
    -webkit-align-items: center;
}
.flex-ver {-webkit-align-items: center;align-items: center;}
.flex-col {-webkit-flex-flow: column;flex-flow: column;}
/* flex 换行 */
.flex-wrap {-webkit-flex-wrap: wrap;flex-wrap: wrap;}
.flex-end{
    -webkit-justify-content: flex-end;
    justify-content: flex-end;
}
.flex-sb{
    -webkit-justify-content: space-between;
    justify-content: space-between;
}
.ell{text-overflow:ellipsis;overflow:hidden;white-space:nowrap;}
.wsnw{white-space: nowrap;}
input::-webkit-input-placeholder, textarea::-webkit-input-placeholder{
    color: #999;
}
/* css arrow */
.arrow{
    display: inline-block;
    border: solid #29262A;
    border-width: 1px 1px 0 0;
}
.ar-h24{width: 9px;height: 9px;}
.ar-h20{width: 7px;height: 7px;}
.arrow-right{-webkit-transform: rotate(45deg);transform: rotate(45deg);}
.arrow-down{-webkit-transform: rotate(135deg) translate(-2px,2px);transform: rotate(135deg) translate(-2px,2px);}
.ac{text-align: center;}.ar{text-align: right;}
.hide{display: none!important;}
input::-webkit-outer-spin-button,
input::-webkit-inner-spin-button {
-webkit-appearance: none;
}
input[type="number"]{
-moz-appearance: textfield;
}
/*page common*/
.w{
    width: 1280px;
    margin: 0 auto;
}
.fl{float: left}
.fr{float: right}
.al{text-align: left;}.ar{text-align: right;}
.tdu{text-decoration: underline;}
.clear,.clr,.clearfix:after {
    content: '';
    display: block;
    overflow: hidden;
    clear: both;
    height: 0;
    line-height: 0;
    font-size: 0;
}
.blue{color: #0084e6;}
/* 浅色#23a9fd 中间#218ad9 深色#005bbd */
.gray{color: #666;}.g4{color: #444;}
.red{color: red;}.green{color: green;}
.orange{color: #FC6621;}
.vm{vertical-align: middle;}
.ml20{margin-left: 20px;}
.fs16{font-size: 16px;}
/* ====================================================================== */
.fs20{font-size:20px}
html, body{
    min-height: 100%;
}
body{
    height: 100%;
    /* background: #000; */
    -webkit-text-size-adjust: 100% !important;
    text-size-adjust: 100% !important;
    -moz-text-size-adjust: 100% !important;
}
#app{
    user-select: none;
    -webkit-user-select: none;
    /* width: 100vw;
    height: 100vh;
    background: url(../imgs/bg.jpg) no-repeat;
    background-size: 100% 100%;
    min-height: 1204px; */
}
/* .appContainer{
    position: relative;width: 100%;
    min-height: 1204px;
} */
/* 遮罩 */
.mask{
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 10;
    background: rgba(0,0,0,0.8);
}
src/assets/imgs/AI.png
src/assets/imgs/mySelf.png
src/components/ChatList/1.vue
New file
@@ -0,0 +1,44 @@
<template>
  <div>
    <!-- 气泡组件 -->
    <!-- 头像、气泡、时间 -->
    <!-- 文字 -->
    <div v-if="mode=='text'">
      <textPop />
    </div>
    <!-- 图片 -->
    <picPop />
    <!-- 视频 -->
    <videoPop />
    <!-- 音频 -->
    <audioPop />
    <!-- 链接 -->
    <linkPop />
  </div>
</template>
<script>
export default {
  name: 'Mmm',
  data() {
    return {
    }
  },
  mounted() {
  },
  methods: {
  }
}
</script>
<style scoped>
</style>
src/components/ChatList/chat_list.vue
New file
@@ -0,0 +1,315 @@
<template>
  <div>
    <ul ref="chatList" class="chat-list" :style="`height:${height};`">
      <li v-for="(item, index) in list" :key="index">
        <!-- 时间 -->
        <div v-if="item.time_tx" class="flex"><div class="chat-time">{{ item.time_tx }}</div></div>
        <!-- 聊天主体 ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ -->
        <!-- 通常主体 有用户信息 -->
        <div class="chat-item" :class="{'me-item': getDirection(item.fromUser)=='right'}">
          <img :src="item.avatar" alt="" class="avatar" fit="cover">
          <div class="info">
            <!-- ({{ item.msgtype }}) -->
            <div class="name">{{ item.name }}</div>
            <!-- 消息内容 ↓↓↓↓↓↓↓↓↓↓ -->
            <!-- 文本 -->
            <textPop v-if="item.msgtype === 'text'" :data="item.content" :direction="getDirection(item.fromUser)" />
            <!-- 图片 -->
            <imagePop v-if="item.msgtype === 'image'" :data="item" :direction="getDirection(item.fromUser)" />
            <!-- 消息内容 ↑↑↑↑↑↑↑↑↑↑ -->
          </div>
        </div>
        <!-- 聊天主体 ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ -->
      </li>
      <div v-if="loading" class="text-center">
        <div class="ball-rotate"><div /></div>
      </div>
    </ul>
    <div class="handle-wrap flex flex-ver">
      <input v-model="myTalk" type="text" class="input flex-1" maxlength="100" @keyup.enter="handleSend">
      <div class="send-btn" @click="handleSend">发送</div>
    </div>
    <!-- audioFileUrl -->
    <audio ref="refAudio" src="">
      您的浏览器不支持 audio 标签。
    </audio>
  </div>
</template>
<script>
// const listMockData = require('./chat_list_mockData')
const chatFn = require('@/utils/chat/chat.js')
import imagePop from './msg/imagePop.vue'
import textPop from './msg/textPop.vue'
import config from '../../config/index'
export default {
  name: 'ChatList',
  components: { textPop, imagePop },
  mixins: [],
  props: {
    // 高度
    height: {
      type: String,
      default: '100vh'
    }
  },
  data() {
    return {
      userId: '',
      myTalk: '',
      // 列表数据
      loading: false,
      list: []
    }
  },
  mounted() {
    this.init()
  },
  methods: {
    init() {
      this.userId = this.getQueryString('userId')
      console.log('this.userId', this.userId)
      if (!this.userId) window.appToast('缺少userID,请重新进入')
    },
    // 判断位置 return 'left' 'right'
    getDirection(itemUserId) {
      return itemUserId === 'mySelf' ? 'right' : 'left'
    },
    // 点击发送
    handleSend() {
      if (this.loading) return window.appToast('操作过快,请稍后')
      if (!this.userId) return window.appToast('缺少userID,请重新进入')
      const myTalk = JSON.parse(JSON.stringify(this.myTalk))
      const list = JSON.parse(JSON.stringify(this.list))
      if (!myTalk) return window.appToast('请输入内容')
      this.myTalk = ''
      list.push({
        content: { 'content': myTalk },
        name: '我',
        msgtime: this.$moment().format('yyyy-MM-DD HH:mm:ss'),
        action: 'send',
        id: this.$moment().format('yyyyMMDDHHmmss'),
        avatar: require('@/assets/imgs/mySelf.png'),
        msgtype: 'text',
        fromUser: 'mySelf'
      })
      // 处理聊天显示的日期
      list && list.forEach((item, index) => {
        if (index !== 0) item.time_tx = chatFn.chatTime(item.msgtime, list[index - 1] && list[index - 1].msgtime)
      })
      this.list = list
      this.$nextTick(() => { this.scrollBottom() })
      this.getData(myTalk)
    },
    // 获取ai返回内容
    getData(keyWord) {
      console.log('getChatList')
      var { loading, userId } = this
      const list = JSON.parse(JSON.stringify(this.list))
      if (loading) return
      this.loading = true
      const url = config.is_use_test_server ? 'crm-user/chatGpt/chat' : 'chat/msg'
      this.Req.http.post({
        url: url,
        data: {
          msg: keyWord,
          userId: userId
        },
        udData: { noLoading: true, nokey: true },
        mockData: {
          code: 100,
          msg: '',
          data: {
            text: '机器人回复'
          }
        }
      }).then(res => {
        this.loading = false
        if (res.data) {
          const content = res.data.replace(/\n/g, '</br>')
          list.push({
            content: { 'content': content },
            name: 'AI',
            msgtime: this.$moment().format('yyyy-MM-DD HH:mm:ss'),
            action: 'send',
            id: this.$moment().format('yyyyMMDDHHmmss'),
            avatar: require('@/assets/imgs/AI.png'),
            msgtype: 'text',
            fromUser: 'robot'
          })
        }
        // 处理聊天显示的日期
        list && list.forEach((item, index) => {
          if (index !== 0) item.time_tx = chatFn.chatTime(item.msgtime, list[index - 1] && list[index - 1].msgtime)
        })
        this.list = list
        this.$nextTick(() => { this.scrollBottom() })
      }).catch(res => {
        this.loading = false
        const warnText = res.msg || ('请稍后再试...')
        window.appToast(warnText)
      })
    },
    scrollBottom() {
      this.$nextTick(() => {
        this.$refs.chatList.scrollTop = this.$refs.chatList.scrollHeight
      })
    }
  }
}
</script>
<style scoped>
.loading-tx{
  font-size: 22px;
  color: #999;
}
.chat-list{
  overflow-y: auto;
  background-color: #fff;
  padding-top: 40px;
  box-sizing: border-box;
  padding-bottom: 140px;
}
.chat-time{
  background-color: #DADADA;
  color: #fff;
  font-size: 24px;
  line-height: 1.2;
  padding: 4px 0;
  border-radius: 6px;
  text-align: center;
  margin: 6px auto 10px;
  padding: 4px 10px;
}
.chat-item{
  margin-bottom: 20px;
  padding: 0 20px ;
}
.chat-item .avatar{
  height: 60px;
  width: 60px;
  display: block;
  margin-right: 10px;
  border-radius: 4px;
  float: left;
}
.chat-item .info{
  /* padding-top: 4px; */
  display: inline-block;
  text-align: left;
}
.chat-item .info .name{
  line-height: 1.2;
  height: 1.2em;
  margin-bottom: 4px;
  font-size: 22px;
}
.chat-item.me-item{
  text-align: right;
}
.chat-item.me-item .avatar{
  float: right;
  margin-right: 0;
  margin-left: 10px;
}
.chat-item.me-item .name{
  text-align: right;
}
.handle-wrap{
  background-color: #F7F7F7;
  position: fixed;
  bottom: 0;
  left: 0;
  width: 100%;
  padding: 20px 30px 60px 30px;
  box-sizing: border-box;
}
.handle-wrap .input{
  height: 60px;
  line-height: 30px;
  font-size: 26px;
  border: none;
  background-color: #fff;
  border-radius: 6px;
  outline: none;
}
.handle-wrap .input:focus{
  border: none;
}
.handle-wrap .send-btn{
  background-color: #45B1D7;
  height: 60px;
  line-height: 60px;
  color: #fff;
  padding: 0 20px;
  border-radius: 6px;
  letter-spacing: 4px;
  margin-left: 20px;
  font-size: 28px;
}
@keyframes ball-rotate {
  0% {transform: rotate(0deg) scale(1); }
  50% {transform: rotate(180deg) scale(0.6);}
  100% {transform: rotate(360deg) scale(1);}
}
.ball-rotate{position: relative;width: 100px;height: 70px;margin: 0 auto 20px;}
.ball-rotate > div {
    background-color: #000;
    width: 30px;
    height: 30px;
    border-radius: 100%;
    margin: 4px;
    animation-fill-mode: both;
    position: absolute;
    top: 50%;
    left: 50%;
    margin: -16px 0 0 -16px;
}
.ball-rotate > div:first-child {
    animation: ball-rotate 1s 0s cubic-bezier(.7, -.13, .22, .86) infinite;
}
.ball-rotate > div:before, .ball-rotate > div:after {
    background-color: #000;
    width: 30px;
    height: 30px;
    border-radius: 100%;
    margin: 4px;
    content: ""!important;
    position: absolute;
    opacity: .8;
}
.ball-rotate > div:before {top: 0px;left: -56px}
.ball-rotate > div:after {top: 0px;left: 50px}
</style>
src/components/ChatList/chat_list_mockData.js
New file
@@ -0,0 +1,79 @@
module.exports = {
  code: 100,
  msg: '',
  data: {
    'total': 26,
    'list': [
      {
        'fromUser': 'wmp4WCDwAAMbMkbHrDPZ2tOv_7QT5vYw',
        'name': 'LONG。',
        'msgtime': '2021-11-08 18:17:00',
        'action': 'send',
        'id': '2c2d2eff41d511eca7cd30d0422e31b5',
        'avatar': 'http://wx.qlogo.cn/mmhead/5K48YNcpF3YOicxsljPcJgMyNpThJrpeFzN1qeBxxCVqhs84kib69o1g/0',
        'msgtype': 'text',
        'content': '{"content":"[吃瓜]"}'
      },
      {
        'fromUser': 'wmp4WCDwAAMbMkbHrDPZ2tOv_7QT5vYw',
        'name': 'LONG。',
        'msgtime': '2021-11-08 18:16:54',
        'action': 'send',
        'id': '2c284aed41d511eca7cd30d0422e31b5',
        'avatar': 'http://wx.qlogo.cn/mmhead/5K48YNcpF3YOicxsljPcJgMyNpThJrpeFzN1qeBxxCVqhs84kib69o1g/0',
        'msgtype': 'text',
        'content': '{"content":"自适应"}'
      },
      {
        'fromUser': 'wmp4WCDwAAMbMkbHrDPZ2tOv_7QT5vYw',
        'name': 'LONG。',
        'msgtime': '2021-11-09 11:08:26',
        'action': 'send',
        'id': '75e23d2941d511eca7cd30d0422e31b5',
        'avatar': 'http://wx.qlogo.cn/mmhead/5K48YNcpF3YOicxsljPcJgMyNpThJrpeFzN1qeBxxCVqhs84kib69o1g/0',
        'msgtype': 'text',
        'content': '{"content":"👦"}'
      },
      {
        'fromUser': 'wmp4WCDwAAMbMkbHrDPZ2tOv_7QT5vYw',
        'name': 'LONG。',
        'msgtime': '2021-11-09 10:58:27',
        'action': 'send',
        'id': '75df89fa41d511eca7cd30d0422e31b5',
        'avatar': 'http://wx.qlogo.cn/mmhead/5K48YNcpF3YOicxsljPcJgMyNpThJrpeFzN1qeBxxCVqhs84kib69o1g/0',
        'msgtype': 'text',
        'content': '{"content":"[微笑][撇嘴][色][发呆][得意][流泪][害羞][闭嘴][睡][大哭][尴尬][发怒][调皮][呲牙][惊讶][难过][冷汗][抓狂][吐][偷笑][愉快][白眼][傲慢][困][惊恐][憨笑][悠闲][咒骂][疑问][嘘][晕][衰][骷髅][敲打][再见][擦汗][抠鼻][鼓掌][坏笑][右哼哼][鄙视][委屈][快哭了][阴险][亲亲][可怜][笑脸][生病][脸红][破涕为笑][恐惧][失望][无语][嘿哈][捂脸][奸笑][机智][皱眉][耶][吃瓜][加油][汗][天啊][Emm][社会社会][旺柴][好的][打脸][哇][翻白眼][666][让我看看][叹气][苦涩][裂开][嘴唇][爱心][心碎][拥抱][强][弱][握手][胜利][抱拳][勾引][拳头][OK][合十][啤酒][咖啡][蛋糕][玫瑰][凋谢][菜刀][炸弹][便便][月亮][太阳][庆祝][礼物][红包][發][福][烟花][爆竹][猪头][跳跳][发抖][转圈]"}'
      },
      {
        'fromUser': 'wmp4WCDwAAMbMkbHrDPZ2tOv_7QT5vYw',
        'name': 'LONG。',
        'msgtime': '2021-11-09 10:16:57',
        'action': 'send',
        'id': '73183ac841d511eca7cd30d0422e31b5',
        'avatar': 'http://wx.qlogo.cn/mmhead/5K48YNcpF3YOicxsljPcJgMyNpThJrpeFzN1qeBxxCVqhs84kib69o1g/0',
        'msgtype': 'text',
        'content': '{"content":"[转圈][发抖][烟花][便便][炸弹][咖啡][红包][握手][拳头]"}'
      }
    ],
    'pageNum': 1,
    'pageSize': 20,
    'size': 20,
    'startRow': 1,
    'endRow': 20,
    'pages': 2,
    'prePage': 0,
    'nextPage': 2,
    'isFirstPage': true,
    'isLastPage': true,
    'hasPreviousPage': false,
    'hasNextPage': true,
    'navigatePages': 8,
    'navigatepageNums': [
      1,
      2
    ],
    'navigateFirstPage': 1,
    'navigateLastPage': 2
  }
}
src/components/ChatList/msg/imagePop.vue
New file
@@ -0,0 +1,101 @@
<template>
  <div class="pop-box">
    <!-- 已加载 -->
    <div v-if="data.fileUrl">
      <el-image :src="data.fileUrl" class="img" fit="contain" :preview-src-list="[data.fileUrl]" />
    </div>
    <!-- 加载失败 -->
    <div v-else class="com_chat_notx">[图片]</div>
    <!-- <div v-else class="no-box" @click="tapNoReal">
      <i class="el-icon-picture img-icon" />
      <div class="download-btn" :class="{'show':loading}">
        <i :class="loading?'el-icon-loading':'el-icon-download'" />
      </div>
    </div> -->
  </div>
</template>
<script>
export default {
  name: 'ImagePop',
  props: {
    // 数据
    data: {
      type: Object,
      default: null
    },
    // 方向
    direction: {
      type: String,
      default: ''
    }
  },
  data() {
    return {
      loading: false
    }
  },
  // watch: {
  //   data(value) {
  //     if (value && value.fileUrl) this.loading = false
  //   }
  // },
  mounted() {
  },
  methods: {
    // 点击获取真实数据
    tapNoReal() {
      if (this.loading) return
      this.loading = true
      this.$emit('tapNoReal')
    }
  }
}
</script>
<style scoped>
.pop-box .img {
  width: 150px;
  min-height: 150px;
  border-radius: 4px;
  display: block;
}
.pop-box .no-box {
  position: relative;
  width: 60px;
  height: 60px;
  cursor: pointer;
  border-radius: 4px;
  overflow: hidden;
}
.pop-box .no-box .img-icon {
  font-size: 60px;
  text-align: center;
}
.pop-box .no-box:hover .download-btn {
  opacity: 1;
  transition: opacity .2s;
}
.pop-box .no-box .download-btn.show {
  opacity: 1;
}
.pop-box .no-box .download-btn {
  opacity: 0;
  position: absolute;
  left: 0;
  right: 0;
  top: 0;
  bottom: 0;
  text-align: center;
  font-size: 30px;
  line-height: 60px;
  color: #fff;
  background: rgba(0, 0, 0, 0.5);
}
</style>
src/components/ChatList/msg/textPop.vue
New file
@@ -0,0 +1,47 @@
<template>
  <div class="pop-box">
    <!-- <pre>{{ data.content }}</pre> -->
    <div v-html="data.content" />
  </div>
</template>
<script>
export default {
  name: 'TextPop',
  props: {
    // 数据
    data: {
      type: Object,
      default: null
    },
    // 方向
    direction: {
      type: String,
      default: ''
    }
  },
  data() {
    return {
    }
  },
  watch: {
  },
  mounted() {
  },
  methods: {
  }
}
</script>
<style scoped>
.pop-box{
  background-color: #F1F1F1;
  padding: 10px;
  border-radius: 6px;
  word-wrap: break-word;
  max-width: 500px;
  line-height: 1.4;
  font-size: 26px;
}
</style>
src/config/appid.js
New file
@@ -0,0 +1,12 @@
/**
 * 微信公众号 appId
 *
 * 允许根据测试服务器域名,设置测试用的(公司自己的)公众号
 */
let appId = 'wxf7fdbeea741166be'
// if (/hymctest/.test(location.href)) {
//     appId = 'wxc09b57e6e10c7676' // 测试用
// }
export default appId
src/config/index.js
New file
@@ -0,0 +1,87 @@
/**
 * ismock 0不用mock 1用mock
 * istest 0线上 1本地
 *
 * 发布时请设置3个变量为0
 */
import appId from './appid.js'
var ismock = 0// 虚拟数据 0不使用 1使用
var istest = 2// 0线上 1本地 2测试环境
var isConsole = 1// 是否屏蔽console 0屏蔽 1开放
var debug = isConsole
var isWxLoginType = 0 // 微信登录 0无 1静默微信登录snsapi_base 2手动微信登录snsapi_userinfo
var is_use_test_server = 0 // 是否部署在测试服
// 打包后的环境
if (process.env && process.env.NODE_ENV !== 'development') {
  istest = 0
  ismock = 0
}
let pdomain = 'http://chatgpt.phiskin.com/' // 线上
if (is_use_test_server) pdomain = 'https://test5.phiskin.com/' // 测试服
const mock_domain = ''
// api_local 本地 http://192.168.1.106:8080
// api_test 测试环境
const tdomain = '/api_local/' // 本地
const pdomain_test = '/api_test/' // 测试环境
// 微信长链
const appid = appId
let scope = ''
if (isWxLoginType === 1) scope = 'snsapi_base' // snsapi_base 静默授权【只有openid】
if (isWxLoginType === 2) scope = 'snsapi_userinfo' // snsapi_userinfo 手动授权【有用户信息】
const long = 'https://open.weixin.qq.com/connect/oauth2/authorize?appid={{appid}}&redirect_uri={{url}}&response_type=code&scope={{scope}}&state=1&connect_redirect=1#wechat_redirect'
// 生成微信重定向链接
function createCodeUrl() {
  // 首页链接
  let indexUrl = location.origin + location.pathname
  const search = location.search
  // 参数数组
  let querys = []
  if (search && search.indexOf('?') > -1) {
    querys = search.split('?')[1].split('&').filter((str) => {
      const key = str.split('=')[0]
      const value = str.split('=')[1]
      return (key != 'code' && key != 'state')
    })
  }
  console.log(querys)
  // 补充参数
  if (querys.length) {
    indexUrl += '?' + querys.join('&')
  }
  return long.replace('{{appid}}', appid).replace('{{scope}}', scope).replace('{{url}}', encodeURIComponent(indexUrl))
}
// 屏蔽console.log
if (!isConsole) {
  console.log = () => {}
}
var domain
if (istest == 0) domain = pdomain // 线上
if (istest == 1) domain = tdomain // 本地
if (istest == 2) domain = pdomain_test // 测试环境
if (ismock == 1) domain = mock_domain // 本地模拟
export default {
  ismock,
  istest,
  isWxLoginType,
  isConsole,
  debug,
  domain,
  devtools: !!debug,
  is_use_test_server,
  createCodeUrl
}
src/filter/index.js
New file
@@ -0,0 +1,43 @@
// 过滤器
import Vue from 'vue'
function install(){
  // // 分转价格
  // Vue.filter('amount', function (number) {
  //   // var number = +val.replace(/[^\d.]/g, '');
  //   return isNaN(number) ? 0.00 : parseFloat((number/100).toFixed(2));
  // });
  // // 省和城市一样的时候,不显示城市
  // Vue.filter('city', function (city, province) {
  //   if (city == province) {
  //     return ''
  //   } else {
  //     return city
  //   }
  // })
  // // 需求标题,只显示分类第二级第三级
  // Vue.filter('serviceTypeTitle', function (serviceType) {
  //   var [type1, type2, type3] = serviceType.split('|')[0].split('-')
  //   return [type2, type3].join('-')
  // })
  // // 缩减订单编号
  // Vue.filter('shortNo', function (no) {
  //   if (no.length > 20) {
  //     return no.substring(no.length - 20, no.length)
  //   }
  //   return no
  // })
  // // 订单、师傅评分转换
  // Vue.filter('score', function (score) {
  //   return score / 100
  // })
}
export default {
  install
}
src/libs/axios.js
New file
@@ -0,0 +1,41 @@
// axios 请求配置
import Axios from 'axios'
import Config from '../config'
Axios.install = (Vue) => {
  Vue.prototype.$axios = Axios
}
// content-type 默认使用 form-urlencoded
Axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded';
// 添加请求拦截器
Axios.interceptors.request.use(function (config) {
  // 在发送请求之前做些什么
  // if (!/^https?:/.test(config.url)) {
  //   config.url = Config.domain + config.url // 拼接完整
  // }
  return config
}, function (error) {
  // 对请求错误做些什么
  return Promise.reject(error)
})
// 添加响应拦截器
Axios.interceptors.response.use(function (response) {
  // 对响应数据做点什么
  // console.log(response) // 打印返回的数据
  return response
}, function (error) {
  // 对响应错误做点什么
  // console.log(error) // 打印失败返回的数据
  return Promise.reject(error)
})
export default Axios
// 样例
// this.$axios.get('test', {} ).then((inf) => {
//     console.log('成功回调')
// }).catch((res) => {
//     console.log('失败回调')
// })
src/main.js
New file
@@ -0,0 +1,42 @@
// The Vue build version to load with the `import` command
// (runtime-only or standalone) has been set in webpack.base.conf with an alias.
// 直接从core-js引入全局的polyfill
import 'core-js/fn/promise'
import Vue from 'vue'
import App from './App'
import router from './router' // 路由表
import config from './config' // 域名配置
import Axios from './libs/axios'
import store from './store'
import filter from './filter'
import moment from 'moment'
import Req from './utils/jun_httpInstall' // http 请求
import fn from './utils/g_fn_install'
Vue.config.productionTip = false
Vue.prototype.$moment = moment
Vue.config.devtools = config.devtools
if (config.ismock) {
  console.log('%c当前为测试模式,若要切换到线上请先将/src/main.js的ismock设置为0。', 'font-size:2em;font-weight: bold;')
  require('./mock.js')
}
Vue.config.productionTip = false
// Vue.use(element)
Vue.use(Axios)
Vue.use(filter)
Vue.use(Req)
Vue.use(fn)
/* eslint-disable no-new */
new Vue({
  el: '#app',
  router,
  store,
  components: { App },
  template: '<App/>'
})
src/mock.js
New file
@@ -0,0 +1,75 @@
// 引入mockjs
import Mock from 'mockjs'
const Random = Mock.Random
// 项目域名
import config from './config'
const delay = 500 // 模拟请求时长
// const config = 'http://192.168.1.163:8080/sk_anchor_pc/'
const res = { status: 0, errMsg: '' }
// var Req = require('@/libs/request')
// var OPTIONS = Req.OPTIONS || Req.default.OPTIONS
const commonReturn = {res, inf: {}} // 通用返回
const imgUrl = 'static/imgs/headimg.jpg' // 通用示例图片
// const videoUrl = 'https://vjs.zencdn.net/v/oceans.mp4' // 通用示例视频
var mockJson = {
  commSuc: commonReturn,
  // 获取key
  'weixin!ajaxGetInfoByCode': {
    res,
    inf: {
      "key": "123",
      "nickname": "昵称",
      "imgUrl": imgUrl,
      "shareImg": imgUrl,
      "shareTitle": "分享标题"
    }
  },
  // 获取OSS参数
  'oss!ajaxGetAccess': {
    res,
    "inf": {
      "accessId": "",
      "policy": "",
      "signature": "",
      "dir": "",
      "host": "",
      "expire": ""
    }
  },
  // ticket
  'weixin!ajaxGetJsTicket': {
    res,
    inf: {
      ticket: '123'
    }
  }
}
/**
 * 生成回调函数
 * @param {object|function} data mockJson 对应项
 */
function createCallback (data) {
  return function () {
    if (typeof data === 'function') {
      return data()
    } else {
      return data
    }
  }
}
// 设置延迟响应,模拟向后端请求数据
Mock.setup({timeout: delay})
for (var key in mockJson) {
  var obj = mockJson[key]
  var url = key
  Mock.mock(config.domain + url, createCallback(obj))
}
// Mock.mock( url, post/get , 返回的数据);
// Mock.mock('/news/index', 'post', {});
src/pages/index.vue
New file
@@ -0,0 +1,76 @@
<!--index.vue-->
<template>
  <div>
    <div class="chat-wrap">
      <!-- 聊天人名称 -->
      <!-- <div class="chat-title">chatGPT</div> -->
      <!-- 聊天内容 -->
      <chatList
        ref="refChatList"
        :userid="id"
      />
    </div>
  </div>
</template>
<script>
import Login from '../utils/jun_login.js'
import chatList from '@/components/ChatList/chat_list'
export default {
  name: 'Index',
  components: { chatList },
  inject: ['noop'],
  data() {
    return {
      id: '',
      chatInfo: ''
    }
  },
  computed: {},
  mounted() {
    console.log('index mounted')
    this.init()
  },
  destroyed() {},
  methods: {
    init() {
    },
    login() {
      const code = this.getQueryString('code')
      Login.loginReq({
        data: {
          code
        }
      }).then((res) => {
        this.setLocalStorage('code', code || '')
      }).catch((res) => {
        if (res.data.msg.indexOf('用户') > -1) {
          Login.toLongUrl()
        } else {
          window.appToast(res.data.msg)
        }
      })
    }
  }
}
</script>
<style scoped>
.chat-wrap{
  background-color: #fff;
  width: 100%;
  box-sizing: border-box;
  margin: 0 auto;
}
.chat-title{
  height: 60PX;
  line-height: 60PX;
  font-size: 28px;
  border-bottom: 1px solid #dcdfe6;
  margin-left: 20px;
}
</style>
src/router/index.js
New file
@@ -0,0 +1,27 @@
/**
 * router 配置,使用路由懒加载
 */
import Vue from 'vue'
import Router from 'vue-router'
import Store from '../store'
// import HelloWorld from '@/components/HelloWorld'
Vue.use(Router)
let router = new Router({
  // mode: 'history',
  routes: [
    {
      path: '/',
      name: 'root',
      component: () => import('@/pages/index'),
    },
    {
      path: '/index',
      name: 'index',
      component: () => import('@/pages/index'),
    }
  ]
})
export default router
src/store/index.js
New file
@@ -0,0 +1,59 @@
/*
 * @Author: 丘骏_jun
 * @Date: 2019-11-21 10:59:17
 * @Last Modified by: 丘骏_jun
 * @Last Modified time: 2021-05-14 18:28:18
 */
import Vue from 'vue'
import Vuex from 'vuex'
// import fn from '../utils/fn.js'
Vue.use(Vuex)
const state = {}
// 显式更改state,内部只允许同步,调用方式:$store.commit(key, value)
const mutations = {}
// 相当于computed,调用方式:store.getters.xxx
const getters = {}
// 允许异步,需通过store.dispath分发,调用store.commit更改state
const actions = {}
/**
 * 首字母变大写
 * @param {string} name
 */
function getBigName(name){
    return [name[0].toUpperCase(), ...name.substring(1)].join('')
}
/**
 * 使用create,快捷创建简易字段
 * 通用创建store字段,包含state,getters,mutations
 * @param {string} key
 * @param {*} default_value
 * @param {function} mutation
 */
function create (key, default_value, mutation) {
    state[key] = default_value
    mutations['set' + getBigName(key)] = mutation || ((state, value) => {state[key] = value})
    getters['get' + getBigName(key)] = state => state[key]
}
// 用户信息
create('userData', {})
// danmuData: [], // 弹幕数据
// luckNum: 0, // 幸运值数据
// shareNum: 0, // 分享次数
// leftNum: 0, // 剩余抽奖次数
const store = new Vuex.Store({
    state,
    getters,
    mutations,
    actions,
})
export default store
src/utils/chat/chat.js
New file
@@ -0,0 +1,50 @@
/**
 * 生成时间字符串
 * 规则,暂定
 * 5分钟内间隔,不显示
 * 非今天显示全部
 * @param {string} time yyyy-mm-dd hh:mm:ss
 * @param {string} prevTime yyyy-mm-dd hh:mm:ss
 */
function chatTime(time, prevTime) {
  if (!isShowChatTime(time, prevTime)) {
    return ''
  }
  var date = new Date()
  var y = date.getFullYear()
  var m = date.getMonth() + 1
  var d = date.getDate()
  var str_time = time.split(' ')[1]
  var str_date = time.split(' ')[0]
  var temp = str_date.split('-')
  var isToday = y === temp[0] && m === temp[1] * 1 && d === temp[2] * 1
  if (isToday) {
    return str_time.substring(0, 5)
  }
  return time.substring(0, 16)
}
/**
 * 是否显示时间
 * @param {string} time yyyy-mm-dd hh:mm:ss
 * @param {string} prevTime yyyy-mm-dd hh:mm:ss
 */
function isShowChatTime(time, prevTime) {
  if (!prevTime) {
    return true
  }
  var date = new Date(time.replace(/-/g, '/')).getTime()
  var prev_date = new Date(prevTime.replace(/-/g, '/')).getTime()
  if (date - prev_date < 5 * 60 * 1000) {
    return false
  }
  return true
}
module.exports = {
  chatTime
}
src/utils/districts.js
New file
Diff too large
src/utils/fn.js
New file
@@ -0,0 +1,333 @@
/*
 * @Author: 丘骏_jun
 * @Date: 2019-11-21 10:59:17
 * @Last Modified by: 丘骏_jun
 * @Last Modified time: 2019-12-30 16:29:45
 */
// 通用function,通过全局安装,或import引用调用
let scrollBarWidth // 滚动条宽度
// 获取浏览器滚动条宽度
function getScrollBarWidth () {
    if (scrollBarWidth !== undefined) return scrollBarWidth;
    const outer = document.createElement('div');
    outer.className = 'el-scrollbar__wrap';
    outer.style.visibility = 'hidden';
    outer.style.width = '100px';
    outer.style.position = 'absolute';
    outer.style.top = '-9999px';
    document.body.appendChild(outer);
    const widthNoScroll = outer.offsetWidth;
    outer.style.overflow = 'scroll';
    const inner = document.createElement('div');
    inner.style.width = '100%';
    outer.appendChild(inner);
    const widthWithScroll = inner.offsetWidth;
    outer.parentNode.removeChild(outer);
    scrollBarWidth = widthNoScroll - widthWithScroll;
    return scrollBarWidth;
}
// 获取最近样式
// function getCurrentStyle (obj, prop) {
//     if (obj.currentStyle) {
//         return obj.currentStyle[prop];
//     } else if (window.getComputedStyle) {
//         prop = prop.replace(/([A-Z])/g, "-$1");
//         prop = prop.toLowerCase();
//         return document.defaultView.getComputedStyle(obj, null)[prop];
//     }
//     return null;
// }
// // 判断是否有滚动条
// function hasScrollBar (obj) {
//     return getCurrentStyle(obj, 'overflow') == 'hidden'
//         ? 0
//         : document.body.scrollHeight > (window.innerHeight || document.documentElement.clientHeight);
// }
// 日期格式化
Date.prototype.format = function(format){
    var o = {
        "M+" : this.getMonth()+1, //month
        "d+" : this.getDate(), //day
        "H+" : this.getHours(), //hour
        "m+" : this.getMinutes(), //minute
        "s+" : this.getSeconds(), //second
        "q+" : Math.floor((this.getMonth()+3)/3), //quarter
        "S" : this.getMilliseconds() //millisecond
    };
    if(/(y+)/.test(format)) {
        format = format.replace(RegExp.$1, (this.getFullYear()+"").substr(4 - RegExp.$1.length));
    }
    for(var k in o) {
        if(new RegExp("("+ k +")").test(format)) {
            format = format.replace(RegExp.$1, RegExp.$1.length==1 ? o[k] : ("00"+ o[k]).substr((""+ o[k]).length));
        }
    }
    return format;
};
// 消除字符串多余空格
if (!String.prototype.trim) {
    String.prototype.trim = function () {
      return this.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, '');
    };
}
/**
 * 获取本地缓存 localStorage
 * @param {string} key storage 标记
 * @return {*} 值
 */
function getLocalStorage(key){
    if (!key) {
        return
    }
    var result = localStorage.getItem(key) || ''
    try {
        result = JSON.parse(result)
    } catch(e) {
    }
    return result
}
/**
 * 获取本地缓存 sessionStorage
 * @param {string} key storage 标记
 * @return {*} 值
 */
function getSessionStorage(key){
    var result = sessionStorage.getItem(key) || ''
    try {
        result = JSON.parse(result)
    } catch(e) {
    }
    return result
}
/**
 * 保存本地缓存 localStorage
 * @param {string} key storage 标记
 * @param {*} value 值
 */
function setLocalStorage(key, value){
    if (typeof value === 'object') {
        value = JSON.stringify(value)
    }
    localStorage.setItem(key, value)
}
/**
 * 保存本地缓存 sessionStorage
 * @param {string} key storage 标记
 * @param {*} value 值
 */
function setSessionStorage(key, value){
    if (typeof value === 'object') {
        value = JSON.stringify(value)
    }
    sessionStorage.setItem(key, value)
}
/**
 * 移除本地缓存 localStorage
 * @param {string} key storage 标记
 */
function removeLocalStorage(key){
    localStorage.removeItem(key)
}
/**
 * 移除本地缓存 sessionStorage
 * @param {string} key storage 标记
 */
function removeSessionStorage(key){
    sessionStorage.removeItem(key)
}
/**
 * 输入的价格限制在两位小数
 * @param {string} number 价格
 * @return {string} 过滤后的价格
 */
function toFixed2 (number) {
    number += ''
    if (number == '') {
        return ''
    }
    if (isNaN(number)) {
        return ''
    }
    if (number.indexOf('.')>-1) {
        var arr = number.split('.')
        if (arr[1].length>2) {
            arr[1] = arr[1].substring(0, 2)
        }
        number = arr[0] + '.' + arr[1]
        return number
    }
    return number
}
/**
 * 价格转,分,为单位
 * @param {string|number} price
 */
function amountFen (price) {
    return parseInt(price*100)
}
/**
 * 获取查询字符串
 * @param {string} name
 * @param {string} url 默认 location.href
 */
function getQueryString(name, url){
    url = url || location.href
    url = url.replace(/\#\S*/g, '')
    var reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)");
    var r = /\?/.test(url) && url.split('?')[1].match(reg);
    if(r != null) {
        return r[2];
    }
    return "";
}
// 全局缓存
var g_el = null
/**
 * 绑定div滚动到底部
 * @param {dom} el
 * @param {function} callback
 */
function onReachBottom (el, callback) {
    let offsetHeight = el.offsetHeight || window.outerHeight
    let isWindow = el === window
    g_el = el
    el.onscroll = ()=>{
        let scrollTop = isWindow ? document.documentElement.scrollTop : el.scrollTop
        let scrollHeight = isWindow ? document.body.scrollHeight : el.scrollHeight
        // console.log(scrollTop, scrollHeight)
        // console.log(offsetHeight, scrollTop, scrollHeight)
        if ((offsetHeight + scrollTop) - scrollHeight >= -1) {
            typeof callback === 'function' && callback()
        }
    }
}
/**
 * 取消绑定
 * @param {dom} el
 */
function offReachBottom (el) {
    el = el || g_el
    el.onscroll = null
    g_el = null
}
/**
 * 生成唯一id
 */
function uuid () {
    var s = []
    var hexDigits = '0123456789abcdef'
    for (var i = 0; i < 36; i++) {
        s[i] = hexDigits.substr(Math.floor(Math.random() * 0x10), 1)
    }
    s[14] = '4';
    s[19] = hexDigits.substr((s[19] & 0x3) | 0x8, 1);
    s[8] = s[13] = s[18] = s[23] = '-'
    var uuid = s.join('')
    return uuid
}
/**
 * 替换当前链接,无返回
 * @param {string} href
 */
function urlReplace (href) {
    if (!href) {
        return;
    }
    if (href && /^#|javasc/.test(href) === false) {
        if (history.replaceState) {
            history.replaceState(null, document.title, href.split('#')[0] + '#');
            location.replace(href);
        } else {
             location.replace(href);
        }
    }
};
var fn = {
    getLocalStorage,
    getSessionStorage,
    setLocalStorage,
    setSessionStorage,
    removeLocalStorage,
    removeSessionStorage,
    // 判断是否有滚动条,并获取滚动宽度
    // getScrollBarWidth () {
    //     return hasScrollBar(document.body) ? getScrollBarWidth() : 0
    // },
    getScrollBarWidth,
    toFixed2,
    amountFen,
    getQueryString,
    onReachBottom,
    offReachBottom,
    uuid,
    urlReplace,
    /**
     * 深拷贝
     * @param {object} obj 被复制的对象
     * @return {object} 复制完成的对象
     */
    deepCopyFN (obj) {
        if (typeof obj !== 'object') {
            return obj
        }
        let cloneObj = {}
        switch (obj.constructor) {
            case Array:
                cloneObj = []
            case Object:
                for (var property in obj) {
                    cloneObj[property] = typeof obj[property] === 'object' ? this.deepCopyFN(obj[property]) : obj[property]
                }
                break
            case Map:
                cloneObj = new Map()
                obj.forEach((value, key) => {
                    cloneObj.set(key, typeof  value === 'object' ? this.deepCopyFN(value) : value)
                })
                break
            case Set:
                cloneObj = new Set()
                obj.forEach(value => {
                    cloneObj.add(typeof value === 'object' ? this.deepCopyFN(value) : value)
                })
                break
        }
        return cloneObj
    },
}
export default fn
src/utils/g_fn_install.js
New file
@@ -0,0 +1,14 @@
/*
 * @Author: 陈志陶_cor
 * @Date: 2019-05-07 16:30:54
 * @Last Modified by: 丘骏_jun
 * @Last Modified time: 2019-12-10 10:57:31
 */
// 全局方法安装文件
import fn from './fn.js';
import Vue from 'vue'
var install = Fn => {Vue.prototype[Fn] = fn[Fn]}
for(var item in fn){
    install(item)
}
export default install;
src/utils/jun_form_rules.js
New file
@@ -0,0 +1,169 @@
/**
 * elementUI 表单验证
 */
const validators = {
    required: {
      rule: /.+/,
      msg: '必填项不能为空'
    },
    phone: {
      rule: /^[1][3,4,5,6,7,8][0-9]{9}$/,
      msg: '手机号格式不正确'
    },
    mail: {
      rule: /^[a-zA-Z0-9_.-]+@[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)*\.[a-zA-Z0-9]{2,6}$/,
      msg: 'E-mail格式不正确'
    },
    id_card: {
      rule: /(^\d{18}$)|(^\d{17}(\d|X|x)$)|(^\d{15}$)/,
      msg: '身份证格式不正确'
    },
    password: {
      rule: /^\d{6}$/,
      msg: '密码必须为6位数字'
    },
    code: {
      rule: /^\d{4}$/,
      msg: '验证码格式不正确'
    },
    number: {
      rule: /^\d+$/,
      msg: '必须为整数'
    },
    digit: {
      rule: /^\d+(\.\d+)?$/,
      msg: '必须为数值'
    },
    same: {
      rule (val='', sVal='') {
        return val===this.data[sVal]
      },
      msg: '密码不一致'
    },
    // https://blog.csdn.net/xjun0812/article/details/81806118
    carNo: {
        // 含新能源车
        rule: /^(([京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领][A-Z](([0-9]{5}[DF])|([DF]([A-HJ-NP-Z0-9])[0-9]{4})))|([京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领][A-Z][A-HJ-NP-Z0-9]{4}[A-HJ-NP-Z0-9挂学警港澳使领]))$/,
        msg: '车牌号格式不正确'
    },
  }
/**
 * 验证手机号
 * @param {string} options.trigger 触发事件
 */
function validatorTel(options = {}){
    return [
        {
            validator: (rule, value, callback)=>{
                if (!value) {
                    return callback(new Error('请输入手机号码'))
                }
                if (!validators.phone.rule.test(value)) {
                    return callback(new Error('请输入正确格式手机号'))
                }
                callback()
            },
            trigger: options.trigger || 'blur'
        }
    ]
}
/**
 * 验证电子邮箱
 * @param {string} options.trigger 触发事件
 */
function validatorEmail(options = {}){
    return [
        {
            validator: (rule, value, callback) => {
                if (!value) {
                    return callback(new Error('请输入电子邮箱'))
                }
                if (!validators.mail.rule.test(value)) {
                    return callback(new Error('请输入正确格式电子邮箱'))
                }
                callback()
            },
            trigger: options.trigger || 'blur'
        }
    ]
}
/**
 * 验证非空
 * @param {object} options 选项
 * @param {string} options.message 提示
 * @param {string} options.trigger 触发事件
 */
function validatorRequired(options = {}){
    return [{
        validator: (rule, value, callback) => {
            // =='' 可以兼容数组为空的情况
            if (value=='') {
                return callback(new Error(options.message || validators.required.msg))
            }
            callback()
        },
        trigger: options.trigger || 'blur'
    }]
}
/**
 * 验证密码
 * @param {object} options
 * @param {string} options.message 可选,提示文本
 * @param {function} options.beforeCallback 可选,用于检查确认密码
 * @param {string} optionss.trigger 触发事件
 */
function validatorPassword(options = {}){
    return [{
        validator: (rule, value, callback)=>{
            if (!value) {
                return callback(new Error(options.message || '请输入密码'))
            }
            // 预留,用于检查确认密码
            typeof options.beforeCallback === 'function' && options.beforeCallback()
            callback()
        },
        trigger: options.trigger || 'blur'
    }]
}
/**
 * 验证确认密码
 * @param {object} options
 * @param {function} options.password 必须,用于获取密码
 * @param {string} optionss.trigger 触发事件
 */
function validatorConfirmPwd(options = {}){
    return [{
        validator: (rule, value, callback)=>{
            // 动态获取密码
            var password
            if (typeof options.password === 'function') {
                password = options.password()
            } else {
                password = options.password
            }
            if (!value) {
                callback(new Error('请输入确认密码'))
            } else if (value !== password) {
                callback (new Error('两次输入密码不一致!'))
            } else{
                callback()
            }
        },
        trigger: options.trigger || 'blur'
    }]
}
export default {
    validators,
    validatorTel,
    validatorEmail,
    validatorRequired,
    validatorPassword,
    validatorConfirmPwd,
}
src/utils/jun_http.js
New file
@@ -0,0 +1,289 @@
/**
 * Http 请求
 * * 小程序请使用增强编译
 *
 * 调用例子
 * Req.http.post({
 *     url: '',
 *     data: {},
 * })
 */
import Axios from '../libs/axios'
/* json转formdata(序列化),仅支持一级字面量和字面量数组 */
function jsonToFormData (json) {
    var arr = []
    var e = encodeURIComponent
    for (var key in json) {
        var item = json[key]
        var res
        if (item instanceof Array) {
            res = [];
            item.map(function (o, i) {
                res.push(e(key) + '=' + e(o))
            });
            res = res.join('&')
        } else {
            res = e(key) + '=' + e(item)
        }
        arr.push(res)
    }
    return arr.join('&')
}
/**
 * Http 类
 * @param http_option {Object}
 * -@key {string}   baseUrl --请求链接前置
 * -@key {object}   getChangeRequestParameter --get请求参数
 * -@key {object}   postChangeRequestParameter --post请求参数
 * -@key {function} beforeRequest --请求前处理
 * -@key {function} beforeFlow --请求前流程
 * -@key {function} successChangeData --请求完成后,处理数据
 * -@key {function} httpEventCode --请求完成后,处理委托
 * -@key {function} afterFlow --请求完成后流程
 * -@key {function} afterRequest --请求后事件
 * -@key {function} afterMultiRequests --同时多次请求完成之后
 */
function Http (http_option) {
    // 请求配置数组,标记是否批量请求
    var requestArr = []
    // 请求总线
    function Request (request_option) {
        // 默认 data 为对象
        request_option.data = request_option.data || {}
        // 请求配置加入数组
        requestArr.push(request_option)
        // 触发请求前事件
        if (http_option.beforeRequest) {
            http_option.beforeRequest({http_option, request_option})
        }
        // 使用 promise 完成请求
        return new Promise ((resolve, reject) => {
            // vue-cli中改用mockjs
            // mock假数据流程
            // 20210121 - 暂停使用mockjs
            if (http_option.mockFlow && http_option.ismock) {
                resolve(http_option.mockFlow({http_option, Request, request_option}))
                return
            }
            // 请求前自定义流程
            if (http_option.beforeFlow && !request_option.skip_before_flow) {
                // skip 为 true 时,不仅如此此流程
                requestArr.splice(0, 1)
                resolve(http_option.beforeFlow({http_option, Request, request_option}))
                return
            }
            var url = request_option.url
            if (!url) {
                throw new Error('url不能为空')
            }
            // 不增加domain设定
            if (request_option.udData && request_option.udData.nodomain) {
                if (!/^https?:\/\//.test(url)) {
                    // 使用本项目域名
                    if (/^\//.test(url)) {
                        url = location.origin + url
                    } else {
                        // 修复访问本地json问题
                        url = location.origin + location.pathname.replace('index.html', '') + url
                    }
                }
            } else if (!/^https?:\/\//.test(url)) {
                url = http_option.baseUrl + url
            }
            // 定义请求配置
            var request_config = {
                method: request_option.method,
                url,
                data: request_option.data,
                header: request_option.header || {'Content-type':'application/x-www-form-urlencoded'},
                // 请求成功
                success (res) {
                    // 请求成功后,处理数据
                    if (http_option.successChangeData) {
                        res = http_option.successChangeData(res)
                    }
                    // 触发 code 委托事件
                    if (http_option.httpEventCode && http_option.httpEventCode['code'+res.status]) {
                        http_option.httpEventCode['code'+res.status](res);
                    }
                    // 请求后自定义流程
                    if (http_option.afterFlow) {
                        resolve(http_option.afterFlow({
                            res,
                            request_config,
                            request_option,
                            http_option,
                            Request,
                        }))
                    }
                    // 触发请求后事件
                    if (http_option.afterRequest) {
                        http_option.afterRequest({http_option, res})
                    }
                    if (request_option.udData) {
                        // 返回全部数据
                        if (request_option.udData.fullData === true) {
                            resolve(res)
                        }
                    }
                    resolve(res.data)
                },
                // 请求失败
                fail (err) {
                    // console.error(err)
                    var code = err.response && err.response.status
                    // 触发 code 委托事件
                    if (code && http_option.httpEventCode && http_option.httpEventCode['code'+code]) {
                        http_option.httpEventCode['code'+code](err, url)
                    }
                    // alert('请求失败,错误代号:'+code)
                    reject(err)
                },
                // 无论成功或者失败都会执行
                complete () {
                    requestArr.splice(0, 1)
                    if (requestArr.length === 0) {
                        // http_option.debug && console.log('触发 afterMultiRequests', request_config)
                        // 批量请求全部完成,触发事件
                        if (http_option.afterMultiRequests) {
                            http_option.afterMultiRequests(request_option)
                        }
                    }
                }
            };
            // get 方法时,去掉 data
            if (request_option.method === 'GET') {
                delete request_config.data
            }
            // 打印请求信息
            http_option.debug && console.log('请求', request_config)
            // 处理header和data的关系
            if (request_config.header['Content-type'] == 'application/x-www-form-urlencoded') {
                request_config.data = request_config.data ? jsonToFormData(request_config.data) : {}
            }
            // 开始请求
            // wx.request(request_config)
            Axios({
                method: request_config.method,
                headers: request_config.header,
                url: request_config.url,
                data: request_config.data
            }).then((res) => {
                // http_option.debug && console.log('成功回调', res)
                request_config.success(res)
                request_config.complete(res)
            }).catch((res) => {
                // http_option.debug && console.log('失败回调', res)
                request_config.fail(res)
                request_config.complete(res)
            })
        })
    }
    var obj = {
        // get请求
        // @param get_option {Object} 请求配置
        // -@key url {String} 请求路径
        // -@key params {Object} 请求参数
        // -@key udData {Object} 自定义扩展字段
        // @return {Promise}
        get (get_option) {
            // 请求前,统一处理请求参数
            if (http_option.getChangeRequestOption) {
                get_option = http_option.getChangeRequestOption(get_option)
            }
            // params对象序列化到url中
            if (get_option.params) {
                let arr = []
                for (let i in get_option.params) {
                    if (get_option.params.hasOwnProperty(i)) {
                        arr.push(encodeURIComponent(i) + '=' + encodeURIComponent(get_option.params[i]))
                    }
                }
                if (/\?/.test(get_option.url)) {
                    get_option.url += '&' + arr.join('&')
                } else {
                    get_option.url += '?' + arr.join('&')
                }
            }
            // 使用promise完成Request调用
            return new Promise ((resolve, reject) => {
                Request({
                    method: 'GET',
                    url: get_option.url,
                    udData: get_option.udData,
                    header: get_option.header,
                    mockData: get_option.mockData,
                }).then((data) => {
                    resolve(data)
                }).catch((res)=>{
                    // ↓↓↓↓↓↓↓↓↓↓ 临时处理失败
                    // var junPage = getApp().getCurPage()
                    // junPage = junPage && junPage.selectComponent('#junPage')
                    // if (junPage && junPage.fail) {
                    //     console.log('设置fail')
                    //     junPage.fail()
                    // }
                    // ↑↑↑↑↑↑↑↑↑↑ 临时处理失败
                    reject(res)
                })
            })
        },
        // post请求
        // @param post_option {Object} 请求配置
        // -@key url {String} 请求路径
        // -@key data {Object} 请求参数
        // -@key udData {Object} 自定义扩展字段
        // @return {Promise}
        post (post_option) {
            // 请求前,统一处理请求参数
            if (http_option.postChangeRequestOption) {
                post_option = http_option.postChangeRequestOption(post_option)
            }
            // 使用promise完成Request调用
            return new Promise ((resolve, reject) => {
                Request({
                    method: 'POST',
                    url: post_option.url,
                    data: post_option.data,
                    udData: post_option.udData,
                    header: post_option.header,
                    mockData: post_option.mockData,
                }).then((data) => {
                    resolve(data)
                }).catch((res)=>{
                    // ↓↓↓↓↓↓↓↓↓↓ 临时处理失败
                    // var junPage = getApp().getCurPage()
                    // junPage = junPage && junPage.selectComponent('#junPage')
                    // if (junPage && junPage.fail) {
                    //     console.log('设置fail')
                    //     junPage.fail()
                    // }
                    // ↑↑↑↑↑↑↑↑↑↑ 临时处理失败
                    reject(res)
                })
            })
        }
    }
    obj.getFN = obj.get
    obj.postFN = obj.post
    return obj;
}
export default Http
src/utils/jun_httpEvent.js
New file
@@ -0,0 +1,376 @@
/**
 * http事件委托
 *
 * udData 说明
 * udData.noloading 不需要请求
 * udData.fullData 使用 reponse 返回的对象进行 success 回调 (jun_http.js)
 * udData.nodomain 仅限于web端使用,不适用 baseUrl 前置(即当前网址根路径) (jun_http.js)
 * udData.nokey 不需要mpToken参数
 */
import Login from './jun_login'
// import { MessageBox, Message, Loading } from 'element-ui'
import fn from './fn'
import resStatusCode from './jun_httpStatus'
// 过滤 html
function filterHtml (str) {
    if (typeof str !== 'string') {
        return str
    }
    return str.replace(/\n|\t|\s/g, '').replace(/<script\s?.+><\/script>/g, '').replace(/<[^>]+>/g, '')
}
/**
 * 使用 element-ui loading service
 */
// let loadingInstance // 定义loading变量
// // 开始loading
// function startLoading(){
//     loadingInstance = Loading.service({
//         lock: true,
//         text: '加载中...',
//         background: 'rgba(255,255,255,0.7)',
//         fullscreen: true,
//     })
// }
// // 结束loading
// function endLoading(){
//     loadingInstance && loadingInstance.close()
// }
// 开始loading
function startLoading(request_option){
    //
    // console.log(request_option.udData)
    // request_option.udData && typeof request_option.udData.loading === 'function' && request_option.udData.loading()
    typeof window.appLoading === 'function' && window.appLoading()
}
// 结束loading
function endLoading(request_option){
    //
    // request_option.udData && typeof request_option.udData.hideLoading === 'function' && request_option.udData.hideLoading()
    typeof window.appHideLoading === 'function' && window.appHideLoading()
}
// 请求前处理参数 - get
function getChangeRequestOption (get_option) {
    if (!get_option.udData || !get_option.udData.nokey) {
        if (!get_option.params) {
            get_option.params = {}
        }
        // 补充参数
        // 需要 we_session
        var we_session = fn.getLocalStorage('we_session')
        if (we_session && we_session.we_session) {
            get_option.params.key = we_session.we_session
            get_option.params.mpToken = we_session.we_session
        }
    }
    return get_option
}
// 请求前处理参数 - post
function postChangeRequestOption (post_option) {
    if (!post_option.udData || !post_option.udData.nokey) {
        if (!post_option.data) {
            post_option.data = {}
        }
        // 补充参数
        // 需要 we_session
        var we_session = fn.getLocalStorage('we_session')
        if (we_session && we_session.we_session) {
            post_option.data.key = we_session.we_session
            post_option.data.mpToken = we_session.we_session
        }
    }
    return post_option
}
var httpEventCode = {
    code200 (data) {
        // console.log(data, 200)
    },
    code404 (data, url) {
        // element-ui
        // Message.error('无法访问接口,状态404:' + url)
    },
    code500 (data, url) {
        // element-ui
        // Message.error('请求失败,状态500:' + url)
    }
}
var g_login_counter = 0 // 登录请求次数
const g_LOGIN_MAX = 10 // 最大请求次数
var g_flag_loading = false // 是否已经loading
var g_flag_login_requested = false // 是否正在请求登录
var g_login_result = null // 登录返回数据
var g_flag_config_requested = false // 其余配置请求
var g_config_result = null // 配置请求返回
// 请求前
function beforeRequest (res) {
    // 开启loading
    if (!g_flag_loading && (!res.request_option.udData || !res.request_option.udData.noLoading)) {
        res.http_option.debug && console.log('jun_httpEvent beforeRequest loading')
        // wx.showLoading({
        //     title: '加载中',
        //     mask: true
        // })
        // loading
        startLoading(res.request_option)
        g_flag_loading = true
    }
    // 重置登录请求标记
    var we_session = fn.getLocalStorage('we_session')
    if (we_session && we_session.we_session) {
        g_flag_login_requested = false
    }
}
// 请求后
function afterRequest (res) {
    // console.log('请求后')
}
// 处理返回数据
// @return {Object} 处理过的数据
function successChangeData (res) {
    return res
}
// 批量请求完成
function afterMultiRequests (request_option) {
    // console.log("多个请求结束之后")
    // 关闭loading
    if (g_flag_loading) {
        // wx.hideLoading()
        endLoading(request_option)
        g_flag_loading = false
    }
}
function updateKey (res, key) {
    // 更新 key 值
    if (res.request_option.method === 'GET' && (!res.request_option.udData || !res.request_option.udData.nokey)) {
        res.request_option.url = res.request_option.url.replace(/mpToken=[^&]*/g, `mpToken=${key}`)
    }
    if (res.request_option.method === 'POST' && (!res.request_option.udData || !res.request_option.udData.nokey)) {
        res.request_option.data.mpToken = key
    }
    return res
}
// 模拟数据处理流程
function mockFlow (res) {
    return new Promise(async (resolve, reject) => {
        // 打印请求信息
        res.http_option.debug && console.log('模拟请求', res.request_option)
        var mockData = res.request_option.mockData || {code: 100, data: {}, message: 'success'}
        // 打印返回信息
        res.http_option.debug && console.log('模拟返回', {data: mockData})
        res.http_option.debug && console.log('开始模拟等待800ms')
        var timer = setTimeout(()=>{
            clearTimeout(timer)
            // 关闭loading
            if (g_flag_loading) {
                // wx.hideLoading()
                endLoading(res.request_option)
                g_flag_loading = false
            }
            res.http_option.debug && console.log('结束模拟等待800ms')
            if (res.request_option.udData && res.request_option.fullData) {
                resolve({data: mockData})
            } else {
                resolve(mockData)
            }
        }, 800)
    })
}
// 请求前处理流程
function beforeFlow (res) {
    return new Promise(async (resolve, reject) => {
        // 预留请求前处理
        // 设置为true,下次处理跳过 beforeFlow,避免死循环
        res.request_option.skip_before_flow = true
        // 再次请求
        resolve(res.Request(res.request_option))
    })
}
// 预留其余处理
function configRequest(){
    return new Promise((resolve, reject) => {
        resolve({})
    })
}
// 登录处理
function appLogin (option={}) {
    return new Promise((resolve, reject) => {
        // Login.checkLogin({
        //     force: !!option.forceLogin,
        //     callback: (key)=>{
        //         resolve(key)
        //     }
        // })
        Login.toLongUrl()
    })
}
// 请求后处理
function afterFlow (res) {
    return new Promise((resolve, reject)=>{
        var data = res.res.data
        // 登录超时
        if (
            (data && data.res && data.res.status == 2)
            || (data && data.code == 603)
        ) {
            res.http_option.debug && console.log('登录超时,需要重新登录', res.res)
            // 登录超时,需要重新登录
            // 清空we_session
            // getApp().globalData.we_session = null
            fn.removeLocalStorage('we_session')
            resolve(retryLogin(res))
            return
        }
        // status不为0
        if (
            (data && data.res && data.res.status != 0)
            || (data && data.code != 100)
        ) {
            if (data.res) {
                console.error('status不为0:' + (res.res.errMsg || ''), res.res)
                // 弹出提示
                // wx.showModal({
                //     title: '请求提示',
                //     content: data.res.errMsg || `请求有误,status=${data.res.status}`,
                //     confirmText: '确定',
                //     confirmColor: '#576B95',
                //     showCancel: false
                // })
                // element-ui
                // Message.error(data.res.errMsg || `请求有误,status=${data.res.status}`)
                // 根据status处理
                if (typeof resStatusCode['status' + data.res.status] === 'function') {
                    resStatusCode['status' + data.res.status](data)
                }
            }
            if (typeof data.code !== 'undefined') {
                console.error('code不为100:' + data.code, data.msg, res.res)
            }
            reject(res.res)
            return
        }
        // status为空
        if (data && !data.res && typeof data.code === 'undefined') {
            console.error('status为空', res.res)
            let tips = ''
            if (res.res.statusCode=='500') {
                tips = '当前网络状态不佳,请稍后再试:statusCode=500;'
            } else if (res.res.statusCode=='200') {
                if (typeof res.data === 'string') {
                    tips = filterHtml(res.data)
                }
                if (typeof res.data === 'object') {
                    tips = filterHtml(JSON.stringify(res.data))
                }
                tips = '网络状况不佳,点击确定重试:statusCode=200;' + tips
            } else {
                tips = `连接服务器失败,点击确定重试:statusCode=${res.res.statusCode};`
            }
            // 请求提示
            // 小程序
            // wx.showModal({
            //     title: '请求提示',
            //     content: tips,
            //     confirmText: '确定',
            //     confirmColor: '#576B95',
            //     showCancel: false,
            //     success (res) {
            //         if (res.confirm) {
            //             // 点击确认后重新请求
            //             resolve(res.Request(res.request_option))
            //         }
            //     }
            // })
            // element-ui
            // MessageBox.alert(tips, '网络提示', {
            //     confirmButtonText: '确定',
            //     type: 'error'
            // }).then(()=>{
            //     resolve(res.Request(res.request_option))
            // }).catch(()=>{
            //     // 点击取消
            // })
            return
        }
        // 打印返回信息
        res.http_option.debug && console.log('返回', res.res)
        if (res.request_option.udData) {
            // 返回全部数据
            if (res.request_option.udData.fullData === true) {
                resolve(res.res)
            }
        }
        g_login_counter && (g_login_counter = 0)
        resolve(data)
    })
}
// 重新登录
async function retryLogin (res) {
    // 次数+1
    g_login_counter++
    return new Promise(async (resolve, reject) => {
        // 等待登录
        var login_result = await appLogin({forceLogin: true})
        // 登录失败
        if (!login_result && g_login_counter < g_LOGIN_MAX) {
            // 2秒后重试
            var timer = setTimeout(()=>{
                clearTimeout(timer)
                resolve(retryLogin(res))
            }, 2000)
        }
        // 登录成功
        if (login_result) {
            // 更新key值
            res = updateKey(res, login_result)
            // 再次请求
            resolve(res.Request(res.request_option))
        }
    })
}
export default {
    httpEventCode,
    getChangeRequestOption,
    postChangeRequestOption,
    successChangeData,
    beforeRequest,
    afterRequest,
    afterMultiRequests,
    mockFlow,
    beforeFlow,
    afterFlow,
}
src/utils/jun_httpInstall.js
New file
@@ -0,0 +1,71 @@
// 创建 http 实例
import Http from './jun_http'
import config from '../config'
// var switchConfig = require('../../config/jun_switchConfig')
import httpEvent from './jun_httpEvent'
// import Upload from './jun_upload'
// baseConfig key
// var keys = [
//     'baseUrl', 'testBaseUrl', 'testOnlineBaseUrl'
// ]
// var baseUrlkey = switchConfig.istest ? keys[1]
//             : switchConfig.istestonline ? keys[2]
//             : keys[0]
// 根据配置生成 httpEvent
function createHttpEvent(option){
    let opt = {...httpEvent}
    if (!option) {
        return opt
    } else {
        if (option.ignores && option.ignores.length) {
            option.ignores.forEach((key, idx)=>{
                if (opt[key]) {
                    delete opt[key]
                }
            })
        }
        return opt
    }
}
var http = new Http({
    baseUrl: config.domain,
    // 使用全部 httpEvent
    ...createHttpEvent(),
    ...config,
})
// 静态json用
var http2 = new Http({
    // 可以更改baseUrl
    baseUrl: config.domain,
    // 不使用 afterFlow
    ...createHttpEvent({
        ignores: ['afterFlow']
    }),
    ...config,
})
// 登录用
var http3 = new Http({
    baseUrl: config.domain,
    // 不使用 afterFlow
    ...createHttpEvent({
        ignores: ['beforeFlow']
    }),
    ...config,
})
// vue 安装
function install(Vue){
    Vue.prototype.Req = this
}
export default {
    http,
    http2,
    http3,
    install,
}
src/utils/jun_httpStatus.js
New file
@@ -0,0 +1,31 @@
/**
 * http请求,status不为0的处理
 */
// 引入 router
import router from '../router'
// import {MessageBox} from 'element-ui'
var resStatusCode = {
    // 1001 需要认证
    // status1001 () {
    //     MessageBox.confirm('您尚未进行账户认证,是否前往认证?', '提示', {
    //         confirmButtonText: '马上认证',
    //         cancelButtonText: '取消',
    //     }).then(()=>{
    //         router.push({name: 'userAuth'})
    //     })
    // },
    // // 1002 需要保证金
    // status1002 () {
    //     MessageBox.confirm('您尚未交缴保证金,是否前往交缴?', '提示', {
    //         confirmButtonText: '马上交缴',
    //         cancelButtonText: '取消',
    //     }).then(()=>{
    //         router.push({name: 'userDeposit'})
    //     })
    // }
}
export default resStatusCode
src/utils/jun_login.js
New file
@@ -0,0 +1,125 @@
/**
 * 登录
 */
// const DEBUG = require('../config').isConsole
// const ismock = require('../config').ismock
import fn from './fn'
import Req from './jun_httpInstall'
import Store from '../store'
import Config from '../config'
// import { Message } from 'element-ui'
let config = {
    // 登录请求接口链接
    url: 'weixin!ajaxGetInfoByCode',
    // 2小时过期
    expire: 7.2e6,
}
/**
 * 检查登录缓存过期,默认不过期
 * @param {object} we_session 缓存对象
 */
function checkExpire(we_session){
    // if ((we_session && Date.now() >= we_session.expire) || !we_session) {
    //     // 删除store缓存
    //  Store.commit('unLogined')
    //     // wx.removeStorageSync('we_session')
    //     localStorage.removeItem('we_session')
    //     return true
    // }
    return false
}
/**
 * 进入微信长链,可获取code
 */
function toLongUrl () {
    // fn.urlReplace(Config.codeUrl)
    // location.href = Config.codeUrl
    location.href = Config.createCodeUrl()
}
/**
 * 登录
 * @param {object}   option 登录选项
 * @param {boolean}  option.force 是否强制重新登录
 * @param {function} option.callback 登录成功后回调
 */
function checkLogin(option={}){
    loginReq(option).then(()=>{
        if (typeof option.callback === 'function') {
            option.callback()
        }
    })
}
/**
 * 登录请求
 * @param {object} option
 * @param {object} option.data 请求参数
 */
function loginReq(option){
    if (Config.ismock || Config.istest) {
        option.data.code = '011h5c000ySdaK1lbw000ubdtm2h5c00'
    }
    return new Promise((resolve, reject)=>{
        // 前置判断
        let userData = Store.getters.getUserData
        if (userData.key) {
            resolve(userData)
            return
        }
        if (!option || !option.data) {
            reject()
            console.error('调用loginReq,缺失 option')
            return
        }
        if (!option.data.code) {
            console.error('调用loginReq,缺失 code')
            reject()
            return
        }
        Req.http3.post({
            url: config.url,
            data: option.data
        }).then((res)=>{
            // 缓存key值
            fn.setLocalStorage('we_session', {
                we_session: res.inf.key,
                expire: Date.now() + config.expire
            })
            // 缓存用户数据
            Store.commit('setUserData', res.inf)
            console.log(res.inf)
            // if (res.inf.name) {
            //     Store.commit('setNickName', res.inf.name)
            // }
            // 处理登录完成 store
            // getUserInfo().then(_res=>{
            //     // Store.commit('setLogined', _res.inf)
            //     resolve(res)
            // })
            // Store.commit('setLogined')
            resolve(res)
        }).catch((res)=>{
            console.log(res)
            reject(res)
        })
    })
}
export default {
    checkLogin,
    loginReq,
    toLongUrl
}
src/utils/jun_upload.js
New file
@@ -0,0 +1,35 @@
/**
 * 文件上传封装 - elementUI 用
 */
import Axios from '../libs/axios'
// 文件上传路径
const uploadUrl = 'pc/requirement!ajaxAddRequirement'
export default {
    // 文件上传路径
    uploadUrl,
    // 文件上传方法
    uploadFile (content) {
        var formData = new FormData()
        formData.append(content.filename, content.file)
        Axios({
            method: 'post',
            headers: { 'Content-Type': 'multipart/form-data' },
            url: content.action,
            data: formData
        }).then((res)=>{
            console.log('文件上传完成', res)
            content.onSuccess(res.data, content)
        }).catch((res)=>{
            if (res.response) {
                content.onError('上传文件失败' + res.response.status + ',' + res.response.data, res)
            } else if (res.request) {
                content.onError('上传文件失败,服务器端无响应')
            } else {
                content.onError('上传文件失败,请求封装失败')
            }
        })
    }
}
src/utils/wxsign.js
New file
@@ -0,0 +1,92 @@
//==== 微信签名功能 ====
import Req from './jun_httpInstall' // http 请求
import appId from '../config/appid' // appid
// js SHA
(function(D){function p(b,e,c){var a=0,d=[0],f="",g=null,f=c||"UTF8";if("UTF8"!==f&&"UTF16"!==f)throw"encoding must be UTF8 or UTF16";if("HEX"===e){if(0!==b.length%2)throw"srcString of HEX type must be in byte increments";g=v(b);a=g.binLen;d=g.value}else if("TEXT"===e)g=w(b,f),a=g.binLen,d=g.value;else if("B64"===e)g=x(b),a=g.binLen,d=g.value;else if("BYTES"===e)g=y(b),a=g.binLen,d=g.value;else throw"inputFormat must be HEX, TEXT, B64, or BYTES";this.getHash=function(b,f,c,e){var g=null,h=d.slice(),l=a,n;3===arguments.length?"number"!==typeof c&&(e=c,c=1):2===arguments.length&&(c=1);if(c!==parseInt(c,10)||1>c)throw"numRounds must a integer >= 1";switch(f){case "HEX":g=z;break;case "B64":g=A;break;case "BYTES":g=B;break;default:throw"format must be HEX, B64, or BYTES";}if("SHA-1"===b)for(n=0;n<c;n+=1)h=s(h,l),l=160;else throw"Chosen SHA variant is not supported";return g(h,C(e))};this.getHMAC=function(c,b,e,g,q){var h,l,n,r,p=[],t=[];h=null;switch(g){case "HEX":g=z;break;case "B64":g=A;break;case "BYTES":g=B;break;default:throw"outputFormat must be HEX, B64, or BYTES";}if("SHA-1"===e)l=64,r=160;else throw"Chosen SHA variant is not supported";if("HEX"===b)h=v(c),n=h.binLen,h=h.value;else if("TEXT"===b)h=w(c,f),n=h.binLen,h=h.value;else if("B64"===b)h=x(c),n=h.binLen,h=h.value;else if("BYTES"===b)h=y(c),n=h.binLen,h=h.value;else throw"inputFormat must be HEX, TEXT, B64, or BYTES";c=8*l;b=l/4-1;if(l<n/8){if("SHA-1"===e)h=s(h,n);else throw"Unexpected error in HMAC implementation";h[b]&=4294967040}else l>n/8&&(h[b]&=4294967040);for(l=0;l<=b;l+=1)p[l]=h[l]^909522486,t[l]=h[l]^1549556828;if("SHA-1"===e)e=s(t.concat(s(p.concat(d),c+a)),c+r);else throw"Unexpected error in HMAC implementation";return g(e,C(q))}}function w(b,e){var c=[],a,d=[],f=0,g;if("UTF8"===e)for(g=0;g<b.length;g+=1)for(a=b.charCodeAt(g),d=[],128>a?d.push(a):2048>a?(d.push(192|a>>>6),d.push(128|a&63)):55296>a||57344<=a?d.push(224|a>>>12,128|a>>>6&63,128|a&63):(g+=1,a=65536+((a&1023)<<10|b.charCodeAt(g)&1023),d.push(240|a>>>18,128|a>>>12&63,128|a>>>6&63,128|a&63)),a=0;a<d.length;a+=1)(f>>>2)+1>c.length&&c.push(0),c[f>>>2]|=d[a]<<24-f%4*8,f+=1;else if("UTF16"===e)for(g=0;g<b.length;g+=1)(f>>>2)+1>c.length&&c.push(0),c[f>>>2]|=b.charCodeAt(g)<<16-f%4*8,f+=2;return{value:c,binLen:8*f}}function v(b){var e=[],c=b.length,a,d;if(0!==c%2)throw"String of HEX type must be in byte increments";for(a=0;a<c;a+=2){d=parseInt(b.substr(a,2),16);if(isNaN(d))throw"String of HEX type contains invalid characters";e[a>>>3]|=d<<24-a%8*4}return{value:e,binLen:4*c}}function y(b){var e=[],c,a;for(a=0;a<b.length;a+=1)c=b.charCodeAt(a),(a>>>2)+1>e.length&&e.push(0),e[a>>>2]|=c<<24-a%4*8;return{value:e,binLen:8*b.length}}function x(b){var e=[],c=0,a,d,f,g,m;if(-1===b.search(/^[a-zA-Z0-9=+\/]+$/))throw"Invalid character in base-64 string";a=b.indexOf("=");b=b.replace(/\=/g,"");if(-1!==a&&a<b.length)throw"Invalid '=' found in base-64 string";for(d=0;d<b.length;d+=4){m=b.substr(d,4);for(f=g=0;f<m.length;f+=1)a="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".indexOf(m[f]),g|=a<<18-6*f;for(f=0;f<m.length-1;f+=1)e[c>>2]|=(g>>>16-8*f&255)<<24-c%4*8,c+=1}return{value:e,binLen:8*c}}function z(b,e){var c="",a=4*b.length,d,f;for(d=0;d<a;d+=1)f=b[d>>>2]>>>8*(3-d%4),c+="0123456789abcdef".charAt(f>>>4&15)+"0123456789abcdef".charAt(f&15);return e.outputUpper?c.toUpperCase():c}function A(b,e){var c="",a=4*b.length,d,f,g;for(d=0;d<a;d+=3)for(g=(b[d>>>2]>>>8*(3-d%4)&255)<<16|(b[d+1>>>2]>>>8*(3-(d+1)%4)&255)<<8|b[d+2>>>2]>>>8*(3-(d+2)%4)&255,f=0;4>f;f+=1)c=8*d+6*f<=32*b.length?c+"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".charAt(g>>>6*(3-f)&63):c+e.b64Pad;return c}function B(b){var e="",c=4*b.length,a,d;for(a=0;a<c;a+=1)d=b[a>>>2]>>>8*(3-a%4)&255,e+=String.fromCharCode(d);return e}function C(b){var e={outputUpper:!1,b64Pad:"="};try{b.hasOwnProperty("outputUpper")&&(e.outputUpper=b.outputUpper),b.hasOwnProperty("b64Pad")&&(e.b64Pad=b.b64Pad)}catch(c){}if("boolean"!==typeof e.outputUpper)throw"Invalid outputUpper formatting option";if("string"!==typeof e.b64Pad)throw"Invalid b64Pad formatting option";return e}function q(b,e){return b<<e|b>>>32-e}function r(b,e){var c=(b&65535)+(e&65535);return((b>>>16)+(e>>>16)+(c>>>16)&65535)<<16|c&65535}function t(b,e,c,a,d){var f=(b&65535)+(e&65535)+(c&65535)+(a&65535)+(d&65535);return((b>>>16)+(e>>>16)+(c>>>16)+(a>>>16)+(d>>>16)+(f>>>16)&65535)<<16|f&65535}function s(b,e){var c=[],a,d,f,g,m,p,u,k,s,h=[1732584193,4023233417,2562383102,271733878,3285377520];b[e>>>5]|=128<<24-e%32;b[(e+65>>>9<<4)+15]=e;s=b.length;for(u=0;u<s;u+=16){a=h[0];d=h[1];f=h[2];g=h[3];m=h[4];for(k=0;80>k;k+=1)c[k]=16>k?b[k+u]:q(c[k-3]^c[k-8]^c[k-14]^c[k-16],1),p=20>k?t(q(a,5),d&f^~d&g,m,1518500249,c[k]):40>k?t(q(a,5),d^f^g,m,1859775393,c[k]):60>k?t(q(a,5),d&f^d&g^f&g,m,2400959708,c[k]):t(q(a,5),d^f^g,m,3395469782,c[k]),m=g,g=f,f=q(d,30),d=a,a=p;h[0]=r(a,h[0]);h[1]=r(d,h[1]);h[2]=r(f,h[2]);h[3]=r(g,h[3]);h[4]=r(m,h[4])}return h}/*"function"===typeof define&&define.amd?define(function(){return p}):"undefined"!==typeof exports?"undefined"!==typeof module&&module.exports?module.exports=exports=p:exports=p:D.jsSHA=p*/D.jsSHA=p})(window);
// 随机数
function GetRndCode(a,b){a=a||6;var d="";b=b||new Array(0,1,2,3,4,5,6,7,8,9);for(var c=0;c<a;c++){var e=Math.floor(Math.random()*10);d+=b[e]}return d};
var wxConfig = {
    debug: false,
    appId: appId,
    timestamp: 0,
    nonceStr: "",
    signature: "",
    jsApiList: ['onMenuShareTimeline','onMenuShareAppMessage','closeWindow','previewImage','updateAppMessageShareData','updateTimelineShareData']
};
// 保存js_ticket 到sessionStorage
function getJsTicket(cb){
    var hasSes = !!window.sessionStorage;
    var jsapi_ticket = window.sessionStorage.getItem('jsapi_ticket') || '';
    if( hasSes && jsapi_ticket ){
        // sessionStorage.setItem('jsapi_ticket', jsapi_ticket);
        typeof cb==='function' && cb();
    }else{
        refreshTicket(cb)
    }
}
// 刷新ticket
function refreshTicket(cb){
    Req.http.post({
        url: 'weixin!ajaxGetJsTicket',
        udData: {nokey: true}
    }).then((res)=>{
        // jssdk
        console.log(res)
        var ticket = res.inf.ticket
        sessionStorage.setItem('jsapi_ticket', ticket);
        sessionStorage.setItem('shareImg', res.inf.shareImg);
        sessionStorage.setItem('shareTitle', res.inf.shareTitle);
        sessionStorage.setItem('shareDesc', res.inf.shareDesc);
        typeof cb === 'function' && cb();
    })
}
var times = 0
// 微信签名
function sign(){
    times += 1
    var timestamp = ((new Date()).getTime()+'').substr(0,10),
        nonceStr = GetRndCode(8)+'',
        // url = encodeURIComponent(location.href.split('#')[0]),
        url = location.href.split('#')[0],
        jsapi_ticket = window.sessionStorage.getItem('jsapi_ticket'),
        str = "jsapi_ticket="+jsapi_ticket+"&noncestr="+nonceStr+"&timestamp="+timestamp+"&url="+url,
        signature = new jsSHA(str,"TEXT").getHash("SHA-1","HEX");
    wxConfig.timestamp = timestamp;
    wxConfig.nonceStr  = nonceStr;
    wxConfig.signature = signature;
    console.log(str)
    wx.config(wxConfig);
    wx.error(function(res){
        console.log(res)
        if(refreshTicket && times<2){
            refreshTicket(sign);
        }else{
            // alert(res.errMsg);
        }
    });
}
// 主函数
function main(callback){
    var isWx = /micromessenger/.test(navigator.userAgent.toLowerCase());
    // https去掉
    //  && /^https/.test(location.href)
    if(isWx){
        getJsTicket(function(){
            sign()
        })
    }
}
export default {
    main
}
static/.gitkeep
static/bgm/.gitkeep
static/imgs/headimg.jpg
test/e2e/custom-assertions/elementCount.js
New file
@@ -0,0 +1,27 @@
// A custom Nightwatch assertion.
// The assertion name is the filename.
// Example usage:
//
//   browser.assert.elementCount(selector, count)
//
// For more information on custom assertions see:
// http://nightwatchjs.org/guide#writing-custom-assertions
exports.assertion = function (selector, count) {
  this.message = 'Testing if element <' + selector + '> has count: ' + count
  this.expected = count
  this.pass = function (val) {
    return val === this.expected
  }
  this.value = function (res) {
    return res.value
  }
  this.command = function (cb) {
    var self = this
    return this.api.execute(function (selector) {
      return document.querySelectorAll(selector).length
    }, [selector], function (res) {
      cb.call(self, res)
    })
  }
}
test/e2e/nightwatch.conf.js
New file
@@ -0,0 +1,46 @@
require('babel-register')
var config = require('../../config')
// http://nightwatchjs.org/gettingstarted#settings-file
module.exports = {
  src_folders: ['test/e2e/specs'],
  output_folder: 'test/e2e/reports',
  custom_assertions_path: ['test/e2e/custom-assertions'],
  selenium: {
    start_process: true,
    server_path: require('selenium-server').path,
    host: '127.0.0.1',
    port: 4444,
    cli_args: {
      'webdriver.chrome.driver': require('chromedriver').path
    }
  },
  test_settings: {
    default: {
      selenium_port: 4444,
      selenium_host: 'localhost',
      silent: true,
      globals: {
        devServerURL: 'http://localhost:' + (process.env.PORT || config.dev.port)
      }
    },
    chrome: {
      desiredCapabilities: {
        browserName: 'chrome',
        javascriptEnabled: true,
        acceptSslCerts: true
      }
    },
    firefox: {
      desiredCapabilities: {
        browserName: 'firefox',
        javascriptEnabled: true,
        acceptSslCerts: true
      }
    }
  }
}
test/e2e/runner.js
New file
@@ -0,0 +1,48 @@
// 1. start the dev server using production config
process.env.NODE_ENV = 'testing'
const webpack = require('webpack')
const DevServer = require('webpack-dev-server')
const webpackConfig = require('../../build/webpack.prod.conf')
const devConfigPromise = require('../../build/webpack.dev.conf')
let server
devConfigPromise.then(devConfig => {
  const devServerOptions = devConfig.devServer
  const compiler = webpack(webpackConfig)
  server = new DevServer(compiler, devServerOptions)
  const port = devServerOptions.port
  const host = devServerOptions.host
  return server.listen(port, host)
})
.then(() => {
  // 2. run the nightwatch test suite against it
  // to run in additional browsers:
  //    1. add an entry in test/e2e/nightwatch.conf.js under "test_settings"
  //    2. add it to the --env flag below
  // or override the environment flag, for example: `npm run e2e -- --env chrome,firefox`
  // For more information on Nightwatch's config file, see
  // http://nightwatchjs.org/guide#settings-file
  let opts = process.argv.slice(2)
  if (opts.indexOf('--config') === -1) {
    opts = opts.concat(['--config', 'test/e2e/nightwatch.conf.js'])
  }
  if (opts.indexOf('--env') === -1) {
    opts = opts.concat(['--env', 'chrome'])
  }
  const spawn = require('cross-spawn')
  const runner = spawn('./node_modules/.bin/nightwatch', opts, { stdio: 'inherit' })
  runner.on('exit', function (code) {
    server.close()
    process.exit(code)
  })
  runner.on('error', function (err) {
    server.close()
    throw err
  })
})
test/e2e/specs/test.js
New file
@@ -0,0 +1,19 @@
// For authoring Nightwatch tests, see
// http://nightwatchjs.org/guide#usage
module.exports = {
  'default e2e tests': function (browser) {
    // automatically uses dev Server port from /config.index.js
    // default: http://localhost:8080
    // see nightwatch.conf.js
    const devServer = browser.globals.devServerURL
    browser
      .url(devServer)
      .waitForElementVisible('#app', 5000)
      .assert.elementPresent('.hello')
      .assert.containsText('h1', 'Welcome to Your Vue.js App')
      .assert.elementCount('img', 1)
      .end()
  }
}
test/unit/.eslintrc
New file
@@ -0,0 +1,6 @@
{
  "env": {
  },
  "globals": {
  }
}
test/unit/specs/HelloWorld.spec.js
New file
@@ -0,0 +1,11 @@
import Vue from 'vue'
import HelloWorld from '@/components/HelloWorld'
describe('HelloWorld.vue', () => {
  it('should render correct contents', () => {
    const Constructor = Vue.extend(HelloWorld)
    const vm = new Constructor().$mount()
    expect(vm.$el.querySelector('.hello h1').textContent)
  })
})