├── .eslintrc.js ├── .gitignore ├── .vscode ├── extensions.json └── settings.json ├── LICENSE ├── README.en.md ├── README.md ├── index.html ├── package.json ├── prettier.config.js ├── public └── favicon.ico ├── src ├── App.vue ├── api │ ├── http.ts │ ├── model.ts │ └── request.ts ├── assets │ ├── 3.png │ ├── 4.jpg │ ├── 5.jpg │ ├── 6.jpg │ ├── 6.png │ ├── bck.jpg │ ├── border │ │ ├── border_1.png │ │ ├── border_10.gif │ │ ├── border_11.gif │ │ ├── border_2.png │ │ ├── border_3.png │ │ ├── border_4.png │ │ ├── border_5.png │ │ ├── border_6.png │ │ ├── border_7.png │ │ ├── border_8.png │ │ └── border_9.gif │ ├── echarts │ │ ├── bar_1.png │ │ ├── bar_2.png │ │ ├── circle_1.png │ │ ├── line_1.png │ │ ├── map_1.png │ │ ├── pie_1.png │ │ ├── pie_2.png │ │ ├── pie_3.gif │ │ ├── pie_3.png │ │ ├── pie_4.png │ │ ├── radar_1.png │ │ ├── rotation_1.gif │ │ ├── sankey.png │ │ ├── table_1.png │ │ └── text_1.png │ └── logo.png ├── components │ ├── background.jsx │ ├── draggable │ │ ├── DraggableContainer.ts │ │ ├── Vue3DraggableResizable.ts │ │ ├── conflict.ts │ │ ├── hooks.ts │ │ ├── index.css │ │ ├── types.ts │ │ └── utils.ts │ ├── hader-menu │ │ ├── index.jsx │ │ ├── item.jsx │ │ └── type.ts │ ├── list-component.jsx │ ├── ruler │ │ ├── index.ts │ │ └── index.vue │ ├── style │ │ └── list.module.scss │ ├── svgIcon.vue │ ├── title.vue │ ├── widget │ │ ├── build.ts │ │ ├── chart-parts.vue │ │ ├── map.vue │ │ ├── table.vue │ │ └── title.vue │ ├── widgetBuilds │ │ ├── bar │ │ │ ├── config.json │ │ │ ├── config.ts │ │ │ └── index.jsx │ │ ├── index.ts │ │ ├── line │ │ │ ├── config.ts │ │ │ └── index.jsx │ │ ├── map │ │ │ ├── config.ts │ │ │ └── index.jsx │ │ ├── pie │ │ │ ├── config.ts │ │ │ └── index.jsx │ │ ├── radar │ │ │ ├── config.ts │ │ │ └── index.jsx │ │ ├── share │ │ │ ├── backImg.jsx │ │ │ └── style.module.scss │ │ ├── table │ │ │ ├── config.ts │ │ │ └── index.jsx │ │ └── text │ │ │ ├── config.ts │ │ │ └── index.jsx │ └── zu-form-item.jsx ├── hooks │ ├── useConfig.ts │ ├── useDrag.ts │ ├── useGetter.ts │ ├── useMethod.ts │ ├── useState.ts │ └── userCharts.ts ├── icons │ └── svg │ │ ├── Number.svg │ │ ├── bar.svg │ │ ├── big.svg │ │ ├── border.svg │ │ ├── circle.svg │ │ ├── close.svg │ │ ├── data-model.svg │ │ ├── data-source.svg │ │ ├── delete.svg │ │ ├── echarts_line.svg │ │ ├── echarts_mappie.svg │ │ ├── folder_down.svg │ │ ├── folder_up.svg │ │ ├── line.svg │ │ ├── main_gods.svg │ │ ├── map.svg │ │ ├── pie.svg │ │ ├── pre.svg │ │ ├── radar.svg │ │ ├── save.svg │ │ ├── shrink.svg │ │ ├── table.svg │ │ ├── text.svg │ │ ├── updata.svg │ │ └── varchar.svg ├── json │ └── china.json ├── layout │ ├── components │ │ ├── index.module.scss │ │ ├── layout-header.jsx │ │ └── menu-item.jsx │ └── index.vue ├── main.ts ├── route │ └── index.ts ├── shim.d.ts ├── store │ ├── index.ts │ ├── modules │ │ ├── Config │ │ │ └── index.ts │ │ ├── Global │ │ │ └── index.ts │ │ └── HomePage │ │ │ └── index.ts │ └── store.d.ts ├── style │ ├── common.scss │ └── index.scss ├── type │ └── config.ts ├── utils │ ├── aes.ts │ ├── ceater.ts │ ├── chartsType.ts │ ├── dom.ts │ ├── tool.ts │ └── util.ts └── view │ ├── components │ ├── config-builds │ │ ├── hooks │ │ │ └── chartConfig.ts │ │ ├── index.vue │ │ └── item │ │ │ ├── coordinateConfig.vue │ │ │ ├── dataConfig.vue │ │ │ ├── interConfig.vue │ │ │ └── themeConfig.vue │ ├── draggableBody.vue │ ├── draggableHeader.vue │ ├── draggableLeft.vue │ └── draggableRight.vue │ └── screen │ ├── dashboard-preview.vue │ ├── dashboard.vue │ └── index.vue ├── tsconfig.json ├── vite.config.js └── yarn.lock /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parser: 'vue-eslint-parser', 3 | parserOptions: { 4 | parser: '@typescript-eslint/parser', 5 | ecmaVersion: 2020, 6 | sourceType: 'module', 7 | ecmaFeatures: { 8 | jsx: true 9 | } 10 | }, 11 | extends: [ 12 | 'plugin:vue/vue3-recommended', 13 | 'plugin:@typescript-eslint/recommended', 14 | 'prettier/@typescript-eslint', 15 | 'plugin:prettier/recommended' 16 | ], 17 | rules: { 18 | '@typescript-eslint/ban-ts-ignore': 'off', 19 | '@typescript-eslint/explicit-function-return-type': 'off', 20 | '@typescript-eslint/no-explicit-any': 'off', 21 | '@typescript-eslint/no-var-requires': 'off', 22 | '@typescript-eslint/no-empty-function': 'off', 23 | 'vue/custom-event-name-casing': 'off', 24 | 'no-use-before-define': 'off', 25 | // 'no-use-before-define': [ 26 | // 'error', 27 | // { 28 | // functions: false, 29 | // classes: true, 30 | // }, 31 | // ], 32 | '@typescript-eslint/no-use-before-define': 'off', 33 | // '@typescript-eslint/no-use-before-define': [ 34 | // 'error', 35 | // { 36 | // functions: false, 37 | // classes: true, 38 | // }, 39 | // ], 40 | '@typescript-eslint/ban-ts-comment': 'off', 41 | '@typescript-eslint/ban-types': 'off', 42 | '@typescript-eslint/no-non-null-assertion': 'off', 43 | '@typescript-eslint/explicit-module-boundary-types': 'off', 44 | '@typescript-eslint/no-unused-vars': [ 45 | 'error', 46 | { 47 | argsIgnorePattern: '^h$', 48 | varsIgnorePattern: '^h$' 49 | } 50 | ], 51 | 'no-unused-vars': [ 52 | 'error', 53 | { 54 | argsIgnorePattern: '^h$', 55 | varsIgnorePattern: '^h$' 56 | } 57 | ], 58 | 'space-before-function-paren': 'off', 59 | quotes: ['error', 'single'], 60 | 'comma-dangle': ['error', 'never'] 61 | } 62 | }; -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | dist 4 | dist-ssr 5 | *.local -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["johnsoncodehk.volar"] 3 | } 4 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "typescript.tsdk": "node_modules/typescript/lib" 3 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 殷浩玮 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 | -------------------------------------------------------------------------------- /README.en.md: -------------------------------------------------------------------------------- 1 | # vue3-drag-visualization 2 | 3 | #### Description 4 | {**When you're done, you can delete the content in this README and update the file with details for others getting started with your repository**} 5 | 6 | #### Software Architecture 7 | Software architecture description 8 | 9 | #### Installation 10 | 11 | 1. xxxx 12 | 2. xxxx 13 | 3. xxxx 14 | 15 | #### Instructions 16 | 17 | 1. xxxx 18 | 2. xxxx 19 | 3. xxxx 20 | 21 | #### Contribution 22 | 23 | 1. Fork the repository 24 | 2. Create Feat_xxx branch 25 | 3. Commit your code 26 | 4. Create Pull Request 27 | 28 | 29 | #### Gitee Feature 30 | 31 | 1. You can use Readme\_XXX.md to support different languages, such as Readme\_en.md, Readme\_zh.md 32 | 2. Gitee blog [blog.gitee.com](https://blog.gitee.com) 33 | 3. Explore open source project [https://gitee.com/explore](https://gitee.com/explore) 34 | 4. The most valuable open source project [GVP](https://gitee.com/gvp) 35 | 5. The manual of Gitee [https://gitee.com/help](https://gitee.com/help) 36 | 6. The most popular members [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/) 37 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vue3-drag-visualization 2 | 3 | ## 介绍 4 | 本项目为Zeus BI开源简化版,一款简单的可视化搭建工具,重点放在了功能实现上,已实现基本的可视化图表配置 5 | 完整版功能请查看 6 | Zeus BI官网 http://www.maingods.xyz (暂未开放) 7 | 体验网址 http://www.maingods.xyz/screen (最新) 8 | 9 | ## 为什么vue3项目要抛弃SFC写法而尝试jsx/tsx方式来编码? 10 | 事实上我这个项目并没有完全抛弃,只是尝试结合两种语法去开发。 11 | 这个问题在掘金上Jokcy老哥的一篇博客已经总结得相当不错,我就不再多赘述相关细节,博客链接如下: 12 | [为什么我推荐使用JSX开发Vue3](https://juejin.cn/post/6911175470255964174) 13 | #### 基本架构 14 | Vite + Vue 3 + Typescript + jsx + sass + axios + router + vuex + elementplus 15 | 基本架构以vue3的模板语法为基础,组件的开发以jsx为主。 16 | 整个配置页面的交互用hooks来实现 17 | 此项目的基础脚手架将会在以后发出来 当然你们也可以自己删除这个项目无用的地方 得到一个vite vue3的脚手架 18 | ### 涉及到的主要依赖: 19 | 20 | 1. `vue@^3.2.13` 21 | 2. `vite@^2.5.10` 22 | 3. `vue-router@4.0.8` 23 | 4. `vuex@4` 24 | 5. `typescript@^4.5.2` 25 | 6. `@types/node": "^16.11.11` 26 | 7. `sass@^1.43.5` 27 | 8. `@vitejs/plugin-vue-jsx@^1.3.0` 28 | 9. `axios": "^0.24.0` 29 | 10. `element-plus@^1.2.0-beta.4` 30 | 11. `echarts@5.0.1` 31 | #### 已实现的相关功能示范 32 | - [x] typescript 33 | - [x] vue3大部分语法示例 34 | - [x] vite脚手架配置 35 | - [x] tsx/jsx开发模式 36 | - [x] axios简单封装 37 | - [x] sass 38 | - [x] router 39 | - [x] vuex (推荐使用hooks和inject来实现,可以等一手vuex5) 40 | - [x] Element-plus 41 | - [x] svg封装 42 | #### 安装教程 43 | 1. 推荐使用`yarn` 44 | ```bash 45 | $ yarn install 46 | ``` 47 | 2. 启动 48 | ```bash 49 | $ yarn dev 50 | ``` 51 | 52 | 下面是整个项目的基本目录结构(只对重要文件做说明): 53 | 54 | ```bash 55 | │ ├─public # 静态资源目录 56 | │ │ favicon.ico 57 | │ │ 58 | │ ├─src 59 | │ │ │ App.vue # 入口vue文件 60 | │ │ │ main.ts # 入口文件 61 | │ │ │ shims-vue.d.ts # vue文件模块声明文件 62 | │ │ │ vite-env.d.ts # vite环境变量声明文件 63 | │ │ │ 64 | │ │ ├─assets # 资源文件目录 65 | │ │ │ └─border # 边框文件夹 66 | │ │ │ 67 | │ │ ├─components # 组件文件目录 68 | │ │ │ ├─draggable # 拖拽组件 69 | │ │ │ ├─hader-menu # 导航栏组件 70 | │ │ │ ├─ruler # 标尺组件 71 | │ │ │ ├─widget # 图表构建组件 72 | │ │ │ └─widgetBuilds # 配置项构建 73 | │ │ │ 74 | │ │ └─hooks # 公用状态目录 75 | │ │ ├─useConfig # 画布配置项 76 | │ │ ├─useDrag # 图表钩子 77 | │ │ └─userCharts # 画布数据项 78 | │ │ 79 | │ │ .gitignore 80 | │ │ index.html # Vite项目的入口文件 81 | │ │ package.json 82 | │ │ README.md 83 | │ │ tsconfig.json # tsconfig配置文件 84 | │ │ vite.config.ts # vite配置文件 85 | ``` 86 | #### jsx/tsx语法规范 87 | 如果有过react的开发经验,可以发现除了vue中独有的几个新概念:`slot`、`directive`、`emit`等以外,大部分支持vue的jsx语法规范和react的都是一样的,相同的部分我就不多说了,不了解的可以百度!!!值得一提的是`@vue/babel-plugin-jsx`帮我们解析了几个常见的vue指令,比如`v-show`、`v-model`,这两个的用法和功能与vue中一摸一样。其他的直接按照react的语法写就行了 88 | ##### jsx/tsx Render方式 89 | jsx和tsx目前还支持render方式的写法,这种写法目前也是大多数开源UI库的写法,个人比较推荐这种写法,它将逻辑层和模板层分开后期更易维护,具体可以看一下建议demo 90 | ```tsx 91 | import { ref, renderSlot, onUnmounted, defineComponent } from "vue"; 92 | // 带render函数的组件 优点:可将逻辑区与模版区分开 93 | export const RenderComponent = defineComponent({ 94 | props: { 95 | title: String, 96 | }, 97 | // 逻辑层 98 | setup() { 99 | const count = ref(1); 100 | 101 | const timer = setInterval(() => { 102 | count.value++; 103 | }, 2000); 104 | 105 | onUnmounted(() => { 106 | clearInterval(timer); 107 | }); 108 | 109 | return { 110 | count, 111 | }; 112 | }, 113 | // 渲染层 114 | render() { 115 | // render函数在响应式数据发生更改时会自动触发(与react类似) 116 | const { count, $slots, title } = this; 117 | return ( 118 |
119 | {renderSlot($slots, "prefix")} {count} 120 |
121 | 这是props:{title} 122 |
123 | {renderSlot($slots, "default")} 124 |
125 | ); 126 | }, 127 | }); 128 | ``` 129 | #### 参与贡献 130 | 在完成Zeus BI的第一迭代之前只接受 `fixbug PR`,不接受 `feature PR`。 131 | #### 已经实现的组件 132 | 133 | ``` 134 | - 基础组件库 135 | - [ ] 柱图 136 | * [x] 基本柱状图 137 | * [ ] 弧形柱图 138 | * [ ] 折线柱图 139 | * [ ] 水平基本柱状图 140 | - [ ] 折线 141 | * [x] 基本折线图 142 | * [x] 区域图 143 | - [ ] 饼图 144 | * [x] 基本饼图 145 | * [x] 环形饼图 146 | * [x] 南丁格尔玫瑰图 147 | * [ ] 指标占比图 148 | - [ ] 雷达图 149 | * [x] 基本雷达图 150 | - [ ] 环状图 151 | * [x] 基本环状图 152 | - [ ] 地图 153 | * [x] 基础平面地图 154 | - [ ] 动态气泡层 155 | - [ ] 飞线层 156 | * [ ] 世界地图 157 | - [x] 文本标题 158 | * [x] 通用标题 159 | * [ ] 数字翻牌器 160 | * [ ] 跑马灯 161 | * [ ] 多行文本 162 | * [ ] 时间器 163 | * [ ] 词云 164 | - [ ] 列表 165 | * [x] 基础列表 166 | * [ ] 轮播列表 167 | * [ ] 轮播列表柱状图 168 | - [] 媒体 169 | * [ ] 单张图片 170 | * [ ] 装饰 171 | * [x] 边框 172 | * [x] 自定义背景 173 | - [ ] 控件 174 | * [ ] 全屏切换 175 | * [ ] Tab列表 176 | * [ ] 进度条 177 | ``` 178 | ## 注意 179 | 180 | 本项目主要用来研究与学习。 181 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vite App 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vite-project", 3 | "version": "0.0.0-bate.1", 4 | "scripts": { 5 | "dev": "vite", 6 | "build": "vite build", 7 | "serve": "vite preview" 8 | }, 9 | "dependencies": { 10 | "axios": "^0.24.0", 11 | "canvg": "^3.0.9", 12 | "echarts": "5.0.1", 13 | "element-plus": "^1.2.0-beta.4", 14 | "html2canvas": "^1.4.0", 15 | "nprogress": "^0.2.0", 16 | "vue": "^3.2.13", 17 | "vue-router": "4", 18 | "vue3-eventbus": "^2.0.0", 19 | "vuex": "^4.0.2" 20 | }, 21 | "devDependencies": { 22 | "@types/echarts": "^4.9.12", 23 | "@types/node": "^16.11.11", 24 | "@vitejs/plugin-vue": "^1.9.0", 25 | "@vitejs/plugin-vue-jsx": "^1.3.0", 26 | "crypto-js": "^4.1.1", 27 | "node-sass": "^6.0.1", 28 | "sass": "^1.43.5", 29 | "sass-loader": "^12.3.0", 30 | "svg-sprite-loader": "^6.0.11", 31 | "typescript": "4.5.2", 32 | "vite": "^2.5.10" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /prettier.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | printWidth: 100, 3 | tabWidth: 2, 4 | useTabs: false, 5 | semi: false, // 未尾逗号 6 | vueIndentScriptAndStyle: true, 7 | singleQuote: true, // 单引号 8 | quoteProps: 'as-needed', 9 | bracketSpacing: true, 10 | trailingComma: 'none', // 未尾分号 11 | jsxBracketSameLine: false, 12 | jsxSingleQuote: false, 13 | arrowParens: 'always', 14 | insertPragma: false, 15 | requirePragma: false, 16 | proseWrap: 'never', 17 | htmlWhitespaceSensitivity: 'strict', 18 | endOfLine: 'lf' 19 | } -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yinhw0210/vue3-drag-visualization/04f7d5d2da32bf8106363acc7dfc2edbeac51dc6/public/favicon.ico -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 13 | 16 | 17 | 26 | -------------------------------------------------------------------------------- /src/api/http.ts: -------------------------------------------------------------------------------- 1 | import { request } from './request' 2 | interface parameter { 3 | readonly method: string 4 | url: string 5 | data?: object 6 | params?: object 7 | } 8 | export function http(method: string, url: string, data: object | undefined) { 9 | const config: parameter = { 10 | method, 11 | url 12 | } 13 | config.method === 'post' ? (config.data = data) : (config.params = data) 14 | return request(config) 15 | } 16 | -------------------------------------------------------------------------------- /src/api/model.ts: -------------------------------------------------------------------------------- 1 | import { http } from './http' 2 | //添加数据模型 3 | export function addDateModel(data?:object){ 4 | return http('post','/api/doge/insertDataModel',data) 5 | } 6 | //查询数据源选项 7 | export function querySource(data?:object){ 8 | return http('post','/api/doge/getDataSourceOption',data) 9 | } 10 | //查询数据模型列表 11 | export function queryDataModel(data?:object){ 12 | return http('post','/api/doge/findAllDataModel',data) 13 | } 14 | //删除数据模型 15 | export function deleteDataModel(data?:object){ 16 | return http('post','/api/doge/deleteDataModelById',data) 17 | } 18 | //修改排序 19 | export function updateSort(data?:object){ 20 | return http('post','/api/doge/updateSortByIds',data) 21 | } 22 | //查询数据源下表信息 23 | export function queryModelTable(data?:object){ 24 | return http('post','/api/doge/findAllTableByDataSourceId',data) 25 | } 26 | //查询列信息 27 | export function queryColumnTable(data?:object){ 28 | return http('post','/api/doge/findAllColumnByTableName',data) 29 | } 30 | //保存数据模型 31 | export function saveDataModel(data?:object){ 32 | return http('post','/api/doge/updateDataModel',data) 33 | } 34 | //查询数据模型详情 35 | export function queryDataModelDetails(data?:object){ 36 | return http('post','/api/doge/findDataModelById',data) 37 | } -------------------------------------------------------------------------------- /src/api/request.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios' 2 | import { ElMessage } from 'element-plus' 3 | import { useRouter } from 'vue-router' 4 | const router = useRouter() 5 | export function request(config: object) { 6 | // 创建axios的实例 7 | const instance = axios.create({ 8 | baseURL: '', 9 | timeout: 10000 10 | }) 11 | // 请求拦截器配置 12 | instance.interceptors.request.use( 13 | (config) => { 14 | if (localStorage.getItem('token')) { 15 | config.headers.token = localStorage.getItem('token') 16 | } 17 | return config 18 | }, 19 | (error) => { 20 | console.log(error) 21 | return Promise.error(error) 22 | } 23 | ) 24 | // 响应拦截器配置 25 | instance.interceptors.response.use( 26 | (response) => { 27 | if (!response.data.success) { 28 | ElMessage({ 29 | message: response.data.message || '未知错误', 30 | type: 'warning' 31 | }) 32 | return Promise.reject(response) 33 | } else { 34 | return response.data 35 | } 36 | }, 37 | (error) => { 38 | switch (error.response.status) { 39 | case 401: 40 | ElMessage({ 41 | message: '无权访问', 42 | type: 'warning' 43 | }) 44 | // router.push({path: '/login'}) 45 | break 46 | case 403: 47 | // ElMessage({ 48 | // message: 'token过期', 49 | // type: 'warning' 50 | // }) 51 | localStorage.removeItem('token') 52 | sessionStorage.removeItem('menuPath') 53 | router.push({ path: '/login' }) 54 | break 55 | case 404: 56 | ElMessage({ 57 | message: '找不到了', 58 | type: 'warning' 59 | }) 60 | // router.push({path: '/404'}) 61 | break 62 | case 500: 63 | ElMessage({ 64 | message: '服务器内部错误', 65 | type: 'warning' 66 | }) 67 | // router.push({path: '/404'}) 68 | break 69 | default: 70 | return Promise.reject(error) 71 | } 72 | return Promise.reject(error) 73 | } 74 | ) 75 | // 发送真正的网络请求 76 | return instance(config) 77 | } 78 | export default request 79 | -------------------------------------------------------------------------------- /src/assets/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yinhw0210/vue3-drag-visualization/04f7d5d2da32bf8106363acc7dfc2edbeac51dc6/src/assets/3.png -------------------------------------------------------------------------------- /src/assets/4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yinhw0210/vue3-drag-visualization/04f7d5d2da32bf8106363acc7dfc2edbeac51dc6/src/assets/4.jpg -------------------------------------------------------------------------------- /src/assets/5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yinhw0210/vue3-drag-visualization/04f7d5d2da32bf8106363acc7dfc2edbeac51dc6/src/assets/5.jpg -------------------------------------------------------------------------------- /src/assets/6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yinhw0210/vue3-drag-visualization/04f7d5d2da32bf8106363acc7dfc2edbeac51dc6/src/assets/6.jpg -------------------------------------------------------------------------------- /src/assets/6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yinhw0210/vue3-drag-visualization/04f7d5d2da32bf8106363acc7dfc2edbeac51dc6/src/assets/6.png -------------------------------------------------------------------------------- /src/assets/bck.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yinhw0210/vue3-drag-visualization/04f7d5d2da32bf8106363acc7dfc2edbeac51dc6/src/assets/bck.jpg -------------------------------------------------------------------------------- /src/assets/border/border_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yinhw0210/vue3-drag-visualization/04f7d5d2da32bf8106363acc7dfc2edbeac51dc6/src/assets/border/border_1.png -------------------------------------------------------------------------------- /src/assets/border/border_10.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yinhw0210/vue3-drag-visualization/04f7d5d2da32bf8106363acc7dfc2edbeac51dc6/src/assets/border/border_10.gif -------------------------------------------------------------------------------- /src/assets/border/border_11.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yinhw0210/vue3-drag-visualization/04f7d5d2da32bf8106363acc7dfc2edbeac51dc6/src/assets/border/border_11.gif -------------------------------------------------------------------------------- /src/assets/border/border_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yinhw0210/vue3-drag-visualization/04f7d5d2da32bf8106363acc7dfc2edbeac51dc6/src/assets/border/border_2.png -------------------------------------------------------------------------------- /src/assets/border/border_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yinhw0210/vue3-drag-visualization/04f7d5d2da32bf8106363acc7dfc2edbeac51dc6/src/assets/border/border_3.png -------------------------------------------------------------------------------- /src/assets/border/border_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yinhw0210/vue3-drag-visualization/04f7d5d2da32bf8106363acc7dfc2edbeac51dc6/src/assets/border/border_4.png -------------------------------------------------------------------------------- /src/assets/border/border_5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yinhw0210/vue3-drag-visualization/04f7d5d2da32bf8106363acc7dfc2edbeac51dc6/src/assets/border/border_5.png -------------------------------------------------------------------------------- /src/assets/border/border_6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yinhw0210/vue3-drag-visualization/04f7d5d2da32bf8106363acc7dfc2edbeac51dc6/src/assets/border/border_6.png -------------------------------------------------------------------------------- /src/assets/border/border_7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yinhw0210/vue3-drag-visualization/04f7d5d2da32bf8106363acc7dfc2edbeac51dc6/src/assets/border/border_7.png -------------------------------------------------------------------------------- /src/assets/border/border_8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yinhw0210/vue3-drag-visualization/04f7d5d2da32bf8106363acc7dfc2edbeac51dc6/src/assets/border/border_8.png -------------------------------------------------------------------------------- /src/assets/border/border_9.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yinhw0210/vue3-drag-visualization/04f7d5d2da32bf8106363acc7dfc2edbeac51dc6/src/assets/border/border_9.gif -------------------------------------------------------------------------------- /src/assets/echarts/bar_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yinhw0210/vue3-drag-visualization/04f7d5d2da32bf8106363acc7dfc2edbeac51dc6/src/assets/echarts/bar_1.png -------------------------------------------------------------------------------- /src/assets/echarts/bar_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yinhw0210/vue3-drag-visualization/04f7d5d2da32bf8106363acc7dfc2edbeac51dc6/src/assets/echarts/bar_2.png -------------------------------------------------------------------------------- /src/assets/echarts/circle_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yinhw0210/vue3-drag-visualization/04f7d5d2da32bf8106363acc7dfc2edbeac51dc6/src/assets/echarts/circle_1.png -------------------------------------------------------------------------------- /src/assets/echarts/line_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yinhw0210/vue3-drag-visualization/04f7d5d2da32bf8106363acc7dfc2edbeac51dc6/src/assets/echarts/line_1.png -------------------------------------------------------------------------------- /src/assets/echarts/map_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yinhw0210/vue3-drag-visualization/04f7d5d2da32bf8106363acc7dfc2edbeac51dc6/src/assets/echarts/map_1.png -------------------------------------------------------------------------------- /src/assets/echarts/pie_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yinhw0210/vue3-drag-visualization/04f7d5d2da32bf8106363acc7dfc2edbeac51dc6/src/assets/echarts/pie_1.png -------------------------------------------------------------------------------- /src/assets/echarts/pie_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yinhw0210/vue3-drag-visualization/04f7d5d2da32bf8106363acc7dfc2edbeac51dc6/src/assets/echarts/pie_2.png -------------------------------------------------------------------------------- /src/assets/echarts/pie_3.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yinhw0210/vue3-drag-visualization/04f7d5d2da32bf8106363acc7dfc2edbeac51dc6/src/assets/echarts/pie_3.gif -------------------------------------------------------------------------------- /src/assets/echarts/pie_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yinhw0210/vue3-drag-visualization/04f7d5d2da32bf8106363acc7dfc2edbeac51dc6/src/assets/echarts/pie_3.png -------------------------------------------------------------------------------- /src/assets/echarts/pie_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yinhw0210/vue3-drag-visualization/04f7d5d2da32bf8106363acc7dfc2edbeac51dc6/src/assets/echarts/pie_4.png -------------------------------------------------------------------------------- /src/assets/echarts/radar_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yinhw0210/vue3-drag-visualization/04f7d5d2da32bf8106363acc7dfc2edbeac51dc6/src/assets/echarts/radar_1.png -------------------------------------------------------------------------------- /src/assets/echarts/rotation_1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yinhw0210/vue3-drag-visualization/04f7d5d2da32bf8106363acc7dfc2edbeac51dc6/src/assets/echarts/rotation_1.gif -------------------------------------------------------------------------------- /src/assets/echarts/sankey.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yinhw0210/vue3-drag-visualization/04f7d5d2da32bf8106363acc7dfc2edbeac51dc6/src/assets/echarts/sankey.png -------------------------------------------------------------------------------- /src/assets/echarts/table_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yinhw0210/vue3-drag-visualization/04f7d5d2da32bf8106363acc7dfc2edbeac51dc6/src/assets/echarts/table_1.png -------------------------------------------------------------------------------- /src/assets/echarts/text_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yinhw0210/vue3-drag-visualization/04f7d5d2da32bf8106363acc7dfc2edbeac51dc6/src/assets/echarts/text_1.png -------------------------------------------------------------------------------- /src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yinhw0210/vue3-drag-visualization/04f7d5d2da32bf8106363acc7dfc2edbeac51dc6/src/assets/logo.png -------------------------------------------------------------------------------- /src/components/background.jsx: -------------------------------------------------------------------------------- 1 | import { defineComponent, ref } from 'vue' 2 | import back from './style/list.module.scss' 3 | import { config, setWH, Marks } from '@/hooks/useConfig' 4 | export default defineComponent({ 5 | props: {}, 6 | emits: [], 7 | components: {}, 8 | setup(props, ctx) { 9 | const color = ref() 10 | //上传成功的钩子 11 | const handleAvatarSuccess = (res, file) => { 12 | config.customImg = URL.createObjectURL(file.raw) 13 | } 14 | //上传前的钩子 限制上传格式 15 | const beforeAvatarUpload = (file) => { 16 | const isJPG = file.type === 'image/jpeg' 17 | const isLt2M = file.size / 1024 / 1024 < 2 18 | if (!isJPG) { 19 | console.log('Avatar picture must be JPG format!') 20 | } 21 | if (!isLt2M) { 22 | console.log('Avatar picture size can not exceed 2MB!') 23 | } 24 | return isJPG && isLt2M 25 | } 26 | //自定义颜色 27 | const colorRender = () => { 28 | return ( 29 | <> 30 |
背景颜色(背景图片透明时可见)
31 |
32 | { 37 | return 38 | } 39 | }} 40 | > 41 |
42 | 43 | ) 44 | } 45 | //内置背景 46 | const builtRender = () => { 47 | return ( 48 | <> 49 |
请选择大屏背景:
50 |
51 |
52 |
53 | 54 | ) 55 | } 56 | //上传图片 57 | const customRender = () => { 58 | return ( 59 | <> 60 | 67 | {config.customImg ? ( 68 | 69 | ) : ( 70 | 71 | 72 | 73 | )} 74 | 75 | 76 | ) 77 | } 78 | const vagueRender = () => { 79 | return ( 80 | <> 81 | 82 | 83 |
背景图片模糊度
84 |
85 | 91 |
92 |
93 | 94 |
背景图片透明度
95 |
96 | 102 |
103 |
104 |
105 | 106 | ) 107 | } 108 | return () => ( 109 | <> 110 |
111 | {config.background === 0 ? builtRender() : config.background === 1 ? customRender() : ''} 112 | {config.background !== 2 ? vagueRender() : ''} 113 | {colorRender()} 114 |
115 | 116 | ) 117 | } 118 | }) 119 | -------------------------------------------------------------------------------- /src/components/draggable/DraggableContainer.ts: -------------------------------------------------------------------------------- 1 | import { computed, defineComponent, h, provide, reactive, toRef } from 'vue' 2 | import { 3 | PositionStore, 4 | Position, 5 | UpdatePosition, 6 | GetPositionStore, 7 | MatchedLine, 8 | SetMatchedLine 9 | } from './types' 10 | import { IDENTITY } from './utils' 11 | 12 | export default defineComponent({ 13 | name: 'DraggableContainer', 14 | props: { 15 | disabled: { 16 | type: Boolean, 17 | default: false 18 | }, 19 | adsorbParent: { 20 | type: Boolean, 21 | default: true 22 | }, 23 | adsorbCols: { 24 | type: Array, 25 | default: null 26 | }, 27 | adsorbRows: { 28 | type: Array, 29 | default: null 30 | }, 31 | referenceLineVisible: { 32 | type: Boolean, 33 | default: true 34 | }, 35 | referenceLineColor: { 36 | type: String, 37 | default: '#f00' 38 | } 39 | }, 40 | setup(props) { 41 | const positionStore = reactive({}) 42 | const updatePosition: UpdatePosition = (id: string, position: Position) => { 43 | positionStore[id] = position 44 | } 45 | const getPositionStore: GetPositionStore = (excludeId?: string) => { 46 | const _positionStore = Object.assign({}, positionStore) 47 | if (excludeId) { 48 | delete _positionStore[excludeId] 49 | } 50 | return _positionStore 51 | } 52 | const state = reactive<{ 53 | matchedLine: MatchedLine | null 54 | }>({ 55 | matchedLine: null 56 | }) 57 | const matchedRows = computed(() => (state.matchedLine && state.matchedLine.row) || []) 58 | const matchedCols = computed(() => (state.matchedLine && state.matchedLine.col) || []) 59 | const setMatchedLine: SetMatchedLine = (matchedLine: MatchedLine | null) => { 60 | state.matchedLine = matchedLine 61 | } 62 | provide('identity', IDENTITY) 63 | provide('updatePosition', updatePosition) 64 | provide('getPositionStore', getPositionStore) 65 | provide('setMatchedLine', setMatchedLine) 66 | provide('disabled', toRef(props, 'disabled')) 67 | provide('adsorbParent', toRef(props, 'adsorbParent')) 68 | provide('adsorbCols', props.adsorbCols || []) 69 | provide('adsorbRows', props.adsorbRows || []) 70 | return { 71 | matchedRows, 72 | matchedCols 73 | } 74 | }, 75 | methods: { 76 | renderReferenceLine() { 77 | if (!this.referenceLineVisible) { 78 | return [] 79 | } 80 | return [ 81 | ...this.matchedCols.map((item) => { 82 | return h('div', { 83 | style: { 84 | width: '0', 85 | height: '100%', 86 | top: '0', 87 | left: item + 'px', 88 | borderLeft: `1px dashed ${this.referenceLineColor}`, 89 | position: 'absolute' 90 | } 91 | }) 92 | }), 93 | ...this.matchedRows.map((item) => { 94 | return h('div', { 95 | style: { 96 | width: '100%', 97 | height: '0', 98 | left: '0', 99 | top: item + 'px', 100 | borderTop: `1px dashed ${this.referenceLineColor}`, 101 | position: 'absolute' 102 | } 103 | }) 104 | }) 105 | ] 106 | } 107 | }, 108 | render() { 109 | return h( 110 | 'div', 111 | { 112 | style: { width: '100%', height: '100%', position: 'relative' } 113 | }, 114 | [this.$slots.default && this.$slots.default(), ...this.renderReferenceLine()] 115 | ) 116 | } 117 | }) 118 | -------------------------------------------------------------------------------- /src/components/draggable/Vue3DraggableResizable.ts: -------------------------------------------------------------------------------- 1 | import { defineComponent, ref, toRef, h, Ref, inject } from 'vue' 2 | import { 3 | initDraggableContainer, 4 | watchProps, 5 | initState, 6 | initParent, 7 | initLimitSizeAndMethods, 8 | initResizeHandle 9 | } from './hooks' 10 | import './index.css' 11 | import { getElSize, filterHandles, IDENTITY } from './utils' 12 | import { 13 | UpdatePosition, 14 | GetPositionStore, 15 | ResizingHandle, 16 | ContainerProvider, 17 | SetMatchedLine 18 | } from './types' 19 | 20 | export const ALL_HANDLES: ResizingHandle[] = ['tl', 'tm', 'tr', 'ml', 'mr', 'bl', 'bm', 'br'] 21 | 22 | const VdrProps = { 23 | initW: { 24 | type: Number, 25 | default: null 26 | }, 27 | initH: { 28 | type: Number, 29 | default: null 30 | }, 31 | w: { 32 | type: Number, 33 | default: 0 34 | }, 35 | h: { 36 | type: Number, 37 | default: 0 38 | }, 39 | x: { 40 | type: Number, 41 | default: 0 42 | }, 43 | y: { 44 | type: Number, 45 | default: 0 46 | }, 47 | draggable: { 48 | type: Boolean, 49 | default: true 50 | }, 51 | resizable: { 52 | type: Boolean, 53 | default: true 54 | }, 55 | disabledX: { 56 | type: Boolean, 57 | default: false 58 | }, 59 | disabledY: { 60 | type: Boolean, 61 | default: false 62 | }, 63 | disabledW: { 64 | type: Boolean, 65 | default: false 66 | }, 67 | disabledH: { 68 | type: Boolean, 69 | default: false 70 | }, 71 | minW: { 72 | type: Number, 73 | default: 20 74 | }, 75 | minH: { 76 | type: Number, 77 | default: 20 78 | }, 79 | active: { 80 | type: Boolean, 81 | default: false 82 | }, 83 | parent: { 84 | type: Boolean, 85 | default: false 86 | }, 87 | handles: { 88 | type: Array, 89 | default: ALL_HANDLES, 90 | validator: (handles: ResizingHandle[]) => { 91 | return filterHandles(handles).length === handles.length 92 | } 93 | }, 94 | classNameDraggable: { 95 | type: String, 96 | default: 'draggable' 97 | }, 98 | classNameResizable: { 99 | type: String, 100 | default: 'resizable' 101 | }, 102 | classNameDragging: { 103 | type: String, 104 | default: 'dragging' 105 | }, 106 | classNameResizing: { 107 | type: String, 108 | default: 'resizing' 109 | }, 110 | classNameActive: { 111 | type: String, 112 | default: 'active' 113 | }, 114 | classNameHandle: { 115 | type: String, 116 | default: 'handle' 117 | }, 118 | lockAspectRatio: { 119 | type: Boolean, 120 | default: false 121 | }, 122 | isConflictCheck: { 123 | type: Boolean, 124 | default: true 125 | } 126 | } 127 | 128 | const emits = [ 129 | 'activated', 130 | 'deactivated', 131 | 'drag-start', 132 | 'resize-start', 133 | 'dragging', 134 | 'resizing', 135 | 'drag-end', 136 | 'resize-end', 137 | 'update:w', 138 | 'update:h', 139 | 'update:x', 140 | 'update:y', 141 | 'update:active' 142 | ] 143 | const VueDraggableResizable = defineComponent({ 144 | name: 'Vue3DraggableResizable', 145 | props: VdrProps, 146 | emits: emits, 147 | setup(props, { emit }) { 148 | const containerProps = initState(props, emit) 149 | const provideIdentity = inject('identity') 150 | let containerProvider: ContainerProvider | null = null 151 | if (provideIdentity === IDENTITY) { 152 | containerProvider = { 153 | updatePosition: inject('updatePosition')!, 154 | getPositionStore: inject('getPositionStore')!, 155 | disabled: inject>('disabled')!, 156 | adsorbParent: inject>('adsorbParent')!, 157 | adsorbCols: inject('adsorbCols')!, 158 | adsorbRows: inject('adsorbRows')!, 159 | setMatchedLine: inject('setMatchedLine')! 160 | } 161 | } 162 | const containerRef = ref() 163 | const parentSize = initParent(containerRef) 164 | const limitProps = initLimitSizeAndMethods(props, parentSize, containerProps) 165 | initDraggableContainer( 166 | containerRef, 167 | containerProps, 168 | limitProps, 169 | toRef(props, 'draggable'), 170 | toRef(props, 'isConflictCheck'), 171 | emit, 172 | containerProvider, 173 | parentSize 174 | ) 175 | const resizeHandle = initResizeHandle(containerProps, limitProps, parentSize, props, emit) 176 | watchProps(props, limitProps) 177 | return { 178 | containerRef, 179 | containerProvider, 180 | ...containerProps, 181 | ...parentSize, 182 | ...limitProps, 183 | ...resizeHandle 184 | } 185 | }, 186 | computed: { 187 | style(): { [propName: string]: string } { 188 | return { 189 | width: this.width + 'px', 190 | height: this.height + 'px', 191 | top: this.top + 'px', 192 | left: this.left + 'px' 193 | } 194 | }, 195 | klass(): { [propName: string]: string | boolean } { 196 | return { 197 | [this.classNameActive]: this.enable, 198 | [this.classNameDragging]: this.dragging, 199 | [this.classNameResizing]: this.resizing, 200 | [this.classNameDraggable]: this.draggable, 201 | [this.classNameResizable]: this.resizable 202 | } 203 | } 204 | }, 205 | mounted() { 206 | if (!this.containerRef) return 207 | this.containerRef.ondragstart = () => false 208 | const { width, height } = getElSize(this.containerRef) 209 | this.setWidth(this.initW === null ? this.w || width : this.initW) 210 | this.setHeight(this.initH === null ? this.h || height : this.initH) 211 | if (this.containerProvider) { 212 | this.containerProvider.updatePosition(this.id, { 213 | x: this.left, 214 | y: this.top, 215 | w: this.width, 216 | h: this.height 217 | }) 218 | } 219 | }, 220 | render() { 221 | return h( 222 | 'div', 223 | { 224 | ref: 'containerRef', 225 | class: ['vdr-container', this.klass], 226 | style: this.style 227 | }, 228 | [ 229 | this.$slots.default && this.$slots.default(), 230 | ...this.handlesFiltered.map((item) => 231 | h('div', { 232 | class: [ 233 | 'vdr-handle', 234 | 'vdr-handle-' + item, 235 | this.classNameHandle, 236 | `${this.classNameHandle}-${item}` 237 | ], 238 | style: { display: this.enable ? 'block' : 'none' }, 239 | onMousedown: (e: MouseEvent) => this.resizeHandleDown(e, item), 240 | onTouchstart: (e: TouchEvent) => this.resizeHandleDown(e, item) 241 | }) 242 | ) 243 | ] 244 | ) 245 | } 246 | }) 247 | 248 | export default VueDraggableResizable 249 | -------------------------------------------------------------------------------- /src/components/draggable/conflict.ts: -------------------------------------------------------------------------------- 1 | function formatTransformVal(tl, tt) { 2 | let left = Number(tl.replace('px', '')) 3 | let top = Number(tt.replace('px', '')) 4 | return [left, top] 5 | } 6 | export function conflict(top, left, width, height, nodes, oneself) { 7 | for (const item of nodes) { 8 | //去除无用text元素及自身dom元素进行对比 9 | if (item.className !== undefined && !item.className.includes(oneself.className)) { 10 | const tw = item.offsetWidth 11 | const th = item.offsetHeight 12 | // 获取left与right 13 | const [tl, tt] = formatTransformVal(item.style.left, item.style.top) 14 | // 左上角与右下角重叠 15 | const tfAndBr = 16 | (top >= tt && left >= tl && tt + th > top && tl + tw > left) || 17 | (top <= tt && left < tl && top + height > tt && left + width > tl) 18 | // 右上角与左下角重叠 19 | const brAndTf = 20 | (left <= tl && top >= tt && left + width > tl && top < tt + th) || 21 | (top < tt && left > tl && top + height > tt && left < tl + tw) 22 | // 下边与上边重叠 23 | const bAndT = 24 | (top <= tt && left >= tl && top + height > tt && left < tl + tw) || 25 | (top >= tt && left <= tl && top < tt + th && left > tl + tw) 26 | // 上边与下边重叠(宽度不一样) 27 | const tAndB = 28 | (top <= tt && left >= tl && top + height > tt && left < tl + tw) || 29 | (top >= tt && left <= tl && top < tt + th && left > tl + tw) 30 | // 左边与右边重叠 31 | const lAndR = 32 | (left >= tl && top >= tt && left < tl + tw && top < tt + th) || 33 | (top > tt && left <= tl && left + width > tl && top < tt + th) 34 | // 左边与右边重叠(高度不一样) 35 | const rAndL = 36 | (top <= tt && left >= tl && top + height > tt && left < tl + tw) || 37 | (top >= tt && left <= tl && top < tt + th && left + width > tl) 38 | 39 | // // 如果冲突,就返回状态 40 | if (tfAndBr || brAndTf || bAndT || tAndB || lAndR || rAndL) { 41 | return true 42 | } else { 43 | return false 44 | } 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/components/draggable/index.css: -------------------------------------------------------------------------------- 1 | .vdr-container { 2 | position: absolute; 3 | border: 1px solid transparent; 4 | box-sizing: border-box; 5 | } 6 | .vdr-container.active { 7 | border-color: #000; 8 | border-style: dashed; 9 | } 10 | .vdr-container.dragging { 11 | border-color: #000; 12 | border-style: solid; 13 | } 14 | .vdr-handle { 15 | box-sizing: border-box; 16 | position: absolute; 17 | width: 7px; 18 | height: 7px; 19 | background: #f0f0f0; 20 | border: 1px solid #333; 21 | } 22 | .vdr-handle-tl { 23 | top: -4px; 24 | left: -4px; 25 | cursor: nw-resize; 26 | } 27 | .vdr-handle-tm { 28 | top: -4px; 29 | left: 50%; 30 | margin-left: -3px; 31 | cursor: n-resize; 32 | } 33 | .vdr-handle-tr { 34 | top: -4px; 35 | right: -4px; 36 | cursor: ne-resize; 37 | } 38 | .vdr-handle-ml { 39 | top: 50%; 40 | margin-top: -3px; 41 | left: -4px; 42 | cursor: w-resize; 43 | } 44 | .vdr-handle-mr { 45 | top: 50%; 46 | margin-top: -3px; 47 | right: -4px; 48 | cursor: e-resize; 49 | } 50 | .vdr-handle-bl { 51 | bottom: -4px; 52 | left: -4px; 53 | cursor: sw-resize; 54 | } 55 | .vdr-handle-bm { 56 | bottom: -4px; 57 | left: 50%; 58 | margin-left: -4px; 59 | cursor: s-resize; 60 | } 61 | .vdr-handle-br { 62 | bottom: -4px; 63 | right: -4px; 64 | cursor: se-resize; 65 | } 66 | -------------------------------------------------------------------------------- /src/components/draggable/types.ts: -------------------------------------------------------------------------------- 1 | import { Ref } from 'vue' 2 | import { initParent } from './hooks' 3 | 4 | export interface Position { 5 | x: number 6 | y: number 7 | w: number 8 | h: number 9 | } 10 | 11 | export interface PositionStore { 12 | [propName: string]: Position 13 | } 14 | 15 | export type UpdatePosition = (id: string, position: Position) => void 16 | 17 | export type GetPositionStore = (excludeId?: string) => PositionStore 18 | 19 | export interface ContainerProvider { 20 | updatePosition: UpdatePosition 21 | getPositionStore: GetPositionStore 22 | setMatchedLine: SetMatchedLine 23 | disabled: Ref 24 | adsorbParent: Ref 25 | adsorbCols: number[] 26 | adsorbRows: number[] 27 | } 28 | 29 | export interface MatchedLine { 30 | row: number[] 31 | col: number[] 32 | } 33 | export type SetMatchedLine = (matchedLine: MatchedLine | null) => void 34 | 35 | export type ResizingHandle = 'tl' | 'tm' | 'tr' | 'ml' | 'mr' | 'bl' | 'bm' | 'br' | '' 36 | 37 | export type ParentSize = ReturnType 38 | 39 | export type ReferenceLineMap = Record< 40 | 'col' | 'row', 41 | { 42 | [propName: number]: Record<'min' | 'max' | 'value', number> 43 | } 44 | > 45 | -------------------------------------------------------------------------------- /src/components/draggable/utils.ts: -------------------------------------------------------------------------------- 1 | import { ContainerProvider, ParentSize, ReferenceLineMap, ResizingHandle } from './types' 2 | import { ALL_HANDLES } from './Vue3DraggableResizable' 3 | 4 | export const IDENTITY = Symbol('Vue3DraggableResizable') 5 | 6 | export function getElSize(el: Element) { 7 | const style = window.getComputedStyle(el) 8 | return { 9 | width: parseFloat(style.getPropertyValue('width')), 10 | height: parseFloat(style.getPropertyValue('height')) 11 | } 12 | } 13 | 14 | function createEventListenerFunction(type: 'addEventListener' | 'removeEventListener') { 15 | return (el: HTMLElement, events: K | K[], handler: any) => { 16 | if (!el) { 17 | return 18 | } 19 | if (typeof events === 'string') { 20 | events = [events] 21 | } 22 | events.forEach((e) => el[type](e, handler, { passive: false })) 23 | } 24 | } 25 | 26 | export const addEvent = createEventListenerFunction('addEventListener') 27 | 28 | export const removeEvent = createEventListenerFunction('removeEventListener') 29 | 30 | export function filterHandles(handles: ResizingHandle[]) { 31 | if (handles && handles.length > 0) { 32 | const result: ResizingHandle[] = [] 33 | handles.forEach((item) => { 34 | if (ALL_HANDLES.includes(item) && !result.includes(item)) { 35 | result.push(item) 36 | } 37 | }) 38 | return result 39 | } else { 40 | return [] 41 | } 42 | } 43 | 44 | export function getId() { 45 | return String(Math.random()).substr(2) + String(Date.now()) 46 | } 47 | 48 | export function getReferenceLineMap( 49 | containerProvider: ContainerProvider, 50 | parentSize: ParentSize, 51 | id?: string 52 | ) { 53 | if (containerProvider.disabled.value) { 54 | return null 55 | } 56 | const referenceLine = { 57 | row: [] as number[], 58 | col: [] as number[] 59 | } 60 | const { parentWidth, parentHeight } = parentSize 61 | referenceLine.row.push(...containerProvider.adsorbRows) 62 | referenceLine.col.push(...containerProvider.adsorbCols) 63 | if (containerProvider.adsorbParent.value) { 64 | referenceLine.row.push(0, parentHeight.value, parentHeight.value / 2) 65 | referenceLine.col.push(0, parentWidth.value, parentWidth.value / 2) 66 | } 67 | const widgetPositionStore = containerProvider.getPositionStore(id) 68 | Object.values(widgetPositionStore).forEach(({ x, y, w, h }) => { 69 | referenceLine.row.push(y, y + h, y + h / 2) 70 | referenceLine.col.push(x, x + w, x + w / 2) 71 | }) 72 | const referenceLineMap: ReferenceLineMap = { 73 | row: referenceLine.row.reduce((pre, cur) => { 74 | return { ...pre, [cur]: { min: cur - 5, max: cur + 5, value: cur } } 75 | }, {}), 76 | col: referenceLine.col.reduce((pre, cur) => { 77 | return { ...pre, [cur]: { min: cur - 5, max: cur + 5, value: cur } } 78 | }, {}) 79 | } 80 | return referenceLineMap 81 | } 82 | -------------------------------------------------------------------------------- /src/components/hader-menu/index.jsx: -------------------------------------------------------------------------------- 1 | import { defineComponent } from 'vue' 2 | import menuItem from './item' 3 | import style from '../style/list.module.scss' 4 | export default defineComponent({ 5 | props: {}, 6 | emits: [], 7 | components: {}, 8 | setup(props, ctx) { 9 | const fullPath = '1' 10 | return () => ( 11 | <> 12 | 19 | 20 | 21 | 22 | ) 23 | } 24 | }) 25 | -------------------------------------------------------------------------------- /src/components/hader-menu/item.jsx: -------------------------------------------------------------------------------- 1 | import { defineComponent } from 'vue' 2 | import { partsList } from './type' 3 | import style from '../style/list.module.scss' 4 | import { importImg } from '../../utils/tool' 5 | import { ChartConfig } from '@/hooks/userCharts' 6 | export default defineComponent({ 7 | props: {}, 8 | emits: [], 9 | components: {}, 10 | setup(props, ctx) { 11 | const renderSubMenu = (item, index) => { 12 | return ( 13 | { 18 | return 19 | } 20 | }} 21 | > 22 | {item.chart.map((citem, cindex) => renderSubMenuCh(index, cindex, citem))} 23 | 24 | ) 25 | } 26 | const renderSubMenuCh = (index, cindex, citem) => { 27 | return ( 28 | { 32 | return ( 33 | <> 34 | 35 | {citem.title} 36 | 37 | ) 38 | } 39 | }} 40 | > 41 | {citem.data.map((ditem, dindex) => renderMenuItem(ditem, dindex))} 42 | 43 | ) 44 | } 45 | const renderMenuItem = (ditem, dindex) => { 46 | return ( 47 |
{ 50 | ChartConfig.chartList.push({ 51 | name: ditem.name, 52 | id: ChartConfig.chartList.length + 1, 53 | type: ditem.type, 54 | code: ditem.code, 55 | w: 300, 56 | h: 200, 57 | x: 0, 58 | y: 0, 59 | enble: false, 60 | options: '', 61 | border: { 62 | type: '3', 63 | color: '', 64 | width: 0, 65 | style: '', 66 | num: 0 67 | } 68 | }) 69 | }} 70 | > 71 | 72 |

{ditem.name}

73 |
74 | ) 75 | } 76 | return () => ( 77 | <> 78 | {partsList.map((item, index) => renderSubMenu(item, index))} 79 | {/* return ( 80 | { 85 | return 86 | } 87 | }} 88 | > 89 | {item.chart.map((citem, cindex) => { 90 | return ( 91 | { 95 | return ( 96 | <> 97 | 102 | {citem.title} 103 | 104 | ) 105 | } 106 | }} 107 | > 108 | {citem.data.map((ditem, dindex) => { 109 | return ( 110 |
{ 113 | ChartConfig.chartList.push({ 114 | name: ditem.name, 115 | id: ChartConfig.chartList.length + 1, 116 | type: ditem.type, 117 | w: 200, 118 | h: 200, 119 | x: 0, 120 | y: 0, 121 | enble: false 122 | }) 123 | }} 124 | > 125 | 126 |

{ditem.name}

127 |
128 | ) 129 | })} 130 |
131 | ) 132 | })} 133 |
134 | ) 135 | })} */} 136 | 137 | ) 138 | } 139 | }) 140 | -------------------------------------------------------------------------------- /src/components/hader-menu/type.ts: -------------------------------------------------------------------------------- 1 | export const partsList = [ 2 | { 3 | icon: 'bar', 4 | chart: [ 5 | { 6 | title: '柱状图', 7 | icon: 'bar', 8 | data: [ 9 | { 10 | name: '柱状图', 11 | type: 'bar', 12 | code: 1 13 | } 14 | ] 15 | }, 16 | { 17 | title: '折线图', 18 | icon: 'line', 19 | data: [ 20 | { 21 | name: '折线图', 22 | type: 'line', 23 | code: 1 24 | } 25 | ] 26 | }, 27 | { 28 | title: '饼状图', 29 | icon: 'pie', 30 | data: [ 31 | { 32 | name: '基础饼图', 33 | type: 'pie', 34 | code: 1 35 | }, 36 | { 37 | name: '环形饼图', 38 | type: 'pie', 39 | code: 2 40 | }, 41 | { 42 | name: '南丁格尔玫瑰图', 43 | type: 'pie', 44 | code: 3 45 | } 46 | ] 47 | }, 48 | // { 49 | // title: '环形图', 50 | // icon: 'circle', 51 | // data: [ 52 | // { 53 | // name: '环形图', 54 | // type: 'circle', 55 | // code: 1 56 | // } 57 | // ] 58 | // }, 59 | { 60 | title: '雷达图', 61 | icon: 'radar', 62 | data: [ 63 | { 64 | name: '雷达图', 65 | type: 'radar', 66 | code: 1 67 | } 68 | ] 69 | } 70 | ] 71 | }, 72 | { 73 | icon: 'table', 74 | chart: [ 75 | { 76 | title: '普通表格', 77 | icon: 'table', 78 | data: [ 79 | { 80 | name: '普通表格', 81 | type: 'table', 82 | code: 1 83 | } 84 | ] 85 | }, 86 | { 87 | title: '轮播表格', 88 | icon: 'table', 89 | data: [ 90 | { 91 | name: '轮播表格', 92 | type: 'rotation', 93 | code: 1 94 | } 95 | ] 96 | } 97 | ] 98 | }, 99 | { 100 | icon: 'text', 101 | chart: [ 102 | { 103 | title: '文本类', 104 | icon: 'text', 105 | data: [ 106 | { 107 | name: '文本框', 108 | type: 'text', 109 | code: 1 110 | } 111 | ] 112 | } 113 | ] 114 | }, 115 | { 116 | icon: 'map', 117 | chart: [ 118 | { 119 | title: '离线地图', 120 | icon: 'text', 121 | data: [ 122 | { 123 | name: '基础色彩图', 124 | type: 'map', 125 | code: 1 126 | } 127 | ] 128 | } 129 | ] 130 | } 131 | ] 132 | -------------------------------------------------------------------------------- /src/components/list-component.jsx: -------------------------------------------------------------------------------- 1 | import { defineComponent, onMounted, reactive, watch } from 'vue' 2 | import list from './style/list.module.scss' 3 | export default defineComponent({ 4 | props: { 5 | totalelements: { 6 | type: Number, 7 | default:100 8 | } 9 | }, 10 | emits: ['sizeChange', 'pageChange'], 11 | setup(props, { emit, slots }) { 12 | const data = reactive({ 13 | pageNum: 1, //页码 14 | pageSize: 10, //条数 15 | totalelements:100 //总条数 16 | }) 17 | //监听页码改变 18 | watch( 19 | () => data.pageNum, 20 | (newVal, oldVal) => { 21 | emit('pageChange', newVal) 22 | } 23 | ) 24 | //监听页数改变 25 | watch( 26 | () => data.pageSize, 27 | (newVal, oldVal) => { 28 | emit('sizeChange', newVal) 29 | } 30 | ) 31 | watch(props,(newVal, oldVal) => { 32 | data.totalelements = newVal.totalelements 33 | } 34 | ) 35 | return () => ( 36 | <> 37 |
38 |
39 |

{slots.left && slots.left()}

40 |

{slots.right && slots.right()}

41 |
42 |
{slots.default && slots.default()}
43 |
44 | 53 |
54 |
55 | 56 | ) 57 | } 58 | }) 59 | -------------------------------------------------------------------------------- /src/components/ruler/index.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 144 | 145 | 280 | -------------------------------------------------------------------------------- /src/components/style/list.module.scss: -------------------------------------------------------------------------------- 1 | .list_main { 2 | width: 100%; 3 | height: 100%; 4 | padding: 0 10px; 5 | .list_header { 6 | height: 50px; 7 | display: flex; 8 | justify-content: space-between; 9 | align-items: center; 10 | padding: 10px; 11 | box-sizing: border-box; 12 | .list_left { 13 | font-weight: 600; 14 | } 15 | } 16 | .list_body { 17 | width: 100%; 18 | height: calc(100% - 50px - 72px); 19 | overflow: auto; 20 | } 21 | .list_page { 22 | margin: 20px 0px; 23 | } 24 | } 25 | //图表类型样式 26 | .item_main { 27 | width: 500px; 28 | overflow: auto; 29 | .item_header { 30 | width: 100%; 31 | height: 40px; 32 | line-height: 40px; 33 | text-align: left; 34 | border-bottom: 1px solid #ccc; 35 | color: #108cee; 36 | } 37 | //单一类型图表 38 | .senior { 39 | display: flex; 40 | justify-content: flex-start; 41 | align-items: center; 42 | .senior_item { 43 | width: 80px; 44 | height: 80px; 45 | display: flex; 46 | justify-content: center; 47 | flex-wrap: wrap; 48 | font-size: 12px; 49 | cursor: pointer; 50 | img { 51 | width: 55px; 52 | height: 44px; 53 | } 54 | p { 55 | height: 25px; 56 | line-height: 25px; 57 | } 58 | } 59 | } 60 | //多种类型图表 61 | .item_body { 62 | width: 100%; 63 | display: flex; 64 | justify-content: space-between; 65 | margin-top: 20px; 66 | .item_item { 67 | width: 80px; 68 | height: 80px; 69 | display: flex; 70 | justify-content: center; 71 | flex-wrap: wrap; 72 | font-size: 12px; 73 | cursor: pointer; 74 | img { 75 | width: 55px; 76 | height: 44px; 77 | } 78 | p { 79 | height: 25px; 80 | line-height: 25px; 81 | } 82 | } 83 | } 84 | } 85 | //选择背景 86 | .back_main { 87 | width: 100%; 88 | padding: 10px; 89 | .back_header { 90 | width: 100%; 91 | height: 21px; 92 | line-height: 21px; 93 | text-align: left; 94 | font-size: 12px; 95 | margin-bottom: 5px; 96 | } 97 | .slider{ 98 | margin-top: 20px; 99 | margin-bottom: 0px!important; 100 | } 101 | .back_content { 102 | width: 100%; 103 | .back_select { 104 | width: 100%; 105 | height: 160px; 106 | border: 1px solid #333; 107 | } 108 | } 109 | :global(.el-row){ 110 | justify-content: space-between; 111 | padding: 0 5px; 112 | margin-bottom: 20px; 113 | } 114 | :global(.el-color-picker__trigger){ 115 | border: none; 116 | } 117 | } 118 | .avatar_uploader { 119 | :global(.el-upload) { 120 | border: 1px dashed #d9d9d9; 121 | border-radius: 6px; 122 | cursor: pointer; 123 | position: relative; 124 | overflow: hidden; 125 | } 126 | } 127 | .avatar_uploader { 128 | :global(.el-upload:hover) { 129 | border-color: #409eff; 130 | } 131 | } 132 | .avatar_uploader_icon { 133 | font-size: 28px; 134 | color: #8c939d; 135 | width: 178px; 136 | height: 178px; 137 | text-align: center; 138 | } 139 | .avatar_uploader_icon svg { 140 | margin-top: 74px; /* (178px - 28px) / 2 - 1px */ 141 | } 142 | .avatar { 143 | width: 178px; 144 | height: 178px; 145 | display: block; 146 | } 147 | 148 | ////菜单栏 149 | .menu_menu { 150 | width: auto; 151 | display: flex; 152 | height: 100%; 153 | :global(.el-sub-menu) { 154 | &:hover { 155 | background-color: #1a1e20 !important; 156 | } 157 | } 158 | :global(.el-sub-menu__title) { 159 | padding: 10px !important; 160 | height: 100%; 161 | } 162 | :global(.el-sub-menu__title) { 163 | background: #1d1e1f !important; 164 | } 165 | } 166 | .menu_sub { 167 | width: 80px; 168 | } 169 | ._icon { 170 | margin-right: 5px; 171 | } 172 | .menu_item{ 173 | padding: 8px; 174 | width: 150px; 175 | height: 120px; 176 | cursor: pointer; 177 | &:hover{ 178 | img{ 179 | border:1px solid rgb(59, 149, 218); 180 | } 181 | } 182 | img { 183 | width: 100%; 184 | box-sizing: border-box; 185 | height: 90px; 186 | } 187 | p{ 188 | margin-top: 5px; 189 | text-align: center; 190 | font-size: 12px; 191 | color: #00a680; 192 | } 193 | } 194 | .menu_body{ 195 | width: 100%; 196 | } -------------------------------------------------------------------------------- /src/components/svgIcon.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 25 | 26 | -------------------------------------------------------------------------------- /src/components/title.vue: -------------------------------------------------------------------------------- 1 | 4 | 12 | -------------------------------------------------------------------------------- /src/components/widget/build.ts: -------------------------------------------------------------------------------- 1 | import tableType from './table.vue' 2 | import titleType from './title.vue' 3 | import map from './map.vue' 4 | export default { 5 | chartBuild: { 6 | table: tableType, 7 | text: titleType, 8 | map: map 9 | }, 10 | build(type: string) { 11 | return this.chartBuild[type] 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/components/widget/chart-parts.vue: -------------------------------------------------------------------------------- 1 | 70 | 76 | -------------------------------------------------------------------------------- /src/components/widget/map.vue: -------------------------------------------------------------------------------- 1 | 82 | -------------------------------------------------------------------------------- /src/components/widget/table.vue: -------------------------------------------------------------------------------- 1 | 90 | 121 | -------------------------------------------------------------------------------- /src/components/widget/title.vue: -------------------------------------------------------------------------------- 1 | 70 | 73 | -------------------------------------------------------------------------------- /src/components/widgetBuilds/bar/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "xAxis": { "type": "category", "data": ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"] }, 3 | "yAxis": { "type": "value" }, 4 | "series": [ 5 | { 6 | "data": [120, 200, 150, 80, 70, 110, 130], 7 | "type": "bar", 8 | "showBackground": true, 9 | "backgroundStyle": { "color": "rgba(180, 180, 180, 0.2)" }, 10 | "barWidth": 20 11 | } 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /src/components/widgetBuilds/bar/config.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | build() { 3 | return { 4 | title: { 5 | x: 'center', 6 | show: true, 7 | text: '主标题', 8 | textStyle: { 9 | color: 'red', 10 | fontSize: 12 11 | } 12 | }, 13 | tooltip: { 14 | show: true, 15 | trigger: 'item', 16 | axisPointer: { 17 | type: 'shadow', 18 | axis: 'auto' 19 | }, 20 | padding: 5, 21 | textStyle: { 22 | color: '#333' 23 | } 24 | }, 25 | xAxis: { 26 | name: '', 27 | show: true, 28 | type: 'category', 29 | data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'], 30 | splitLine: { 31 | show: false, 32 | lineStyle: { 33 | color: 'red', 34 | width: 1, 35 | type: 'solid' 36 | } 37 | }, 38 | axisLabel: { 39 | show: true, 40 | inside: false, 41 | rotate: 0, 42 | margin: 5, 43 | fontFamily: '微软雅黑', 44 | color: 'red', 45 | fontSize: 12 46 | } 47 | }, 48 | yAxis: { 49 | show: true, 50 | name: '销量', 51 | type: 'value', 52 | splitLine: { 53 | show: false, 54 | lineStyle: { 55 | color: 'red', 56 | width: 1, 57 | type: 'solid' 58 | } 59 | }, 60 | axisLabel: { 61 | show: true, 62 | color: 'red', 63 | fontSize: 12 64 | } 65 | }, 66 | grid: { 67 | left: 40, 68 | top: 50, 69 | right: 20, 70 | bottom: 20 71 | }, 72 | series: [ 73 | { 74 | data: [120, 200, 150, 80, 70, 110, 130], 75 | type: 'bar', 76 | showBackground: true, 77 | itemStyle: { 78 | color: 'red', 79 | borderRadius: [18] 80 | }, 81 | label: { 82 | show: true, 83 | position: 'top', 84 | color: '#fff', 85 | fontSize: 13 86 | }, 87 | backgroundStyle: { 88 | color: 'rgba(180, 180, 180, 0.2)' 89 | }, 90 | barWidth: 15 91 | } 92 | ] 93 | } 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/components/widgetBuilds/index.ts: -------------------------------------------------------------------------------- 1 | import barConfig from './bar/config' 2 | import lineConfig from './line/config' 3 | import textConfig from './text/config' 4 | import tableConfig from './table/config' 5 | import pieConfig from './pie/config' 6 | import radarConfig from './radar/config' 7 | import mapConfig from './map/config' 8 | import axios from 'axios' 9 | export default { 10 | chartBuild: { 11 | bar: barConfig, 12 | line: lineConfig, 13 | text: textConfig, 14 | table: tableConfig, 15 | pie: pieConfig, 16 | radar: radarConfig, 17 | map:mapConfig 18 | }, 19 | build(type: string, code: number) { 20 | return this.chartBuild[type].build(code) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/components/widgetBuilds/line/config.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | build() { 3 | return { 4 | title: { 5 | x: 'center', 6 | show: true, 7 | text: '主标题', 8 | textStyle: { 9 | color: '', 10 | fontSize: 12 11 | } 12 | }, 13 | tooltip: { 14 | show: true, 15 | trigger: 'item', 16 | axisPointer: { 17 | type: 'shadow', 18 | axis: 'auto' 19 | }, 20 | padding: 5, 21 | textStyle: { 22 | color: '#333' 23 | } 24 | }, 25 | xAxis: { 26 | name: '', 27 | show: true, 28 | type: 'category', 29 | data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'], 30 | splitLine: { 31 | show: false, 32 | lineStyle: { 33 | color: 'red', 34 | width: 1, 35 | type: 'solid' 36 | } 37 | }, 38 | axisLabel: { 39 | show: true, 40 | inside: false, 41 | rotate: 0, 42 | margin: 5, 43 | fontFamily: '微软雅黑', 44 | color: 'red', 45 | fontSize: 12 46 | } 47 | }, 48 | yAxis: { 49 | show: true, 50 | name: '', 51 | type: 'value', 52 | splitLine: { 53 | show: false, 54 | lineStyle: { 55 | color: 'red', 56 | width: 1, 57 | type: 'solid' 58 | } 59 | }, 60 | axisLabel: { 61 | show: true, 62 | color: 'red', 63 | fontSize: 12 64 | } 65 | }, 66 | grid: { 67 | left: 40, 68 | top: 50, 69 | right: 20, 70 | bottom: 20 71 | }, 72 | series: [ 73 | { 74 | data: [150, 230, 224, 218, 135, 147, 260], 75 | type: 'line', 76 | showSymbol: false, 77 | smooth: true, 78 | symbolSize: 5, 79 | lineStyle: { 80 | width: 1 81 | }, 82 | label: { 83 | show: true, 84 | position: 'top', 85 | color: '#fff', 86 | fontSize: 13 87 | }, 88 | areaStyle: { 89 | opacity: 1 90 | } 91 | } 92 | ] 93 | } 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/components/widgetBuilds/map/config.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | build(code: number) { 3 | return { 4 | // geo: { 5 | // map: 'china', 6 | // roam: false, 7 | // zoom: 1.2, 8 | // scaleLimit: { min: 0, max: 3 }, // 缩放级别 9 | // regions: [ 10 | // { 11 | // name: '南海诸岛', 12 | // value: 0, 13 | // itemStyle: { 14 | // normal: { 15 | // opacity: 0, 16 | // label: { 17 | // show: false 18 | // } 19 | // } 20 | // } 21 | // } 22 | // ], 23 | // itemStyle: { 24 | // areaColor: '#BEDAEE', //默认的地图板块颜色 25 | // borderWidth: 1, 26 | // borderColor: '#009ce0' 27 | // } 28 | // }, 29 | title: { 30 | text: 'Referer of a Website', 31 | left: 'left', 32 | show: true, 33 | textStyle: { 34 | color: '', 35 | fontSize: 12 36 | } 37 | }, 38 | tooltip: { 39 | trigger: 'item' 40 | }, 41 | dataRange: { 42 | x: '-1000 px', //图例横轴位置 43 | y: '-1000 px', //图例纵轴位置 44 | splitList: [] 45 | }, //各省地图颜色;start:值域开始值;end:值域结束值;label:图例名称;color:自定义颜色值; 46 | series: [ 47 | { 48 | name: '数据', 49 | type: 'map', 50 | mapType: 'china', 51 | selectedMode: 'multiple', 52 | zoom:1, 53 | itemStyle: { 54 | normal: { 55 | //未选中状态 56 | borderWidth: 1, //边框大小 57 | borderColor: 'lightgreen', 58 | areaColor: '#4a8bfe', //背景颜色 59 | label: { 60 | show: false //显示名称 61 | } 62 | }, 63 | emphasis: { 64 | // 也是选中样式 65 | borderWidth: 1, 66 | borderColor: '#fff', 67 | areaColor: '#416a94', 68 | label: { 69 | show: true, 70 | textStyle: { 71 | color: '#fff' 72 | } 73 | } 74 | } 75 | }, 76 | data: [] 77 | } 78 | ] 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/components/widgetBuilds/map/index.jsx: -------------------------------------------------------------------------------- 1 | import { defineComponent, onMounted, ref, watch, reactive } from 'vue' 2 | import { ChartConfig } from '@/hooks/userCharts' 3 | export default defineComponent({ 4 | props: { 5 | id: Number, //活跃状态的组件 6 | options: String 7 | }, 8 | emits: [], 9 | components: {}, 10 | setup(props, ctx) { 11 | const formData = reactive(JSON.parse(props.options)) 12 | watch(formData, (val) => { 13 | ChartConfig.chartList.forEach((item, index) => { 14 | if (item.id === props.id) { 15 | item.options = JSON.stringify(formData) 16 | } 17 | }) 18 | }) 19 | return () => ( 20 | <> 21 | 22 | {/* 地图设置 */} 23 | 24 | {/* 25 | 26 | 27 | 28 | 29 | 30 | */} 31 | 32 | 33 | 34 | {/* 35 | 36 | */} 37 | 38 | { 43 | return ( 44 | 48 | ) 49 | } 50 | }} 51 | > 52 | 53 | 54 | { 59 | return ( 60 | 64 | ) 65 | } 66 | }} 67 | > 68 | 69 | 70 | 71 | 72 | {/* 73 | 74 | */} 75 | 76 | 77 | 78 | { 83 | return ( 84 | 88 | ) 89 | } 90 | }} 91 | > 92 | 93 | 94 | { 99 | return ( 100 | 104 | ) 105 | } 106 | }} 107 | > 108 | 109 | 110 | {/* 标题设置 */} 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | { 124 | return 125 | } 126 | }} 127 | > 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | ) 143 | } 144 | }) 145 | -------------------------------------------------------------------------------- /src/components/widgetBuilds/pie/config.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | build(code: number) { 3 | const radius = { 4 | 1: '50%', 5 | 2: ['40%', '55%'], 6 | 3: [20, 100] 7 | } 8 | const roseType = { 9 | 1: false, 10 | 2: false, 11 | 3: 'radius' 12 | } 13 | return { 14 | title: { 15 | text: 'Referer of a Website', 16 | left: 'left', 17 | show: true, 18 | textStyle: { 19 | color: '', 20 | fontSize: 12 21 | } 22 | }, 23 | tooltip: { 24 | trigger: 'item', 25 | show: false, 26 | textStyle: { 27 | fontSize: 12, 28 | color: '#333' 29 | } 30 | }, 31 | legend: { 32 | orient: 'vertical', 33 | left: 'left', 34 | y: 'center', 35 | show: false, 36 | textStyle: { 37 | color: '#fff', 38 | fontSize: 16 39 | } 40 | }, 41 | color: [ 42 | 'rgb(203,155,255)', 43 | 'rgb(149,162,255)', 44 | 'rgb(58,186,255)', 45 | 'rgb(119,168,249)', 46 | 'rgb(235,161,159)' 47 | ], 48 | series: [ 49 | { 50 | name: 'Access From', 51 | type: 'pie', 52 | radius: radius[code], 53 | center: ['50%', '50%'], 54 | roseType: roseType[code], 55 | labelLine: { 56 | show: false 57 | }, 58 | label: { 59 | show: false, 60 | color: '#fff', 61 | fontSize: 13 62 | }, 63 | data: [ 64 | { value: 1048, name: 'Search Engine' }, 65 | { value: 735, name: 'Direct' }, 66 | { value: 580, name: 'Email' }, 67 | { value: 484, name: 'Union Ads' }, 68 | { value: 300, name: 'Video Ads' } 69 | ], 70 | emphasis: { 71 | itemStyle: { 72 | shadowBlur: 10, 73 | shadowOffsetX: 0, 74 | shadowColor: 'rgba(0, 0, 0, 0.5)' 75 | } 76 | } 77 | } 78 | ] 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/components/widgetBuilds/pie/index.jsx: -------------------------------------------------------------------------------- 1 | import { defineComponent, onMounted, ref, watch, reactive, computed } from 'vue' 2 | import { ChartConfig } from '@/hooks/userCharts' 3 | export default defineComponent({ 4 | props: { 5 | id: Number, //活跃状态的组件 6 | options: String 7 | }, 8 | emits: [], 9 | components: {}, 10 | setup(props, ctx) { 11 | const formData = reactive(JSON.parse(props.options)) 12 | let ColorList = computed({ 13 | get: () => { 14 | const arr = [...formData.color] 15 | return arr 16 | }, 17 | set: (val) => { 18 | formData.color = val 19 | } 20 | }) 21 | const arr = ['red', 'yellow'] 22 | watch(formData, (val) => { 23 | ChartConfig.chartList.forEach((item, index) => { 24 | if (item.id === props.id) { 25 | item.options = JSON.stringify(formData) 26 | } 27 | }) 28 | }) 29 | return () => ( 30 | <> 31 | 32 | {/* 饼图设置 */} 33 | {/* 34 | 35 | 36 | 37 | 38 | 39 | 40 | */} 41 | {/* 标题设置 */} 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | { 55 | return 56 | } 57 | }} 58 | > 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | {/* 数值设置 */} 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | { 85 | return 86 | } 87 | }} 88 | > 89 | 90 | 91 | {/* 提示语设置 */} 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | { 105 | return ( 106 | 107 | ) 108 | } 109 | }} 110 | > 111 | 112 | 113 | {/* 图例设置 */} 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | {/* 自定义配色 */} 133 | 134 | 135 | {ColorList.value.map((item, index) => { 136 | return ( 137 | <> 138 | 139 | { 142 | formData.color[index] = e 143 | }} 144 | size="mini" 145 | v-slots={{ 146 | suffix: () => { 147 | return ( 148 | { 151 | formData.color[index] = e 152 | }} 153 | size="mini" 154 | /> 155 | ) 156 | } 157 | }} 158 | > 159 | 160 | 161 | ) 162 | })} 163 | 164 | 165 | 166 | 167 | ) 168 | } 169 | }) 170 | -------------------------------------------------------------------------------- /src/components/widgetBuilds/radar/config.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | build() { 3 | return { 4 | title: { 5 | text: '雷达', 6 | left: 'left', 7 | show: true, 8 | textStyle: { 9 | color: '', 10 | fontSize: 12 11 | } 12 | }, 13 | textStyle: { 14 | fontSize: 12, 15 | color: '' 16 | }, 17 | legend: { 18 | data: ['Allocated Budget', 'Actual Spending'], 19 | left: 'left', 20 | show: false, 21 | top: 'top', 22 | textStyle: { 23 | color: '#fff', 24 | fontSize: 16 25 | } 26 | }, 27 | color: [ 28 | 'rgb(203,155,255)', 29 | 'rgb(149,162,255)', 30 | 'rgb(58,186,255)', 31 | 'rgb(119,168,249)', 32 | 'rgb(235,161,159)' 33 | ], 34 | tooltip: { 35 | trigger: 'item', 36 | show: false, 37 | textStyle: { 38 | fontSize: 12, 39 | color: '#333' 40 | } 41 | }, 42 | radar: { 43 | // shape: 'circle', 44 | indicator: [ 45 | { name: 'Sales', max: 6500 }, 46 | { name: 'Administration', max: 16000 }, 47 | { name: 'Information Technology', max: 30000 }, 48 | { name: 'Customer Support', max: 38000 }, 49 | { name: 'Development', max: 52000 }, 50 | { name: 'Marketing', max: 25000 } 51 | ] 52 | }, 53 | series: [ 54 | { 55 | name: 'Budget vs spending', 56 | type: 'radar', 57 | label: { 58 | show: false, 59 | color: '#fff', 60 | fontSize: 13 61 | }, 62 | areaStyle: { 63 | opacity: 0.5 64 | }, 65 | data: [ 66 | { 67 | value: [4200, 3000, 20000, 35000, 50000, 18000], 68 | name: 'Allocated Budget' 69 | }, 70 | { 71 | value: [5000, 14000, 28000, 26000, 42000, 21000], 72 | name: 'Actual Spending' 73 | } 74 | ] 75 | } 76 | ] 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/components/widgetBuilds/radar/index.jsx: -------------------------------------------------------------------------------- 1 | import { defineComponent, onMounted, ref, watch, reactive, computed } from 'vue' 2 | import { ChartConfig } from '@/hooks/userCharts' 3 | export default defineComponent({ 4 | props: { 5 | id: Number, //活跃状态的组件 6 | options: String 7 | }, 8 | emits: [], 9 | components: {}, 10 | setup(props, ctx) { 11 | const formData = reactive(JSON.parse(props.options)) 12 | let ColorList = computed({ 13 | get: () => { 14 | const arr = [...formData.color] 15 | return arr 16 | }, 17 | set: (val) => { 18 | formData.color = val 19 | } 20 | }) 21 | const opacity = computed({ 22 | get: () => { 23 | const [row] = formData.series 24 | return row.areaStyle.opacity * 100 25 | }, 26 | set: (val) => { 27 | formData.series[0].areaStyle.opacity = val / 100 28 | } 29 | }) 30 | watch(formData, (val) => { 31 | ChartConfig.chartList.forEach((item, index) => { 32 | if (item.id === props.id) { 33 | item.options = JSON.stringify(formData) 34 | } 35 | }) 36 | }) 37 | return () => ( 38 | <> 39 | 40 | {/* 饼图设置 */} 41 | 42 | 43 | 44 | 45 | 46 | { 51 | return 52 | } 53 | }} 54 | > 55 | 56 | 57 | 58 | 59 | 60 | {/* 标题设置 */} 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | { 74 | return 75 | } 76 | }} 77 | > 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | {/* 数值设置 */} 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | { 104 | return 105 | } 106 | }} 107 | > 108 | 109 | 110 | {/* 提示语设置 */} 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | { 124 | return ( 125 | 126 | ) 127 | } 128 | }} 129 | > 130 | 131 | 132 | {/* 图例设置 */} 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | {/* 自定义配色 */} 152 | 153 | 154 | {ColorList.value.map((item, index) => { 155 | return ( 156 | <> 157 | 158 | { 161 | formData.color[index] = e 162 | }} 163 | size="mini" 164 | v-slots={{ 165 | suffix: () => { 166 | return ( 167 | { 170 | formData.color[index] = e 171 | }} 172 | size="mini" 173 | /> 174 | ) 175 | } 176 | }} 177 | > 178 | 179 | 180 | ) 181 | })} 182 | 183 | 184 | 185 | 186 | ) 187 | } 188 | }) 189 | -------------------------------------------------------------------------------- /src/components/widgetBuilds/share/backImg.jsx: -------------------------------------------------------------------------------- 1 | import { defineComponent, reactive, watch, ref } from 'vue' 2 | import style from './style.module.scss' 3 | import { borderImg } from '@/utils/tool' 4 | import { ChartConfig } from '@/hooks/userCharts' 5 | export default defineComponent({ 6 | props: { 7 | id: Number, //活跃状态的组件 8 | options: Object 9 | }, 10 | emits: [], 11 | components: {}, 12 | setup(props, ctx) { 13 | let arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] 14 | const drawer = ref(false) 15 | const formData = reactive(props.options) 16 | watch(formData, (val) => { 17 | ChartConfig.chartList.forEach((item, index) => { 18 | if (item.id === props.id) { 19 | item.border = formData 20 | } 21 | }) 22 | }) 23 | const renderSimple = () => { 24 | return ( 25 | <> 26 | 27 | { 32 | return 33 | } 34 | }} 35 | > 36 | 37 | 38 | 39 | 40 | 41 | 42 | 实线 43 | 虚线 44 | 点状 45 | 46 | 47 | 48 | ) 49 | } 50 | const renderBuidIn = () => { 51 | return ( 52 |
53 | 54 |
{ 56 | drawer.value = true 57 | }} 58 | > 59 | 点击更换背景边框 60 |
61 |
62 | ) 63 | } 64 | const renderBorderImg = (item, index) => { 65 | return ( 66 |
{ 69 | ChartConfig.chartList.forEach((citem, cindex) => { 70 | if (citem.id === props.id) { 71 | citem.border.num = item 72 | } 73 | }) 74 | }} 75 | > 76 | 77 |
78 | ) 79 | } 80 | return () => ( 81 | <> 82 | 83 | 84 | 85 | 86 | 基础边框 87 | 内置边框 88 | 无边框 89 | 90 | 91 | {formData.type === '1' ? renderSimple() : formData.type === '2' ? renderBuidIn() : ''} 92 | 93 | 94 | 95 |
{arr.map((item, index) => renderBorderImg(item, index))}
96 |
97 | 98 | ) 99 | } 100 | }) 101 | -------------------------------------------------------------------------------- /src/components/widgetBuilds/share/style.module.scss: -------------------------------------------------------------------------------- 1 | ._select{ 2 | width: 100%; 3 | height: 200px; 4 | border: 1px solid #434b55; 5 | position: relative; 6 | cursor: pointer; 7 | h5{ 8 | position: absolute; 9 | width: 100%; 10 | height: 100%; 11 | line-height: 200px; 12 | top: 0; 13 | left: 0; 14 | color: white; 15 | } 16 | img{ 17 | width: 100%; 18 | height: 100%; 19 | } 20 | } 21 | ._draw{ 22 | width: 100%; 23 | height: 100%; 24 | display: flex; 25 | justify-content: space-between; 26 | ._item{ 27 | width: 150px; 28 | height: 100px; 29 | padding: 20px; 30 | box-sizing: content-box; 31 | border: 1px solid #434b55; 32 | cursor: pointer; 33 | &:hover{ 34 | border: 1px solid white; 35 | } 36 | img{ 37 | width: 100%; 38 | height: 100%; 39 | } 40 | } 41 | } -------------------------------------------------------------------------------- /src/components/widgetBuilds/table/config.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | build() { 3 | return { 4 | bgColor:'black', 5 | align: 'center', 6 | showHeader: true, 7 | headerColor: '', 8 | headerBackColor: '#5c7bd9', 9 | lineHeight: 20, 10 | rowColor: '', 11 | rowBackColor: '#0e2a42', 12 | fontSize: 12, 13 | isRoll: true, 14 | rollTime: 11, 15 | rollRows:10, 16 | isBorder: true, 17 | oddColor: '', 18 | eventColor:'' 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /src/components/widgetBuilds/table/index.jsx: -------------------------------------------------------------------------------- 1 | import { defineComponent, onMounted, ref, watch, reactive } from 'vue' 2 | import { ChartConfig } from '@/hooks/userCharts' 3 | export default defineComponent({ 4 | props: { 5 | id: Number, //活跃状态的组件 6 | options: String 7 | }, 8 | emits: [], 9 | components: {}, 10 | setup(props, ctx) { 11 | const formData = reactive(JSON.parse(props.options)) 12 | watch(formData, (val) => { 13 | ChartConfig.chartList.forEach((item, index) => { 14 | if (item.id === props.id) { 15 | item.options = JSON.stringify(formData) 16 | } 17 | }) 18 | }) 19 | return () => ( 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | { 47 | return 48 | } 49 | }} 50 | > 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | { 69 | return 70 | } 71 | }} 72 | > 73 | 74 | 75 | { 80 | return 81 | } 82 | }} 83 | > 84 | 85 | 86 | 87 | 88 | { 93 | return 94 | } 95 | }} 96 | > 97 | 98 | 99 | { 104 | return 105 | } 106 | }} 107 | > 108 | 109 | {/* 110 | { 115 | return 116 | } 117 | }} 118 | > 119 | 120 | 121 | { 126 | return 127 | } 128 | }} 129 | > 130 | */} 131 | 132 | 133 | ) 134 | } 135 | }) 136 | -------------------------------------------------------------------------------- /src/components/widgetBuilds/text/config.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | build() { 3 | return { 4 | name: '标题', 5 | fontSize: 12, 6 | fontColor: 'red', 7 | letterSpacing: 10, 8 | backColor: '', 9 | fontWeight: '500', 10 | textAlign: 'center', 11 | lineHeight:10 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /src/components/widgetBuilds/text/index.jsx: -------------------------------------------------------------------------------- 1 | import { defineComponent, onMounted, ref, watch, reactive } from 'vue' 2 | import { ChartConfig } from '@/hooks/userCharts' 3 | export default defineComponent({ 4 | props: { 5 | id: Number, //活跃状态的组件 6 | options: String 7 | }, 8 | emits: [], 9 | components: {}, 10 | setup(props, ctx) { 11 | const formData = reactive(JSON.parse(props.options)) 12 | watch(formData, (val) => { 13 | ChartConfig.chartList.forEach((item, index) => { 14 | if (item.id === props.id) { 15 | item.options = JSON.stringify(formData) 16 | } 17 | }) 18 | }) 19 | return () => ( 20 |
21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | { 33 | return 34 | } 35 | }} 36 | > 37 | 38 | 39 | 40 | 41 | 42 | { 47 | return 48 | } 49 | }} 50 | > 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 |
71 | ) 72 | } 73 | }) 74 | -------------------------------------------------------------------------------- /src/components/zu-form-item.jsx: -------------------------------------------------------------------------------- 1 | import { defineComponent } from 'vue' 2 | export default defineComponent({ 3 | props: { 4 | label: { 5 | type: String, 6 | default: '' 7 | } 8 | }, 9 | emits: [], 10 | components: {}, 11 | setup(props, { emit, slots }) { 12 | return () => ( 13 | <> 14 |
15 |
{props.label}
16 |
{slots.default && slots.default()}
17 |
18 | 19 | ) 20 | } 21 | }) 22 | -------------------------------------------------------------------------------- /src/hooks/useConfig.ts: -------------------------------------------------------------------------------- 1 | // 公共状态文件:替代VUEX 2 | import { reactive } from 'vue' 3 | /** 4 | * @name: 全局样式配置 5 | * @author: yhw 6 | */ 7 | interface CONFIG { 8 | name: string //大屏名称 9 | width: number //宽度 10 | height: number //高度 11 | zoom: number //适配方式 12 | background: number //背景设置 0:内置背景 1:自定义背景 13 | builtId: number //内置背景 编号 14 | customImg: string //自定义背景图片 15 | color: string //自定义背景颜色 16 | vagueNum: number //背景图片模糊度 17 | transNum: number //背景图片透明度 18 | scale:number //缩放比例 19 | 20 | } 21 | export const config: CONFIG = reactive({ 22 | name: '新建大屏', 23 | //画布宽高 24 | width: 1920, 25 | height: 1080, 26 | zoom: 1, 27 | background: 0, 28 | builtId: 0, 29 | customImg: '', 30 | color: '#0e2a42', 31 | vagueNum: 10, 32 | transNum: 10, 33 | scale:1 34 | }) 35 | export const Marks = reactive({ 36 | 0: '0', 37 | 10: '10', 38 | 20: '20' 39 | }) 40 | //修改画布宽高 41 | export function setWH(key: string, value: number) { 42 | config[key] = value 43 | } 44 | -------------------------------------------------------------------------------- /src/hooks/useDrag.ts: -------------------------------------------------------------------------------- 1 | import { onMounted, reactive, ref } from 'vue' 2 | import { ChartConfig } from './userCharts' 3 | import Bus from 'vue3-eventbus' 4 | export function useDrag(data) { 5 | const activeObject = {} 6 | let dragMethod = { 7 | //组件按下 8 | onActivated(item) { 9 | ChartConfig.chartList.forEach((el) => { 10 | if (item.id === el.id) { 11 | el.enble = true 12 | } 13 | }) 14 | ChartConfig.isSelect = true 15 | data.isBorShow = true 16 | }, 17 | //失去焦点 18 | onDeactivated(item) { 19 | ChartConfig.chartList.forEach((el) => { 20 | if (item.id === el.id) { 21 | el.enble = false 22 | } 23 | }) 24 | ChartConfig.isSelect = false 25 | data.isBorShow = false 26 | }, 27 | //组件拖动开始 28 | onDrag(e) { 29 | data.isGridShow = true 30 | }, 31 | //组件拖动中-持续触发 32 | onDraging(event, item) { 33 | ChartConfig.chartList.forEach((el) => { 34 | if (item.id === el.id) { 35 | el.x = event.x 36 | el.y = event.y 37 | } 38 | }) 39 | }, 40 | //组件拖动完成 41 | onDragstop() { 42 | data.list = [] 43 | data.isGridShow = false 44 | data.isBorShow = false 45 | }, 46 | //改变组件大小开始 47 | onResize(e) {}, 48 | //改变组件大小中-持续触发 49 | onResizeIng(event, item) { 50 | ChartConfig.chartList.forEach((el) => { 51 | if (item.id === el.id) { 52 | el.x = event.x 53 | el.y = event.y 54 | el.w = event.w 55 | el.h = event.h 56 | } 57 | }) 58 | }, 59 | //改变组件大小结束 60 | onResizeStop(e) {} 61 | } 62 | return { 63 | ...dragMethod 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/hooks/useGetter.ts: -------------------------------------------------------------------------------- 1 | import { mapGetters, createNamespacedHelpers } from "vuex"; 2 | import { computed, ComputedRef } from "vue"; 3 | import { ModuleState } from "@/store/store"; 4 | import { useStore } from "@/store"; 5 | 6 | /** 7 | * 对store导出数据做封装 8 | */ 9 | export const useGetter = (module_name:keyof ModuleState | string[],wrapper:string[] = [])=>{ 10 | 11 | const store = useStore(); 12 | 13 | let mapFn = mapGetters; 14 | 15 | if(typeof module_name === "string"){ // 访问子模块的getter 16 | mapFn = createNamespacedHelpers(module_name).mapGetters; 17 | }else{ // 访问根模块的getter 18 | wrapper = module_name; 19 | } 20 | 21 | const storeGettersFns = mapFn(wrapper); 22 | 23 | // 对数据进行转换 24 | const storeGetter:{ [key:string]: ComputedRef} = {}; 25 | 26 | // 使用computed将状态包裹一层再返回 27 | Object.keys(storeGettersFns).forEach(fnKey => { 28 | const fn = storeGettersFns[fnKey].bind({$store: store}) 29 | storeGetter[fnKey] = computed(fn) 30 | }) 31 | 32 | return storeGetter; 33 | 34 | } -------------------------------------------------------------------------------- /src/hooks/useMethod.ts: -------------------------------------------------------------------------------- 1 | import { ModuleState } from "@/store/store"; 2 | import { mapActions,mapMutations } from "vuex"; 3 | import { useStore } from "@/store"; 4 | 5 | export const useMethod = (module_name:keyof ModuleState | string[],wrapper:string[] = [])=>{ 6 | const store = useStore(); 7 | // @ts-ignore 8 | let options = store._modules.root._rawModule; // 获取根模块的配置 9 | 10 | if(typeof module_name === "string"){ 11 | options = options.modules[module_name]; // 获取子模块的配置 12 | }else{ 13 | wrapper = module_name; 14 | } 15 | 16 | const { mutations = {},actions = {} } = options; 17 | 18 | const mutation_keys = Object.keys(mutations); 19 | 20 | const action_keys = Object.keys(actions); 21 | 22 | const action_wrapper:string[] = []; 23 | 24 | const mutation_wrapper:string[] = []; 25 | 26 | wrapper.forEach((item)=>{ // 过滤掉原始配置中不包含的方法 27 | if(mutation_keys.includes(item)){ 28 | mutation_wrapper.push(item); 29 | } 30 | if(action_keys.includes(item)){ 31 | action_wrapper.push(item); 32 | } 33 | }) 34 | 35 | const aactions = typeof module_name === "string"?mapActions(module_name, action_wrapper):mapActions(action_wrapper); 36 | 37 | const mmutations = typeof module_name === "string"?mapMutations(module_name,mutation_wrapper):mapMutations(mutation_wrapper); 38 | 39 | bindStore([aactions,mmutations]); // 不绑定store,vuex执行时会报错 40 | 41 | return [aactions,mmutations]; 42 | } 43 | 44 | function bindStore(list:any[]){ 45 | const store = useStore() as any; 46 | list.forEach((item)=>{ 47 | for(let key in item){ 48 | item[key] = item[key].bind({$store:store}); 49 | } 50 | }) 51 | } -------------------------------------------------------------------------------- /src/hooks/useState.ts: -------------------------------------------------------------------------------- 1 | import { mapState, createNamespacedHelpers } from "vuex"; 2 | import { computed, ComputedRef } from "vue"; 3 | import { ModuleState } from "@/store/store"; 4 | import { useStore } from "@/store"; 5 | 6 | /** 7 | * 对store导出数据做封装 8 | */ 9 | export const useState = (module_name:keyof ModuleState | string[],wrapper:string[] = [])=>{ 10 | 11 | const store = useStore(); 12 | 13 | let mapFn = mapState; 14 | 15 | if(typeof module_name === "string"){ // 访问子模块的状态 16 | mapFn = createNamespacedHelpers(module_name).mapState; 17 | }else{ // 访问RootState 18 | wrapper = module_name; 19 | } 20 | 21 | const storeStateFns = mapFn(wrapper); 22 | 23 | // 对数据进行转换 24 | const storeState:{ [key:string]: ComputedRef} = {}; 25 | // 使用computed将状态包裹一层再返回 26 | Object.keys(storeStateFns).forEach(fnKey => { 27 | const fn = storeStateFns[fnKey].bind({$store: store}) 28 | storeState[fnKey] = computed(fn) 29 | }) 30 | return storeState; 31 | 32 | } -------------------------------------------------------------------------------- /src/hooks/userCharts.ts: -------------------------------------------------------------------------------- 1 | import { reactive } from 'vue' 2 | interface chart { 3 | name: string 4 | x: number 5 | y: number 6 | w: number 7 | h: number 8 | type: string 9 | id: number 10 | enble: boolean 11 | options: string 12 | border: object 13 | code: number 14 | } 15 | interface ChartConfig { 16 | chartList: Array 17 | isSelect: boolean 18 | } 19 | export const ChartConfig: ChartConfig = reactive({ 20 | chartList: [ 21 | { 22 | name: '基础色彩图', 23 | id: 1, 24 | type: 'map', 25 | code: 1, 26 | w: 300, 27 | h: 200, 28 | x: 0, 29 | y: 0, 30 | enble: false, 31 | border: { 32 | type: '3', 33 | color: '', 34 | width: 0, 35 | style: '', 36 | num: 0 37 | }, 38 | options: '' 39 | }, 40 | { 41 | name: '柱状图', 42 | id: 2, 43 | type: 'bar', 44 | code: 1, 45 | w: 300, 46 | h: 200, 47 | x: 700, 48 | y: 0, 49 | enble: false, 50 | border: { 51 | type: '2', 52 | color: '', 53 | width: 0, 54 | style: '', 55 | num: 10 56 | }, 57 | options: '' 58 | } 59 | ], 60 | isSelect: false 61 | }) 62 | -------------------------------------------------------------------------------- /src/icons/svg/Number.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/svg/bar.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/svg/big.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/svg/border.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/svg/circle.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/svg/close.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/svg/data-model.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/svg/data-source.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/svg/delete.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/svg/echarts_line.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/svg/echarts_mappie.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/svg/folder_down.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/svg/folder_up.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/svg/line.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/svg/main_gods.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/svg/pie.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/svg/pre.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/svg/radar.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/svg/save.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/svg/shrink.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/svg/table.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/svg/text.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/svg/updata.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/svg/varchar.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/layout/components/index.module.scss: -------------------------------------------------------------------------------- 1 | .header_main { 2 | width: 100%; 3 | height: 50px; 4 | padding: 2px; 5 | background-color: #fff; 6 | box-sizing: border-box; 7 | display: flex; 8 | align-items: center; 9 | .shrink{ 10 | float: left; 11 | cursor: pointer; 12 | margin-left: 12px; 13 | width: 25px!important; 14 | height: 25px!important; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/layout/components/layout-header.jsx: -------------------------------------------------------------------------------- 1 | import { defineComponent } from 'vue' 2 | import header from './index.module.scss' 3 | export default defineComponent({ 4 | props: {}, 5 | emits: ['isShrink'], 6 | components: {}, 7 | setup (props, ctx) { 8 | return () => ( 9 | <> 10 |
11 | {ctx.emit('isShrink')}}> 12 |
13 | 14 | ) 15 | } 16 | }) 17 | -------------------------------------------------------------------------------- /src/layout/components/menu-item.jsx: -------------------------------------------------------------------------------- 1 | import { defineComponent, getCurrentInstance, computed } from 'vue' 2 | export default defineComponent({ 3 | props: { 4 | routeList: Array, 5 | isCollapse: Boolean 6 | }, 7 | setup(props, ctx) { 8 | const proxy = getCurrentInstance()?.proxy 9 | const currentPath = computed(() => proxy?.$route) 10 | const routeList = currentPath.value?.matched[0].children 11 | const renderMenu = (item, index) => { 12 | if (item.children) { 13 | return ( 14 | { 18 | return ( 19 | <> 20 | 25 | {item.meta.title} 26 | 27 | ) 28 | } 29 | }} 30 | > 31 | {item.children.map((chitem, chindex) => ( 32 | {chitem.meta.title} 33 | ))} 34 | 35 | ) 36 | } 37 | return ( 38 | { 42 | return {item.meta.title} 43 | } 44 | }} 45 | > 46 | 47 | 48 | ) 49 | } 50 | return () => <>{routeList.map((item, index) => renderMenu(item, index))} 51 | } 52 | }) 53 | -------------------------------------------------------------------------------- /src/layout/index.vue: -------------------------------------------------------------------------------- 1 | 2 | 5 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import { createApp } from 'vue' 2 | import zhCn from 'element-plus/es/locale/lang/zh-cn' 3 | import zuFormItem from './components/zu-form-item.jsx' 4 | import router from './route' 5 | import '../src/style/index.scss' 6 | import '../src/style/common.scss' 7 | import store, { key } from './store' 8 | import App from './App.vue' 9 | import ElementPlus from 'element-plus' 10 | import 'element-plus/dist/index.css' 11 | import * as echarts from 'echarts' 12 | import svgIcon from './components/svgIcon.vue' 13 | import eventBus from 'vue3-eventbus' 14 | const app = createApp(App) 15 | app.component('svg-icon', svgIcon) 16 | app.use(router) 17 | app.use(store, key) 18 | app.use(eventBus) 19 | app.component('zu-form-item',zuFormItem) 20 | app.use(ElementPlus, { locale: zhCn}) 21 | app.mount('#app') 22 | app.config.globalProperties.$echarts = echarts 23 | -------------------------------------------------------------------------------- /src/route/index.ts: -------------------------------------------------------------------------------- 1 | import { createWebHistory, createRouter, RouteRecordRaw } from 'vue-router' 2 | import NProgress from 'nprogress' 3 | import layout from '@/layout/index.vue' 4 | const history = createWebHistory() 5 | 6 | const routes: Array = [ 7 | { 8 | path: '/', 9 | name: '/', 10 | component: layout, 11 | redirect: '/dashboard', 12 | children: [ 13 | { 14 | path: '/dashboard', 15 | name: 'dashboard', 16 | meta: { title: '仪表盘' }, 17 | component: () => import('../view/screen/dashboard.vue') 18 | }, 19 | { 20 | path: '/dashboard-preview', 21 | name: 'dashboard-preview', 22 | meta: { title: '仪表盘预览' }, 23 | component: () => import('../view/screen/dashboard-preview.vue') 24 | } 25 | ] 26 | } 27 | ] 28 | const router = createRouter({ 29 | history, 30 | routes: routes 31 | }) 32 | router.beforeEach((to: any, from: any, next: any) => { 33 | NProgress.start() 34 | next() 35 | }) 36 | router.afterEach(() => { 37 | NProgress.done() 38 | }) 39 | export default router 40 | -------------------------------------------------------------------------------- /src/shim.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.vue' { 2 | import { ComponentOptions } from 'vue' 3 | const componentOptions: ComponentOptions 4 | export default componentOptions 5 | } -------------------------------------------------------------------------------- /src/store/index.ts: -------------------------------------------------------------------------------- 1 | import { createStore, Store, useStore as baseStore } from 'vuex'; 2 | import { m as HomePage } from './modules/HomePage'; 3 | import { m as Global } from './modules/Global'; 4 | import { m as Config } from './modules/Config'; 5 | import { RootState,AllState } from './store'; 6 | import { InjectionKey } from 'vue'; 7 | 8 | 9 | export default createStore({ 10 | state:{ 11 | content:"hello guy" 12 | }, 13 | modules:{ 14 | HomePage, 15 | Global, 16 | Config 17 | }, 18 | mutations:{ 19 | updateContent (state,payload) { 20 | if(typeof payload === "object"){ 21 | payload = "welcome" 22 | } 23 | state.content = `hello ${payload}`; 24 | } 25 | }, 26 | actions:{ 27 | update({ state, commit, rootState }) { 28 | commit('updateContent','world') 29 | } 30 | }, 31 | getters:{ 32 | getContent(state){ //字符串颠倒 33 | return state.content.split("").reverse().join(""); 34 | } 35 | } 36 | }); 37 | 38 | 39 | export const key:InjectionKey> = Symbol(); 40 | 41 | export function useStore() { 42 | return baseStore(key); 43 | } -------------------------------------------------------------------------------- /src/store/modules/Config/index.ts: -------------------------------------------------------------------------------- 1 | import { RootState } from "@/store/store"; 2 | import { Module } from "vuex"; 3 | 4 | export interface ConfigType { 5 | width:number, 6 | height:number 7 | } 8 | 9 | export const m:Module = { 10 | namespaced:true, 11 | state: { 12 | width: 1920, 13 | height:1080 14 | }, 15 | mutations:{ 16 | modifyWH(state,{key,value}) { 17 | state[key] = value 18 | } 19 | }, 20 | actions:{ 21 | modify({ state, commit, rootState},value) { 22 | commit('modifyWH',{...value}) 23 | } 24 | }, 25 | getters:{ 26 | getName(state){ 27 | // return state.name.split("").reverse().join(""); 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /src/store/modules/Global/index.ts: -------------------------------------------------------------------------------- 1 | import { RootState } from "@/store/store"; 2 | import { Module } from "vuex"; 3 | 4 | export interface GlobalType { 5 | data:string, 6 | } 7 | 8 | export const m:Module = { 9 | namespaced:true, 10 | state: { 11 | data:"hello world" 12 | }, 13 | mutations:{ 14 | increment (state) { 15 | state.data = "hello"; 16 | } 17 | }, 18 | actions:{ 19 | incrementIfOddOnRootSum ({ state, commit, rootState }) { 20 | commit('increment') 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /src/store/modules/HomePage/index.ts: -------------------------------------------------------------------------------- 1 | import { RootState } from "@/store/store"; 2 | import { Module } from "vuex"; 3 | 4 | export interface HomeType { 5 | name:string, 6 | age:number 7 | } 8 | 9 | export const m:Module = { 10 | namespaced:true, 11 | state: { 12 | name:"hello world", 13 | age:18 14 | }, 15 | mutations:{ 16 | incrementAge(state, value) { 17 | state.age = value 18 | } 19 | }, 20 | actions:{ 21 | increment({ state, commit, rootState},value) { 22 | commit('incrementAge',value) 23 | } 24 | }, 25 | getters:{ 26 | getName(state){ 27 | return state.name.split("").reverse().join(""); 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /src/store/store.d.ts: -------------------------------------------------------------------------------- 1 | import { HomeType } from "./modules/HomePage"; 2 | import { GlobalType } from "./modules"; 3 | import {ConfigType} from './modules/Config' 4 | 5 | export interface RootState { 6 | content:string 7 | } 8 | 9 | export interface ModuleState { 10 | HomePage:HomeType, 11 | Global: GlobalType, 12 | Config:ConfigType 13 | 14 | } 15 | 16 | export interface AllState extends RootState,ModuleState {} 17 | 18 | -------------------------------------------------------------------------------- /src/style/common.scss: -------------------------------------------------------------------------------- 1 | /* 2 | layout菜单栏组件 3 | */ 4 | .el-menu-vertical-demo:not(.el-menu--collapse) { 5 | width: 200px; 6 | min-height: 400px; 7 | .svg-icon { 8 | // width: 100%; 9 | margin-right: 15px; 10 | } 11 | .el-menu-item-group__title { 12 | padding: 0px !important; 13 | } 14 | ul { 15 | .el-menu-item { 16 | margin-left: 10px !important; 17 | } 18 | } 19 | } 20 | .el-popper.is-light { 21 | background-color: #545c64 !important; 22 | border: none !important; 23 | } 24 | .el-sub-menu__title{ 25 | &:hover{ 26 | background-color: #2e343c!important; 27 | } 28 | } 29 | .el-menu--horizontal { 30 | border: none !important; 31 | } 32 | .el-menu--popup-right-start { 33 | width: 600px !important; 34 | display: flex; 35 | // justify-content: space-between; 36 | flex-wrap: wrap; 37 | } 38 | .is-opened { 39 | &:hover { 40 | background-color: #1d1f26 !important; 41 | } 42 | } 43 | .menu_svg { 44 | margin-left: 5px; 45 | } 46 | /* 47 | el选择框 48 | */ 49 | .el-select { 50 | width: 100%; 51 | } 52 | .el-select-dropdown__item { 53 | color: white !important; 54 | } 55 | .el-select-dropdown__item.hover, 56 | .el-select-dropdown__item:hover { 57 | background-color: #2e343c !important; 58 | } 59 | .el-popper__arrow { 60 | display: none !important; 61 | } 62 | //输入框背景颜色 63 | .el-textarea__inner, 64 | .el-input__inner { 65 | background: #0f1014 !important; 66 | border: 1px solid #282e3a !important; 67 | } 68 | //计数器背景色 69 | .el-input-number__decrease, 70 | .el-input-number__increase { 71 | background-color: #1d1f26 !important; 72 | } 73 | .el-input-number__increase { 74 | border: none !important; 75 | } 76 | .el-input-number__decrease { 77 | border: none !important; 78 | } 79 | //折叠栏 80 | .el-collapse { 81 | border: none !important; 82 | .el-collapse-item__header { 83 | // border: none!important; 84 | font-weight: 600; 85 | } 86 | } 87 | .el-collapse-item__header { 88 | background-color: #1d1f26 !important; 89 | color: white !important; 90 | border-bottom-color: #2e343c !important; 91 | } 92 | .el-collapse-item__wrap { 93 | background-color: #0f1014 !important; 94 | border: none !important; 95 | padding: 0 10px !important; 96 | } 97 | .el-collapse-item__content { 98 | padding-top: 10px !important; 99 | } 100 | //抽屉 101 | .el-drawer { 102 | background-color: #2e343c !important; 103 | color: white !important; 104 | } 105 | -------------------------------------------------------------------------------- /src/style/index.scss: -------------------------------------------------------------------------------- 1 | /**样式重置*/ 2 | * { 3 | margin: 0; 4 | padding: 0; 5 | } 6 | body { 7 | height: 100%; 8 | min-width: 1200px; 9 | -moz-osx-font-smoothing: grayscale; 10 | -webkit-font-smoothing: antialiased; 11 | text-rendering: optimizeLegibility; 12 | } 13 | html { 14 | height: 100%; 15 | box-sizing: border-box; 16 | } 17 | #app { 18 | height: 100%; 19 | } 20 | div,p{ 21 | box-sizing: border-box; 22 | } 23 | 24 | /**修改全局的滚动条*/ 25 | /**滚动条的宽度*/ 26 | ::-webkit-scrollbar { 27 | width: 5px; 28 | height: 5px; 29 | } 30 | //滚动条的滑块 31 | ::-webkit-scrollbar-thumb { 32 | background-color: #434b55; 33 | border-radius: 3px; 34 | } 35 | //全局表单 36 | .zu-config { 37 | width: 100%; 38 | display: flex; 39 | justify-content: space-between; 40 | align-items: center; 41 | margin-bottom: 10px; 42 | .zu-header { 43 | height: 100%; 44 | line-height: 100%; 45 | text-align: left; 46 | font-size: 12px; 47 | max-width: 30%; 48 | flex: 0 0 30%; 49 | color: white; 50 | } 51 | .zu-content { 52 | text-align: left; 53 | max-width: 66.6666666667%; 54 | flex: 0 0 66.6666666667%; 55 | .el-slider{ 56 | padding-left: 10px; 57 | --el-slider-button-size:10px; 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/type/config.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | chart: ['bar', 'line', 'pie', 'circle', 'radar'] 3 | } 4 | -------------------------------------------------------------------------------- /src/utils/aes.ts: -------------------------------------------------------------------------------- 1 | import CryptoJS from 'crypto-js' 2 | 3 | const KEY = CryptoJS.enc.Utf8.parse('ZGJ76CSSHTMLWTYT') 4 | const IV = CryptoJS.enc.Utf8.parse('YXG87HJ657WYHSLS') 5 | 6 | export function Encrypt(word: string, keyStr?: string, ivStr?: string) { 7 | let key = KEY 8 | let iv = IV 9 | if (keyStr) { 10 | key = CryptoJS.enc.Utf8.parse(keyStr) 11 | iv = CryptoJS.enc.Utf8.parse(ivStr) 12 | } 13 | let srcs = CryptoJS.enc.Utf8.parse(word) 14 | var encrypted = CryptoJS.AES.encrypt(srcs, key, { 15 | iv: iv, 16 | mode: CryptoJS.mode.ECB, 17 | padding: CryptoJS.pad.Pkcs7 18 | }) 19 | return CryptoJS.enc.Base64.stringify(encrypted.ciphertext) 20 | } 21 | 22 | /** 23 | * AES 解密 :字符串 key iv 返回base64 24 | * 25 | */ 26 | export function Decrypt(word: string, keyStr?: string, ivStr?: string) { 27 | let key = KEY 28 | let iv = IV 29 | if (keyStr) { 30 | key = CryptoJS.enc.Utf8.parse(keyStr) 31 | iv = CryptoJS.enc.Utf8.parse(ivStr) 32 | } 33 | let base64 = CryptoJS.enc.Base64.parse(word) 34 | let src = CryptoJS.enc.Base64.stringify(base64) 35 | var decrypt = CryptoJS.AES.decrypt(src, key, { 36 | iv: iv, 37 | mode: CryptoJS.mode.ECB, 38 | padding: CryptoJS.pad.Pkcs7 39 | }) 40 | var decryptedStr = decrypt.toString(CryptoJS.enc.Utf8) 41 | return decryptedStr.toString() 42 | } 43 | -------------------------------------------------------------------------------- /src/utils/ceater.ts: -------------------------------------------------------------------------------- 1 | import { Plugin } from 'vite' 2 | import { readFileSync, readdirSync } from 'fs' 3 | 4 | let idPerfix = '' 5 | const svgTitle = /+].*?)>/ 6 | const clearHeightWidth = /(width|height)="([^>+].*?)"/g 7 | 8 | const hasViewBox = /(viewBox="[^>+].*?")/g 9 | 10 | const clearReturn = /(\r)|(\n)/g 11 | 12 | function findSvgFile(dir): string[] { 13 | const svgRes = [] 14 | const dirents = readdirSync(dir, { 15 | withFileTypes: true 16 | }) 17 | for (const dirent of dirents) { 18 | if (dirent.isDirectory()) { 19 | svgRes.push(...findSvgFile(dir + dirent.name + '/')) 20 | } else { 21 | const svg = readFileSync(dir + dirent.name) 22 | .toString() 23 | .replace(clearReturn, '') 24 | .replace(svgTitle, ($1, $2) => { 25 | // console.log(++i) 26 | // console.log(dirent.name) 27 | let width = 0 28 | let height = 0 29 | let content = $2.replace( 30 | clearHeightWidth, 31 | (s1, s2, s3) => { 32 | if (s2 === 'width') { 33 | width = s3 34 | } else if (s2 === 'height') { 35 | height = s3 36 | } 37 | return '' 38 | } 39 | ) 40 | if (!hasViewBox.test($2)) { 41 | content += `viewBox="0 0 ${width} ${height}"` 42 | } 43 | return `` 47 | }) 48 | .replace('', '') 49 | svgRes.push(svg) 50 | } 51 | } 52 | return svgRes 53 | } 54 | 55 | export const svgBuilder = ( 56 | path: string, 57 | perfix = 'icon' 58 | ): Plugin => { 59 | if (path === '') return 60 | idPerfix = perfix 61 | const res = findSvgFile(path) 62 | // console.log(res.length) 63 | // const res = [] 64 | return { 65 | name: 'svg-transform', 66 | transformIndexHtml(html): string { 67 | return html.replace( 68 | '', 69 | ` 70 | 71 | 72 | ${res.join('')} 73 | 74 | ` 75 | ) 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/utils/chartsType.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | line: '折线图', 3 | bar: '柱状图', 4 | pre:'饼图' 5 | } -------------------------------------------------------------------------------- /src/utils/dom.ts: -------------------------------------------------------------------------------- 1 | import { camelize, isObject } from './util' 2 | 3 | const trim = function(s: string) { 4 | return (s || '').replace(/^[\s\uFEFF]+|[\s\uFEFF]+$/g, '') 5 | } 6 | 7 | export const on = function( 8 | element: HTMLElement | Document | Window, 9 | event: string, 10 | handler: any, // EventListenerOrEventListenerObject 11 | useCapture = false, 12 | ): void { 13 | if (element && event && handler) { 14 | element.addEventListener(event, handler, useCapture) 15 | } 16 | } 17 | 18 | export const off = function( 19 | element: HTMLElement | Document | Window, 20 | event: string, 21 | handler: any, // EventListenerOrEventListenerObject 22 | ): void { 23 | if (element && event && handler) { 24 | element.removeEventListener(event, handler, false) 25 | } 26 | } 27 | 28 | export function hasClass(el: HTMLElement, cls: string): boolean { 29 | if (!el || !cls) return false 30 | if (cls.indexOf(' ') !== -1) 31 | throw new Error('className should not contain space.') 32 | if (el.classList) { 33 | return el.classList.contains(cls) 34 | } else { 35 | return (` ${el.className} `).indexOf(` ${cls} `) > -1 36 | } 37 | } 38 | 39 | export function addClass(el: HTMLElement, cls: string): void { 40 | if (!el) return 41 | let curClass = el.className 42 | const classes = (cls || '').split(' ') 43 | 44 | for (let i = 0, j = classes.length; i < j; i++) { 45 | const clsName = classes[i] 46 | if (!clsName) continue 47 | 48 | if (el.classList) { 49 | el.classList.add(clsName) 50 | } else if (!hasClass(el, clsName)) { 51 | curClass += ` ${clsName}` 52 | } 53 | } 54 | if (!el.classList) { 55 | el.className = curClass 56 | } 57 | } 58 | 59 | export function removeClass(el: HTMLElement, cls: string): void { 60 | if (!el || !cls) return 61 | const classes = cls.split(' ') 62 | let curClass = ` ${el.className} ` 63 | 64 | for (let i = 0, j = classes.length; i < j; i++) { 65 | const clsName = classes[i] 66 | if (!clsName) continue 67 | 68 | if (el.classList) { 69 | el.classList.remove(clsName) 70 | } else if (hasClass(el, clsName)) { 71 | curClass = curClass.replace(` ${clsName} `, ' ') 72 | } 73 | } 74 | if (!el.classList) { 75 | el.className = trim(curClass) 76 | } 77 | } 78 | 79 | 80 | // Here I want to use the type CSSStyleDeclaration, but the definition for CSSStyleDeclaration 81 | // has { [index: number]: string } in its type annotation, which does not satisfy the method 82 | // camelize(s: string) 83 | // Same as the return type 84 | export const getStyle = function( 85 | element: HTMLElement, 86 | styleName: string, 87 | ): string { 88 | if (!element || !styleName) return null 89 | styleName = camelize(styleName) 90 | if (styleName === 'float') { 91 | styleName = 'cssFloat' 92 | } 93 | try { 94 | const style = element.style[styleName] 95 | if (style) return style 96 | const computed = document.defaultView.getComputedStyle(element, '') 97 | return computed ? computed[styleName] : '' 98 | } catch (e) { 99 | return element.style[styleName] 100 | } 101 | } 102 | 103 | export function setStyle( 104 | element: HTMLElement, 105 | styleName: CSSStyleDeclaration | string, 106 | value?: string, 107 | ): void { 108 | if (!element || !styleName) return 109 | 110 | if (isObject(styleName)) { 111 | Object.keys(styleName).forEach(prop => { 112 | setStyle(element, prop, styleName[prop]) 113 | }) 114 | } else { 115 | styleName = camelize(styleName) 116 | element.style[styleName] = value 117 | } 118 | } 119 | 120 | export function removeStyle(element: HTMLElement, style: CSSStyleDeclaration | string) { 121 | if (!element || !style) return 122 | 123 | if (isObject(style)) { 124 | Object.keys(style).forEach(prop => { 125 | setStyle(element, prop, '') 126 | }) 127 | } else { 128 | setStyle(element, style, '') 129 | } 130 | } 131 | 132 | export const getOffsetTop = (el: HTMLElement) => { 133 | let offset = 0 134 | let parent = el 135 | 136 | while (parent) { 137 | offset += parent.offsetTop 138 | parent = parent.offsetParent as HTMLElement 139 | } 140 | 141 | return offset 142 | } 143 | -------------------------------------------------------------------------------- /src/utils/tool.ts: -------------------------------------------------------------------------------- 1 | const white = ['rotation'] 2 | const isGif = (type: string) => { 3 | if (white.includes(type)) { 4 | return '.gif' 5 | } else { 6 | return '.png' 7 | } 8 | } 9 | //动态图片工具 10 | export function importImg(type: string,code:number): string { 11 | return new URL(`../assets/echarts/${type}_${code}${isGif(type)}`, import.meta.url).href 12 | } 13 | //动态图片边框 14 | export function borderImg(id: number): string { 15 | return new URL(`../assets/border/border_${id}${id > 8 ? '.gif' : '.png'}`, import.meta.url).href 16 | } 17 | export function base64ToBlob (urlData, type = 'image/png') { 18 | const arr = urlData.split(','); 19 | const mime = arr[0].match(/:(.*?);/)[1] || type; 20 | const bytes = window.atob(arr[1]); 21 | const ab = new ArrayBuffer(bytes.length); 22 | const ia = new Uint8Array(ab); 23 | for (let i = 0; i < bytes.length; i++) { 24 | ia[i] = bytes.charCodeAt(i); 25 | } 26 | return new Blob([ab], { 27 | type: mime 28 | }); 29 | } -------------------------------------------------------------------------------- /src/utils/util.ts: -------------------------------------------------------------------------------- 1 | import { 2 | isObject, 3 | isPlainObject, 4 | isArray, 5 | isString, 6 | capitalize, 7 | hyphenate, 8 | looseEqual, 9 | extend, 10 | camelize, 11 | hasOwn, 12 | toRawType, 13 | } from '@vue/shared' 14 | // import shortid from 'shortid' 15 | 16 | /** 17 | * Generate shortId 18 | */ 19 | // export const generateId = (prefix?: string) => { 20 | // const id = shortid.generate() 21 | // return prefix ? `${prefix}_${id}` : id 22 | // } 23 | 24 | export { 25 | isObject, 26 | isPlainObject, 27 | isArray, 28 | isString, 29 | capitalize, 30 | looseEqual, 31 | extend, 32 | camelize, 33 | hasOwn, 34 | } 35 | 36 | export const kebabCase = hyphenate 37 | 38 | /** 39 | * Remove leading and trailing whitespace and non-word 40 | * characters from the given string. 41 | * 42 | * @param {String} `str` 43 | * @return {String} 44 | */ 45 | export const chop = (str: string) => { 46 | if (!isString(str)) return '' 47 | const re = /^[-_.\W\s]+|[-_.\W\s]+$/g 48 | return str.trim().replace(re, '') 49 | } 50 | 51 | /** 52 | * Change casing on the given `string`, optionally 53 | * passing a delimiter to use between words in the 54 | * returned string. 55 | * 56 | * ```js 57 | * utils.changeCase('fooBarBaz'); 58 | * //=> 'foo bar baz' 59 | * 60 | * utils.changeCase('fooBarBaz' '-'); 61 | * //=> 'foo-bar-baz' 62 | * ``` 63 | * @param {String} `string` The string to change. 64 | * @return {String} 65 | * @api public 66 | */ 67 | export const changeCase = (str: string, fn?: (str: string) => string) => { 68 | if (!isString(str)) return '' 69 | if (str.length === 1) { 70 | return str.toLowerCase() 71 | } 72 | 73 | str = chop(str).toLowerCase() 74 | 75 | const re = /[-_.\W\s]+(\w|$)/g 76 | return str.replace(re, (_, ch) => { 77 | return fn ? fn(ch) : '' 78 | }) 79 | } 80 | 81 | /** 82 | * PascalCase the characters in `string`. 83 | * 84 | * ```js 85 | * {{pascalCase "foo bar baz"}} 86 | * 87 | * ``` 88 | * @param {String} `string` 89 | * @return {String} 90 | * @api public 91 | */ 92 | export const pascalCase = (str: string) => { 93 | if (!isString(str)) return '' 94 | str = changeCase(str, (ch: string) => { 95 | return ch.toUpperCase() 96 | }) 97 | return str.charAt(0).toUpperCase() + str.slice(1) 98 | } 99 | 100 | /** 101 | * Generating a random int in range (0, max - 1) 102 | * @param max {number} 103 | */ 104 | export function getRandomInt(max: number) { 105 | return Math.floor(Math.random() * Math.floor(max)) 106 | } 107 | 108 | export const isIE = function(): boolean { 109 | return !isNaN(Number(document.DOCUMENT_NODE)) 110 | } 111 | 112 | export const isEdge = function(): boolean { 113 | return navigator.userAgent.indexOf('Edge') > -1 114 | } 115 | 116 | export const isFirefox = function(): boolean { 117 | return !!window.navigator.userAgent.match(/firefox/i) 118 | } 119 | 120 | export const isMac = () => { 121 | return /macintosh|mac os x/i.test(navigator.userAgent) 122 | } 123 | 124 | export const isBool = (val: unknown) => typeof val === 'boolean' 125 | export const isNumber = (val: unknown) => typeof val === 'number' 126 | export const isHTMLElement = (val: unknown) => toRawType(val).startsWith('HTML') 127 | 128 | export function isUndefined(val: any) { 129 | return val === void 0 130 | } 131 | 132 | export function isEmpty(val: unknown) { 133 | if ( 134 | !val && val !== 0 || 135 | isArray(val) && !val.length || 136 | isObject(val) && !Object.keys(val).length 137 | ) return true 138 | 139 | return false 140 | } 141 | 142 | export function isUrl(val: string) { 143 | return /^[a-zA-z]+:\/\/[^\s]*$/.test(val) 144 | } 145 | 146 | export function deduplicate(arr: T[]) { 147 | return Array.from(new Set(arr)) 148 | } 149 | 150 | export function toObject(arr: Array): Record { 151 | const res = {} 152 | for (let i = 0; i < arr.length; i++) { 153 | if (arr[i]) { 154 | extend(res, arr[i]) 155 | } 156 | } 157 | return res 158 | } 159 | 160 | export function ArrayToObject(arr: Array, key: string, value: string): Record { 161 | return arr.reduce((prev, curr) => { 162 | prev[curr[key]] = curr[value] 163 | return prev 164 | }, {}) 165 | } 166 | 167 | export function StringArrayToObject(arr: string[]): Record { 168 | return arr.reduce((prev, curr) => { 169 | prev[curr] = curr 170 | return prev 171 | }, {}) 172 | } 173 | 174 | export function toJson(data: any, defaultValue: T) { 175 | try { 176 | if (!data) { 177 | return defaultValue 178 | } 179 | 180 | if (isString(data)) { 181 | return JSON.parse(data) 182 | } 183 | 184 | return data 185 | } catch { 186 | return defaultValue 187 | } 188 | } 189 | 190 | export const copyText = (text: string) => { 191 | try { 192 | const input = document.createElement('textarea') 193 | input.value = text 194 | document.body.appendChild(input) 195 | input.select() 196 | document.execCommand('copy') 197 | document.body.removeChild(input) 198 | return true 199 | } catch (error) { 200 | return false 201 | } 202 | } 203 | 204 | /** 205 | * 获取字符串中 :value 形式的参数 206 | */ 207 | export const getTextParams = (text: string) => { 208 | const reg = /:([\d\w\u4e00-\u9fa5_$@*]+)/ig 209 | return text.match(reg) ?? [] 210 | } 211 | 212 | /** 213 | * 替换字符串中 :value 形式的参数 214 | */ 215 | export const replaceTextParams = (text: string, data: Record) => { 216 | if (Object.keys(data).length === 0) { 217 | return text 218 | } 219 | const reg = /:([\d\w\u4e00-\u9fa5_$@*]+)/ig 220 | return text.replace(reg, (key: string) => { 221 | return data[key.substr(1)] ?? key 222 | }) 223 | } 224 | 225 | /** 226 | * 简单计算字符串长度 227 | */ 228 | export const calcStrLen = (str: string) => { 229 | let len = 0 230 | for (let i = 0; i < str.length; i++) { 231 | if (str.charCodeAt(i) > 127 || str.charCodeAt(i) == 94) { 232 | len += 2 233 | } else { 234 | len ++ 235 | } 236 | } 237 | return len 238 | } 239 | 240 | /** 241 | * 简单计算字符串宽度 242 | */ 243 | let TextCanvas: HTMLCanvasElement | null = null 244 | export const calcStrWidth = (str: string, font: string) => { 245 | if (!TextCanvas) { 246 | TextCanvas = document.createElement('canvas') 247 | } 248 | const ctx = TextCanvas.getContext('2d') 249 | ctx.font = font 250 | ctx.font 251 | return ctx.measureText(str).width 252 | } -------------------------------------------------------------------------------- /src/view/components/config-builds/hooks/chartConfig.ts: -------------------------------------------------------------------------------- 1 | // 构建部件配置项 2 | import bar from '@/components/widgetBuilds/bar/index.jsx' 3 | import line from '@/components/widgetBuilds/line/index.jsx' 4 | import text from '@/components/widgetBuilds/text/index.jsx' 5 | import table from '@/components/widgetBuilds/table/index.jsx' 6 | import pie from '@/components/widgetBuilds/pie/index.jsx' 7 | import radar from '@/components/widgetBuilds/radar/index.jsx' 8 | import map from '@/components/widgetBuilds/map/index.jsx' 9 | const themeType = { 10 | bar: bar, 11 | line: line, 12 | text: text, 13 | table: table, 14 | pie: pie, 15 | radar: radar, 16 | map: map 17 | } 18 | export function buildChange(type: string) { 19 | return themeType[type] 20 | } 21 | -------------------------------------------------------------------------------- /src/view/components/config-builds/index.vue: -------------------------------------------------------------------------------- 1 | 31 | 40 | -------------------------------------------------------------------------------- /src/view/components/config-builds/item/coordinateConfig.vue: -------------------------------------------------------------------------------- 1 | 4 | 7 | -------------------------------------------------------------------------------- /src/view/components/config-builds/item/dataConfig.vue: -------------------------------------------------------------------------------- 1 | 4 | 7 | -------------------------------------------------------------------------------- /src/view/components/config-builds/item/interConfig.vue: -------------------------------------------------------------------------------- 1 | 4 | 7 | -------------------------------------------------------------------------------- /src/view/components/config-builds/item/themeConfig.vue: -------------------------------------------------------------------------------- 1 | 12 | 31 | 42 | -------------------------------------------------------------------------------- /src/view/components/draggableBody.vue: -------------------------------------------------------------------------------- 1 | 44 | 92 | -------------------------------------------------------------------------------- /src/view/components/draggableHeader.vue: -------------------------------------------------------------------------------- 1 | 50 | 64 | -------------------------------------------------------------------------------- /src/view/components/draggableLeft.vue: -------------------------------------------------------------------------------- 1 | 7 | 21 | -------------------------------------------------------------------------------- /src/view/components/draggableRight.vue: -------------------------------------------------------------------------------- 1 | 27 | 94 | -------------------------------------------------------------------------------- /src/view/screen/dashboard-preview.vue: -------------------------------------------------------------------------------- 1 | 4 | 9 | -------------------------------------------------------------------------------- /src/view/screen/dashboard.vue: -------------------------------------------------------------------------------- 1 | 18 | 34 | 69 | -------------------------------------------------------------------------------- /src/view/screen/index.vue: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | // 允许从没有设置默认导出的模块中默认导入。这并不影响代码的输出,仅为了类型检查。 4 | "allowSyntheticDefaultImports": true, 5 | "jsx": "preserve", 6 | // 解析非相对模块名的基准目录 7 | "baseUrl": ".", 8 | "suppressImplicitAnyIndexErrors": true, 9 | "esModuleInterop": true, 10 | "resolveJsonModule":true, 11 | // 从 tslib 导入辅助工具函数(比如 __extends, __rest等) 12 | "importHelpers": true, 13 | "noImplicitAny": false, 14 | // 指定生成哪个模块系统代码 15 | "module": "esnext", 16 | 17 | // 决定如何处理模块。 18 | "moduleResolution": "Node", 19 | 20 | // 启用所有严格类型检查选项。 21 | // 启用 --strict相当于启用 --noImplicitAny, --noImplicitThis, --alwaysStrict, 22 | // --strictNullChecks和 --strictFunctionTypes和--strictPropertyInitialization。 23 | "strict": true, 24 | 25 | // 生成相应的 .map文件。 26 | "sourceMap": true, 27 | 28 | // 忽略所有的声明文件( *.d.ts)的类型检查。 29 | "skipLibCheck": true, 30 | 31 | // 指定ECMAScript目标版本 32 | "target": "esnext", 33 | 34 | // 要包含的类型声明文件名列表 35 | "types": [ 36 | 37 | ], 38 | 39 | "isolatedModules": true, 40 | 41 | // 模块名到基于 baseUrl的路径映射的列表。 42 | "paths": { 43 | "@/*": [ 44 | "src/*" 45 | ] 46 | }, 47 | // 编译过程中需要引入的库文件的列表。 48 | "lib": [ 49 | "ESNext", 50 | "DOM", 51 | "DOM.Iterable", 52 | "ScriptHost" 53 | ] 54 | }, 55 | "include": [ 56 | "src/**/*.ts", 57 | "src/**/*.tsx", 58 | "src/**/*.vue", 59 | "tests/**/*.ts", 60 | "tests/**/*.tsx" 61 | ], 62 | "exclude": [ 63 | "node_modules" 64 | ] 65 | } -------------------------------------------------------------------------------- /vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import vue from '@vitejs/plugin-vue' 3 | const { resolve } = require('path') 4 | import { svgBuilder } from './src/utils/ceater' 5 | import vueJsx from '@vitejs/plugin-vue-jsx' 6 | export default defineConfig({ 7 | plugins: [vue(), svgBuilder('./src/icons/svg/'), vueJsx()], 8 | resolve: { 9 | alias: [ 10 | { 11 | find: '@', 12 | replacement: resolve(__dirname, 'src') 13 | } 14 | ] 15 | }, 16 | server: { 17 | host: 'localhost', 18 | port: 8080, 19 | open: true, 20 | cors: true, 21 | proxy: { 22 | '/api': { 23 | // target: 'http://121.41.67.22:88', 24 | target: 'http://121.41.67.22:88', 25 | changeOrigin: true 26 | // rewrite: (path) => path.replace(/^\/api/, ''), 27 | } 28 | } 29 | }, 30 | /* 打包配置 */ 31 | base: './', 32 | build: { 33 | brotliSize: false, 34 | emptyOutDir: false, 35 | outDir: 'dist', 36 | assetsDir: 'static' 37 | } 38 | }) 39 | --------------------------------------------------------------------------------