New file |
| | |
| | | { |
| | | "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"] |
| | | } |
| | | } |
| | | } |
New file |
| | |
| | | root = true |
| | | |
| | | [*] |
| | | charset = utf-8 |
| | | indent_style = space |
| | | indent_size = 2 |
| | | end_of_line = lf |
| | | insert_final_newline = true |
| | | trim_trailing_whitespace = true |
New file |
| | |
| | | build/*.js |
| | | src/assets |
| | | public |
| | | dist |
New file |
| | |
| | | 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'] |
| | | } |
| | | } |
New file |
| | |
| | | .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 |
New file |
| | |
| | | // 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": {} |
| | | } |
| | | } |
| | |
| | | <<<<<<< 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 |
New file |
| | |
| | | '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' |
| | | )) |
| | | }) |
| | | }) |
New file |
| | |
| | | '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) |
| | | } |
| | | } |
New file |
| | |
| | | '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') |
| | | }) |
| | | } |
| | | } |
New file |
| | |
| | | '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' |
| | | } |
| | | } |
New file |
| | |
| | | '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' |
| | | } |
| | | } |
New file |
| | |
| | | '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) |
| | | } |
| | | }) |
| | | }) |
New file |
| | |
| | | '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 |
New file |
| | |
| | | <!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> |
New file |
| | |
| | | 'use strict' |
| | | const merge = require('webpack-merge') |
| | | const prodEnv = require('./prod.env') |
| | | |
| | | module.exports = merge(prodEnv, { |
| | | NODE_ENV: '"development"' |
| | | }) |
New file |
| | |
| | | '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 |
| | | } |
| | | } |
New file |
| | |
| | | 'use strict' |
| | | module.exports = { |
| | | NODE_ENV: '"production"' |
| | | } |
New file |
| | |
| | | 'use strict' |
| | | const merge = require('webpack-merge') |
| | | const devEnv = require('./dev.env') |
| | | |
| | | module.exports = merge(devEnv, { |
| | | NODE_ENV: '"testing"' |
| | | }) |
New file |
| | |
| | | <!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> |
New file |
| | |
| | | { |
| | | "compilerOptions": { |
| | | "baseUrl": "./", |
| | | "paths": { |
| | | "@/*": ["src/*"] |
| | | } |
| | | }, |
| | | "exclude": ["node_modules", "dist"] |
| | | } |
New file |
| | |
| | | { |
| | | "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" |
| | | ] |
| | | } |
New file |
| | |
| | | <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> |
New file |
| | |
| | | /*@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); |
| | | } |
New file |
| | |
| | | <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> |
New file |
| | |
| | | <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> |
New file |
| | |
| | | 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 |
| | | } |
| | | } |
| | | |
New file |
| | |
| | | <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> |
New file |
| | |
| | | <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> |
New file |
| | |
| | | /** |
| | | * 微信公众号 appId |
| | | * |
| | | * 允许根据测试服务器域名,设置测试用的(公司自己的)公众号 |
| | | */ |
| | | let appId = 'wxf7fdbeea741166be' |
| | | |
| | | // if (/hymctest/.test(location.href)) { |
| | | // appId = 'wxc09b57e6e10c7676' // 测试用 |
| | | // } |
| | | |
| | | export default appId |
New file |
| | |
| | | /** |
| | | * 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 |
| | | } |
New file |
| | |
| | | // 过滤器 |
| | | |
| | | 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 |
| | | } |
New file |
| | |
| | | // 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('失败回调') |
| | | // }) |
New file |
| | |
| | | // 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/>' |
| | | }) |
New file |
| | |
| | | // 引入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', {}); |
New file |
| | |
| | | <!--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> |
New file |
| | |
| | | /** |
| | | * 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 |
New file |
| | |
| | | /* |
| | | * @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 |
New file |
| | |
| | | /** |
| | | * 生成时间字符串 |
| | | * 规则,暂定 |
| | | * 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 |
| | | } |
New file |
| | |
| | | /* |
| | | * @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 |
New file |
| | |
| | | /* |
| | | * @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; |
New file |
| | |
| | | /** |
| | | * 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, |
| | | } |
New file |
| | |
| | | /** |
| | | * 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 |
New file |
| | |
| | | /** |
| | | * 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, |
| | | } |
New file |
| | |
| | | // 创建 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, |
| | | } |
New file |
| | |
| | | /** |
| | | * 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 |
New file |
| | |
| | | /** |
| | | * 登录 |
| | | */ |
| | | // 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 |
| | | } |
New file |
| | |
| | | /** |
| | | * 文件上传封装 - 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('上传文件失败,请求封装失败') |
| | | } |
| | | }) |
| | | } |
| | | } |
New file |
| | |
| | | //==== 微信签名功能 ==== |
| | | 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+"×tamp="+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 |
| | | } |
New file |
| | |
| | | // 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) |
| | | }) |
| | | } |
| | | } |
New file |
| | |
| | | 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 |
| | | } |
| | | } |
| | | } |
| | | } |
New file |
| | |
| | | // 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 |
| | | }) |
| | | }) |
New file |
| | |
| | | // 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() |
| | | } |
| | | } |
New file |
| | |
| | | { |
| | | "env": { |
| | | }, |
| | | "globals": { |
| | | } |
| | | } |
New file |
| | |
| | | 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) |
| | | |
| | | }) |
| | | }) |