├── .babelrc ├── .editorconfig ├── .gitignore ├── .postcssrc.js ├── .vscode └── launch.json ├── CHANGELOG ├── LICENSE ├── README.md ├── build ├── build.js ├── check-versions.js ├── dev-client.js ├── dev-server.js ├── utils.js ├── vue-loader.conf.js ├── webpack.base.conf.js ├── webpack.dev.conf.js └── webpack.prod.conf.js ├── buildH5 ├── webpack.baseH5.conf.js ├── webpack.devH5.conf.js └── webpack.proH5.conf.js ├── config ├── dev.env.js ├── index.js └── prod.env.js ├── configH5 └── index.js ├── gulpfile.js ├── index.html ├── package-lock.json ├── package.json ├── project.config.json ├── server ├── app.js ├── bin │ └── www.js ├── libs │ ├── MD5.js │ └── http.js ├── logConf.js ├── router │ ├── chat.js │ └── stock.js ├── service │ ├── chatService.js │ └── stockService.js └── testData │ ├── questions.js │ └── stock.js ├── src ├── App.vue ├── AppH5.vue ├── api │ ├── httpService.js │ └── wxService.js ├── components │ └── fundCharts.vue ├── filter │ └── index.js ├── main.js ├── mainH5.js ├── pages │ ├── chat │ │ ├── index.vue │ │ └── main.js │ ├── chatDetail │ │ ├── index.vue │ │ └── main.js │ └── newsDetail │ │ ├── index.vue │ │ └── main.js ├── router │ └── index.js ├── scss │ ├── chat.scss │ ├── chatDetail.scss │ ├── common.scss │ └── newsDetail.scss ├── services │ ├── KlineService.js │ └── chatService.js ├── utils │ ├── echarts-bar.js │ ├── echarts-dateKline.js │ ├── echarts-pie.js │ ├── echarts-timeKline.js │ └── util.js └── vuex │ └── store.js └── static ├── .gitkeep ├── ec-canvas ├── ec-canvas.js ├── ec-canvas.json ├── ec-canvas.wxml ├── ec-canvas.wxss ├── echarts.js └── wx-canvas.js ├── libs ├── echarts.min.js └── vue.all.js └── res ├── img ├── head_2.jpg └── xiaogua.jpg └── readmeImg ├── demo_web.gif └── demo_wx.gif /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["env", { 4 | "modules": false, 5 | "targets": { 6 | "browsers": ["> 1%", "last 2 versions", "not ie <= 8"] 7 | } 8 | }], 9 | "stage-2" 10 | ], 11 | "plugins": ["transform-runtime"], 12 | "env": { 13 | "test": { 14 | "presets": ["env", "stage-2"], 15 | "plugins": ["istanbul"] 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | dist/ 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Editor directories and files 9 | .idea 10 | *.suo 11 | *.ntvs* 12 | *.njsproj 13 | *.sln 14 | -------------------------------------------------------------------------------- /.postcssrc.js: -------------------------------------------------------------------------------- 1 | // https://github.com/michael-ciniawsky/postcss-load-config 2 | 3 | module.exports = { 4 | "plugins": { 5 | "postcss-mpvue-wxss": {} 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // 使用 IntelliSense 了解相关属性。 3 | // 悬停以查看现有属性的描述。 4 | // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "PowerShell", 9 | "request": "launch", 10 | "name": "PowerShell Launch Current File", 11 | "script": "${file}", 12 | "args": [], 13 | "cwd": "${file}" 14 | }, 15 | { 16 | "type": "PowerShell", 17 | "request": "launch", 18 | "name": "PowerShell Launch Current File in Temporary Console", 19 | "script": "${file}", 20 | "args": [], 21 | "cwd": "${file}", 22 | "createTemporaryIntegratedConsole": true 23 | }, 24 | { 25 | "type": "PowerShell", 26 | "request": "launch", 27 | "name": "PowerShell Launch Current File w/Args Prompt", 28 | "script": "${file}", 29 | "args": [ 30 | "${command:SpecifyScriptArgs}" 31 | ], 32 | "cwd": "${file}" 33 | }, 34 | { 35 | "type": "PowerShell", 36 | "request": "attach", 37 | "name": "PowerShell Attach to Host Process", 38 | "processId": "${command:PickPSHostProcess}", 39 | "runspaceId": 1 40 | }, 41 | { 42 | "type": "PowerShell", 43 | "request": "launch", 44 | "name": "PowerShell Interactive Session", 45 | "cwd": "${workspaceRoot}" 46 | } 47 | ] 48 | } -------------------------------------------------------------------------------- /CHANGELOG: -------------------------------------------------------------------------------- 1 | # change log -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018-present, RuShi 4 | 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 股票机器人 2 | 3 | ## 前端(移动端&小程序)/后端(nodejs) 4 | 5 | *前端 : Vue + Mpvue(支持移动端与小程序) ; 后端 : koa(使用Koa2开发服务端)* 6 | 7 | > [Vue](https://cn.vuejs.org/) 是一套用于构建用户界面的渐进式框架。`Vue` 的核心库只关注视图层,不仅易于上手,还便于与第三方库或既有项目整合。另一方面,当与现代化的工具链以及各种支持类库结合使用时,`Vue` 也完全能够为复杂的单页应用提供驱动。 8 | 9 | > [mpvue](http://mpvue.com/) 是一个使用 `Vue.js` 开发小程序的前端框架。框架基于 `Vue.js` 核心,`mpvue` 修改了 `Vue.js` 的 runtime 和 compiler 实现,使其可以运行在小程序环境中,从而为小程序开发引入了整套 `Vue.js` 开发体验。 10 | 11 | > [koa](https://koa.bootcss.com/) 是由 Express 原班人马打造的,致力于成为一个更小、更富有表现力、更健壮的 Web 框架。 使用 koa 编写 web 应用,通过组合不同的 generator,可以免除重复繁琐的回调函数嵌套, 并极大地提升错误处理的效率。koa 不在内核方法中绑定任何中间件, 它仅仅提供了一个轻量优雅的函数库,使得编写 Web 应用变得得心应手。 12 | 13 | ## 简介 14 | 15 | > [一个使用node服务同时支持移动端与小程序的聊天机器人](https://www.jianshu.com/p/91e566bfeedf) 16 | 17 | ## Build Setup 18 | 19 | ``` bash 20 | # install dependencies 21 | npm install 22 | 23 | # build mini program 24 | npm start 25 | 26 | # build H5 pages 27 | npm run startH5 28 | 29 | # build pages for production with minification for mini program 30 | npm run build 31 | 32 | # build pages for production with minification for H5 program 33 | npm run buildH5 34 | 35 | # build node resource for server 36 | gulp build 37 | 38 | ``` 39 | 40 | ## 前后端分离 41 | 42 | ```bash 43 | # start mini progrom 44 | npm run dev 45 | 46 | # start H5 pages 47 | npm run devH5 48 | 49 | # start server 50 | npm run server 51 | ``` 52 | *(服务端代码仅位于项目`server`文件夹下,可根据自身需求进行拆分)* 53 | 54 | ## 重要!!! 55 | 56 | - 请在`project.config.json`文件,`appid`处填写自己的微信开发账号`appid`; 57 | - [echarts-for-weixin](https://github.com/ecomfe/echarts-for-weixin)支持微信版本 >= 6.6.3,对应基础库版本 >= 1.9.91。 58 | 59 | ## 特性 60 | 61 | * 使用 `Vue.js` 构建移动端应用 62 | * 使用 `mpvue` 复用代码构建小程序应用 63 | * 使用 `Koa` 构建服务端 64 | * 使用 [Vuex](https://github.com/vuejs/vuex) 数据管理方案,方便构建复杂应用 65 | * 使用 [webpack](https://github.com/webpack/webpack) 构建机制:自定义构建策略、开发阶段 hotReload 66 | * 支持使用 [npm](https://github.com/npm/npm) 外部依赖 67 | * 使用[OLAMI](https://cn.olami.ai/open/website/home/home_show)人工智能开放平台自然语言语义理解 API 68 | 69 | ## 插件 70 | 71 | - [gulp](https://www.gulpjs.com.cn/) 72 | is a toolkit for automating painful or time-consuming tasks in your development workflow, so you can stop messing around and build something. 73 | 74 | - [Echarts](https://github.com/apache/incubator-echarts) 75 | 一个使用 JavaScript 实现的开源可视化库,可以流畅的运行在 PC 和移动设备上,兼容当前绝大部分浏览器(IE8/9/10/11,Chrome,Firefox,Safari等),底层依赖轻量级的矢量图形库 ZRender,提供直观,交互丰富,可高度个性化定制的数据可视化图表。 76 | 77 | - [Day.js](https://github.com/iamkun/dayjs) 78 | 一个轻量的处理时间和日期的 JavaScript 库,和 Moment.js 的 API 设计保持完全一样. 如果您曾经用过 Moment.js, 那么您已经知道如何使用 Day.js 79 | 80 | ## 截图 81 | 82 | **移动端 & 小程序** 83 | 84 | ![移动端](./static/res/readmeImg/demo_web.gif) ![小程序](./static/res/readmeImg/demo_wx.gif) 85 | 86 | ## Contribution 87 | 88 | [RuShi](https://github.com/zz570557024) 89 | 90 | ## License 91 | 92 | [MIT](http://opensource.org/licenses/MIT) 93 | -------------------------------------------------------------------------------- /build/build.js: -------------------------------------------------------------------------------- 1 | require('./check-versions')() 2 | 3 | process.env.NODE_ENV = 'production' 4 | 5 | var ora = require('ora') 6 | var rm = require('rimraf') 7 | var path = require('path') 8 | var chalk = require('chalk') 9 | var webpack = require('webpack') 10 | var config = require('../config') 11 | var webpackConfig = require('./webpack.prod.conf') 12 | 13 | var spinner = ora('building for production...') 14 | spinner.start() 15 | 16 | rm(path.join(config.build.assetsRoot, config.build.assetsSubDirectory), err => { 17 | if (err) throw err 18 | webpack(webpackConfig, function (err, stats) { 19 | spinner.stop() 20 | if (err) throw err 21 | process.stdout.write(stats.toString({ 22 | colors: true, 23 | modules: false, 24 | children: false, 25 | chunks: false, 26 | chunkModules: false 27 | }) + '\n\n') 28 | 29 | if (stats.hasErrors()) { 30 | console.log(chalk.red(' Build failed with errors.\n')) 31 | process.exit(1) 32 | } 33 | 34 | console.log(chalk.cyan(' Build complete.\n')) 35 | console.log(chalk.yellow( 36 | ' Tip: built files are meant to be served over an HTTP server.\n' + 37 | ' Opening index.html over file:// won\'t work.\n' 38 | )) 39 | }) 40 | }) 41 | -------------------------------------------------------------------------------- /build/check-versions.js: -------------------------------------------------------------------------------- 1 | var chalk = require('chalk') 2 | var semver = require('semver') 3 | var packageConfig = require('../package.json') 4 | var shell = require('shelljs') 5 | function exec (cmd) { 6 | return require('child_process').execSync(cmd).toString().trim() 7 | } 8 | 9 | var versionRequirements = [ 10 | { 11 | name: 'node', 12 | currentVersion: semver.clean(process.version), 13 | versionRequirement: packageConfig.engines.node 14 | } 15 | ] 16 | 17 | if (shell.which('npm')) { 18 | versionRequirements.push({ 19 | name: 'npm', 20 | currentVersion: exec('npm --version'), 21 | versionRequirement: packageConfig.engines.npm 22 | }) 23 | } 24 | 25 | module.exports = function () { 26 | var warnings = [] 27 | for (var i = 0; i < versionRequirements.length; i++) { 28 | var mod = versionRequirements[i] 29 | if (!semver.satisfies(mod.currentVersion, mod.versionRequirement)) { 30 | warnings.push(mod.name + ': ' + 31 | chalk.red(mod.currentVersion) + ' should be ' + 32 | chalk.green(mod.versionRequirement) 33 | ) 34 | } 35 | } 36 | 37 | if (warnings.length) { 38 | console.log('') 39 | console.log(chalk.yellow('To use this template, you must update following to modules:')) 40 | console.log() 41 | for (var i = 0; i < warnings.length; i++) { 42 | var warning = warnings[i] 43 | console.log(' ' + warning) 44 | } 45 | console.log() 46 | process.exit(1) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /build/dev-client.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | require('eventsource-polyfill') 3 | var hotClient = require('webpack-hot-middleware/client?noInfo=true&reload=true') 4 | 5 | hotClient.subscribe(function (event) { 6 | if (event.action === 'reload') { 7 | window.location.reload() 8 | } 9 | }) 10 | -------------------------------------------------------------------------------- /build/dev-server.js: -------------------------------------------------------------------------------- 1 | require('./check-versions')() 2 | 3 | var config = require('../config') 4 | if (!process.env.NODE_ENV) { 5 | process.env.NODE_ENV = JSON.parse(config.dev.env.NODE_ENV) 6 | } 7 | 8 | // var opn = require('opn') 9 | var path = require('path') 10 | var express = require('express') 11 | var webpack = require('webpack') 12 | var proxyMiddleware = require('http-proxy-middleware') 13 | var webpackConfig = require('./webpack.dev.conf') 14 | 15 | // default port where dev server listens for incoming traffic 16 | var port = process.env.PORT || config.dev.port 17 | // automatically open browser, if not set will be false 18 | var autoOpenBrowser = !!config.dev.autoOpenBrowser 19 | // Define HTTP proxies to your custom API backend 20 | // https://github.com/chimurai/http-proxy-middleware 21 | var proxyTable = config.dev.proxyTable 22 | 23 | var app = express() 24 | var compiler = webpack(webpackConfig) 25 | 26 | // var devMiddleware = require('webpack-dev-middleware')(compiler, { 27 | // publicPath: webpackConfig.output.publicPath, 28 | // quiet: true 29 | // }) 30 | 31 | // var hotMiddleware = require('webpack-hot-middleware')(compiler, { 32 | // log: false, 33 | // heartbeat: 2000 34 | // }) 35 | // force page reload when html-webpack-plugin template changes 36 | // compiler.plugin('compilation', function (compilation) { 37 | // compilation.plugin('html-webpack-plugin-after-emit', function (data, cb) { 38 | // hotMiddleware.publish({ action: 'reload' }) 39 | // cb() 40 | // }) 41 | // }) 42 | 43 | // proxy api requests 44 | Object.keys(proxyTable).forEach(function (context) { 45 | var options = proxyTable[context] 46 | if (typeof options === 'string') { 47 | options = { target: options } 48 | } 49 | app.use(proxyMiddleware(options.filter || context, options)) 50 | }) 51 | 52 | // handle fallback for HTML5 history API 53 | app.use(require('connect-history-api-fallback')()) 54 | 55 | // serve webpack bundle output 56 | // app.use(devMiddleware) 57 | 58 | // enable hot-reload and state-preserving 59 | // compilation error display 60 | // app.use(hotMiddleware) 61 | 62 | // serve pure static assets 63 | var staticPath = path.posix.join(config.dev.assetsPublicPath, config.dev.assetsSubDirectory) 64 | app.use(staticPath, express.static('./static')) 65 | 66 | var uri = 'http://localhost:' + port 67 | 68 | var _resolve 69 | var readyPromise = new Promise(resolve => { 70 | _resolve = resolve 71 | }) 72 | 73 | // console.log('> Starting dev server...') 74 | // devMiddleware.waitUntilValid(() => { 75 | // console.log('> Listening at ' + uri + '\n') 76 | // // when env is testing, don't need open it 77 | // if (autoOpenBrowser && process.env.NODE_ENV !== 'testing') { 78 | // opn(uri) 79 | // } 80 | // _resolve() 81 | // }) 82 | 83 | var server = app.listen(port, 'localhost') 84 | 85 | // for 小程序的文件保存机制 86 | require('webpack-dev-middleware-hard-disk')(compiler, { 87 | publicPath: webpackConfig.output.publicPath, 88 | quiet: true 89 | }) 90 | 91 | module.exports = { 92 | ready: readyPromise, 93 | close: () => { 94 | server.close() 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /build/utils.js: -------------------------------------------------------------------------------- 1 | var path = require('path') 2 | var config = require('../config') 3 | var ExtractTextPlugin = require('extract-text-webpack-plugin') 4 | 5 | exports.assetsPath = function (_path) { 6 | var assetsSubDirectory = process.env.NODE_ENV === 'production' 7 | ? config.build.assetsSubDirectory 8 | : config.dev.assetsSubDirectory 9 | return path.posix.join(assetsSubDirectory, _path) 10 | } 11 | 12 | exports.cssLoaders = function (options) { 13 | options = options || {} 14 | 15 | var cssLoader = { 16 | loader: 'css-loader', 17 | options: { 18 | minimize: process.env.NODE_ENV === 'production', 19 | sourceMap: options.sourceMap 20 | } 21 | } 22 | 23 | var postcssLoader = { 24 | loader: 'postcss-loader', 25 | options: { 26 | sourceMap: true 27 | } 28 | } 29 | 30 | var px2rpxLoader = { 31 | loader: 'px2rpx-loader', 32 | options: { 33 | baseDpr: 1, 34 | rpxUnit: 0.5 35 | } 36 | } 37 | 38 | // generate loader string to be used with extract text plugin 39 | function generateLoaders (loader, loaderOptions) { 40 | var loaders = [cssLoader, px2rpxLoader, postcssLoader] 41 | if (loader) { 42 | loaders.push({ 43 | loader: loader + '-loader', 44 | options: Object.assign({}, loaderOptions, { 45 | sourceMap: options.sourceMap 46 | }) 47 | }) 48 | } 49 | 50 | // Extract CSS when that option is specified 51 | // (which is the case during production build) 52 | if (options.extract) { 53 | return ExtractTextPlugin.extract({ 54 | use: loaders, 55 | fallback: 'vue-style-loader' 56 | }) 57 | } else { 58 | return ['vue-style-loader'].concat(loaders) 59 | } 60 | } 61 | 62 | // https://vue-loader.vuejs.org/en/configurations/extract-css.html 63 | return { 64 | css: generateLoaders(), 65 | postcss: generateLoaders(), 66 | less: generateLoaders('less'), 67 | sass: generateLoaders('sass', { indentedSyntax: true }), 68 | scss: generateLoaders('sass'), 69 | stylus: generateLoaders('stylus'), 70 | styl: generateLoaders('stylus') 71 | } 72 | } 73 | 74 | // Generate loaders for standalone style files (outside of .vue) 75 | exports.styleLoaders = function (options) { 76 | var output = [] 77 | var loaders = exports.cssLoaders(options) 78 | for (var extension in loaders) { 79 | var loader = loaders[extension] 80 | output.push({ 81 | test: new RegExp('\\.' + extension + '$'), 82 | use: loader 83 | }) 84 | } 85 | return output 86 | } 87 | -------------------------------------------------------------------------------- /build/vue-loader.conf.js: -------------------------------------------------------------------------------- 1 | var utils = require('./utils') 2 | var config = require('../config') 3 | // var isProduction = process.env.NODE_ENV === 'production' 4 | // for mp 5 | var isProduction = true 6 | 7 | module.exports = { 8 | loaders: utils.cssLoaders({ 9 | sourceMap: isProduction 10 | ? config.build.productionSourceMap 11 | : config.dev.cssSourceMap, 12 | extract: isProduction 13 | }), 14 | transformToRequire: { 15 | video: 'src', 16 | source: 'src', 17 | img: 'src', 18 | image: 'xlink:href' 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /build/webpack.base.conf.js: -------------------------------------------------------------------------------- 1 | var path = require('path') 2 | var fs = require('fs') 3 | var utils = require('./utils') 4 | var config = require('../config') 5 | var vueLoaderConfig = require('./vue-loader.conf') 6 | var MpvuePlugin = require('webpack-mpvue-asset-plugin') 7 | var glob = require('glob') 8 | 9 | function resolve (dir) { 10 | return path.join(__dirname, '..', dir) 11 | } 12 | 13 | function getEntry (rootSrc, pattern) { 14 | var files = glob.sync(path.resolve(rootSrc, pattern)) 15 | return files.reduce((res, file) => { 16 | var info = path.parse(file) 17 | var key = info.dir.slice(rootSrc.length + 1) + '/' + info.name 18 | res[key] = path.resolve(file) 19 | return res 20 | }, {}) 21 | } 22 | 23 | const appEntry = { app: resolve('./src/main.js') } 24 | const pagesEntry = getEntry(resolve('./src'), 'pages/**/main.js') 25 | const entry = Object.assign({}, appEntry, pagesEntry) 26 | 27 | module.exports = { 28 | // 如果要自定义生成的 dist 目录里面的文件路径, 29 | // 可以将 entry 写成 {'toPath': 'fromPath'} 的形式, 30 | // toPath 为相对于 dist 的路径, 例:index/demo,则生成的文件地址为 dist/index/demo.js 31 | entry, 32 | target: require('mpvue-webpack-target'), 33 | output: { 34 | path: config.build.assetsRoot, 35 | filename: '[name].js', 36 | publicPath: process.env.NODE_ENV === 'production' 37 | ? config.build.assetsPublicPath 38 | : config.dev.assetsPublicPath 39 | }, 40 | resolve: { 41 | extensions: ['.js', '.vue', '.json'], 42 | alias: { 43 | 'vue': 'mpvue', 44 | '@': resolve('src') 45 | }, 46 | symlinks: false 47 | }, 48 | module: { 49 | rules: [ 50 | { 51 | test: /\.vue$/, 52 | loader: 'mpvue-loader', 53 | options: vueLoaderConfig 54 | }, 55 | { 56 | test: /\.js$/, 57 | include: [resolve('src'), resolve('test')], 58 | use: [ 59 | 'babel-loader', 60 | { 61 | loader: 'mpvue-loader', 62 | options: { 63 | checkMPEntry: true 64 | } 65 | }, 66 | ] 67 | }, 68 | { 69 | test: /\.(png|jpe?g|gif|svg)(\?.*)?$/, 70 | loader: 'url-loader', 71 | options: { 72 | limit: 10000, 73 | name: utils.assetsPath('img/[name].[ext]') 74 | } 75 | }, 76 | { 77 | test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/, 78 | loader: 'url-loader', 79 | options: { 80 | limit: 10000, 81 | name: utils.assetsPath('media/[name]].[ext]') 82 | } 83 | }, 84 | { 85 | test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/, 86 | loader: 'url-loader', 87 | options: { 88 | limit: 10000, 89 | name: utils.assetsPath('fonts/[name].[ext]') 90 | } 91 | } 92 | ] 93 | }, 94 | plugins: [ 95 | new MpvuePlugin() 96 | ] 97 | } 98 | -------------------------------------------------------------------------------- /build/webpack.dev.conf.js: -------------------------------------------------------------------------------- 1 | var utils = require('./utils') 2 | var webpack = require('webpack') 3 | var config = require('../config') 4 | var merge = require('webpack-merge') 5 | var baseWebpackConfig = require('./webpack.base.conf') 6 | // var HtmlWebpackPlugin = require('html-webpack-plugin') 7 | var FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin') 8 | 9 | // copy from ./webpack.prod.conf.js 10 | var path = require('path') 11 | var ExtractTextPlugin = require('extract-text-webpack-plugin') 12 | var CopyWebpackPlugin = require('copy-webpack-plugin') 13 | var OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin') 14 | 15 | // add hot-reload related code to entry chunks 16 | // Object.keys(baseWebpackConfig.entry).forEach(function (name) { 17 | // baseWebpackConfig.entry[name] = ['./build/dev-client'].concat(baseWebpackConfig.entry[name]) 18 | // }) 19 | 20 | module.exports = merge(baseWebpackConfig, { 21 | module: { 22 | rules: utils.styleLoaders({ 23 | sourceMap: config.dev.cssSourceMap, 24 | extract: true 25 | }) 26 | }, 27 | // cheap-module-eval-source-map is faster for development 28 | // devtool: '#cheap-module-eval-source-map', 29 | devtool: '#source-map', 30 | output: { 31 | path: config.build.assetsRoot, 32 | // filename: utils.assetsPath('js/[name].[chunkhash].js'), 33 | // chunkFilename: utils.assetsPath('js/[id].[chunkhash].js') 34 | filename: utils.assetsPath('js/[name].js'), 35 | chunkFilename: utils.assetsPath('js/[id].js') 36 | }, 37 | plugins: [ 38 | new webpack.DefinePlugin({ 39 | 'process.env': config.dev.env 40 | }), 41 | 42 | // copy from ./webpack.prod.conf.js 43 | // extract css into its own file 44 | new ExtractTextPlugin({ 45 | // filename: utils.assetsPath('css/[name].[contenthash].css') 46 | filename: utils.assetsPath('css/[name].wxss') 47 | }), 48 | // Compress extracted CSS. We are using this plugin so that possible 49 | // duplicated CSS from different components can be deduped. 50 | new OptimizeCSSPlugin({ 51 | cssProcessorOptions: { 52 | safe: true 53 | } 54 | }), 55 | new webpack.optimize.CommonsChunkPlugin({ 56 | name: 'vendor', 57 | minChunks: function (module, count) { 58 | // any required modules inside node_modules are extracted to vendor 59 | return ( 60 | module.resource && 61 | /\.js$/.test(module.resource) && 62 | module.resource.indexOf('node_modules') >= 0 63 | ) || count > 1 64 | } 65 | }), 66 | new webpack.optimize.CommonsChunkPlugin({ 67 | name: 'manifest', 68 | chunks: ['vendor'] 69 | }), 70 | // copy custom static assets 71 | new CopyWebpackPlugin([ 72 | { 73 | from: path.resolve(__dirname, '../static'), 74 | to: config.build.assetsSubDirectory, 75 | ignore: ['.*'] 76 | } 77 | ]), 78 | 79 | // https://github.com/glenjamin/webpack-hot-middleware#installation--usage 80 | // new webpack.HotModuleReplacementPlugin(), 81 | new webpack.NoEmitOnErrorsPlugin(), 82 | // https://github.com/ampedandwired/html-webpack-plugin 83 | // new HtmlWebpackPlugin({ 84 | // filename: 'index.html', 85 | // template: 'index.html', 86 | // inject: true 87 | // }), 88 | new FriendlyErrorsPlugin() 89 | ] 90 | }) 91 | -------------------------------------------------------------------------------- /build/webpack.prod.conf.js: -------------------------------------------------------------------------------- 1 | var path = require('path') 2 | var utils = require('./utils') 3 | var webpack = require('webpack') 4 | var config = require('../config') 5 | var merge = require('webpack-merge') 6 | var baseWebpackConfig = require('./webpack.base.conf') 7 | var CopyWebpackPlugin = require('copy-webpack-plugin') 8 | // var HtmlWebpackPlugin = require('html-webpack-plugin') 9 | var ExtractTextPlugin = require('extract-text-webpack-plugin') 10 | var OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin') 11 | 12 | var env = config.build.env 13 | 14 | var webpackConfig = merge(baseWebpackConfig, { 15 | module: { 16 | rules: utils.styleLoaders({ 17 | sourceMap: config.build.productionSourceMap, 18 | extract: true 19 | }) 20 | }, 21 | devtool: config.build.productionSourceMap ? '#source-map' : false, 22 | output: { 23 | path: config.build.assetsRoot, 24 | // filename: utils.assetsPath('js/[name].[chunkhash].js'), 25 | // chunkFilename: utils.assetsPath('js/[id].[chunkhash].js') 26 | filename: utils.assetsPath('js/[name].js'), 27 | chunkFilename: utils.assetsPath('js/[id].js') 28 | }, 29 | plugins: [ 30 | // http://vuejs.github.io/vue-loader/en/workflow/production.html 31 | new webpack.DefinePlugin({ 32 | 'process.env': env 33 | }), 34 | new webpack.optimize.UglifyJsPlugin({ 35 | compress: { 36 | warnings: false 37 | }, 38 | sourceMap: true 39 | }), 40 | // extract css into its own file 41 | new ExtractTextPlugin({ 42 | // filename: utils.assetsPath('css/[name].[contenthash].css') 43 | filename: utils.assetsPath('css/[name].wxss') 44 | }), 45 | // Compress extracted CSS. We are using this plugin so that possible 46 | // duplicated CSS from different components can be deduped. 47 | new OptimizeCSSPlugin({ 48 | cssProcessorOptions: { 49 | safe: true 50 | } 51 | }), 52 | // generate dist index.html with correct asset hash for caching. 53 | // you can customize output by editing /index.html 54 | // see https://github.com/ampedandwired/html-webpack-plugin 55 | // new HtmlWebpackPlugin({ 56 | // filename: config.build.index, 57 | // template: 'index.html', 58 | // inject: true, 59 | // minify: { 60 | // removeComments: true, 61 | // collapseWhitespace: true, 62 | // removeAttributeQuotes: true 63 | // // more options: 64 | // // https://github.com/kangax/html-minifier#options-quick-reference 65 | // }, 66 | // // necessary to consistently work with multiple chunks via CommonsChunkPlugin 67 | // chunksSortMode: 'dependency' 68 | // }), 69 | // keep module.id stable when vender modules does not change 70 | new webpack.HashedModuleIdsPlugin(), 71 | // split vendor js into its own file 72 | new webpack.optimize.CommonsChunkPlugin({ 73 | name: 'vendor', 74 | minChunks: function (module, count) { 75 | // any required modules inside node_modules are extracted to vendor 76 | return ( 77 | module.resource && 78 | /\.js$/.test(module.resource) && 79 | module.resource.indexOf('node_modules') >= 0 80 | ) || count > 1 81 | } 82 | }), 83 | // extract webpack runtime and module manifest to its own file in order to 84 | // prevent vendor hash from being updated whenever app bundle is updated 85 | new webpack.optimize.CommonsChunkPlugin({ 86 | name: 'manifest', 87 | chunks: ['vendor'] 88 | }), 89 | // copy custom static assets 90 | new CopyWebpackPlugin([ 91 | { 92 | from: path.resolve(__dirname, '../static'), 93 | to: config.build.assetsSubDirectory, 94 | ignore: ['.*'] 95 | } 96 | ]) 97 | ] 98 | }) 99 | 100 | // if (config.build.productionGzip) { 101 | // var CompressionWebpackPlugin = require('compression-webpack-plugin') 102 | 103 | // webpackConfig.plugins.push( 104 | // new CompressionWebpackPlugin({ 105 | // asset: '[path].gz[query]', 106 | // algorithm: 'gzip', 107 | // test: new RegExp( 108 | // '\\.(' + 109 | // config.build.productionGzipExtensions.join('|') + 110 | // ')$' 111 | // ), 112 | // threshold: 10240, 113 | // minRatio: 0.8 114 | // }) 115 | // ) 116 | // } 117 | 118 | if (config.build.bundleAnalyzerReport) { 119 | var BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin 120 | webpackConfig.plugins.push(new BundleAnalyzerPlugin()) 121 | } 122 | 123 | module.exports = webpackConfig 124 | -------------------------------------------------------------------------------- /buildH5/webpack.baseH5.conf.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | // const ExtractTextPlugin = require('extract-text-webpack-plugin') 3 | 4 | function resolve(dir) { 5 | return path.join(__dirname, '..', dir) 6 | } 7 | 8 | module.exports = { 9 | // context: path.resolve(__dirname, '../'), 10 | entry: { 11 | app: './src/mainH5.js', 12 | // vendor: ["babel-polyfill", 'vue', 'vue-router', 'vuex', 'mint-ui', 'axios', 'rxjs'] 13 | vendor: ["babel-polyfill"] 14 | }, 15 | output: { 16 | filename: '[name].bundle.js', 17 | path: path.resolve(__dirname, '../dist') 18 | // publicPath: '/' 19 | }, 20 | resolve: { 21 | extensions: ['.js', '.vue', '.json'], 22 | alias: { 23 | 'vue$': 'vue/dist/vue.esm.js' 24 | } 25 | }, 26 | externals: { 27 | 'vue': 'Vue', 28 | 'vue-router': 'VueRouter', 29 | 'vuex': 'Vuex', 30 | 'rxjs': 'Rx', 31 | 'axios': 'axios' 32 | }, 33 | module: { 34 | rules: [{ 35 | test: /\.scss$/, 36 | loaders: ['style-loader', 'css-loader', 'resolve-url-loader', 'sass-loader?sourceMap'] 37 | }, 38 | { 39 | test: /\.vue$/, 40 | loader: 'vue-loader', 41 | options: { 42 | loaders: { 43 | scss: ['vue-style-loader', 'css-loader', 'resolve-url-loader', 'sass-loader?sourceMap'] // 32 | -------------------------------------------------------------------------------- /src/AppH5.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 25 | 26 | 34 | -------------------------------------------------------------------------------- /src/api/httpService.js: -------------------------------------------------------------------------------- 1 | export default { 2 | //ajax请求 3 | async httpRequest(option = {}) { 4 | if (option.methods == 'GET' || option.methods == 'get') { 5 | return await axios.get( 6 | option.url, { 7 | params: option.data 8 | } 9 | ) 10 | } else if (option.methods == 'POST' || option.methods == 'post') { 11 | return await axios.post( 12 | option.url, option.data 13 | ) 14 | } else { 15 | console.log('method not allow!') 16 | } 17 | }, 18 | //用户信息 19 | async getUserInfo() { 20 | let data = { 21 | userInfo: { 22 | avatarUrl: './../../res/image/liangPlus.jpg', 23 | nickName: '如是' 24 | } 25 | } 26 | return data 27 | }, 28 | //聊天列表滚动 29 | async pageScrollTo() { 30 | let YAxis = document.getElementsByClassName('chat_area')[0].clientHeight; 31 | let data = await window.scrollTo(0, YAxis - 300) 32 | return await data; 33 | }, 34 | //页面跳转 35 | async navigatePageTo(url = '/') { 36 | location.href = await url; 37 | }, 38 | //设备宽高 39 | async getScreenOption() { 40 | let screenWidth = await screen.availWidth; 41 | let screenHeigth = await screen.availHeight; 42 | return { 43 | screenWidth: screenWidth, 44 | screenHeigth: screenHeigth 45 | } 46 | }, 47 | //滚动顶部 48 | async scrollTop() { 49 | window.scrollTo(0, 0); 50 | }, 51 | async showToast(text) { 52 | alert(text); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/api/wxService.js: -------------------------------------------------------------------------------- 1 | export default { 2 | async getUserInfo() { 3 | let data = await new Promise((resolve, reject) => { 4 | wx.login({ 5 | success: () => { 6 | wx.getUserInfo({ 7 | success: resolve, 8 | fail: reject 9 | }) 10 | } 11 | }) 12 | }) 13 | return data 14 | }, 15 | async httpRequest(options = {}) { 16 | let data = await new Promise((resolve, reject) => { 17 | switch (options.type) { 18 | case 0: 19 | options.url = 'http://127.0.0.1:8000' + options.url.split('/liang')[1]; 20 | break; 21 | default: 22 | break; 23 | } 24 | wx.request({ 25 | url: options.url, 26 | data: Object.assign({}, options.data), 27 | method: options.methods || 'GET', 28 | header: { 29 | 'Content-Type': 'application/json' 30 | }, 31 | success: resolve, 32 | fail: reject 33 | }) 34 | }) 35 | return data 36 | }, 37 | async getScreenWidth() { 38 | let data = await new Promise((resolve, reject) => { 39 | wx.getSystemInfo({ 40 | success: resolve, 41 | fail: reject 42 | }) 43 | }) 44 | return data; 45 | }, 46 | async pageScrollTo(YAxis) { 47 | let data = await wx.createSelectorQuery().select('.chat_area').boundingClientRect(function (rect) { 48 | // 使页面滚动到底部 49 | wx.pageScrollTo({ 50 | scrollTop: rect.bottom - 500 51 | }) 52 | }).exec() 53 | return await data; 54 | }, 55 | async navigatePageTo(url = '/') { 56 | await wx.navigateTo({ 57 | url: url 58 | }) 59 | }, 60 | //滚动顶部 61 | async scrollTop() { 62 | // wx.pageScrollTo({ 63 | // scrollTop: 0 64 | // }) 65 | }, 66 | //打开文件 67 | async downloadFile(url) { 68 | await wx.downloadFile({ 69 | url: url, 70 | success: function (res) { 71 | var filePath = res.tempFilePath 72 | wx.openDocument({ 73 | filePath: filePath, 74 | success: function (res) { 75 | console.log('打开文档成功') 76 | }, 77 | fail: res => { 78 | console.log('打开文档失败') 79 | } 80 | }) 81 | } 82 | }) 83 | }, 84 | async showToast(text) { 85 | await wx.showToast({ 86 | title: text, 87 | icon: 'success', 88 | duration: 2000 89 | }) 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/components/fundCharts.vue: -------------------------------------------------------------------------------- 1 | 10 | 74 | 83 | 84 | 85 | -------------------------------------------------------------------------------- /src/filter/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import numeral from 'numeral'; 3 | import moment from 'dayjs' 4 | 5 | Vue.filter('strToArr', function (value) { 6 | if (!value) { 7 | return ''; 8 | } else { 9 | return value.split(','); 10 | } 11 | }) 12 | 13 | Vue.filter('getArrItem', function (value, index) { 14 | if (!value && value !== 0) { 15 | return ''; 16 | } else { 17 | return value[index]; 18 | } 19 | }) 20 | 21 | Vue.filter('getArrMaxItem', function (value) { 22 | if (!value) { 23 | return ''; 24 | } else { 25 | if (typeof value == 'object') { 26 | return value[value.length - 1]; 27 | } else { 28 | let arr = value.split(','); 29 | return arr[arr.length - 1]; 30 | } 31 | } 32 | }) 33 | 34 | // 数字格式化 35 | Vue.filter('number', function (value, format = '0.00') { 36 | return numeral(value).format(format); 37 | }) 38 | 39 | Vue.filter('money', function (value, format = '0,0.00') { 40 | return numeral(value).format(format) 41 | }) 42 | 43 | // 日期格式化 2017-12-28 15:02:50 => 2017.12.28 44 | Vue.filter('date', function (value, format) { 45 | if (!value) { 46 | return ''; 47 | } else { 48 | return value.split(' ')[0].replace(/-/g, format || '.'); 49 | } 50 | }) 51 | 52 | // 日期格式化 2017-12-28 15:02:50 => 2017.12.28 15:02:50 53 | Vue.filter('dateTime', function (value, format) { 54 | if (!value) { 55 | return ''; 56 | } else { 57 | return value.split(' ')[0].replace(/-/g, format || '.') + ' ' + value.split(' ')[1]; 58 | } 59 | }) 60 | 61 | Vue.filter('timeUnixFormat', function (value, format) { 62 | return value ? moment.unix(value).format(format || 'YYYY-MM-DD HH:mm:ss') : ''; 63 | }) 64 | 65 | // 累计盈亏 66 | Vue.filter('profit', function (trade) { 67 | if (!trade) { 68 | return 0; 69 | } else { 70 | return (trade.currPercent.assetValue || trade.wfCurrPercent || trade.wfPercent) - trade.wfPercent; 71 | } 72 | }) 73 | 74 | // 总操盘资金 75 | Vue.filter('tradeTotal', function (trade) { 76 | if (!trade) { 77 | return 0; 78 | } else { 79 | return trade; 80 | // return bussService.getTotalTrade(trade); 81 | } 82 | }) 83 | 84 | // 累计盈亏百分比-历史 85 | Vue.filter('profitHistoryPercent', function (profit, capitalAmount) { 86 | // 现金=0 87 | if (!capitalAmount) { 88 | if (profit <= 0) { 89 | return 0; 90 | } else { 91 | return 100; 92 | } 93 | } else { 94 | return profit / capitalAmount * 100; 95 | } 96 | }) 97 | 98 | // 正负加减号 99 | Vue.filter('operator', function (value) { 100 | if (!value || isNaN(value)) { 101 | return 0.00; 102 | } else if (value > 0) { 103 | return '+' + value; 104 | } else { 105 | return value; 106 | } 107 | }) 108 | 109 | // 交易日单位 110 | Vue.filter('tradeCycle', function (value) { 111 | if (!value) { 112 | return '交易日'; 113 | } else if (value == '1') { 114 | return '月'; 115 | } else { 116 | return '周'; 117 | } 118 | }) 119 | //银行卡脱敏 120 | Vue.filter('convertCardNo', function (value) { 121 | if (value && new RegExp(/^(\d{12}|\d{16,22})$/).test(value)) { 122 | return ' **** ***** **** ' + value.substring(value.length - 4, value.length); 123 | } 124 | return value; 125 | }) 126 | 127 | // {{item.TurnoverTime|tradeClientTime(0)}} 128 | // 2018-01-12 15:54:21 =>2018-01-12 or =>15:54:21 129 | Vue.filter('timePart', function (value, part) { 130 | if (!value) { 131 | return '-'; 132 | } else { 133 | part = part || 0; 134 | return value.split(' ')[part] 135 | } 136 | }) 137 | // 充值项目 138 | Vue.filter('deltaTypeIdCn', function (value) { 139 | let types = ['快捷支付', '网银支付', '支付宝', '银行转账', 'app支付', '微信支付']; 140 | return types[value] || '其他'; 141 | }) 142 | // 充值装填 143 | Vue.filter('statusCn', function (value) { 144 | let types = ['处理中', '已完成', '已驳回']; 145 | return types[value] || '其他'; 146 | }) 147 | //截取后两位 148 | Vue.filter('splitWord', function (step) { 149 | return step.substring(step.length - 2, step.length); 150 | }) 151 | 152 | Vue.filter('formatVolumn', function (val) { 153 | if (val) {} else { 154 | val = 0 155 | } 156 | if (val < 100000) { 157 | return val.toFixed(0) 158 | } else if (val >= 100000 && val < 1000000) { 159 | val = val / 10000; 160 | return val.toFixed(2) + '万' 161 | } else if (val >= 1000000 && val < 10000000) { 162 | val = val / 10000; 163 | return val.toFixed(1) + '万' 164 | } else if (val >= 10000000 && val < 100000000) { 165 | val = val / 10000; 166 | return val.toFixed(0) + '万' 167 | } else { 168 | val = val / 100000000; 169 | return val.toFixed(2) + '亿' 170 | } 171 | }) 172 | 173 | Vue.filter('formatSyl', function (val) { 174 | if (val) {} else { 175 | val = 0 176 | } 177 | if (val == 0) { 178 | return '--' 179 | } else { 180 | return val.toFixed(2) 181 | } 182 | }) 183 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import App from './App' 3 | import wxService from './api/wxService' 4 | import httpService from './api/httpService' 5 | 6 | Vue.config.productionTip = false 7 | App.mpType = 'app' 8 | Vue.mixin({ 9 | data() { 10 | return { 11 | service: '', 12 | router: '', 13 | imgSrc: '' 14 | } 15 | }, 16 | created() { 17 | if (window) { 18 | console.log('chrome') 19 | this.service = httpService 20 | this.router = '/#' 21 | this.imgSrc = '../..' 22 | } else { 23 | console.log('wx') 24 | this.service = wxService 25 | this.imgSrc = '/static' 26 | } 27 | } 28 | }) 29 | 30 | const app = new Vue(App) 31 | app.$mount() 32 | 33 | export default { 34 | // 这个字段走 app.json 35 | config: { 36 | // 页面前带有 ^ 符号的,会被编译成首页,其他页面可以选填,我们会自动把 webpack entry 里面的入口页面加进去 37 | pages: ['^pages/chat/main'], 38 | window: { 39 | backgroundTextStyle: 'light', 40 | navigationBarBackgroundColor: '#fff', 41 | navigationBarTitleText: 'WeChat', 42 | navigationBarTextStyle: 'black' 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/mainH5.js: -------------------------------------------------------------------------------- 1 | // The Vue build version to load with the `import` command 2 | // (runtime-only or standalone) has been set in webpack.base.conf with an alias. 3 | import Vue from 'vue' 4 | import App from './AppH5' 5 | import router from './router' 6 | import wxService from './api/wxService' 7 | import httpService from './api/httpService' 8 | import './filter'; 9 | 10 | Vue.config.productionTip = false 11 | 12 | Vue.mixin({ 13 | data() { 14 | return { 15 | service: '', 16 | router: '', 17 | imgSrc: '' 18 | } 19 | }, 20 | created() { 21 | if (window) { 22 | console.log('chrome') 23 | this.service = httpService 24 | this.router = '/#' 25 | this.imgSrc = '../..' 26 | } else { 27 | console.log('wx') 28 | this.service = wxService 29 | this.imgSrc = '/static' 30 | } 31 | } 32 | }) 33 | 34 | /* eslint-disable no-new */ 35 | new Vue({ 36 | el: '#app', 37 | router, 38 | }) 39 | -------------------------------------------------------------------------------- /src/pages/chat/index.vue: -------------------------------------------------------------------------------- 1 | 102 | 226 | -------------------------------------------------------------------------------- /src/pages/chat/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import App from './index' 3 | 4 | const app = new Vue(App) 5 | app.$mount() 6 | -------------------------------------------------------------------------------- /src/pages/chatDetail/index.vue: -------------------------------------------------------------------------------- 1 | 132 | 318 | 321 | 322 | 323 | -------------------------------------------------------------------------------- /src/pages/chatDetail/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import App from './index' 3 | 4 | const app = new Vue(App) 5 | app.$mount() 6 | 7 | // 添加 config json 8 | export default { 9 | config: { 10 | // 这儿添加要用的小程序组件 11 | usingComponents: { 12 | 'ec-canvas': '../../../static/ec-canvas/ec-canvas' 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/pages/newsDetail/index.vue: -------------------------------------------------------------------------------- 1 | 14 | 45 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /src/pages/newsDetail/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import App from './index' 3 | 4 | const app = new Vue(App) 5 | app.$mount() 6 | -------------------------------------------------------------------------------- /src/router/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Router from 'vue-router' 3 | import Chat from '../pages/chat/index.vue' 4 | import ChatDetail from '../pages/chatDetail/index.vue' 5 | import newsDetail from '../pages/newsDetail/index.vue' 6 | 7 | Vue.use(Router) 8 | 9 | export default new Router({ 10 | routes: [{ 11 | path: '/', 12 | name: 'index', 13 | component: Chat, 14 | }, { 15 | path: '/chatDetail', 16 | name: 'chatDetail', 17 | component: ChatDetail, 18 | alias: '/pages/chatDetail/main' 19 | }, { 20 | path: '/newsDetail', 21 | name: 'newsDetail', 22 | component: newsDetail, 23 | alias: '/pages/newsDetail/main' 24 | }] 25 | }) 26 | -------------------------------------------------------------------------------- /src/scss/chat.scss: -------------------------------------------------------------------------------- 1 | @import './common.scss'; 2 | .container { 3 | height: 100%; 4 | display: flex; 5 | flex-direction: column; 6 | align-items: center; 7 | justify-content: space-between; 8 | padding: 0 0; 9 | box-sizing: border-box; 10 | -webkit-user-select: none; 11 | user-select: none; 12 | width: 100%; 13 | overflow-x: hidden; 14 | /* font-size: 60px; */ 15 | } 16 | 17 | .qus_area { 18 | text-align: center; 19 | position: relative; 20 | top: 0; 21 | width: 60%; 22 | font-size: 14px; 23 | } 24 | 25 | .list-item { 26 | display: inline-block; 27 | margin-right: 10px; 28 | } 29 | 30 | .list-enter-active, 31 | .list-leave-active { 32 | transition: all 1s; 33 | } 34 | 35 | .list-enter, 36 | .list-leave-to { 37 | opacity: 0; 38 | transform: translateX(30px); 39 | } 40 | 41 | .chatList-item { 42 | display: inline-block; 43 | margin-right: 10px; 44 | } 45 | 46 | .chatList-enter-active, 47 | .chatList-leave-active { 48 | transition: all 1s; 49 | } 50 | 51 | .chatList-enter, 52 | .chatList-leave-to { 53 | opacity: 0; 54 | transform: translateY(30px); 55 | } 56 | 57 | .fade-enter-active, 58 | .fade-leave-active { 59 | transition: opacity 0.5s; 60 | } 61 | 62 | .fade-enter, 63 | .fade-leave-to { 64 | opacity: 0; 65 | } 66 | 67 | .bgc { 68 | /* background-color: #fff; */ 69 | border: #dbdbea 0.5px solid; 70 | padding: 10px; 71 | border-radius: 5px; 72 | } 73 | 74 | .listbgc { 75 | border-bottom: #dbdbea 0.5px solid; 76 | margin-top: 5px; 77 | margin-bottom: 5px; 78 | } 79 | 80 | .item { 81 | margin: 2px; 82 | } 83 | 84 | .counter-warp { 85 | height: -webkit-fill-available; 86 | } 87 | 88 | .title { 89 | color: red; 90 | font-weight: bold; 91 | } 92 | 93 | .qusItem { 94 | padding: 5px 0; 95 | } 96 | 97 | .qusUl { 98 | margin: 10px; 99 | } 100 | 101 | .foot { 102 | padding: 0 13px; 103 | height: 50px; 104 | display: flex; 105 | justify-content: flex-start; 106 | align-items: center; 107 | } 108 | 109 | .foot_input { 110 | margin-right: 9px; 111 | width: 100%; 112 | height: 30px; 113 | border-bottom: 1px solid #e0e0e0; 114 | } 115 | 116 | .inputBox { 117 | display: block; 118 | width: 90%; 119 | padding: 0 10px; 120 | line-height: 30px; 121 | height: 30px; 122 | border: 0; 123 | background: none; 124 | font-size: 16px; 125 | color: #000; 126 | /* border-bottom: 0.5px solid #e0e0e0; */ 127 | } 128 | 129 | .foot_btn { 130 | width: 70px; 131 | height: 38px; 132 | margin-right: 0px; 133 | line-height: 36px; 134 | background-color: #ff6347; 135 | color: #fff; 136 | border-radius: 10px; 137 | } 138 | 139 | .footer { 140 | position: fixed; 141 | bottom: 0; 142 | z-index: 10; 143 | width: 100%; 144 | background-color: #fff; 145 | left: 0px; 146 | } 147 | 148 | .chat_area { 149 | padding-top: 10px; 150 | padding-bottom: 55px; 151 | top: 0px; 152 | /* margin-right: 15px; 153 | margin-left: 15px; */ 154 | position: relative; 155 | } 156 | 157 | .chat_li { 158 | list-style: none; 159 | width: 100%; 160 | } 161 | 162 | .chat_word { 163 | display: flex; 164 | width: 100%; 165 | -webkit-box-pack: start; 166 | justify-content: flex-start; 167 | margin-bottom: 13px; 168 | position: relative; 169 | } 170 | 171 | .chat_qus { 172 | display: flex; 173 | -webkit-box-orient: horizontal; 174 | -webkit-box-direction: reverse; 175 | flex-direction: row-reverse; 176 | } 177 | 178 | .chat-img { 179 | display: block; 180 | width: 44px; 181 | height: 44px; 182 | border-radius: 50%; 183 | } 184 | 185 | .chat_say { 186 | position: relative; 187 | } 188 | 189 | .chat_txt { 190 | margin-left: 16px; 191 | margin-right: 16px; 192 | max-width: 250px; 193 | background: #ffb6c1; 194 | padding: 11px; 195 | border: 1px solid #d9d9d9; 196 | border-radius: 8px; 197 | font-size: 16px; 198 | color: #333; 199 | line-height: 21px; 200 | word-break: break-all; 201 | } 202 | 203 | .chat_list { 204 | margin-left: 16px; 205 | margin-right: 16px; 206 | background: #fff; 207 | padding: 10px; 208 | border: 1px solid #d9d9d9; 209 | border-radius: 8px; 210 | font-size: 12px; 211 | color: #333; 212 | line-height: 21px; 213 | word-break: break-all; 214 | width: 85%; 215 | } 216 | 217 | button { 218 | font-size: 14px; 219 | } 220 | 221 | .chat_table { 222 | font-size: 14px; 223 | } 224 | 225 | .flex-row { 226 | display: flex; 227 | flex-direction: row; 228 | justify-content: center; 229 | align-items: end; 230 | text-align: center; 231 | } 232 | 233 | .flex-column { 234 | display: flex; 235 | flex-direction: column; 236 | justify-content: center; 237 | align-items: stretch; 238 | text-align: center; 239 | } 240 | 241 | .flex-cell { 242 | flex: 1; 243 | } 244 | 245 | .changeQusBtn { 246 | border: 1px solid #999; 247 | padding: 8px; 248 | border-radius: 10px; 249 | line-height: 15px; 250 | width: 90px; 251 | background-color: #fff; 252 | } 253 | 254 | .label_name { 255 | width: 100%; 256 | margin-bottom: 10px; 257 | } 258 | 259 | .label_price { 260 | font-size: 20px; 261 | margin-right: 20px; 262 | } 263 | 264 | .chat_time { 265 | font-size: 12px; 266 | text-align: center; 267 | margin-bottom: 10px; 268 | margin-top: 5px; 269 | color: #999; 270 | } 271 | 272 | .chat_attitude { 273 | font-size: 12px; 274 | float: left; 275 | margin-left: 20px; 276 | margin-top: 5px; 277 | } 278 | 279 | .more_message { 280 | width: 100%; 281 | text-align: center; 282 | border-top: 1px solid #dbdbea; 283 | padding-top: 10px; 284 | margin-top: 5px; 285 | } 286 | 287 | .inputFile { 288 | width: 30px; 289 | height: 30px; 290 | border-radius: 50%; 291 | display: inline-block; 292 | opacity: 0; 293 | cursor: pointer; 294 | position: relative; 295 | } 296 | -------------------------------------------------------------------------------- /src/scss/chatDetail.scss: -------------------------------------------------------------------------------- 1 | @import './common.scss'; 2 | .Kline_scope { 3 | z-index: 1; 4 | } 5 | 6 | .fb_5 { 7 | font-weight: 500; 8 | } 9 | 10 | .stock_msg { 11 | text-align: center; 12 | } 13 | 14 | ec-canvas { 15 | width: 400px; 16 | height: 240px; 17 | } 18 | 19 | .news_List { 20 | position: relative; 21 | } 22 | 23 | .container { 24 | height: 100%; 25 | display: flex; 26 | flex-direction: column; 27 | align-items: center; 28 | justify-content: space-between; 29 | padding: 10px 0; 30 | box-sizing: border-box; 31 | -webkit-user-select: none; 32 | user-select: none; 33 | width: 100%; 34 | overflow-x: hidden; 35 | /* font-size: 60px; */ 36 | } 37 | 38 | .flex-row { 39 | display: flex; 40 | flex-direction: row; 41 | justify-content: center; 42 | align-items: end; 43 | } 44 | 45 | .flex-column { 46 | display: flex; 47 | flex-direction: column; 48 | justify-content: center; 49 | align-items: stretch; 50 | } 51 | 52 | .flex-cell { 53 | flex: 1; 54 | } 55 | 56 | .item { 57 | margin: 2px; 58 | } 59 | 60 | .tab_title { 61 | text-align: center; 62 | width: auto; 63 | border-bottom: 1px solid #999; 64 | padding: 5px; 65 | margin-bottom: 10px; 66 | border-top: 1px solid #999; 67 | font-family: cursive; 68 | } 69 | 70 | .tab { 71 | display: inline; 72 | margin: 10px 15px; 73 | } 74 | 75 | .tabselected { 76 | display: inline; 77 | margin: 10px 15px; 78 | color: red; 79 | } 80 | 81 | .list { 82 | padding: 0; 83 | } 84 | 85 | .list-item { 86 | height: 70px; 87 | margin: 0 15px; 88 | border-bottom: 1px solid #f5f5f5; 89 | width: 90%; 90 | padding-bottom: 10px; 91 | } 92 | 93 | .item-title { 94 | display: inline-block; 95 | padding-top: 15px; 96 | line-height: 20px; 97 | } 98 | 99 | .image-wrapper { 100 | float: right; 101 | padding-top: 15px; 102 | } 103 | 104 | .item-image { 105 | width: 75px; 106 | height: 60px; 107 | } 108 | 109 | .multipic { 110 | display: block; 111 | position: absolute; 112 | right: 0; 113 | bottom: 2px; 114 | width: 40px; 115 | height: 16px; 116 | font-size: 12px; 117 | color: #fff; 118 | background: #000; 119 | opacity: 0.7; 120 | text-align: center; 121 | line-height: 16px; 122 | } 123 | 124 | .news-none { 125 | text-align: center; 126 | padding: 30px; 127 | } 128 | 129 | .stock_info { 130 | font-size: 14px; 131 | } 132 | 133 | .stock_nMatch { 134 | font-size: 35px; 135 | } 136 | 137 | .stock_name { 138 | font-size: 18px; 139 | font-weight: bold; 140 | position: relative; 141 | top: -8px; 142 | left: 22px; 143 | } 144 | 145 | .stock_detail { 146 | width: 100%; 147 | text-align: center; 148 | } 149 | 150 | .stockSummary_li { 151 | float: left; 152 | display: block; 153 | width: 100%; 154 | padding: 20px; 155 | } 156 | 157 | .stockSummary_span { 158 | margin-left: 20px; 159 | } 160 | 161 | .circle { 162 | border-radius: 50%; 163 | width: 45px; 164 | height: 45px; 165 | background: #ffb6c1; 166 | /* 宽度和高度需要相等 */ 167 | position: relative; 168 | top: -225px; 169 | right: -285px; 170 | } 171 | 172 | .circle_txt { 173 | color: red; 174 | width: 30px; 175 | position: relative; 176 | left: 11px; 177 | top: 5px; 178 | font-size: 12px; 179 | } 180 | -------------------------------------------------------------------------------- /src/scss/common.scss: -------------------------------------------------------------------------------- 1 | .font_red { 2 | color: red; 3 | } 4 | 5 | .font_green { 6 | color: green; 7 | } 8 | 9 | .font_gray { 10 | color: #666; 11 | } 12 | -------------------------------------------------------------------------------- /src/scss/newsDetail.scss: -------------------------------------------------------------------------------- 1 | @import './common.scss'; 2 | .news_title { 3 | margin-top: 10px; 4 | padding: 0 20px; 5 | text-align: center; 6 | display: block; 7 | } 8 | 9 | .news_wrap { 10 | font-size: 14px; 11 | margin: 10px 10px; 12 | border-bottom: solid gainsboro 1px; 13 | text-align: center; 14 | padding: 10px 0; 15 | } 16 | 17 | /* .auth { 18 | font-size: 25rpx; 19 | margin:10px 10px; 20 | } */ 21 | 22 | .news_date { 23 | margin-left: 10px; 24 | } 25 | 26 | .news_detail { 27 | display: block; 28 | text-indent: 2em; 29 | text-align: left; 30 | color: gray; 31 | overflow: hidden; 32 | width: 100%; 33 | height: 100%; 34 | line-height: 35px; 35 | font-size: 14px; 36 | } 37 | 38 | .news_link { 39 | font-size: 14px; 40 | } 41 | -------------------------------------------------------------------------------- /src/services/KlineService.js: -------------------------------------------------------------------------------- 1 | import Util from "../utils/util"; 2 | const dateFrom = "2018-01-01"; 3 | const dateTo = Util.nowDate(); 4 | 5 | export default class Kline { 6 | getDailyDate(service, data = {}) { 7 | let option = { 8 | url: '/liang/stock/priceDaily', 9 | methods: 'GET', 10 | data: { 11 | code: data.code || '000002', 12 | type: '14901' 13 | }, 14 | type: 0 15 | } 16 | return service.httpRequest(option) 17 | } 18 | getMouthDate(service, data = {}) { 19 | let option = { 20 | url: '/liang/stock/priceMonthly', 21 | methods: 'GET', 22 | data: { 23 | code: data.code || '000002', 24 | type: '14901' 25 | }, 26 | type: 0 27 | } 28 | return service.httpRequest(option) 29 | } 30 | getWeekData(service, data = {}) { 31 | let option = { 32 | url: '/liang/stock/priceWeekly', 33 | methods: 'GET', 34 | data: { 35 | code: data.code || '000002', 36 | type: '14901' 37 | }, 38 | type: 0 39 | } 40 | return service.httpRequest(option) 41 | } 42 | getTimeDate(service, data = {}) { 43 | let option = { 44 | url: '/liang/stock/timeShareDaily', 45 | methods: 'GET', 46 | data: { 47 | code: data.code || '000002', 48 | type: '14901' 49 | }, 50 | type: 0 51 | } 52 | return service.httpRequest(option) 53 | } 54 | getFiveData(service, data = {}) { 55 | let option = { 56 | url: '/liang/stock/timeShareWeek', 57 | methods: 'GET', 58 | data: { 59 | code: data.code || '000002', 60 | type: '14901' 61 | }, 62 | type: 0 63 | } 64 | return service.httpRequest(option) 65 | } 66 | //股票实时信息 67 | stockRealTime(service, data = {}) { 68 | let option = { 69 | url: '/liang/stock/stockRealTime', 70 | methods: 'GET', 71 | data: { 72 | shrCd: data.code, 73 | typ: data.typ || '14901' 74 | }, 75 | type: 0 76 | } 77 | return service.httpRequest(option) 78 | } 79 | //个股相关新闻 80 | getStockNews(service, data = {}) { 81 | var option = { 82 | url: '/liang/stock/stockNews', 83 | methods: 'GET', 84 | data: { 85 | code: data.code 86 | }, 87 | type: 0 88 | } 89 | return service.httpRequest(option) 90 | } 91 | //个股相关公告 92 | getStockNotices(service, data = {}) { 93 | var option = { 94 | url: '/liang/stock/stockAnnouncement', 95 | methods: 'GET', 96 | data: { 97 | code: data.code 98 | }, 99 | type: 0 100 | } 101 | return service.httpRequest(option) 102 | } 103 | //个股相关研报 104 | getResearch(service, data = {}) { 105 | var option = { 106 | url: '/liang/stock/stockReport', 107 | methods: 'GET', 108 | data: { 109 | code: data.code 110 | }, 111 | type: 0 112 | } 113 | return service.httpRequest(option) 114 | } 115 | //个股简况 116 | getStockSummary(service, data = {}) { 117 | var option = { 118 | url: '/liang/stock/stockSummary', 119 | methods: 'GET', 120 | data: { 121 | code: data.code 122 | }, 123 | type: 0 124 | } 125 | return service.httpRequest(option) 126 | } 127 | //个股资金流向 128 | getCapitalFlows(service, data = {}) { 129 | var option = { 130 | url: '/liang/stock/capitalFlows', 131 | methods: 'GET', 132 | type: 0 133 | } 134 | return service.httpRequest(option) 135 | } 136 | //个股资金五日流向 137 | getCapitalFlowsMain(service, data = {}) { 138 | var option = { 139 | url: '/liang/stock/capitalFlowsMain', 140 | methods: 'GET', 141 | type: 0 142 | } 143 | return service.httpRequest(option) 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /src/services/chatService.js: -------------------------------------------------------------------------------- 1 | export default class Chat { 2 | getChatAnswer(service, question, page, pagesize) { 3 | let option = { 4 | url: '/liang/chat/getAnswer', 5 | data: { 6 | question: question, 7 | page: page || 1, 8 | pagesize: pagesize || 10 9 | }, 10 | methods: 'GET', 11 | type: 0 12 | } 13 | return service.httpRequest(option) 14 | } 15 | getChatQus(service) { 16 | let option = { 17 | url: '/liang/chat/defQuestions', 18 | methods: 'GET', 19 | type: 0 20 | } 21 | return service.httpRequest(option) 22 | } 23 | getChatEvaluation(service, data = {}) { 24 | let option = { 25 | url: '/liang/chat/evaluation', 26 | data: { 27 | question: data.question, 28 | answer: data.answer, 29 | evaluation: data.evaluation 30 | }, 31 | methods: 'GET', 32 | type: 0 33 | } 34 | return service.httpRequest(option) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/utils/echarts-bar.js: -------------------------------------------------------------------------------- 1 | export default { 2 | setBarOption(data) { 3 | function getXData(param) { 4 | var XData = []; 5 | for (let i = 0; i < param.length; i++) { 6 | XData.push(param[i].dt) 7 | } 8 | return XData 9 | } 10 | 11 | function getYData(param) { 12 | var YData = []; 13 | for (let i = 0; i < param.length; i++) { 14 | YData.push((param[i].val / 10000).toFixed(0)) 15 | } 16 | return YData 17 | } 18 | var option = { 19 | title: { 20 | text: '最近五日资金流向(单位:万元)', 21 | x: 'center', 22 | textStyle: { 23 | fontSize: '16' 24 | } 25 | }, 26 | xAxis: { 27 | type: 'category', 28 | data: getXData(data), 29 | axisLine: { 30 | show: false 31 | }, 32 | axisTick: { 33 | show: false 34 | }, 35 | splitLine: { 36 | show: false 37 | }, 38 | }, 39 | yAxis: { 40 | type: 'value', 41 | show: false 42 | }, 43 | series: [{ 44 | data: getYData(data), 45 | type: 'bar', 46 | label: { 47 | normal: { 48 | show: true, 49 | position: 'top' 50 | // function (params) { 51 | // var position; 52 | // if (getYData(data)[params.dataIndex] < 0) { 53 | // position = 'bottom'; 54 | // } else { 55 | // position = 'top'; 56 | // } 57 | // return position; 58 | // } 59 | } 60 | }, 61 | itemStyle: { 62 | normal: { 63 | color: function (params) { 64 | var colorList; 65 | if (getYData(data)[params.dataIndex] < 0) { 66 | colorList = '#14b143'; 67 | } else { 68 | colorList = '#ef232a'; 69 | } 70 | return colorList; 71 | } 72 | } 73 | } 74 | }] 75 | }; 76 | return option; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/utils/echarts-dateKline.js: -------------------------------------------------------------------------------- 1 | import dayjs from 'dayjs'; 2 | 3 | const colorList = ['#c23531', '#2f4554', '#61a0a8', '#d48265', '#91c7ae', '#749f83', '#ca8622', '#bda29a', '#6e7074', '#546570', '#c4ccd3']; 4 | const labelFont = 'bold 12px Sans-serif'; 5 | export default { 6 | methods: { 7 | parseKData: function (array) { 8 | var item = [] 9 | var results = [] 10 | for (var i = 0; i < array.length; i++) { 11 | item.push(array[i].dt) 12 | item.push(array[i].open * 100) 13 | item.push(array[i].close * 100) 14 | item.push(array[i].low * 100) 15 | item.push(array[i].high * 100) 16 | item.push(array[i].vol * 100) 17 | results.push(item) 18 | item = [] 19 | } 20 | return results 21 | }, 22 | splitData: function (rawData) { 23 | var categoryData = []; 24 | var values = [] 25 | for (var i = 0; i < rawData.length; i++) { 26 | categoryData.push(rawData[i].splice(0, 1)[0]); 27 | values.push(rawData[i]) 28 | } 29 | return { 30 | categoryData: categoryData, 31 | values: values 32 | }; 33 | }, 34 | setOption: function (data0) { 35 | var upColor = '#ec0000'; 36 | var upBorderColor = '#8A0000'; 37 | var downColor = '#00da3c'; 38 | var downBorderColor = '#008F28'; 39 | 40 | function calculateMA(data0, dayCount) { 41 | var result = []; 42 | for (var i = 0, len = data0.values.length; i < len; i++) { 43 | if (i < dayCount) { 44 | result.push('-'); 45 | continue; 46 | } 47 | var sum = 0; 48 | for (var j = 0; j < dayCount; j++) { 49 | sum += data0.values[i - j][1]; 50 | } 51 | result.push(sum / dayCount); 52 | } 53 | return result; 54 | } 55 | 56 | function getVolumes(data0) { 57 | let data = []; 58 | for (let i = 0, len = data0.values.length; i < len; i++) { 59 | data.push(data0.values[i][4]) 60 | } 61 | return data 62 | } 63 | 64 | function getOpen(data0) { 65 | let data = []; 66 | for (let i = 0, len = data0.values.length; i < len; i++) { 67 | data.push(data0.values[i][0]) 68 | } 69 | return data 70 | } 71 | var option = { 72 | animation: true, 73 | color: colorList, 74 | tooltip: { 75 | // triggerOn: 'none', 76 | // transitionDuration: 0, 77 | // confine: true, 78 | // bordeRadius: 4, 79 | // borderWidth: 1, 80 | // borderColor: '#333', 81 | // backgroundColor: 'rgba(255,255,255,0.9)', 82 | // textStyle: { 83 | // fontSize: 12, 84 | // color: '#333' 85 | // } 86 | show: true, 87 | trigger: "axis", 88 | formatter: function (params) { 89 | var res; 90 | res = params[0].name; 91 | res += "
今开 : " + params[0].data[1] / 100; 92 | res += "
收盘 : " + params[0].data[2] / 100; 93 | res += "
最低 : " + params[0].data[3] / 100; 94 | res += "
最高 : " + params[0].data[4] / 100; 95 | res += "
成交量 : " + params[0].data[5] / 10000 + '万'; 96 | return res; 97 | } 98 | }, 99 | axisPointer: { 100 | link: [{ 101 | xAxisIndex: [0, 1] 102 | }] 103 | }, 104 | dataZoom: [{ 105 | type: 'inside', 106 | xAxisIndex: [0, 1], 107 | start: 0, 108 | end: 100, 109 | height: 20 110 | }], 111 | xAxis: [{ 112 | type: 'category', 113 | data: data0.categoryData, 114 | boundaryGap: false, 115 | axisLine: { 116 | lineStyle: { 117 | color: '#777' 118 | } 119 | }, 120 | axisLabel: { 121 | formatter: function (value) { 122 | return dayjs(value).format('YY-M-D'); 123 | } 124 | }, 125 | min: 'dataMin', 126 | max: 'dataMax', 127 | axisPointer: { 128 | show: true 129 | } 130 | }, { 131 | type: 'category', 132 | gridIndex: 1, 133 | data: data0.categoryData, 134 | scale: true, 135 | boundaryGap: false, 136 | splitLine: { 137 | show: false 138 | }, 139 | axisLabel: { 140 | show: false 141 | }, 142 | axisTick: { 143 | show: false 144 | }, 145 | axisLine: { 146 | lineStyle: { 147 | color: '#777' 148 | } 149 | }, 150 | splitNumber: 20, 151 | min: 'dataMin', 152 | max: 'dataMax' 153 | }], 154 | yAxis: [{ 155 | scale: true, 156 | splitNumber: 2, 157 | axisLine: { 158 | lineStyle: { 159 | color: '#777' 160 | } 161 | }, 162 | splitLine: { 163 | show: true 164 | }, 165 | axisTick: { 166 | show: false 167 | }, 168 | axisLabel: { 169 | inside: true, 170 | formatter: '{value}\n' 171 | } 172 | }, { 173 | scale: true, 174 | gridIndex: 1, 175 | splitNumber: 2, 176 | axisLabel: { 177 | show: false 178 | }, 179 | axisLine: { 180 | show: false 181 | }, 182 | axisTick: { 183 | show: false 184 | }, 185 | splitLine: { 186 | show: false 187 | } 188 | }], 189 | grid: [{ 190 | left: 20, 191 | right: 20, 192 | top: 10, 193 | height: 120 194 | }, { 195 | left: 20, 196 | right: 20, 197 | height: 40, 198 | top: 160 199 | }], 200 | graphic: [{ 201 | type: 'group', 202 | left: 'center', 203 | top: 70, 204 | width: 200, 205 | bounding: 'raw', 206 | children: [{ 207 | id: 'MA5', 208 | type: 'text', 209 | style: { 210 | fill: colorList[1], 211 | font: labelFont 212 | }, 213 | left: 0 214 | }, { 215 | id: 'MA10', 216 | type: 'text', 217 | style: { 218 | fill: colorList[2], 219 | font: labelFont 220 | }, 221 | left: 'center' 222 | }, { 223 | id: 'MA20', 224 | type: 'text', 225 | style: { 226 | fill: colorList[3], 227 | font: labelFont 228 | }, 229 | right: 0 230 | }] 231 | }], 232 | series: [{ 233 | name: 'Volume', 234 | type: 'bar', 235 | xAxisIndex: 1, 236 | yAxisIndex: 1, 237 | itemStyle: { 238 | normal: { 239 | color: function (params) { 240 | var colorList; 241 | if (getVolumes(data0)[params.dataIndex - 1] > getVolumes(data0)[params.dataIndex]) { 242 | colorList = '#14b143'; 243 | } else { 244 | colorList = '#ef232a'; 245 | } 246 | return colorList; 247 | } 248 | }, 249 | emphasis: { 250 | color: '#140' 251 | } 252 | }, 253 | data: getVolumes(data0) 254 | }, { 255 | type: 'candlestick', 256 | name: '日K', 257 | data: data0.values, 258 | itemStyle: { 259 | normal: { 260 | color: '#ef232a', 261 | color0: '#14b143', 262 | borderColor: '#ef232a', 263 | borderColor0: '#14b143' 264 | }, 265 | emphasis: { 266 | color: 'black', 267 | color0: '#444', 268 | borderColor: 'black', 269 | borderColor0: '#444' 270 | } 271 | } 272 | }, { 273 | name: 'MA5', 274 | type: 'line', 275 | data: calculateMA(data0, 5), 276 | smooth: true, 277 | showSymbol: false, 278 | lineStyle: { 279 | normal: { 280 | width: 1 281 | } 282 | } 283 | }, { 284 | name: 'MA10', 285 | type: 'line', 286 | data: calculateMA(data0, 10), 287 | smooth: true, 288 | showSymbol: false, 289 | lineStyle: { 290 | normal: { 291 | width: 1 292 | } 293 | } 294 | }, { 295 | name: 'MA20', 296 | type: 'line', 297 | data: calculateMA(data0, 20), 298 | smooth: true, 299 | showSymbol: false, 300 | lineStyle: { 301 | normal: { 302 | width: 1 303 | } 304 | } 305 | }] 306 | }; 307 | return option 308 | } 309 | } 310 | } 311 | -------------------------------------------------------------------------------- /src/utils/echarts-pie.js: -------------------------------------------------------------------------------- 1 | import dayjs from 'dayjs'; 2 | 3 | export default { 4 | setPieOption(data) { 5 | var option = { 6 | title: { 7 | text: '资金分布', 8 | x: 'center', 9 | textStyle: { 10 | fontSize: '16' 11 | } 12 | }, 13 | tooltip: { 14 | trigger: 'item', 15 | formatter: "{b}: {d}%" 16 | }, 17 | // legend: { 18 | // orient: 'vertical', 19 | // x: 'left', 20 | // data: ['主户流入', '主户流出', '散户流入', '散户流出'] 21 | // }, 22 | series: [{ 23 | type: 'pie', 24 | radius: ['30%', '60%'], 25 | avoidLabelOverlap: false, 26 | name: '资金分布', 27 | label: { 28 | normal: { 29 | show: true, 30 | }, 31 | emphasis: { 32 | show: true, 33 | textStyle: { 34 | fontSize: '14', 35 | fontWeight: 'bold' 36 | } 37 | } 38 | }, 39 | color: ['#FF0000', '#008000', '#FFA500', '#90EE90'], 40 | labelLine: { 41 | normal: { 42 | show: true 43 | } 44 | }, 45 | data: [{ 46 | value: data.min, 47 | name: '主户流入' 48 | }, 49 | { 50 | value: data.mout, 51 | name: '主户流出' 52 | }, 53 | { 54 | value: data.rin, 55 | name: '散户流入' 56 | }, 57 | { 58 | value: data.rout, 59 | name: '散户流出' 60 | } 61 | ] 62 | }] 63 | }; 64 | return option 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/utils/echarts-timeKline.js: -------------------------------------------------------------------------------- 1 | import dayjs from 'dayjs'; 2 | import echarts from 'echarts'; 3 | 4 | export default { 5 | parserDataDaily(message) { 6 | var array = message.shares 7 | var item = [] 8 | var results = [] 9 | var average = [] 10 | var volume = [] 11 | for (var i = 0; i < array.length; i++) { 12 | item.push(array[i].price * 100) 13 | results.push(array[i].dt.substring(0, 5)) 14 | average.push(array[i].amount / array[i].volume * 100) 15 | if (i > 0) { 16 | volume.push(Math.abs(array[i].volume - array[i - 1].volume) * 100) 17 | } else { 18 | volume.push(Math.abs(array[i].volume) * 100) 19 | } 20 | 21 | } 22 | return { 23 | data: item, 24 | date: results, 25 | average: average, 26 | volume: volume, 27 | min: message.min * 100, 28 | max: message.max * 100, 29 | closeDaily: message.close * 100 30 | } 31 | }, 32 | parserDataWeek(message) { 33 | var array = message.shares 34 | var item = [] 35 | var results = [] 36 | var average = [] 37 | var volume = [] 38 | for (var i = 0; i < array.length; i++) { 39 | item.push(array[i].price * 100) 40 | results.push(dayjs(array[i].dt).format('M-D')) 41 | average.push(array[i].amount / array[i].volume * 100) 42 | if (i > 0) { 43 | if (Math.abs(array[i].volume - array[i - 1].volume) * 100 < Math.abs(array[1].volume) * 1000) { 44 | volume.push(Math.abs(array[i].volume - array[i - 1].volume) * 100) 45 | } 46 | } else { 47 | volume.push(Math.abs(array[i].volume) * 100) 48 | } 49 | } 50 | return { 51 | data: item, 52 | date: results, 53 | average: average, 54 | volume: volume, 55 | min: message.min * 100, 56 | max: message.max * 100, 57 | closeDaily: message.close * 100 58 | } 59 | }, 60 | setOption(data) { 61 | let option = { 62 | tooltip: { 63 | // triggerOn: 'none', 64 | // transitionDuration: 0, 65 | // confine: true, 66 | // bordeRadius: 4, 67 | // borderWidth: 1, 68 | // borderColor: '#333', 69 | // backgroundColor: 'rgba(255,255,255,0.9)', 70 | // textStyle: { 71 | // fontSize: 12, 72 | // color: '#333' 73 | // } 74 | show: true, 75 | trigger: "axis", 76 | formatter: function (params) { 77 | var res; 78 | if (params.length !== 1) { 79 | res = dayjs().format("YYYY-MM-DD"); 80 | res += "
时间 : " + params[0].axisValue; 81 | res += "
价格 : " + params[0].value / 100; 82 | // res += "
涨跌 : " + params[0].value[2]; 83 | // res += "
涨跌幅 : " + params[0].value[3] + "%"; 84 | res += "
成交量 : " + params[2].value / 100 + '手'; 85 | } else { 86 | res = dayjs().format("YYYY-MM-DD"); 87 | res += "
时间 : " + "--"; 88 | res += "
价格 : " + "--"; 89 | // res += "
涨跌 : " + "--"; 90 | // res += "
涨跌幅 : " + "--"; 91 | res += "
成交量 : " + '--'; 92 | } 93 | return res; 94 | } 95 | }, 96 | axisPointer: { 97 | link: [{ 98 | xAxisIndex: [0, 1] 99 | }] 100 | }, 101 | dataZoom: [{ 102 | type: 'inside', 103 | xAxisIndex: [0, 1], 104 | start: 0, 105 | end: 100, 106 | height: 20 107 | }], 108 | xAxis: [{ 109 | type: 'category', 110 | data: data.date, 111 | boundaryGap: false, 112 | axisLine: { 113 | lineStyle: { 114 | color: '#777' 115 | } 116 | }, 117 | // axisLabel: { 118 | // formatter: function (value) { 119 | // return echarts.format.formatTime('HH:mm', value); 120 | // } 121 | // }, 122 | min: 'dataMin', 123 | max: 'dataMax', 124 | axisPointer: { 125 | show: true 126 | } 127 | }, { 128 | type: 'category', 129 | gridIndex: 1, 130 | data: data.date, 131 | scale: true, 132 | boundaryGap: false, 133 | splitLine: { 134 | show: false 135 | }, 136 | axisLabel: { 137 | show: false 138 | }, 139 | axisTick: { 140 | show: false 141 | }, 142 | axisLine: { 143 | lineStyle: { 144 | color: '#777' 145 | } 146 | }, 147 | splitNumber: 20, 148 | min: 'dataMin', 149 | max: 'dataMax' 150 | }], 151 | yAxis: [{ 152 | scale: true, 153 | splitNumber: 2, 154 | axisLine: { 155 | lineStyle: { 156 | color: '#777' 157 | } 158 | }, 159 | splitLine: { 160 | show: true 161 | }, 162 | axisTick: { 163 | show: false 164 | }, 165 | axisLabel: { 166 | inside: true, 167 | formatter: '{value}\n' 168 | } 169 | }, { 170 | scale: true, 171 | gridIndex: 1, 172 | splitNumber: 2, 173 | axisLabel: { 174 | show: false 175 | }, 176 | axisLine: { 177 | show: false 178 | }, 179 | axisTick: { 180 | show: false 181 | }, 182 | splitLine: { 183 | show: false 184 | } 185 | }], 186 | grid: [{ 187 | left: 20, 188 | right: 20, 189 | top: 10, 190 | height: 120 191 | }, { 192 | left: 20, 193 | right: 20, 194 | height: 40, 195 | top: 160 196 | }], 197 | graphic: [{ 198 | type: 'group', 199 | left: 'center', 200 | top: 70, 201 | width: 200, 202 | bounding: 'raw', 203 | }], 204 | series: [{ 205 | name: 'Volume', 206 | type: 'bar', 207 | xAxisIndex: 1, 208 | yAxisIndex: 1, 209 | itemStyle: { 210 | normal: { 211 | color: function (params) { 212 | var colorList; 213 | if (data.volume[0] > data.closeDaily) { 214 | colorList = '#14b143'; 215 | } else { 216 | colorList = '#ef232a'; 217 | } 218 | if (data.volume[params.dataIndex - 1] > data.volume[params.dataIndex]) { 219 | colorList = '#14b143'; 220 | } else { 221 | colorList = '#ef232a'; 222 | } 223 | return colorList; 224 | } 225 | }, 226 | emphasis: { 227 | color: '#140' 228 | } 229 | }, 230 | data: data.volume 231 | }, { 232 | name: 'price', 233 | type: 'line', 234 | smooth: true, 235 | symbol: 'none', 236 | sampling: 'average', 237 | itemStyle: { 238 | normal: { 239 | color: '#3b98d3' 240 | } 241 | }, 242 | areaStyle: { 243 | normal: { 244 | color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{ 245 | offset: 0, 246 | color: '#d5e1f2' 247 | }, { 248 | offset: 1, 249 | color: '#3b98d3' 250 | }]) 251 | } 252 | }, 253 | lineStyle: { 254 | normal: { 255 | width: 1 256 | } 257 | }, 258 | data: data.data 259 | }, { 260 | name: 'average', 261 | data: data.average, 262 | type: 'line', 263 | smooth: true, 264 | color: '#F4A460', 265 | lineStyle: { 266 | normal: { 267 | width: 1 268 | } 269 | }, 270 | symbol: 'none' 271 | }] 272 | } 273 | return option 274 | } 275 | } 276 | -------------------------------------------------------------------------------- /src/utils/util.js: -------------------------------------------------------------------------------- 1 | export default { 2 | nowDate(format) { 3 | let date = Date.now(); 4 | 5 | function add0(m) { 6 | return m < 10 ? '0' + m : m 7 | } 8 | let time = new Date(parseInt(date)); 9 | let y = time.getFullYear(); 10 | let m = time.getMonth() + 1; 11 | let d = time.getDate(); 12 | let h = time.getHours(); 13 | let mm = time.getMinutes(); 14 | let s = time.getSeconds(); 15 | if (format == 'yyyy-mm-dd hh:mm:ss') { 16 | return y + '-' + add0(m) + '-' + add0(d) + ' ' + add0(h) + ':' + add0(mm) + ':' + add0(s); 17 | } else { 18 | return y + '-' + add0(m) + '-' + add0(d); 19 | } 20 | }, 21 | formatVolumn(val) { 22 | if (val) {} else { 23 | val = 0 24 | } 25 | if (val < 100000) { 26 | return val.toFixed(0) 27 | } else if (val >= 100000 && val < 1000000) { 28 | val = val / 10000; 29 | return val.toFixed(2) + '万' 30 | } else if (val >= 1000000 && val < 10000000) { 31 | val = val / 10000; 32 | return val.toFixed(1) + '万' 33 | } else if (val >= 10000000 && val < 100000000) { 34 | val = val / 10000; 35 | return val.toFixed(0) + '万' 36 | } else { 37 | val = val / 100000000; 38 | return val.toFixed(2) + '亿' 39 | } 40 | }, 41 | formatSyl(val) { 42 | if (val) {} else { 43 | val = 0 44 | } 45 | if (val == 0) { 46 | return '--' 47 | } else { 48 | return val.toFixed(2) 49 | } 50 | }, 51 | //取整 52 | formatVol(val) { 53 | if (val == 0) { 54 | return '--' 55 | } else { 56 | return val.toFixed(0) 57 | } 58 | }, 59 | } 60 | -------------------------------------------------------------------------------- /src/vuex/store.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Vuex from 'vuex' 3 | 4 | Vue.use(Vuex) 5 | 6 | const store = new Vuex.Store({ 7 | state: { 8 | stock: { 9 | code: '', 10 | name: '' 11 | }, 12 | news: { 13 | title: '', 14 | auth: '', 15 | date: '', 16 | detail: '' 17 | } 18 | }, 19 | mutations: { 20 | stockDetail(state, stock) { 21 | state.stock.code = stock.code.split('.')[0]; 22 | state.stock.name = stock.name; 23 | setStorage('code', stock.code.split('.')[0]) 24 | setStorage('name', stock.name) 25 | }, 26 | newsDetail(state, news, type) { 27 | state.news.title = news.title; 28 | state.news.auth = news.auth; 29 | state.news.date = news.dt; 30 | state.news.detail = news.sum; 31 | state.news.url = news.url; 32 | state.news.type = type; 33 | } 34 | } 35 | }) 36 | 37 | function setStorage(key, value) { 38 | if (window) { 39 | window.localStorage.setItem(key, value) 40 | } else { 41 | wx.setStorage({ 42 | key: key, 43 | data: value 44 | }) 45 | } 46 | } 47 | export default store 48 | -------------------------------------------------------------------------------- /static/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zz570557024/vue-mpvue-ChatRobot/1108b31ded4414f58e368356d8d1f5692ae84540/static/.gitkeep -------------------------------------------------------------------------------- /static/ec-canvas/ec-canvas.js: -------------------------------------------------------------------------------- 1 | import WxCanvas from './wx-canvas'; 2 | import * as echarts from './echarts'; 3 | 4 | let ctx; 5 | 6 | Component({ 7 | properties: { 8 | canvasId: { 9 | type: String, 10 | value: 'ec-canvas' 11 | }, 12 | 13 | ec: { 14 | type: Object 15 | } 16 | }, 17 | 18 | data: { 19 | 20 | }, 21 | 22 | ready: function () { 23 | // 异步获取 24 | setTimeout(() => { 25 | if (!this.data.ec) { 26 | console.warn('组件需绑定 ec 变量,例:'); 28 | return; 29 | } 30 | 31 | if (!this.data.ec.lazyLoad) { 32 | this.init(); 33 | } 34 | }, 10) 35 | }, 36 | 37 | methods: { 38 | init: function (callback) { 39 | const version = wx.version.version.split('.').map(n => parseInt(n, 10)); 40 | const isValid = version[0] > 1 || (version[0] === 1 && version[1] > 9) || 41 | (version[0] === 1 && version[1] === 9 && version[2] >= 91); 42 | if (!isValid) { 43 | console.error('微信基础库版本过低,需大于等于 1.9.91。' + 44 | '参见:https://github.com/ecomfe/echarts-for-weixin' + 45 | '#%E5%BE%AE%E4%BF%A1%E7%89%88%E6%9C%AC%E8%A6%81%E6%B1%82'); 46 | return; 47 | } 48 | 49 | ctx = wx.createCanvasContext(this.data.canvasId, this); 50 | 51 | const canvas = new WxCanvas(ctx, this.data.canvasId); 52 | 53 | echarts.setCanvasCreator(() => { 54 | return canvas; 55 | }); 56 | 57 | var query = wx.createSelectorQuery().in(this); 58 | query.select('.ec-canvas').boundingClientRect(res => { 59 | if (typeof callback === 'function') { 60 | this.chart = callback(canvas, res.width, res.height); 61 | } else if (this.data.ec && this.data.ec.onInit) { 62 | this.chart = this.data.ec.onInit(canvas, res.width, res.height); 63 | } else if (this.data.ec && this.data.ec.options) { 64 | // 添加接收 options 传参 65 | const ec = this.data.ec 66 | 67 | function initChart(canvas, width, height) { 68 | const chart = echarts.init(canvas, null, { 69 | width: width, 70 | height: height 71 | }); 72 | canvas.setChart(chart); 73 | chart.setOption(ec.options); 74 | return chart; 75 | } 76 | this.chart = initChart(canvas, res.width, res.height); 77 | } 78 | }).exec(); 79 | }, 80 | 81 | canvasToTempFilePath(opt) { 82 | if (!opt.canvasId) { 83 | opt.canvasId = this.data.canvasId; 84 | } 85 | 86 | ctx.draw(true, () => { 87 | wx.canvasToTempFilePath(opt, this); 88 | }); 89 | }, 90 | 91 | touchStart(e) { 92 | if (this.chart && e.touches.length > 0) { 93 | var touch = e.touches[0]; 94 | this.chart._zr.handler.dispatch('mousedown', { 95 | zrX: touch.x, 96 | zrY: touch.y 97 | }); 98 | this.chart._zr.handler.dispatch('mousemove', { 99 | zrX: touch.x, 100 | zrY: touch.y 101 | }); 102 | } 103 | }, 104 | 105 | touchMove(e) { 106 | if (this.chart && e.touches.length > 0) { 107 | var touch = e.touches[0]; 108 | this.chart._zr.handler.dispatch('mousemove', { 109 | zrX: touch.x, 110 | zrY: touch.y 111 | }); 112 | } 113 | }, 114 | 115 | touchEnd(e) { 116 | if (this.chart) { 117 | const touch = e.changedTouches ? e.changedTouches[0] : {}; 118 | this.chart._zr.handler.dispatch('mouseup', { 119 | zrX: touch.x, 120 | zrY: touch.y 121 | }); 122 | this.chart._zr.handler.dispatch('click', { 123 | zrX: touch.x, 124 | zrY: touch.y 125 | }); 126 | } 127 | } 128 | } 129 | }); 130 | -------------------------------------------------------------------------------- /static/ec-canvas/ec-canvas.json: -------------------------------------------------------------------------------- 1 | { 2 | "component": true, 3 | "usingComponents": {} 4 | } -------------------------------------------------------------------------------- /static/ec-canvas/ec-canvas.wxml: -------------------------------------------------------------------------------- 1 | 4 | 5 | -------------------------------------------------------------------------------- /static/ec-canvas/ec-canvas.wxss: -------------------------------------------------------------------------------- 1 | .ec-canvas { 2 | width: 100%; 3 | height: 100%; 4 | } 5 | -------------------------------------------------------------------------------- /static/ec-canvas/wx-canvas.js: -------------------------------------------------------------------------------- 1 | export default class WxCanvas { 2 | constructor(ctx, canvasId) { 3 | this.ctx = ctx; 4 | this.canvasId = canvasId; 5 | this.chart = null; 6 | 7 | // this._initCanvas(zrender, ctx); 8 | this._initStyle(ctx); 9 | this._initEvent(); 10 | } 11 | 12 | getContext(contextType) { 13 | if (contextType === '2d') { 14 | return this.ctx; 15 | } 16 | } 17 | 18 | // canvasToTempFilePath(opt) { 19 | // if (!opt.canvasId) { 20 | // opt.canvasId = this.canvasId; 21 | // } 22 | 23 | // return wx.canvasToTempFilePath(opt, this); 24 | // } 25 | 26 | setChart(chart) { 27 | this.chart = chart; 28 | } 29 | 30 | attachEvent () { 31 | // noop 32 | } 33 | 34 | detachEvent() { 35 | // noop 36 | } 37 | 38 | _initCanvas(zrender, ctx) { 39 | zrender.util.getContext = function () { 40 | return ctx; 41 | }; 42 | 43 | zrender.util.$override('measureText', function (text, font) { 44 | ctx.font = font || '12px sans-serif'; 45 | return ctx.measureText(text); 46 | }); 47 | } 48 | 49 | _initStyle(ctx) { 50 | var styles = ['fillStyle', 'strokeStyle', 'globalAlpha', 51 | 'textAlign', 'textBaseAlign', 'shadow', 'lineWidth', 52 | 'lineCap', 'lineJoin', 'lineDash', 'miterLimit', 'fontSize']; 53 | 54 | styles.forEach(style => { 55 | Object.defineProperty(ctx, style, { 56 | set: value => { 57 | if (style !== 'fillStyle' && style !== 'strokeStyle' 58 | || value !== 'none' && value !== null 59 | ) { 60 | ctx['set' + style.charAt(0).toUpperCase() + style.slice(1)](value); 61 | } 62 | } 63 | }); 64 | }); 65 | 66 | ctx.createRadialGradient = () => { 67 | return ctx.createCircularGradient(arguments); 68 | }; 69 | } 70 | 71 | _initEvent() { 72 | this.event = {}; 73 | const eventNames = [{ 74 | wxName: 'touchStart', 75 | ecName: 'mousedown' 76 | }, { 77 | wxName: 'touchMove', 78 | ecName: 'mousemove' 79 | }, { 80 | wxName: 'touchEnd', 81 | ecName: 'mouseup' 82 | }, { 83 | wxName: 'touchEnd', 84 | ecName: 'click' 85 | }]; 86 | 87 | eventNames.forEach(name => { 88 | this.event[name.wxName] = e => { 89 | const touch = e.touches[0]; 90 | this.chart._zr.handler.dispatch(name.ecName, { 91 | zrX: name.wxName === 'tap' ? touch.clientX : touch.x, 92 | zrY: name.wxName === 'tap' ? touch.clientY : touch.y 93 | }); 94 | }; 95 | }); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /static/res/img/head_2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zz570557024/vue-mpvue-ChatRobot/1108b31ded4414f58e368356d8d1f5692ae84540/static/res/img/head_2.jpg -------------------------------------------------------------------------------- /static/res/img/xiaogua.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zz570557024/vue-mpvue-ChatRobot/1108b31ded4414f58e368356d8d1f5692ae84540/static/res/img/xiaogua.jpg -------------------------------------------------------------------------------- /static/res/readmeImg/demo_web.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zz570557024/vue-mpvue-ChatRobot/1108b31ded4414f58e368356d8d1f5692ae84540/static/res/readmeImg/demo_web.gif -------------------------------------------------------------------------------- /static/res/readmeImg/demo_wx.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zz570557024/vue-mpvue-ChatRobot/1108b31ded4414f58e368356d8d1f5692ae84540/static/res/readmeImg/demo_wx.gif --------------------------------------------------------------------------------