├── vue.config.js ├── cypress.json ├── screenshot ├── 1.png ├── 2.png └── 3.png ├── babel.config.js ├── public ├── chartfun.png ├── favicon.ico └── index.html ├── src ├── assets │ ├── img │ │ ├── bg.png │ │ ├── profile.jpg │ │ ├── borders │ │ │ ├── 1.png │ │ │ ├── 2.png │ │ │ └── 3.png │ │ └── charts │ │ │ ├── bar.png │ │ │ ├── line.png │ │ │ ├── pie.png │ │ │ ├── ring.png │ │ │ ├── text.png │ │ │ ├── border.png │ │ │ ├── candle.png │ │ │ ├── funnel.png │ │ │ ├── gauge.png │ │ │ ├── image.png │ │ │ ├── radar.png │ │ │ ├── sankey.png │ │ │ ├── histogram.png │ │ │ ├── map-china.png │ │ │ ├── map-world.png │ │ │ ├── scatter.png │ │ │ ├── wordcloud.png │ │ │ └── liquidfill.png │ └── font │ │ ├── iconfont.eot │ │ ├── iconfont.ttf │ │ ├── iconfont.woff │ │ ├── iconfont.woff2 │ │ ├── iconfont.css │ │ └── iconfont.svg ├── store.js ├── App.vue ├── components │ ├── Console │ │ ├── Index.vue │ │ ├── PageHeader.vue │ │ └── NavMenu.vue │ └── Editor │ │ ├── Topbar.vue │ │ ├── ScaleBar.vue │ │ ├── Toolbar.vue │ │ ├── Index.vue │ │ ├── SidePanel.vue │ │ └── Config.vue ├── main.js ├── http.js ├── router.js └── views │ ├── Console │ ├── Data.vue │ ├── DataAdd.vue │ └── Chart.vue │ ├── Index.vue │ ├── Viewer │ └── Canvas.vue │ └── Editor │ └── Canvas.vue ├── .editorconfig ├── tests └── e2e │ ├── .eslintrc.js │ ├── specs │ └── test.js │ ├── support │ ├── index.js │ └── commands.js │ └── plugins │ └── index.js ├── mock ├── index.js └── data │ ├── charts.json │ └── charts-1234.json ├── server ├── models │ ├── user.js │ ├── connect.js │ └── chart.js ├── test.http ├── routes │ ├── api.js │ ├── demo.js │ ├── user.js │ ├── connect.js │ └── chart.js └── app.js ├── .gitignore ├── LICENSE ├── .circleci └── config.yml ├── README.md └── package.json /vue.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | publicPath: './', 3 | }; 4 | -------------------------------------------------------------------------------- /cypress.json: -------------------------------------------------------------------------------- 1 | { 2 | "pluginsFile": "tests/e2e/plugins/index.js" 3 | } 4 | -------------------------------------------------------------------------------- /screenshot/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ddiu8081/ChartFun/HEAD/screenshot/1.png -------------------------------------------------------------------------------- /screenshot/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ddiu8081/ChartFun/HEAD/screenshot/2.png -------------------------------------------------------------------------------- /screenshot/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ddiu8081/ChartFun/HEAD/screenshot/3.png -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | '@vue/app', 4 | ], 5 | }; 6 | -------------------------------------------------------------------------------- /public/chartfun.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ddiu8081/ChartFun/HEAD/public/chartfun.png -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ddiu8081/ChartFun/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /src/assets/img/bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ddiu8081/ChartFun/HEAD/src/assets/img/bg.png -------------------------------------------------------------------------------- /src/assets/img/profile.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ddiu8081/ChartFun/HEAD/src/assets/img/profile.jpg -------------------------------------------------------------------------------- /src/assets/font/iconfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ddiu8081/ChartFun/HEAD/src/assets/font/iconfont.eot -------------------------------------------------------------------------------- /src/assets/font/iconfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ddiu8081/ChartFun/HEAD/src/assets/font/iconfont.ttf -------------------------------------------------------------------------------- /src/assets/font/iconfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ddiu8081/ChartFun/HEAD/src/assets/font/iconfont.woff -------------------------------------------------------------------------------- /src/assets/font/iconfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ddiu8081/ChartFun/HEAD/src/assets/font/iconfont.woff2 -------------------------------------------------------------------------------- /src/assets/img/borders/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ddiu8081/ChartFun/HEAD/src/assets/img/borders/1.png -------------------------------------------------------------------------------- /src/assets/img/borders/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ddiu8081/ChartFun/HEAD/src/assets/img/borders/2.png -------------------------------------------------------------------------------- /src/assets/img/borders/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ddiu8081/ChartFun/HEAD/src/assets/img/borders/3.png -------------------------------------------------------------------------------- /src/assets/img/charts/bar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ddiu8081/ChartFun/HEAD/src/assets/img/charts/bar.png -------------------------------------------------------------------------------- /src/assets/img/charts/line.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ddiu8081/ChartFun/HEAD/src/assets/img/charts/line.png -------------------------------------------------------------------------------- /src/assets/img/charts/pie.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ddiu8081/ChartFun/HEAD/src/assets/img/charts/pie.png -------------------------------------------------------------------------------- /src/assets/img/charts/ring.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ddiu8081/ChartFun/HEAD/src/assets/img/charts/ring.png -------------------------------------------------------------------------------- /src/assets/img/charts/text.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ddiu8081/ChartFun/HEAD/src/assets/img/charts/text.png -------------------------------------------------------------------------------- /src/assets/img/charts/border.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ddiu8081/ChartFun/HEAD/src/assets/img/charts/border.png -------------------------------------------------------------------------------- /src/assets/img/charts/candle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ddiu8081/ChartFun/HEAD/src/assets/img/charts/candle.png -------------------------------------------------------------------------------- /src/assets/img/charts/funnel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ddiu8081/ChartFun/HEAD/src/assets/img/charts/funnel.png -------------------------------------------------------------------------------- /src/assets/img/charts/gauge.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ddiu8081/ChartFun/HEAD/src/assets/img/charts/gauge.png -------------------------------------------------------------------------------- /src/assets/img/charts/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ddiu8081/ChartFun/HEAD/src/assets/img/charts/image.png -------------------------------------------------------------------------------- /src/assets/img/charts/radar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ddiu8081/ChartFun/HEAD/src/assets/img/charts/radar.png -------------------------------------------------------------------------------- /src/assets/img/charts/sankey.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ddiu8081/ChartFun/HEAD/src/assets/img/charts/sankey.png -------------------------------------------------------------------------------- /src/assets/img/charts/histogram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ddiu8081/ChartFun/HEAD/src/assets/img/charts/histogram.png -------------------------------------------------------------------------------- /src/assets/img/charts/map-china.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ddiu8081/ChartFun/HEAD/src/assets/img/charts/map-china.png -------------------------------------------------------------------------------- /src/assets/img/charts/map-world.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ddiu8081/ChartFun/HEAD/src/assets/img/charts/map-world.png -------------------------------------------------------------------------------- /src/assets/img/charts/scatter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ddiu8081/ChartFun/HEAD/src/assets/img/charts/scatter.png -------------------------------------------------------------------------------- /src/assets/img/charts/wordcloud.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ddiu8081/ChartFun/HEAD/src/assets/img/charts/wordcloud.png -------------------------------------------------------------------------------- /src/assets/img/charts/liquidfill.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ddiu8081/ChartFun/HEAD/src/assets/img/charts/liquidfill.png -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | [*.{js,jsx,ts,tsx,vue}] 2 | indent_style = space 3 | indent_size = 2 4 | end_of_line = lf 5 | trim_trailing_whitespace = true 6 | insert_final_newline = true 7 | max_line_length = 100 8 | -------------------------------------------------------------------------------- /tests/e2e/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: [ 3 | 'cypress', 4 | ], 5 | env: { 6 | mocha: true, 7 | 'cypress/globals': true, 8 | }, 9 | rules: { 10 | strict: 'off', 11 | }, 12 | }; 13 | -------------------------------------------------------------------------------- /src/store.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import Vuex from 'vuex'; 3 | 4 | Vue.use(Vuex); 5 | 6 | export default new Vuex.Store({ 7 | state: { 8 | 9 | }, 10 | mutations: { 11 | 12 | }, 13 | actions: { 14 | 15 | }, 16 | }); 17 | -------------------------------------------------------------------------------- /mock/index.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable global-require */ 2 | import Mock from 'mockjs'; 3 | 4 | export default function () { 5 | Mock.mock(/\/chart$/, require('./data/charts.json')); 6 | Mock.mock(/\/chart\/1234/, require('./data/charts-1234.json')); 7 | } 8 | -------------------------------------------------------------------------------- /tests/e2e/specs/test.js: -------------------------------------------------------------------------------- 1 | // https://docs.cypress.io/api/introduction/api.html 2 | 3 | describe('My First Test', () => { 4 | it('Visits the app root url', () => { 5 | cy.visit('/'); 6 | cy.contains('h1', 'Welcome to Your Vue.js App'); 7 | }); 8 | }); 9 | -------------------------------------------------------------------------------- /server/models/user.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | const mongoose = require('mongoose'); 3 | const Schema = mongoose.Schema; 4 | 5 | const userSchema = new Schema({ 6 | username: String, 7 | password: String 8 | }, { timestamps: true }); 9 | 10 | module.exports = mongoose.model('user', userSchema); 11 | -------------------------------------------------------------------------------- /server/models/connect.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | const mongoose = require('mongoose'); 3 | const Schema = mongoose.Schema; 4 | 5 | const connectSchema = new Schema({ 6 | name: String, 7 | uid: String, 8 | data: Object 9 | }, { timestamps: true }); 10 | 11 | module.exports = mongoose.model('connect', connectSchema); 12 | -------------------------------------------------------------------------------- /server/test.http: -------------------------------------------------------------------------------- 1 | GET http://localhost:3000/chart 2 | 3 | ### 4 | POST http://localhost:3000/chart 5 | Content-Type: application/json 6 | 7 | { 8 | "title": "金融大数据", 9 | "user": "1234" 10 | } 11 | 12 | ### 13 | 14 | GET http://localhost:3000/demo/percent 15 | 16 | ### 17 | 18 | GET http://localhost:3000/demo/pv 19 | -------------------------------------------------------------------------------- /server/models/chart.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | const mongoose = require('mongoose'); 3 | const Schema = mongoose.Schema; 4 | 5 | const userSchema = new Schema({ 6 | title: String, 7 | img: String, 8 | uid: String, 9 | view: Number, 10 | chartData: Object 11 | }, { timestamps: true }); 12 | 13 | module.exports = mongoose.model('chart', userSchema); 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | 5 | /tests/e2e/videos/ 6 | /tests/e2e/screenshots/ 7 | 8 | # local env files 9 | .env.local 10 | .env.*.local 11 | 12 | # Log files 13 | npm-debug.log* 14 | yarn-debug.log* 15 | yarn-error.log* 16 | 17 | # Editor directories and files 18 | .idea 19 | .vscode 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw* 25 | 26 | # ChartFun uploads 27 | /server/public/upload/* 28 | -------------------------------------------------------------------------------- /server/routes/api.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | const Router = require('koa-router'); 3 | const path = require('path'); 4 | 5 | const router = new Router(); 6 | 7 | router.prefix('/api') 8 | 9 | router.post('/uploadfile', async (ctx, next) => { 10 | const file = ctx.request.files.file; // 获取上传文件 11 | const newFileName = file.path.split('/').pop(); // 取新的文件名 12 | return ctx.body = { 13 | success: true, 14 | url: `http://localhost:3000/upload/${newFileName}`, 15 | }; 16 | }); 17 | 18 | module.exports = router; 19 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | chartfun 9 | 10 | 11 | 14 |
15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 30 | -------------------------------------------------------------------------------- /tests/e2e/support/index.js: -------------------------------------------------------------------------------- 1 | // *********************************************************** 2 | // This example support/index.js is processed and 3 | // loaded automatically before your test files. 4 | // 5 | // This is a great place to put global configuration and 6 | // behavior that modifies Cypress. 7 | // 8 | // You can change the location of this file or turn off 9 | // automatically serving support files with the 10 | // 'supportFile' configuration option. 11 | // 12 | // You can read more here: 13 | // https://on.cypress.io/configuration 14 | // *********************************************************** 15 | 16 | // Import commands.js using ES2015 syntax: 17 | import './commands'; 18 | 19 | // Alternatively you can use CommonJS syntax: 20 | // require('./commands') 21 | -------------------------------------------------------------------------------- /tests/e2e/support/commands.js: -------------------------------------------------------------------------------- 1 | // *********************************************** 2 | // This example commands.js shows you how to 3 | // create various custom commands and overwrite 4 | // existing commands. 5 | // 6 | // For more comprehensive examples of custom 7 | // commands please read more here: 8 | // https://on.cypress.io/custom-commands 9 | // *********************************************** 10 | // 11 | // 12 | // -- This is a parent command -- 13 | // Cypress.Commands.add("login", (email, password) => { ... }) 14 | // 15 | // 16 | // -- This is a child command -- 17 | // Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... }) 18 | // 19 | // 20 | // -- This is a dual command -- 21 | // Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... }) 22 | // 23 | // 24 | // -- This is will overwrite an existing command -- 25 | // Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... }) 26 | -------------------------------------------------------------------------------- /src/components/Console/Index.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 31 | 32 | 47 | -------------------------------------------------------------------------------- /tests/e2e/plugins/index.js: -------------------------------------------------------------------------------- 1 | // https://docs.cypress.io/guides/guides/plugins-guide.html 2 | 3 | // if you need a custom webpack configuration you can uncomment the following import 4 | // and then use the `file:preprocessor` event 5 | // as explained in the cypress docs 6 | // https://docs.cypress.io/api/plugins/preprocessors-api.html#Examples 7 | 8 | /* eslint-disable import/no-extraneous-dependencies, global-require, arrow-body-style */ 9 | // const webpack = require('@cypress/webpack-preprocessor') 10 | 11 | module.exports = (on, config) => { 12 | // on('file:preprocessor', webpack({ 13 | // webpackOptions: require('@vue/cli-service/webpack.config'), 14 | // watchOptions: {} 15 | // })) 16 | 17 | return Object.assign({}, config, { 18 | fixturesFolder: 'tests/e2e/fixtures', 19 | integrationFolder: 'tests/e2e/specs', 20 | screenshotsFolder: 'tests/e2e/screenshots', 21 | videosFolder: 'tests/e2e/videos', 22 | supportFile: 'tests/e2e/support/index.js', 23 | }); 24 | }; 25 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import VueDraggableResizable from 'vue-draggable-resizable'; 3 | import VueDragResize from 'vue-drag-resize'; 4 | import 'vue-draggable-resizable/dist/VueDraggableResizable.css'; 5 | import VCharts from 'v-charts'; 6 | import ElementUI from 'element-ui'; 7 | import 'element-ui/lib/theme-chalk/index.css'; 8 | // import './assets/font/iconfont.css'; 9 | import dayjs from 'dayjs'; 10 | import App from './App.vue'; 11 | import router from './router'; 12 | import store from './store'; 13 | import http from './http'; 14 | // import initMock from '../mock'; 15 | 16 | // 需要 mock 数据时请打开注释 17 | // initMock(); 18 | 19 | Vue.config.productionTip = false; 20 | Vue.prototype.$http = http; 21 | Vue.prototype.$dayjs = dayjs; 22 | Vue.use(VCharts); 23 | Vue.use(ElementUI); 24 | Vue.use(dayjs); 25 | Vue.component('vue-draggable-resizable', VueDraggableResizable); 26 | Vue.component('vue-drag-resize', VueDragResize); 27 | 28 | new Vue({ 29 | router, 30 | store, 31 | render: h => h(App), 32 | }).$mount('#app'); 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Diu 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /server/routes/demo.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | const Router = require('koa-router'); 3 | const chartModel = require('../models/chart'); 4 | 5 | const router = new Router(); 6 | router.prefix('/demo'); 7 | 8 | // 返回一个随机 9 | router.get('/percent', async (ctx, next) => { 10 | ctx.body = { 11 | columns: ['city', 'percent'], 12 | rows: [ 13 | { 14 | city: '南京', 15 | percent: parseFloat(Math.random().toFixed(2)) 16 | } 17 | ] 18 | } 19 | }); 20 | 21 | router.get('/pv', async (ctx, next) => { 22 | ctx.body = { 23 | columns: ['date', 'pv'], 24 | rows: [ 25 | { 26 | date: '1/3', 27 | pv: Math.round(Math.random()*800) + 300, 28 | }, { 29 | date: '1/4', 30 | pv: Math.round(Math.random()*800) + 300, 31 | }, { 32 | date: '1/5', 33 | pv: Math.round(Math.random()*800) + 300, 34 | }, { 35 | date: '1/6', 36 | pv: Math.round(Math.random()*800) + 300, 37 | }, { 38 | date: '1/7', 39 | pv: Math.round(Math.random()*800) + 500, 40 | }, { 41 | date: '1/8', 42 | pv: Math.round(Math.random()*800) + 400, 43 | } 44 | ] 45 | } 46 | }); 47 | 48 | module.exports = router; 49 | -------------------------------------------------------------------------------- /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2.1 2 | executors: 3 | node: 4 | docker: 5 | - image: circleci/node:lts 6 | jobs: 7 | build: 8 | executor: node 9 | working_directory: ~/repo 10 | steps: 11 | - checkout 12 | - restore_cache: 13 | keys: 14 | - v1-dependencies-{{ checksum "package.json" }} 15 | - v1-dependencies- 16 | - run: 17 | name: Install Dependence 18 | command: npm install 19 | - save_cache: 20 | paths: 21 | - node_modules 22 | key: v1-dependencies-{{ checksum "package.json" }} 23 | - run: 24 | name: Build 25 | command: | 26 | npm run build 27 | - run: 28 | name: Deploy to gh-pages 29 | command: | 30 | git config --global user.email ${GH_EMAIL} 31 | git config --global user.name ${GH_NAME} 32 | rm -rf node_modules/gh-pages/.cache 33 | npx gh-pages -d dist -r https://github.com/ddiu8081/ChartFun.git -b gh-pages -m "auto commit ${CIRCLE_SHA1} [ci skip]" 34 | workflows: 35 | version: 2 36 | build_and_deploy: 37 | jobs: 38 | - build: 39 | filters: 40 | branches: 41 | only: master -------------------------------------------------------------------------------- /src/http.js: -------------------------------------------------------------------------------- 1 | /** 2 | * axios封装 3 | * 请求拦截、响应拦截、错误统一处理 4 | */ 5 | import axios from 'axios'; 6 | import router from './router'; 7 | import store from './store'; 8 | 9 | /** 10 | * 跳转登录页 11 | * 携带当前页面路由,以期在登录页面完成登录后返回当前页面 12 | */ 13 | const toLogin = () => { 14 | router.replace({ 15 | path: '/', 16 | query: { 17 | redirect: router.currentRoute.fullPath, 18 | }, 19 | }); 20 | }; 21 | 22 | // 创建axios实例 23 | const instance = axios.create({ 24 | timeout: 1000 * 10, 25 | }); 26 | 27 | // 请求地址 28 | instance.defaults.baseURL = 'http://localhost:3000'; 29 | 30 | // // 设置post请求头 31 | // instance.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded'; 32 | 33 | // 请求拦截器 34 | instance.interceptors.request.use( 35 | (config) => { 36 | // 每次请求附加一个Ticket 37 | const {ticket} = store.state; 38 | // if (ticket) { 39 | // config.params.ticket = ticket; 40 | // } 41 | // ticket && (config.headers.Authorization = ticket); 42 | return config; 43 | }, 44 | error => Promise.error(error), 45 | ); 46 | 47 | // 响应拦截器 48 | instance.interceptors.response.use( 49 | // 请求成功 50 | res => (res.status === 200 ? Promise.resolve(res) : Promise.reject(res)), 51 | // 请求失败 52 | error => Promise.reject(error.response), 53 | ); 54 | 55 | export default instance; 56 | -------------------------------------------------------------------------------- /server/routes/user.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | const Router = require('koa-router'); 3 | const userModel = require('../models/user'); 4 | 5 | const router = new Router(); 6 | router.prefix('/user'); 7 | 8 | router.post('/login', async (ctx, next) => { 9 | const body = ctx.request.body; 10 | const user = body.user; 11 | const pass = body.pass; 12 | 13 | const u = await userModel.findOne(). 14 | where('username').equals(user). 15 | where('password').equals(pass); 16 | if (u) { 17 | ctx.body = { 18 | errno: 0, 19 | data: { 20 | uid: u._id, 21 | name: u.username 22 | } 23 | } 24 | } else { 25 | ctx.body = { 26 | errno: 1, 27 | errmsg: '用户名或密码错误' 28 | } 29 | } 30 | }); 31 | 32 | router.post('/reg', async (ctx, next) => { 33 | const body = ctx.request.body; 34 | 35 | // 检查是否存在 36 | const row = await userModel.findOne(). 37 | where('username').equals(body.user); 38 | if (row) { 39 | ctx.body = { 40 | errno: 2, 41 | errmsg: '用户名已存在' 42 | } 43 | } else { 44 | const u = await userModel.create({ 45 | username: body.user, 46 | password: body.pass 47 | }); 48 | ctx.body = { 49 | errno: 0, 50 | data: { 51 | uid: u._id, 52 | name: u.username 53 | } 54 | } 55 | } 56 | }); 57 | 58 | module.exports = router; 59 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | > notice: 2019毕设作品,后续如无必要将不再做功能性更新。 2 | 3 | 4 | 5 | > Make data reports by dragging and dropping :) 6 | 7 | ## 特性 / Features 8 | 9 | * 通过 Excel 导入数据 10 | * 可视化画布 11 | * 图表、图片、文字、边框支持 12 | * 可拖拽和缩放的组件 13 | * 静态数据、GET接口支持 14 | * 生成公开链接 15 | 16 | ## 截图 / Screenshot 17 | 18 | ![image-1](./screenshot/1.png) 19 | 20 | ![image-2](./screenshot/2.png) 21 | 22 | ![image-3](./screenshot/3.png) 23 | 24 | ## 开发 / Develop 25 | 26 | ### 前端部分:Vue.js 27 | 28 | #### Project setup 29 | 30 | ``` 31 | npm install 32 | ``` 33 | 34 | #### Compiles and hot-reloads for development 35 | 36 | ``` 37 | npm run serve 38 | ``` 39 | 40 | #### Compiles and minifies for production 41 | 42 | ``` 43 | npm run build 44 | ``` 45 | 46 | ### 后端部分:Node.js + Koa + MongoDB 47 | 48 | 准备工作:配置并运行 MongoDB 数据库,新建一个空数据库并命名为`chartfun`。无需手动配置表结构,它们会被自动创建。 49 | 50 | #### Run web-service 51 | 52 | ``` 53 | node ./server/app.js 54 | ``` 55 | 56 | 57 | 58 | ## 鸣谢 / Thanks 59 | 60 | 本项目使用了 Vue.js 及以下第三方库: 61 | 62 | * [ElemeFE / element](https://github.com/ElemeFE/element) 63 | * [ElemeFE / v-charts](https://github.com/ElemeFE/v-charts) 64 | * [josdejong / jsoneditor](https://github.com/josdejong/jsoneditor) 65 | * [SortableJS / Vue.Draggable](https://github.com/SortableJS/Vue.Draggable) 66 | * [mauricius / vue-draggable-resizable](https://github.com/mauricius/vue-draggable-resizable) 67 | * [kirillmurashov / vue-drag-resize](https://github.com/kirillmurashov/vue-drag-resize) 68 | * [koajs / koa](https://github.com/koajs/koa) 69 | 70 | ## LICENSE 71 | 72 | MIT 73 | -------------------------------------------------------------------------------- /src/components/Editor/Topbar.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 27 | 28 | 82 | -------------------------------------------------------------------------------- /mock/data/charts.json: -------------------------------------------------------------------------------- 1 | { 2 | "errno": 0, 3 | "errmsg": "ok", 4 | "data": { 5 | "chartList": [ 6 | { 7 | "id": 1, 8 | "title": "某某电商可视化平台", 9 | "img": "https://image.ddiu.site/img/20190603211609.png", 10 | "timestamp": "1234123412" 11 | }, { 12 | "id": 2, 13 | "title": "某某金融大数据", 14 | "img": "https://image.ddiu.site/img/20190603221132.png", 15 | "timestamp": "1234123412" 16 | }, { 17 | "id": 3, 18 | "title": "金融数据概览", 19 | "img": "https://image.ddiu.site/img/20190603221218.png", 20 | "timestamp": "1234123412" 21 | }, { 22 | "id": 4, 23 | "title": "集团数据监控平台", 24 | "img": "https://image.ddiu.site/img/20190603221708.png", 25 | "timestamp": "1234123412" 26 | }, { 27 | "id": 5, 28 | "title": "校园无线大数据监控平台", 29 | "img": "https://image.ddiu.site/img/20190603221800.png", 30 | "timestamp": "1234123412" 31 | }, { 32 | "id": 6, 33 | "title": "大屏展示方案", 34 | "img": "https://image.ddiu.site/img/20190603221850.png", 35 | "timestamp": "1234123412" 36 | }, { 37 | "id": 7, 38 | "title": "我国大数据市场产值图", 39 | "img": "https://image.ddiu.site/img/20190603221316.png", 40 | "timestamp": "1234123412" 41 | }, { 42 | "id": 8, 43 | "title": "某某产品用户分布图", 44 | "img": "https://image.ddiu.site/img/20190603221455.png", 45 | "timestamp": "1234123412" 46 | }, { 47 | "id": 9, 48 | "title": "金融数据概览", 49 | "img": "https://image.ddiu.site/img/20190603221218.png", 50 | "timestamp": "1234123412" 51 | } 52 | ] 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/components/Console/PageHeader.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 44 | 45 | 77 | -------------------------------------------------------------------------------- /server/routes/connect.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | const Router = require('koa-router'); 3 | const connectModel = require('../models/connect'); 4 | 5 | const router = new Router(); 6 | router.prefix('/connect'); 7 | 8 | // 获取全部数据源列表 9 | router.get('/', async (ctx, next) => { 10 | const rows = await connectModel.find({ 'uid': ctx.request.query.uid }).select('-data'); 11 | ctx.body = { 12 | errno: 0, 13 | data: { 14 | connectList: rows 15 | } 16 | } 17 | }); 18 | 19 | // 获取某一数据源详情 20 | router.get('/:id', async (ctx, next) => { 21 | const item = await connectModel.findById(ctx.params.id); 22 | ctx.body = { 23 | errno: 0, 24 | data: item 25 | } 26 | }); 27 | 28 | // 新增数据源 29 | router.post('/', async (ctx, next) => { 30 | const body = ctx.request.body; 31 | 32 | if (!body.name || !body.data) { 33 | ctx.body = { 34 | errno: 1, 35 | errmsg: '格式错误' 36 | } 37 | return; 38 | } 39 | 40 | const result = await connectModel.create({ 41 | name: body.name, 42 | data: body.data, 43 | uid: body.uid, 44 | }); 45 | 46 | ctx.body = { 47 | errno: 0, 48 | data: result 49 | } 50 | }); 51 | 52 | // 更新可视化图表 53 | router.put('/:id', async (ctx, next) => { 54 | const body = ctx.request.body; 55 | 56 | const item = await connectModel.findById(ctx.params.id); 57 | if (body.name) { 58 | item.name = body.name; 59 | } 60 | item.save(); 61 | 62 | ctx.body = { 63 | errno: 0, 64 | } 65 | }); 66 | 67 | // 删除可视化图表 68 | router.delete('/:id', async (ctx, next) => { 69 | const body = ctx.request.body; 70 | 71 | const item = await connectModel.findById(ctx.params.id); 72 | item.remove(); 73 | 74 | ctx.body = { 75 | errno: 0, 76 | } 77 | }); 78 | 79 | module.exports = router; 80 | -------------------------------------------------------------------------------- /server/app.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | const path = require('path'); 3 | const Koa = require('koa'); 4 | const Router = require('koa-router'); 5 | const koaBody = require('koa-body'); 6 | const cors = require('koa2-cors'); 7 | const mongoose = require('mongoose'); 8 | const koaStatic = require('koa-static'); 9 | 10 | // 路由文件 11 | const apiRouter = require('./routes/api'); 12 | const userRouter = require('./routes/user'); 13 | const chartRouter = require('./routes/chart'); 14 | const connectRouter = require('./routes/connect'); 15 | const demoRouter = require('./routes/demo'); 16 | 17 | const app = new Koa(); 18 | // 解析 POST 请求 19 | app.use(koaBody({ 20 | multipart: true, // 支持文件上传 21 | formidable: { 22 | uploadDir: path.join(__dirname, './public/upload/'), // 设置文件上传目录 23 | keepExtensions: true, // 保持文件的后缀 24 | maxFieldsSize: 2 * 1024 * 1024, // 文件上传大小 25 | }, 26 | })); 27 | 28 | // 连接数据库 29 | mongoose.connect("mongodb://localhost:27017/chartfun", { useNewUrlParser: true }, err => { 30 | if (err) { 31 | console.log('[server] MongoDB connect error: ' + err); 32 | } else { 33 | console.log('[server] MongoDB connected!'); 34 | } 35 | }); 36 | 37 | const router = new Router(); 38 | 39 | app.use(koaStatic( 40 | path.join(__dirname, './public') 41 | )) 42 | app.use(cors()); 43 | 44 | router.get('/', (ctx, next) => { 45 | // ctx.router available 46 | ctx.body = 'Hello ChartFun!'; 47 | }); 48 | 49 | app.use(apiRouter.routes()).use(apiRouter.allowedMethods()); 50 | app.use(userRouter.routes()).use(userRouter.allowedMethods()); 51 | app.use(chartRouter.routes()).use(chartRouter.allowedMethods()); 52 | app.use(connectRouter.routes()).use(connectRouter.allowedMethods()); 53 | app.use(demoRouter.routes()).use(demoRouter.allowedMethods()); 54 | app.use(router.routes()).use(router.allowedMethods()); 55 | app.listen(3000); 56 | -------------------------------------------------------------------------------- /mock/data/charts-1234.json: -------------------------------------------------------------------------------- 1 | { 2 | "errno": 0, 3 | "errmsg": "ok", 4 | "data": { 5 | "title": "我国大数据市场产值图", 6 | "chartData": { 7 | "w": 1200, 8 | "h": 800, 9 | "bgcolor": "#eeeeee", 10 | "bgimage": "", 11 | "bgimagesize": "cover", 12 | "elements": [ 13 | { 14 | "name": "keykey1", 15 | "x": 10, 16 | "y": 100, 17 | "w": 492, 18 | "h": 308, 19 | "bgcolor": "rgba(19, 206, 102, 0.8)", 20 | "active": false, 21 | "data": { 22 | "type": "chart", 23 | "settings": { 24 | "type": "line" 25 | }, 26 | "data": { 27 | "columns": ["日期", "访问用户"], 28 | "rows": [ 29 | { "日期": "1月1日", "访问用户": 1523 }, 30 | { "日期": "1月2日", "访问用户": 1223 }, 31 | { "日期": "1月3日", "访问用户": 2123 }, 32 | { "日期": "1月4日", "访问用户": 4123 }, 33 | { "日期": "1月5日", "访问用户": 3123 }, 34 | { "日期": "1月6日", "访问用户": 7123 } 35 | ] 36 | } 37 | } 38 | }, { 39 | "name": "keykey2", 40 | "x": 300, 41 | "y": 320, 42 | "w": 400, 43 | "h": 160, 44 | "bgcolor": "rgba(0, 206, 255, 0.3)", 45 | "active": false, 46 | "data": { 47 | "type": "chart", 48 | "settings": { 49 | "type": "histogram" 50 | }, 51 | "data": { 52 | "columns": ["日期", "访问用户"], 53 | "rows": [ 54 | { "日期": "1月1日", "访问用户": 1523 }, 55 | { "日期": "1月2日", "访问用户": 1223 }, 56 | { "日期": "1月3日", "访问用户": 2123 } 57 | ] 58 | } 59 | } 60 | } 61 | ] 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/router.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import Router from 'vue-router'; 3 | 4 | Vue.use(Router); 5 | 6 | const router = new Router({ 7 | // mode: 'history', 8 | base: process.env.BASE_URL, 9 | routes: [ 10 | { 11 | path: '/', 12 | name: 'index', 13 | component: () => import('./views/Index.vue'), 14 | meta: { 15 | title: 'ChartFun | 一站式数据大屏制作平台', 16 | }, 17 | }, 18 | { 19 | path: '/console', 20 | component: () => import('./components/Console/Index.vue'), 21 | children: [ 22 | { 23 | path: 'data', 24 | component: () => import('./views/Console/Data.vue'), 25 | meta: { 26 | title: '数据管理', 27 | }, 28 | }, 29 | { 30 | path: 'data/add', 31 | component: () => import('./views/Console/DataAdd.vue'), 32 | meta: { 33 | title: '添加数据源', 34 | }, 35 | }, 36 | { 37 | path: 'chart', 38 | component: () => import('./views/Console/Chart.vue'), 39 | meta: { 40 | title: '我的可视化', 41 | }, 42 | }, 43 | { 44 | path: '', 45 | redirect: 'chart', 46 | }, 47 | ], 48 | }, 49 | { 50 | path: '/edit', 51 | component: () => import('./components/Editor/Index.vue'), 52 | children: [ 53 | { 54 | path: ':id', 55 | component: () => import('./views/Editor/Canvas.vue'), 56 | meta: { 57 | title: '大屏编辑', 58 | }, 59 | }, 60 | ], 61 | }, 62 | { 63 | path: '/view/:id', 64 | name: 'view', 65 | component: () => import('./views/Viewer/Canvas.vue'), 66 | meta: { 67 | title: '大屏查看 | ChartFun', 68 | }, 69 | }, 70 | ], 71 | }); 72 | 73 | router.beforeEach((to, from, next) => { 74 | /* 路由发生变化修改页面title */ 75 | if (to.meta.title) { 76 | document.title = to.meta.title; 77 | } 78 | next(); 79 | }); 80 | 81 | export default router; 82 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "chartfun", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "serve": "vue-cli-service serve", 7 | "build": "vue-cli-service build", 8 | "lint": "vue-cli-service lint", 9 | "test:e2e": "vue-cli-service test:e2e" 10 | }, 11 | "dependencies": { 12 | "axios": "^0.21.2", 13 | "dayjs": "^1.8.14", 14 | "echarts": "^5.4.0", 15 | "element-ui": "^2.9.1", 16 | "html2canvas": "^1.0.0-rc.3", 17 | "js-md5": "^0.7.3", 18 | "jsoneditor": "^9.5.6", 19 | "koa": "^2.7.0", 20 | "koa-body": "^4.1.0", 21 | "koa-router": "^7.4.0", 22 | "koa-static": "^5.0.0", 23 | "koa2-cors": "^2.0.6", 24 | "mockjs": "^1.0.1-beta3", 25 | "mongoose": "^5.13.15", 26 | "v-charts": "^1.19.0", 27 | "vue": "^2.6.6", 28 | "vue-drag-resize": "^1.3.2", 29 | "vue-draggable-resizable": "^2.0.0-rc1", 30 | "vue-json-editor": "^1.3.1", 31 | "vue-router": "^3.0.1", 32 | "vuedraggable": "^2.20.0", 33 | "vuex": "^3.0.1", 34 | "xlsx": "^0.17.0" 35 | }, 36 | "devDependencies": { 37 | "@vue/cli-plugin-babel": "^3.5.0", 38 | "@vue/cli-plugin-eslint": "^5.0.8", 39 | "@vue/cli-service": "^5.0.8", 40 | "@vue/eslint-config-airbnb": "^4.0.0", 41 | "babel-eslint": "^10.0.1", 42 | "eslint": "^5.8.0", 43 | "eslint-plugin-vue": "^5.0.0", 44 | "gh-pages": "^2.0.1", 45 | "node-sass": "^7.0.3", 46 | "pug": "^2.0.3", 47 | "pug-plain-loader": "^1.0.0", 48 | "sass-loader": "^7.1.0", 49 | "vue-template-compiler": "^2.5.21" 50 | }, 51 | "eslintConfig": { 52 | "root": true, 53 | "env": { 54 | "node": true 55 | }, 56 | "extends": [ 57 | "plugin:vue/essential", 58 | "@vue/airbnb" 59 | ], 60 | "rules": {}, 61 | "parserOptions": { 62 | "parser": "babel-eslint" 63 | } 64 | }, 65 | "postcss": { 66 | "plugins": { 67 | "autoprefixer": {} 68 | } 69 | }, 70 | "browserslist": [ 71 | "> 1%", 72 | "last 2 versions", 73 | "not ie <= 8" 74 | ] 75 | } 76 | -------------------------------------------------------------------------------- /src/components/Console/NavMenu.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 46 | 47 | 103 | -------------------------------------------------------------------------------- /src/components/Editor/ScaleBar.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 43 | 44 | 105 | -------------------------------------------------------------------------------- /src/components/Editor/Toolbar.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 60 | 61 | 112 | -------------------------------------------------------------------------------- /src/assets/font/iconfont.css: -------------------------------------------------------------------------------- 1 | @font-face {font-family: "iconfont"; 2 | src: url('iconfont.eot?t=1554565840275'); /* IE9 */ 3 | src: url('iconfont.eot?t=1554565840275#iefix') format('embedded-opentype'), /* IE6-IE8 */ 4 | url('data:application/x-font-woff2;charset=utf-8;base64,d09GMgABAAAAAAVAAAsAAAAACqQAAATzAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHEIGVgCDXgqHeIY1ATYCJAMcCxAABCAFhG0HWhsHCSMRJnyySPaXB/SURsePOhZxN7TtUtb1wKUl4ElKNslS+1OHHvpDKek5z8ED/qa9hEBpaNl2YqaciCtn/ndrAAxuk1VU8z3+qdIfkniFeiqexRrZ+tUSsJLU+ub8Ahu1qjIka9H3gw/MJbz39qbUjtTW1xgcwOTP30/tx/imwuCVXb15BXwFpAGhUMv04pcJRQRqQndCzWi9h2wC00da0adDAJoUZEOUq1CtHjYKYT0BiG6dO7bGdnlQMRyBbbdCdlWImZjYcpK8CszQf198Jo+wQWIaCJvW6lC+HaXeF/B3GF7Ea6YS6MoLAbe9wACyAQrE+NCIh2Gas+lYhy2tqAe42EissmqbwM/nt/Fn+DsiEdyDhZkCV/wHDwwUJhYCCcICcYixjVwWI6mTlTDAT6CEAj+fEib4bZSwwJ+hhAB/hweJdGBtF4gPogCIscDmiyhM7CqQ2gmFokPCIyjLUioYHQzGiDcsOfRPo4WJH81ouqTV5PsNHjzlXv27NpGc9e4ZyHX9hvdrtP92ihWPgvtuyZdeh7rmfF6brLK4KEcQ9sRRNb4Q1bdpg5Yjhq20bVCvPm3VVm0atmef2qRt61ZtG9ZnACEb2JPZdvBAfG0pKq9qRcWNYvr1eMomLdlleq4euDPq9uSb3q2h7uRESxFHKTwjB6bz9Xq21GJx3dYOWP2ucNZUHqeDErIuFvZVr6P5JkcB5FgrHA+2yAK5cNrV73p4c+HQZBuIKYJnkVFFvU8nHAdxbosv4BeDFY1xSr5InGsJau+hLcimGNlXaeVoPp9gPjvXtiKvFk/LWkryHTqkNfr/LYaV8ESkWn4Zixb39gwbtq2KaNNGVCFEamWQ3TL5j29E1LcftCgFvU47e1mtCrWydgLxI1XIalHrjWd6EbmWQ3QpphT9i/llSFLmQ+IPE0Tpxdl2ls/U5yFinbJVzrajfMY+jxChu7z2IzoME1r++vU1SpYstXDa9IVJJhYoYPcdWWplXl1K512plxY/D6dJQopJY8Ijvfp1jkSu0yezuTHJYtx//1icpGnCDf6FXT+GJ8dClUIDblbWjq5ZfeXOGtWTOLryzQHJO7g1x87MFaF8bCl4bA23Y/IBNze/LJcuWCm0q0b1oK5RbfjXLtbEwKbARKuiYKX/iomT156Q9mRFc6LVw13SpXrvuDL2jktGpw8P2R/omeQ03cKtpsTkilSIzXXyTKo389cxAuEOxmF5HsDw5EsAo6/8PQOjlZubTZ6MdZZfjt+/oUyP2XNbRhX7azsKgIfXP9/UPYr8NnuLWUTJmqNVl3KS+hoEb0C9R5bXRAAB+mDCtp1Ho4FjNBGH5+XZ4qfGXBC2+DZIAiQHA5t0pGKzgYlDPrCwKQmarFTc2yEufTAQKghkYRyAIJodIHE5AQbRXCMV+wxMEvAVLKKFAZoGIu4hHVYdRXs4G3KikuIrbamTk/w8GO0fqJ9Dbk6DzvuHhTYvaZR46++YYGVsEV51RjpxpqPc4GwYBhVv2qFk1JD+FMcu7IWiUsfZgbMhJyqh+AptqZNTZ+fBV/sH6ueQW8ywH9V/WGgXLqREJCnIO55SDduWpcOrziAdW8uZjsINQxjEigo+fKUOJSOaHCV/IqbpXFoc1W8bl/McQCN8X0Ck0sY6H9tehleL97pscuN6yL+wrW9LPg0r4sM1VYcwmwEA') format('woff2'), 5 | url('iconfont.woff?t=1554565840275') format('woff'), 6 | url('iconfont.ttf?t=1554565840275') format('truetype'), /* chrome, firefox, opera, Safari, Android, iOS 4.2+ */ 7 | url('iconfont.svg?t=1554565840275#iconfont') format('svg'); /* iOS 4.1- */ 8 | } 9 | 10 | .iconfont { 11 | font-family: "iconfont" !important; 12 | font-size: 16px; 13 | font-style: normal; 14 | -webkit-font-smoothing: antialiased; 15 | -moz-osx-font-smoothing: grayscale; 16 | } 17 | 18 | .icon-preview:before { 19 | content: "\e78f"; 20 | } 21 | 22 | .icon-chart:before { 23 | content: "\e7af"; 24 | } 25 | 26 | .icon-layer:before { 27 | content: "\e636"; 28 | } 29 | 30 | .icon-picture:before { 31 | content: "\e716"; 32 | } 33 | 34 | .icon-text:before { 35 | content: "\e734"; 36 | } 37 | 38 | .icon-tools:before { 39 | content: "\e762"; 40 | } 41 | 42 | -------------------------------------------------------------------------------- /server/routes/chart.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | const Router = require('koa-router'); 3 | const chartModel = require('../models/chart'); 4 | 5 | const router = new Router(); 6 | router.prefix('/chart'); 7 | 8 | // 获取全部实例列表 9 | router.get('/', async (ctx, next) => { 10 | const rows = await chartModel.find({ 'uid': ctx.request.query.uid }).select('-chartData'); 11 | ctx.body = { 12 | errno: 0, 13 | data: { 14 | chartList: rows 15 | } 16 | } 17 | }); 18 | 19 | // 获取某一实例列表 20 | router.get('/:id', async (ctx, next) => { 21 | const item = await chartModel.findById(ctx.params.id); 22 | 23 | ctx.body = { 24 | errno: 0, 25 | data: item 26 | } 27 | }); 28 | 29 | // 大屏访问量增1 30 | router.get('/view/:id', async (ctx, next) => { 31 | const item = await chartModel.findById(ctx.params.id); 32 | 33 | item.view = item.view + 1; 34 | 35 | item.save(); 36 | 37 | ctx.body = { 38 | errno: 0, 39 | data: item 40 | } 41 | }); 42 | 43 | // 新建可视化图表 44 | router.post('/', async (ctx, next) => { 45 | const body = ctx.request.body; 46 | 47 | if (!body.title || typeof body.title != 'string') { 48 | ctx.body = { 49 | errno: 1, 50 | errmsg: '格式错误' 51 | } 52 | return; 53 | } 54 | 55 | const result = await chartModel.create({ 56 | title: body.title, 57 | img: '', 58 | uid: body.uid, 59 | view: 0, 60 | chartData: { 61 | "w": 1200, 62 | "h": 800, 63 | "bgcolor": "#999999", 64 | "bgimage": "", 65 | "bgimagesize": "cover", 66 | "elements": [] 67 | } 68 | }); 69 | 70 | ctx.body = { 71 | errno: 0, 72 | data: result 73 | } 74 | }); 75 | 76 | // 更新可视化图表 77 | router.put('/:id', async (ctx, next) => { 78 | const body = ctx.request.body; 79 | 80 | const item = await chartModel.findById(ctx.params.id); 81 | if (body.title) { 82 | item.title = body.title; 83 | } else if (body.chartData) { 84 | item.chartData = body.chartData; 85 | item.img = body.img; 86 | } 87 | item.save(); 88 | 89 | ctx.body = { 90 | errno: 0, 91 | } 92 | }); 93 | 94 | // 删除可视化图表 95 | router.delete('/:id', async (ctx, next) => { 96 | const body = ctx.request.body; 97 | 98 | const item = await chartModel.findById(ctx.params.id); 99 | item.remove(); 100 | 101 | ctx.body = { 102 | errno: 0, 103 | } 104 | }); 105 | 106 | // 复制、导入可视化图表 107 | router.post('/import/:id', async (ctx, next) => { 108 | const body = ctx.request.body; 109 | 110 | const originItem = await chartModel.findById(ctx.params.id); 111 | 112 | const newItem = await chartModel.create({ 113 | title: body.title ? body.title : originItem.title + '_导入', 114 | img: originItem.img, 115 | uid: body.uid, 116 | view: 0, 117 | chartData: originItem.chartData 118 | }); 119 | 120 | ctx.body = { 121 | errno: 0 122 | } 123 | }); 124 | 125 | module.exports = router; 126 | -------------------------------------------------------------------------------- /src/views/Console/Data.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 105 | 106 | 108 | -------------------------------------------------------------------------------- /src/views/Index.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 74 | 75 | 164 | -------------------------------------------------------------------------------- /src/assets/font/iconfont.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | Created by iconfont 9 | 10 | 11 | 12 | 13 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /src/components/Editor/Index.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 146 | 147 | 195 | -------------------------------------------------------------------------------- /src/views/Console/DataAdd.vue: -------------------------------------------------------------------------------- 1 | 25 | 26 | 170 | 171 | 189 | -------------------------------------------------------------------------------- /src/views/Console/Chart.vue: -------------------------------------------------------------------------------- 1 | 27 | 28 | 181 | 182 | 225 | -------------------------------------------------------------------------------- /src/views/Viewer/Canvas.vue: -------------------------------------------------------------------------------- 1 | 51 | 52 | 141 | 142 | 203 | -------------------------------------------------------------------------------- /src/views/Editor/Canvas.vue: -------------------------------------------------------------------------------- 1 | 89 | 90 | 147 | 148 | 226 | -------------------------------------------------------------------------------- /src/components/Editor/SidePanel.vue: -------------------------------------------------------------------------------- 1 | 27 | 28 | 260 | 261 | 382 | -------------------------------------------------------------------------------- /src/components/Editor/Config.vue: -------------------------------------------------------------------------------- 1 | 233 | 234 | 312 | 313 | 436 | --------------------------------------------------------------------------------