Merge branch 'dev' of http://47.109.205.240:3000/Web/daimp-front into dev
4
.gitignore
vendored
@ -23,3 +23,7 @@ dist-ssr
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
sub-operation-service/package-lock.json
|
||||
main/package-lock.json
|
||||
sub-operation-service/vite.config.js
|
||||
main/.eslintrc.cjs
|
||||
|
@ -39,7 +39,12 @@ module.exports = {
|
||||
},
|
||||
// 这里时配置规则的,自己看情况配置
|
||||
rules: {
|
||||
'prettier/prettier': 'error',
|
||||
'prettier/prettier': [
|
||||
'error',
|
||||
{
|
||||
endOfLine: 'auto', // 允许自动检测换行符
|
||||
},
|
||||
],
|
||||
'no-debugger': 'off',
|
||||
'no-unused-vars': 'off',
|
||||
'vue/multi-word-component-names': 'off',
|
||||
|
@ -57,11 +57,11 @@ export const rightApps = [
|
||||
},
|
||||
{
|
||||
// name: 'sub-government-screen-service',
|
||||
name: 'new-digital-agriculture-screen',
|
||||
name: 'new-digital-agriculture-screen/v2/land',
|
||||
// entry: VITE_APP_SUB_GSS,
|
||||
entry: VITE_APP_SUB_GSR,
|
||||
// activeRule: '/sub-government-screen-service',
|
||||
activeRule: '/new-digital-agriculture-screen',
|
||||
activeRule: '/new-digital-agriculture-screen/v2/land',
|
||||
title: '数据大屏',
|
||||
icon: 'images/platform/icon-screen.png',
|
||||
},
|
||||
|
@ -28,7 +28,7 @@ const props = defineProps({
|
||||
});
|
||||
|
||||
const titles = ref([
|
||||
{ label: '首页', value: 'home' },
|
||||
// { label: '首页', value: 'home' },
|
||||
{ label: '土地资源', value: 'land' },
|
||||
{ label: '投入品', value: 'inputs' },
|
||||
{ label: '生产经营主体', value: 'entities' },
|
||||
|
@ -54,7 +54,7 @@ const props = defineProps({
|
||||
type: Array,
|
||||
default() {
|
||||
return [
|
||||
{ label: '首页', value: '/new-digital-agriculture-screen/v2/home' },
|
||||
// { label: '首页', value: '/new-digital-agriculture-screen/v2/home' },
|
||||
{ label: '土地资源', value: '/new-digital-agriculture-screen/v2/land' },
|
||||
{ label: '投入品监管', value: '/new-digital-agriculture-screen/v2/inputs' },
|
||||
{ label: '产出品管理', value: '/new-digital-agriculture-screen/v2/entities' },
|
||||
|
@ -58,10 +58,10 @@ onMounted(() => {
|
||||
user-select: none;
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
height: 100;
|
||||
height: 100%;
|
||||
background:
|
||||
url('@/assets/images/basic/containerBG.png') no-repeat center 100%,
|
||||
url('@/assets/images/basic/containerBotBG.png') no-repeat bottom center;
|
||||
url('../assets/images/basic/containerBG.png') no-repeat center 100%,
|
||||
url('../assets/images/basic/containerBotBG.png') no-repeat bottom center;
|
||||
&-header {
|
||||
width: 100%;
|
||||
margin-bottom: 16px;
|
||||
|
@ -17,21 +17,22 @@ export const constantRoutes = [
|
||||
component: () => import('@/views/error/403.vue'),
|
||||
hidden: true,
|
||||
},
|
||||
{
|
||||
path: '/new-digital-agriculture-screen',
|
||||
name: 'layout',
|
||||
component: Layout,
|
||||
redirect: '/new-digital-agriculture-screen/v2/home',
|
||||
meta: { title: '首页', icon: 'House' },
|
||||
children: [
|
||||
{
|
||||
path: '/new-digital-agriculture-screen/v2/home',
|
||||
component: () => import('@/views/home/index.vue'),
|
||||
name: 'home',
|
||||
meta: { title: '首页', icon: '' },
|
||||
},
|
||||
],
|
||||
},
|
||||
// {
|
||||
// // 原来的首页默认配置
|
||||
// path: '/new-digital-agriculture-screen',
|
||||
// name: 'layout',
|
||||
// component: Layout,
|
||||
// redirect: '/new-digital-agriculture-screen/v2/home',
|
||||
// meta: { title: '首页', icon: 'House' },
|
||||
// children: [
|
||||
// {
|
||||
// path: '/new-digital-agriculture-screen/v2/home',
|
||||
// component: () => import('@/views/home/index.vue'),
|
||||
// name: 'home',
|
||||
// meta: { title: '首页', icon: '' },
|
||||
// },
|
||||
// ],
|
||||
// },
|
||||
// ...demoRouters,
|
||||
v2,
|
||||
// {
|
||||
|
@ -4,7 +4,7 @@ export default {
|
||||
path: '/new-digital-agriculture-screen',
|
||||
name: 'layout',
|
||||
component: Layout,
|
||||
redirect: '/new-digital-agriculture-screen/v2/home',
|
||||
redirect: '/new-digital-agriculture-screen/v2/land', //原home
|
||||
meta: { title: '首页', icon: '' },
|
||||
children: [
|
||||
{
|
||||
|
1074
package-lock.json
generated
@ -3,7 +3,7 @@ VITE_PORT = 9526
|
||||
VITE_APP_MIAN = 'daimp-front-main'
|
||||
VITE_APP_MIAN_URL = 'http://localhost:9000'
|
||||
VITE_APP_NAME = 'sub-operation-service'
|
||||
VITE_APP_BASE_API = '/platform'
|
||||
VITE_APP_BASE_API = '/apis'
|
||||
VITE_APP_BASE_URL = 'http://192.168.18.99:88'
|
||||
VITE_APP_UPLOAD_API = '/uploadApis'
|
||||
VITE_APP_UPLOAD_URL = 'http://192.168.18.99:9300'
|
||||
|
75
sub-operation-service/auto-imports.d.ts
vendored
Normal file
@ -0,0 +1,75 @@
|
||||
/* eslint-disable */
|
||||
/* prettier-ignore */
|
||||
// @ts-nocheck
|
||||
// noinspection JSUnusedGlobalSymbols
|
||||
// Generated by unplugin-auto-import
|
||||
export {}
|
||||
declare global {
|
||||
const EffectScope: typeof import('vue')['EffectScope']
|
||||
const computed: typeof import('vue')['computed']
|
||||
const createApp: typeof import('vue')['createApp']
|
||||
const customRef: typeof import('vue')['customRef']
|
||||
const defineAsyncComponent: typeof import('vue')['defineAsyncComponent']
|
||||
const defineComponent: typeof import('vue')['defineComponent']
|
||||
const effectScope: typeof import('vue')['effectScope']
|
||||
const getCurrentInstance: typeof import('vue')['getCurrentInstance']
|
||||
const getCurrentScope: typeof import('vue')['getCurrentScope']
|
||||
const h: typeof import('vue')['h']
|
||||
const inject: typeof import('vue')['inject']
|
||||
const isProxy: typeof import('vue')['isProxy']
|
||||
const isReactive: typeof import('vue')['isReactive']
|
||||
const isReadonly: typeof import('vue')['isReadonly']
|
||||
const isRef: typeof import('vue')['isRef']
|
||||
const markRaw: typeof import('vue')['markRaw']
|
||||
const nextTick: typeof import('vue')['nextTick']
|
||||
const onActivated: typeof import('vue')['onActivated']
|
||||
const onBeforeMount: typeof import('vue')['onBeforeMount']
|
||||
const onBeforeRouteLeave: typeof import('vue-router')['onBeforeRouteLeave']
|
||||
const onBeforeRouteUpdate: typeof import('vue-router')['onBeforeRouteUpdate']
|
||||
const onBeforeUnmount: typeof import('vue')['onBeforeUnmount']
|
||||
const onBeforeUpdate: typeof import('vue')['onBeforeUpdate']
|
||||
const onDeactivated: typeof import('vue')['onDeactivated']
|
||||
const onErrorCaptured: typeof import('vue')['onErrorCaptured']
|
||||
const onMounted: typeof import('vue')['onMounted']
|
||||
const onRenderTracked: typeof import('vue')['onRenderTracked']
|
||||
const onRenderTriggered: typeof import('vue')['onRenderTriggered']
|
||||
const onScopeDispose: typeof import('vue')['onScopeDispose']
|
||||
const onServerPrefetch: typeof import('vue')['onServerPrefetch']
|
||||
const onUnmounted: typeof import('vue')['onUnmounted']
|
||||
const onUpdated: typeof import('vue')['onUpdated']
|
||||
const onWatcherCleanup: typeof import('vue')['onWatcherCleanup']
|
||||
const provide: typeof import('vue')['provide']
|
||||
const reactive: typeof import('vue')['reactive']
|
||||
const readonly: typeof import('vue')['readonly']
|
||||
const ref: typeof import('vue')['ref']
|
||||
const resolveComponent: typeof import('vue')['resolveComponent']
|
||||
const shallowReactive: typeof import('vue')['shallowReactive']
|
||||
const shallowReadonly: typeof import('vue')['shallowReadonly']
|
||||
const shallowRef: typeof import('vue')['shallowRef']
|
||||
const toRaw: typeof import('vue')['toRaw']
|
||||
const toRef: typeof import('vue')['toRef']
|
||||
const toRefs: typeof import('vue')['toRefs']
|
||||
const toValue: typeof import('vue')['toValue']
|
||||
const triggerRef: typeof import('vue')['triggerRef']
|
||||
const unref: typeof import('vue')['unref']
|
||||
const useAttrs: typeof import('vue')['useAttrs']
|
||||
const useCssModule: typeof import('vue')['useCssModule']
|
||||
const useCssVars: typeof import('vue')['useCssVars']
|
||||
const useId: typeof import('vue')['useId']
|
||||
const useLink: typeof import('vue-router')['useLink']
|
||||
const useModel: typeof import('vue')['useModel']
|
||||
const useRoute: typeof import('vue-router')['useRoute']
|
||||
const useRouter: typeof import('vue-router')['useRouter']
|
||||
const useSlots: typeof import('vue')['useSlots']
|
||||
const useTemplateRef: typeof import('vue')['useTemplateRef']
|
||||
const watch: typeof import('vue')['watch']
|
||||
const watchEffect: typeof import('vue')['watchEffect']
|
||||
const watchPostEffect: typeof import('vue')['watchPostEffect']
|
||||
const watchSyncEffect: typeof import('vue')['watchSyncEffect']
|
||||
}
|
||||
// for type re-export
|
||||
declare global {
|
||||
// @ts-ignore
|
||||
export type { Component, ComponentPublicInstance, ComputedRef, DirectiveBinding, ExtractDefaultPropTypes, ExtractPropTypes, ExtractPublicPropTypes, InjectionKey, PropType, Ref, MaybeRef, MaybeRefOrGetter, VNode, WritableComputedRef } from 'vue'
|
||||
import('vue')
|
||||
}
|
54
sub-operation-service/components.d.ts
vendored
Normal file
@ -0,0 +1,54 @@
|
||||
/* eslint-disable */
|
||||
/* prettier-ignore */
|
||||
// @ts-nocheck
|
||||
// Generated by unplugin-vue-components
|
||||
// Read more: https://github.com/vuejs/core/pull/3399
|
||||
export {}
|
||||
|
||||
declare module 'vue' {
|
||||
export interface GlobalComponents {
|
||||
CenterMap: typeof import('./src/components/centerMap.vue')['default']
|
||||
CodeDialog: typeof import('./src/components/code-dialog/index.vue')['default']
|
||||
copy: typeof import('./src/components/custom-scroll-title copy/index.vue')['default']
|
||||
CostomImg: typeof import('./src/components/costomImg.vue')['default']
|
||||
CustomBack: typeof import('./src/components/customBack.vue')['default']
|
||||
CustomCarouselPicture: typeof import('./src/components/custom-carousel-picture/index.vue')['default']
|
||||
CustomEchartBar: typeof import('./src/components/custom-echart-bar/index.vue')['default']
|
||||
CustomEchartBubble: typeof import('./src/components/custom-echart-bubble/index.vue')['default']
|
||||
CustomEchartColumnLine: typeof import('./src/components/custom-echart-column-line/index.vue')['default']
|
||||
CustomEchartHyalineCake: typeof import('./src/components/custom-echart-hyaline-cake/index.vue')['default']
|
||||
CustomEchartLine: typeof import('./src/components/custom-echart-line/index.vue')['default']
|
||||
CustomEchartLineLine: typeof import('./src/components/custom-echart-line-line/index.vue')['default']
|
||||
CustomEchartMaps: typeof import('./src/components/custom-echart-maps/index.vue')['default']
|
||||
CustomEchartMixin: typeof import('./src/components/custom-echart-mixin/index.vue')['default']
|
||||
CustomEchartPictorialBar: typeof import('./src/components/custom-echart-pictorial-bar/index.vue')['default']
|
||||
CustomEchartPie: typeof import('./src/components/custom-echart-pie/index.vue')['default']
|
||||
CustomEchartPie3d: typeof import('./src/components/custom-echart-pie-3d/index.vue')['default']
|
||||
CustomEchartPieGauge: typeof import('./src/components/custom-echart-pie-gauge/index.vue')['default']
|
||||
CustomEchartRadar: typeof import('./src/components/custom-echart-radar/index.vue')['default']
|
||||
CustomEchartScatterBlister: typeof import('./src/components/custom-echart-scatter-blister/index.vue')['default']
|
||||
CustomEchartTriangle: typeof import('./src/components/custom-echart-triangle/index.vue')['default']
|
||||
CustomEchartWaterDroplet: typeof import('./src/components/custom-echart-water-droplet/index.vue')['default']
|
||||
CustomEchartWordCloud: typeof import('./src/components/custom-echart-word-cloud/index.vue')['default']
|
||||
CustomIframe: typeof import('./src/components/custom-iframe/index.vue')['default']
|
||||
CustomImportExcel: typeof import('./src/components/custom-import-excel/index.vue')['default']
|
||||
CustomProgress: typeof import('./src/components/customProgress.vue')['default']
|
||||
CustomRankList: typeof import('./src/components/custom-rank-list/index.vue')['default']
|
||||
CustomRichEditor: typeof import('./src/components/custom-rich-editor/index.vue')['default']
|
||||
CustomScrollBoard: typeof import('./src/components/custom-scroll-board/index.vue')['default']
|
||||
CustomScrollTitle: typeof import('./src/components/custom-scroll-title/index.vue')['default']
|
||||
'CustomScrollTitle copy': typeof import('./src/components/custom-scroll-title copy/index.vue')['default']
|
||||
CustomTableOperate: typeof import('./src/components/custom-table-operate/index.vue')['default']
|
||||
CustomTableTree: typeof import('./src/components/custom-table-tree/index.vue')['default']
|
||||
IndexBak: typeof import('./src/components/page-menu/index-bak.vue')['default']
|
||||
NewHyalineCake: typeof import('./src/components/custom-echart-hyaline-cake/new-hyaline-cake.vue')['default']
|
||||
PageLayout: typeof import('./src/components/page-layout/index.vue')['default']
|
||||
PageMenu: typeof import('./src/components/page-menu/index.vue')['default']
|
||||
PagePagination: typeof import('./src/components/page-pagination/index.vue')['default']
|
||||
RouterLink: typeof import('vue-router')['RouterLink']
|
||||
RouterView: typeof import('vue-router')['RouterView']
|
||||
SubTop: typeof import('./src/components/subTop.vue')['default']
|
||||
UpFile: typeof import('./src/components/custom-rich-editor/upFile.js')['default']
|
||||
UpImg: typeof import('./src/components/upImg.vue')['default']
|
||||
}
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "sub-operation-service",
|
||||
"name": "digital-agriculture-screen",
|
||||
"private": true,
|
||||
"version": "0.0.1",
|
||||
"type": "module",
|
||||
@ -7,7 +7,6 @@
|
||||
"dev": "vite --mode development",
|
||||
"build": "vite build --mode production",
|
||||
"test": "vite build --mode test",
|
||||
"pre": "vite build --mode pre",
|
||||
"preview": "vite preview",
|
||||
"format": "prettier --write 'src/**/*.{vue,ts,tsx,js,jsx,css,less,scss,json,md}'",
|
||||
"eslint": "npx eslint --init",
|
||||
@ -18,11 +17,18 @@
|
||||
"dependencies": {
|
||||
"@element-plus/icons-vue": "^2.3.1",
|
||||
"@smallwei/avue": "^3.6.2",
|
||||
"@vuemap/vue-amap": "^2.0",
|
||||
"@vuemap/vue-amap-loca": "^2.0",
|
||||
"@wangeditor/editor": "^5.1.23",
|
||||
"@wangeditor/editor-for-vue": "^5.1.12",
|
||||
"animate.css": "^4.1.1",
|
||||
"axios": "^1.6.5",
|
||||
"echarts": "^5.6.0",
|
||||
"echarts-gl": "^2.0.9",
|
||||
"echarts-liquidfill": "^3.1.0",
|
||||
"echarts-wordcloud": "^2.1.0",
|
||||
"element-plus": "^2.7.2",
|
||||
"hls.js": "^1.6.2",
|
||||
"js-base64": "^3.7.6",
|
||||
"lodash": "^4.17.21",
|
||||
"moment": "^2.30.1",
|
||||
@ -31,9 +37,11 @@
|
||||
"pinia": "^2.1.7",
|
||||
"pinia-plugin-persistedstate": "^3.2.1",
|
||||
"screenfull": "^6.0.2",
|
||||
"splitpanes": "^4.0.3",
|
||||
"vue": "^3.3.11",
|
||||
"vue-router": "^4.2.5"
|
||||
"vue-cesium": "^3.2.9",
|
||||
"vue-echarts": "^7.0.3",
|
||||
"vue-router": "^4.2.5",
|
||||
"vue3-scroll-seamless": "^1.0.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.23.7",
|
||||
|
19
sub-operation-service/src/apis/agricultural.js
Normal file
@ -0,0 +1,19 @@
|
||||
import request from '@/utils/axios';
|
||||
|
||||
//农资
|
||||
|
||||
//获取农资分类查询数据
|
||||
export function transaction(params = {}) {
|
||||
return request('goods/business/category/transactionType?type=1', {
|
||||
method: 'GET',
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
//获取农资列表数据
|
||||
export function agriculturalList(params) {
|
||||
return request('goods/business/category/transactionGoodInfo', {
|
||||
method: 'GET',
|
||||
params,
|
||||
});
|
||||
}
|
BIN
sub-operation-service/src/assets/images/basic/containerBG.png
Normal file
After Width: | Height: | Size: 2.9 MiB |
BIN
sub-operation-service/src/assets/images/basic/containerBotBG.png
Normal file
After Width: | Height: | Size: 88 KiB |
BIN
sub-operation-service/src/assets/images/basic/footerBG.png
Normal file
After Width: | Height: | Size: 88 KiB |
After Width: | Height: | Size: 10 KiB |
BIN
sub-operation-service/src/assets/images/basic/headerBG.png
Normal file
After Width: | Height: | Size: 86 KiB |
BIN
sub-operation-service/src/assets/images/basic/leftArrowIcon.png
Normal file
After Width: | Height: | Size: 517 B |
BIN
sub-operation-service/src/assets/images/basic/leftTitleBG.png
Normal file
After Width: | Height: | Size: 5.9 KiB |
BIN
sub-operation-service/src/assets/images/basic/rightArrowIcon.png
Normal file
After Width: | Height: | Size: 517 B |
BIN
sub-operation-service/src/assets/images/basic/rightTitleBG.png
Normal file
After Width: | Height: | Size: 6.0 KiB |
BIN
sub-operation-service/src/assets/images/basic/tagBG-small.png
Normal file
After Width: | Height: | Size: 8.6 KiB |
BIN
sub-operation-service/src/assets/images/basic/tagBG.png
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
sub-operation-service/src/assets/images/smartFarm/danger.png
Normal file
After Width: | Height: | Size: 9.7 KiB |
BIN
sub-operation-service/src/assets/images/smartFarm/normal.png
Normal file
After Width: | Height: | Size: 9.7 KiB |
BIN
sub-operation-service/src/assets/images/smartFarm/分光器.png
Normal file
After Width: | Height: | Size: 2.1 KiB |
BIN
sub-operation-service/src/assets/images/smartFarm/悬浮物.png
Normal file
After Width: | Height: | Size: 2.0 KiB |
BIN
sub-operation-service/src/assets/images/smartFarm/水质溶解氧.png
Normal file
After Width: | Height: | Size: 2.1 KiB |
BIN
sub-operation-service/src/assets/images/smartFarm/水质电导率.png
Normal file
After Width: | Height: | Size: 1.9 KiB |
BIN
sub-operation-service/src/assets/images/smartFarm/浊度.png
Normal file
After Width: | Height: | Size: 2.6 KiB |
BIN
sub-operation-service/src/assets/images/smartFarm/温度.png
Normal file
After Width: | Height: | Size: 2.1 KiB |
BIN
sub-operation-service/src/assets/images/smartFarm/灌溉控制.png
Normal file
After Width: | Height: | Size: 3.0 KiB |
BIN
sub-operation-service/src/assets/images/smartFarm/病虫害.png
Normal file
After Width: | Height: | Size: 1.8 KiB |
BIN
sub-operation-service/src/assets/images/smartFarm/育苗.png
Normal file
After Width: | Height: | Size: 1.9 KiB |
BIN
sub-operation-service/src/assets/images/smartFarm/酸碱度.png
Normal file
After Width: | Height: | Size: 2.1 KiB |
BIN
sub-operation-service/src/assets/images/userCenter/menu1-1.png
Normal file
After Width: | Height: | Size: 600 B |
BIN
sub-operation-service/src/assets/images/userCenter/menu1.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
sub-operation-service/src/assets/images/userCenter/menu2-1.png
Normal file
After Width: | Height: | Size: 669 B |
BIN
sub-operation-service/src/assets/images/userCenter/menu2.png
Normal file
After Width: | Height: | Size: 804 B |
BIN
sub-operation-service/src/assets/images/userCenter/menu3-1.png
Normal file
After Width: | Height: | Size: 964 B |
BIN
sub-operation-service/src/assets/images/userCenter/menu3.png
Normal file
After Width: | Height: | Size: 772 B |
BIN
sub-operation-service/src/assets/images/vsualized/bottombj.jpg
Normal file
After Width: | Height: | Size: 36 KiB |
BIN
sub-operation-service/src/assets/images/vsualized/gmmap.png
Normal file
After Width: | Height: | Size: 170 KiB |
After Width: | Height: | Size: 7.4 KiB |
After Width: | Height: | Size: 8.2 KiB |
BIN
sub-operation-service/src/assets/images/vsualized/home/area.png
Normal file
After Width: | Height: | Size: 8.7 KiB |
After Width: | Height: | Size: 7.1 KiB |
After Width: | Height: | Size: 7.4 KiB |
After Width: | Height: | Size: 6.1 KiB |
After Width: | Height: | Size: 6.7 KiB |
After Width: | Height: | Size: 47 KiB |
After Width: | Height: | Size: 2.2 KiB |
BIN
sub-operation-service/src/assets/images/vsualized/home/nav.png
Normal file
After Width: | Height: | Size: 1.4 KiB |
After Width: | Height: | Size: 5.5 KiB |
After Width: | Height: | Size: 16 KiB |
After Width: | Height: | Size: 3.1 KiB |
After Width: | Height: | Size: 7.4 KiB |
After Width: | Height: | Size: 1.7 KiB |
After Width: | Height: | Size: 5.2 KiB |
After Width: | Height: | Size: 7.4 KiB |
After Width: | Height: | Size: 7.5 KiB |
BIN
sub-operation-service/src/assets/images/vsualized/home1.png
Normal file
After Width: | Height: | Size: 78 KiB |
BIN
sub-operation-service/src/assets/images/vsualized/home2.png
Normal file
After Width: | Height: | Size: 82 KiB |
BIN
sub-operation-service/src/assets/images/vsualized/home3.png
Normal file
After Width: | Height: | Size: 81 KiB |
BIN
sub-operation-service/src/assets/images/vsualized/home4.png
Normal file
After Width: | Height: | Size: 77 KiB |
BIN
sub-operation-service/src/assets/images/vsualized/homeb.png
Normal file
After Width: | Height: | Size: 812 KiB |
BIN
sub-operation-service/src/assets/images/vsualized/hraderbg.png
Normal file
After Width: | Height: | Size: 69 KiB |
BIN
sub-operation-service/src/assets/images/vsualized/mapopup.png
Normal file
After Width: | Height: | Size: 38 KiB |
BIN
sub-operation-service/src/assets/images/vsualized/mapopup1.png
Normal file
After Width: | Height: | Size: 66 KiB |
BIN
sub-operation-service/src/assets/images/vsualized/marker.png
Normal file
After Width: | Height: | Size: 1.6 KiB |
BIN
sub-operation-service/src/assets/images/vsualized/screenbg.png
Normal file
After Width: | Height: | Size: 285 KiB |
18532
sub-operation-service/src/components/530926geo.json
Normal file
279
sub-operation-service/src/components/centerMap.vue
Normal file
@ -0,0 +1,279 @@
|
||||
<template>
|
||||
<div class="map-center-warp">
|
||||
<!-- <img :src="getAssetsFile('images/vsualized/gmmap.png')" class="map-img" /> -->
|
||||
<div class="map-pos">
|
||||
<custom-echart-maps height="100%" width="100%" :option="chartsData.option" :geo="geoData" :name="mapName" @click="mapClick" />
|
||||
</div>
|
||||
|
||||
<el-dialog
|
||||
v-model="isShow"
|
||||
:title="currentMap.name + dialogTitle"
|
||||
:width="dialogWidth"
|
||||
:style="{ 'background-image': 'url(' + getAssetsFile(bgImageVal) + ')' }"
|
||||
:show-close="false"
|
||||
:before-close="handleClose"
|
||||
custom-class="map-info-dialog"
|
||||
>
|
||||
<template #header="{ close, titleId, titleClass }">
|
||||
<slot name="header"></slot>
|
||||
</template>
|
||||
<slot name="dialogContent"></slot>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
<script setup>
|
||||
import { isEmpty, getAssetsFile } from '@/utils';
|
||||
import { ref, reactive, onMounted, computed } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
import geoJsonData from '../components/530926geo.json'; // 根据实际情况调整路径
|
||||
const route = useRoute();
|
||||
const props = defineProps({
|
||||
dialogTitle: {
|
||||
type: String,
|
||||
default: '首页',
|
||||
},
|
||||
markerData: {
|
||||
type: Array,
|
||||
default: () => {
|
||||
return [];
|
||||
},
|
||||
},
|
||||
bgImage: {
|
||||
type: String,
|
||||
default: 'images/vsualized/mapopup.png',
|
||||
},
|
||||
dialogWidth: {
|
||||
type: Number,
|
||||
default: 360,
|
||||
},
|
||||
});
|
||||
var iconUrl = getAssetsFile('images/vsualized/gmmap2.png').href;
|
||||
let bgImageVal = computed(() => {
|
||||
return props.bgImage;
|
||||
});
|
||||
const isShow = ref(false);
|
||||
let geoData = geoJsonData;
|
||||
let mapName = ref('ZJ' + route.name);
|
||||
let mapConfig = reactive({
|
||||
map: mapName.value,
|
||||
zoom: 1,
|
||||
viewControl: {
|
||||
distance: 115,
|
||||
alpha: 60,
|
||||
beta: 0,
|
||||
minBeta: -360,
|
||||
maxBeta: 720,
|
||||
// 限制视角,使不能旋转缩放平移
|
||||
// rotateSensitivity: 0,
|
||||
// zoomSensitivity: 0,
|
||||
// panSensitivity: 0,
|
||||
},
|
||||
// itemStyle: {
|
||||
// // 三维地理坐标系组件 中三维图形的视觉属性,包括颜色,透明度,描边等。
|
||||
// color: 'rgba(75,255,180,0.2)', // 地图板块的颜色
|
||||
// opacity: 1, // 图形的不透明度 [ default: 1 ]
|
||||
// borderWidth: 1.5, // (地图板块间的分隔线)图形描边的宽度。加上描边后可以更清晰的区分每个区域 [ default: 0 ]
|
||||
// borderColor: '#4bffb4', // 图形描边的颜色。[ default: #333 ]
|
||||
// },
|
||||
itemStyle: {
|
||||
normal: {
|
||||
borderColor: '#4bffb4', // 设置镇边界的颜色
|
||||
borderWidth: 1, // 设置镇边界的宽度
|
||||
areaColor: 'rgba(75,255,180,0.2)', // 设置背景透明,只显示边界
|
||||
},
|
||||
emphasis: {
|
||||
borderColor: '#4bffb4',
|
||||
borderWidth: 2,
|
||||
areaColor: 'rgba(75,255,180,0.5)', // 设置背景透明,只显示边界
|
||||
label: {
|
||||
color: '#fff',
|
||||
},
|
||||
},
|
||||
},
|
||||
select: {
|
||||
// 选中样式:ml-citation{ref="2,7" data="citationList"}
|
||||
itemStyle: {
|
||||
areaColor: 'rgba(75,255,180,0.6)',
|
||||
borderColor: '#4bffb4',
|
||||
borderWidth: 2,
|
||||
},
|
||||
label: {
|
||||
color: '#fff',
|
||||
},
|
||||
},
|
||||
|
||||
label: {
|
||||
show: true,
|
||||
distance: 0,
|
||||
color: '#fff',
|
||||
padding: [6, 4, 2, 4],
|
||||
borderRadius: 4,
|
||||
textStyle: {
|
||||
fontSize: 12,
|
||||
color: '#fff', // 地图初始化区域字体颜色
|
||||
borderWidth: 0,
|
||||
borderColor: '#000',
|
||||
},
|
||||
},
|
||||
// emphasis: {
|
||||
// //高亮状态的效果
|
||||
// label: {
|
||||
// show: true,
|
||||
// color: '#fff',
|
||||
// },
|
||||
// itemStyle: {
|
||||
// color: 'rgba(75,255,180,0.3)', // 地图板块的颜色
|
||||
// },
|
||||
// },
|
||||
});
|
||||
|
||||
const chartsData = reactive({
|
||||
option: {
|
||||
title: {
|
||||
text: '',
|
||||
left: 'center',
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'item',
|
||||
formatter: function (params) {
|
||||
if (params.seriesType === 'effectScatter') {
|
||||
return `${params.name}: (${params.value[0]}, ${params.value[1]})`;
|
||||
}
|
||||
return params.name;
|
||||
},
|
||||
},
|
||||
toolbox: {
|
||||
show: false,
|
||||
orient: 'vertical',
|
||||
left: 'right',
|
||||
top: 'center',
|
||||
feature: {
|
||||
// dataView: { readOnly: false },
|
||||
// restore: {},
|
||||
// saveAsImage: {},
|
||||
},
|
||||
},
|
||||
geo: {
|
||||
roam: true,
|
||||
left: '30px',
|
||||
show: false,
|
||||
zlevel: -1, // 必须设置,
|
||||
...mapConfig,
|
||||
},
|
||||
series: [
|
||||
{
|
||||
roam: true,
|
||||
type: 'map',
|
||||
left: '30px',
|
||||
...mapConfig,
|
||||
},
|
||||
{
|
||||
name: '闪烁散点',
|
||||
type: 'effectScatter', // 使用 effectScatter 类型
|
||||
coordinateSystem: 'geo',
|
||||
data: props.markerData,
|
||||
symbolSize: function (val) {
|
||||
return val[2] ? val[2] / 10 : 10; // 如果没有数值,默认大小
|
||||
},
|
||||
label: {
|
||||
formatter: '{b}',
|
||||
position: 'right',
|
||||
show: false,
|
||||
},
|
||||
rippleEffect: {
|
||||
period: 4, // 波纹动画周期
|
||||
scale: 3, // 波纹缩放比例
|
||||
brushType: 'stroke', // 波纹绘制方式:'stroke' 或 'fill'
|
||||
},
|
||||
hoverAnimation: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
let currentMap = reactive({});
|
||||
|
||||
const mapClick = (data) => {
|
||||
if (props.markerData.length && props.markerData.length > 0) {
|
||||
if (data.seriesType == 'effectScatter') {
|
||||
isShow.value = true;
|
||||
currentMap = data;
|
||||
emit('mapclick', currentMap);
|
||||
}
|
||||
} else {
|
||||
isShow.value = true;
|
||||
currentMap = data;
|
||||
emit('mapclick', currentMap);
|
||||
}
|
||||
};
|
||||
const handleClose = () => {
|
||||
isShow.value = false;
|
||||
};
|
||||
|
||||
const emit = defineEmits(['mapclick']);
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
div {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
::v-deep() {
|
||||
.el-dialog {
|
||||
background: url(iconUrl) no-repeat left top;
|
||||
background-repeat: no-repeat;
|
||||
background-size: cover;
|
||||
border-radius: 8px !important;
|
||||
min-height: 200px;
|
||||
max-height: 500px;
|
||||
overflow-y: auto;
|
||||
background-size: 100% 100%;
|
||||
padding: 16px;
|
||||
margin-top: 15%;
|
||||
}
|
||||
.el-dialog__header {
|
||||
margin-top: 10px;
|
||||
text-align: left;
|
||||
padding-left: 48px;
|
||||
.el-dialog__title,
|
||||
i {
|
||||
color: #fff !important;
|
||||
}
|
||||
.el-dialog__headerbtn {
|
||||
top: 8px !important;
|
||||
}
|
||||
}
|
||||
.map-dialog-my-header {
|
||||
margin-top: 4px;
|
||||
display: inline-flex;
|
||||
justify-content: space-between;
|
||||
h4 {
|
||||
font-weight: normal !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
.map-center-warp {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
position: relative;
|
||||
height: 90%;
|
||||
.map-img {
|
||||
width: 80%;
|
||||
height: 80%;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 50%;
|
||||
object-fit: contain;
|
||||
transform: translateX(-50%);
|
||||
max-width: 1000px;
|
||||
max-height: 1000px;
|
||||
}
|
||||
.map-pos {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
}
|
||||
</style>
|
54
sub-operation-service/src/components/code-dialog/index.vue
Normal file
@ -0,0 +1,54 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
v-model="state.visible"
|
||||
draggable
|
||||
title="溯源码"
|
||||
width="40%"
|
||||
:close-on-click-modal="false"
|
||||
:close-on-press-escape="false"
|
||||
@close="onClose"
|
||||
>
|
||||
<div class="code-panel">
|
||||
<div class="code-panel-picture">
|
||||
<el-image style="width: 200px; height: 200px" :src="row.orCodeUrl" fit="cover" lazy />
|
||||
</div>
|
||||
<el-button type="primary" @click="downloadFile(row.orCodeUrl, `${row.productName}-溯源码.png`, 'image')"> 下载溯源码</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</template>
|
||||
<script setup name="code-dialog">
|
||||
import { reactive } from 'vue';
|
||||
import { downloadFile } from '@/utils';
|
||||
const props = defineProps({
|
||||
row: {
|
||||
type: Object,
|
||||
default: () => {},
|
||||
},
|
||||
});
|
||||
const emit = defineEmits(['on-close']);
|
||||
|
||||
const state = reactive({
|
||||
visible: false,
|
||||
});
|
||||
|
||||
const onClose = () => {
|
||||
state.visible = false;
|
||||
};
|
||||
|
||||
defineExpose({
|
||||
show: () => {
|
||||
state.visible = true;
|
||||
},
|
||||
hide: () => {
|
||||
onClose();
|
||||
},
|
||||
});
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.code {
|
||||
&-panel {
|
||||
padding-bottom: 40px;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
</style>
|
@ -79,24 +79,23 @@ const toPreview = () => {
|
||||
}
|
||||
.viewer-btn-warp {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 0;
|
||||
display: none;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
top: 50%;
|
||||
left: 0;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
.viewer-btn {
|
||||
background: $color-balck-mask;
|
||||
display: inline-block;
|
||||
padding: 4px 8px;
|
||||
font-size: 14px;
|
||||
border-radius: 16px;
|
||||
color: $color-fff;
|
||||
font-size: 14px;
|
||||
background: $color-balck-mask;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.c-custom-img-warp:hover {
|
||||
.viewer-btn-warp {
|
||||
display: inline-block !important;
|
||||
|
@ -0,0 +1,220 @@
|
||||
<template>
|
||||
<div class="carousel" style="width: 500px">
|
||||
<el-carousel
|
||||
v-if="type === 'image'"
|
||||
ref="carouselRef"
|
||||
:interval="option.interval"
|
||||
:arrow="option.arrow"
|
||||
:indicator-position="option.indicatorPosition"
|
||||
@change="handleChange"
|
||||
>
|
||||
<el-carousel-item v-for="(item, index) in data" :key="index">
|
||||
<img :src="item.image" style="width: 100%; height: auto" />
|
||||
</el-carousel-item>
|
||||
</el-carousel>
|
||||
|
||||
<div v-if="type === 'video'" class="carousel-video">
|
||||
<!-- <img :src="state.videoPicture" class="carousel-video-picture" /> -->
|
||||
<video ref="videoRef" controls class="carousel-video-video" width="100%" height="100%" @ended="pauseVideo">
|
||||
<source :src="state.videoUrl" type="video/mp4" />
|
||||
Your browser does not support the video tag.
|
||||
</video>
|
||||
<!-- <span class="carousel-video-btn" @click="handlePlay">
|
||||
<el-icon><VideoPlay /></el-icon>
|
||||
</span> -->
|
||||
</div>
|
||||
|
||||
<div class="carousel-container">
|
||||
<span class="carousel-arrow carousel-arrow-left" @click="handleLeftClick">
|
||||
<el-icon><ArrowLeftBold /></el-icon>
|
||||
</span>
|
||||
<el-scrollbar ref="scrollbarRef" class="carousel-list">
|
||||
<div
|
||||
v-for="(item, index) in data"
|
||||
:key="index"
|
||||
:class="`carousel-list-item ${state.current === index ? 'active' : ''}`"
|
||||
@click="handleClick(index)"
|
||||
>
|
||||
<el-image style="width: 100px; height: 100px" :src="item.image ?? item" fit="cover" />
|
||||
</div>
|
||||
</el-scrollbar>
|
||||
<span class="carousel-arrow carousel-arrow-right" @click="handleRightClick">
|
||||
<el-icon><ArrowRightBold /></el-icon>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script setup name="custom-carousel-picture">
|
||||
import { reactive, ref } from 'vue';
|
||||
|
||||
const props = defineProps({
|
||||
data: { type: Array, default: () => [] },
|
||||
type: { type: String, default: 'image' },
|
||||
option: {
|
||||
type: Object,
|
||||
default: () => {
|
||||
return {
|
||||
height: '',
|
||||
initialIndex: 0,
|
||||
autoplay: true,
|
||||
loop: true,
|
||||
interval: 3000,
|
||||
arrow: 'never',
|
||||
indicatorPosition: 'none',
|
||||
};
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits(['']);
|
||||
|
||||
const carouselRef = ref(null);
|
||||
const scrollbarRef = ref(null);
|
||||
const videoRef = ref(null);
|
||||
|
||||
const state = reactive({
|
||||
current: 0,
|
||||
isReady: false,
|
||||
videoPicture: '',
|
||||
videoUrl: '',
|
||||
});
|
||||
|
||||
const handleChange = (cur, last) => {
|
||||
state.current = cur;
|
||||
};
|
||||
|
||||
const handleLeftClick = () => {
|
||||
// const index = carouselRef.value.activeIndex;
|
||||
// carouselRef.value.setActiveItem(index + 1);
|
||||
scrollbarRef.value.setScrollLeft(scrollbarRef.value.wrapRef.scrollLeft - 120);
|
||||
};
|
||||
|
||||
const handleRightClick = () => {
|
||||
// const index = carouselRef.value.activeIndex;
|
||||
// carouselRef.value.setActiveItem(index - 1);
|
||||
scrollbarRef.value.setScrollLeft(scrollbarRef.value.wrapRef.scrollLeft + 120);
|
||||
};
|
||||
|
||||
const playVideo = () => {
|
||||
if (videoRef.value) {
|
||||
videoRef.value.play();
|
||||
}
|
||||
};
|
||||
|
||||
const pauseVideo = () => {
|
||||
if (videoRef.value) {
|
||||
videoRef.value.pause();
|
||||
}
|
||||
};
|
||||
|
||||
const handleClick = (index) => {
|
||||
const { type, data } = props;
|
||||
switch (type) {
|
||||
case 'image': {
|
||||
carouselRef.value.setActiveItem(index);
|
||||
break;
|
||||
}
|
||||
case 'video': {
|
||||
const url = data[index];
|
||||
state.videoUrl = url;
|
||||
playVideo();
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handlePlay = () => {
|
||||
playVideo();
|
||||
};
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.carousel {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
flex-direction: column;
|
||||
&-container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
margin-top: 30px;
|
||||
}
|
||||
&-list {
|
||||
flex: 3;
|
||||
:deep(.el-scrollbar__bar) {
|
||||
display: none !important;
|
||||
}
|
||||
:deep(.el-scrollbar__view) {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
}
|
||||
&-item {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin: 10px;
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
border: 2px solid #ffffff;
|
||||
border-radius: 4px;
|
||||
text-align: center;
|
||||
color: var(--el-color-danger);
|
||||
background: var(--el-color-danger-light-9);
|
||||
flex-shrink: 0;
|
||||
cursor: pointer;
|
||||
&.active {
|
||||
border-color: var(--el-color-primary);
|
||||
}
|
||||
}
|
||||
}
|
||||
&-arrow {
|
||||
width: 50px;
|
||||
text-align: center;
|
||||
.el-icon {
|
||||
display: inline-block;
|
||||
margin: 0 auto;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
font-size: 32px;
|
||||
line-height: 40px;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
&-video {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
min-height: 300px;
|
||||
&-picture {
|
||||
position: absolute;
|
||||
z-index: 10;
|
||||
}
|
||||
&-video {
|
||||
position: absolute;
|
||||
z-index: 11;
|
||||
}
|
||||
&-btn {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
z-index: 12;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin-top: -25px;
|
||||
margin-left: -25px;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
border-radius: 25px;
|
||||
background: rgb(0 0 0 / 20%);
|
||||
flex-direction: row;
|
||||
cursor: pointer;
|
||||
.el-icon {
|
||||
font-size: 32px;
|
||||
color: #ffffff;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
115
sub-operation-service/src/components/custom-echart-bar/index.vue
Normal file
@ -0,0 +1,115 @@
|
||||
<template>
|
||||
<div ref="chartRef" :style="{ height, width }"></div>
|
||||
</template>
|
||||
<script>
|
||||
import { ref, reactive, watchEffect } from 'vue';
|
||||
import { cloneDeep } from 'lodash';
|
||||
import { useEcharts } from '@/hooks/useEcharts';
|
||||
export default {
|
||||
name: 'CustomEchartBar',
|
||||
props: {
|
||||
chartData: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
required: true,
|
||||
},
|
||||
option: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
default: 'bar',
|
||||
},
|
||||
width: {
|
||||
type: String,
|
||||
default: '100%',
|
||||
},
|
||||
height: {
|
||||
type: String,
|
||||
default: 'calc(100vh - 78px)',
|
||||
},
|
||||
isSeries: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
emits: ['click'],
|
||||
setup(props, { emit }) {
|
||||
const chartRef = ref(null);
|
||||
const { setOptions, getInstance, startAutoPlay } = useEcharts(chartRef);
|
||||
const option = reactive({
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: {
|
||||
type: 'shadow',
|
||||
label: {
|
||||
show: true,
|
||||
backgroundColor: '#333',
|
||||
},
|
||||
},
|
||||
},
|
||||
legend: {
|
||||
top: 30,
|
||||
},
|
||||
grid: {
|
||||
top: 60,
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
data: [],
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
},
|
||||
series: [],
|
||||
});
|
||||
|
||||
watchEffect(() => {
|
||||
props.chartData && initCharts();
|
||||
});
|
||||
|
||||
function initCharts() {
|
||||
if (props.option) {
|
||||
Object.assign(option, cloneDeep(props.option));
|
||||
}
|
||||
let typeArr = Array.from(new Set(props.chartData.map((item) => item.type)));
|
||||
let xAxisData = Array.from(new Set(props.chartData.map((item) => item.name)));
|
||||
let seriesData = [];
|
||||
typeArr.forEach((type, index) => {
|
||||
const barStyle = props.option?.barStyle ?? {};
|
||||
let obj = { name: type, type: props.type, ...barStyle };
|
||||
let data = [];
|
||||
xAxisData.forEach((x) => {
|
||||
let dataArr = props.chartData.filter((item) => type === item.type && item.name == x);
|
||||
if (dataArr && dataArr.length > 0) {
|
||||
data.push(dataArr[0].value);
|
||||
} else {
|
||||
data.push(null);
|
||||
}
|
||||
});
|
||||
obj['data'] = data;
|
||||
if (props.option?.color) {
|
||||
obj.color = props.option?.color[index];
|
||||
}
|
||||
seriesData.push(obj);
|
||||
});
|
||||
option.series = props.isSeries && option.series.length > 0 ? option.series : seriesData;
|
||||
option.xAxis.data = xAxisData;
|
||||
setOptions(option);
|
||||
startAutoPlay({
|
||||
interval: 2000,
|
||||
seriesIndex: 0,
|
||||
showTooltip: true,
|
||||
});
|
||||
getInstance()?.off('click', onClick);
|
||||
getInstance()?.on('click', onClick);
|
||||
}
|
||||
|
||||
function onClick(params) {
|
||||
emit('click', params);
|
||||
}
|
||||
return { chartRef };
|
||||
},
|
||||
};
|
||||
</script>
|
@ -0,0 +1,103 @@
|
||||
<template>
|
||||
<div ref="chartRef" :style="{ height, width }"></div>
|
||||
</template>
|
||||
<script>
|
||||
import { ref, reactive, watch, watchEffect } from 'vue';
|
||||
import { cloneDeep } from 'lodash';
|
||||
import { useEcharts } from '@/hooks/useEcharts';
|
||||
|
||||
export default {
|
||||
name: 'customEchartBubble',
|
||||
props: {
|
||||
chartData: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
size: {
|
||||
type: Object,
|
||||
default: () => {},
|
||||
},
|
||||
option: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
width: {
|
||||
type: String,
|
||||
default: '100%',
|
||||
},
|
||||
height: {
|
||||
type: String,
|
||||
default: 'calc(100vh - 78px)',
|
||||
},
|
||||
},
|
||||
emits: ['click'],
|
||||
setup(props, { emit }) {
|
||||
const chartRef = ref(null);
|
||||
const { setOptions, getInstance, resize } = useEcharts(chartRef);
|
||||
const option = reactive({
|
||||
tooltip: {
|
||||
formatter: function (params) {
|
||||
console.log(params);
|
||||
var str = params.marker + '' + params.data.name + '</br>' + '交易额:' + params.data.value + '万元</br>';
|
||||
return str;
|
||||
},
|
||||
},
|
||||
animationDurationUpdate: function (idx) {
|
||||
// 越往后的数据延迟越大
|
||||
return idx * 100;
|
||||
},
|
||||
animationEasingUpdate: 'bounceIn',
|
||||
color: ['#fff', '#fff', '#fff'],
|
||||
series: [
|
||||
{
|
||||
type: 'graph',
|
||||
layout: 'force',
|
||||
force: {
|
||||
repulsion: 80,
|
||||
edgeLength: 20,
|
||||
},
|
||||
roam: true,
|
||||
label: {
|
||||
normal: {
|
||||
show: true,
|
||||
},
|
||||
},
|
||||
data: props.chartData,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
watchEffect(() => {
|
||||
props.chartData && initCharts();
|
||||
});
|
||||
|
||||
watch(
|
||||
() => props.size,
|
||||
() => {
|
||||
console.info(' option.series[0]', props.chartData);
|
||||
resize();
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
}
|
||||
);
|
||||
|
||||
function initCharts() {
|
||||
if (props.option) {
|
||||
Object.assign(option, cloneDeep(props.option));
|
||||
}
|
||||
option.series[0].data = props.chartData;
|
||||
setOptions(option);
|
||||
resize();
|
||||
getInstance()?.off('click', onClick);
|
||||
getInstance()?.on('click', onClick);
|
||||
}
|
||||
|
||||
function onClick(params) {
|
||||
emit('click', params);
|
||||
}
|
||||
|
||||
return { chartRef };
|
||||
},
|
||||
};
|
||||
</script>
|
@ -0,0 +1,147 @@
|
||||
<template>
|
||||
<div ref="chartRef" :style="{ height, width }"></div>
|
||||
</template>
|
||||
<script>
|
||||
import { ref, reactive, watchEffect, watch } from 'vue';
|
||||
import { cloneDeep } from 'lodash';
|
||||
import { useEcharts } from '@/hooks/useEcharts';
|
||||
|
||||
export default {
|
||||
name: 'CustomEchartLine',
|
||||
props: {
|
||||
chartData: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
required: true,
|
||||
},
|
||||
option: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
default: 'line',
|
||||
},
|
||||
width: {
|
||||
type: String,
|
||||
default: '100%',
|
||||
},
|
||||
height: {
|
||||
type: String,
|
||||
default: 'calc(100vh - 78px)',
|
||||
},
|
||||
},
|
||||
emits: ['click'],
|
||||
setup(props, { emit }) {
|
||||
const chartRef = ref(null);
|
||||
const { setOptions, getInstance, startAutoPlay } = useEcharts(chartRef);
|
||||
const option = reactive({
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: {
|
||||
type: 'shadow',
|
||||
label: {
|
||||
show: true,
|
||||
backgroundColor: '#333',
|
||||
},
|
||||
},
|
||||
},
|
||||
legend: {
|
||||
top: 30,
|
||||
},
|
||||
grid: {
|
||||
top: 60,
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
data: [],
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
},
|
||||
series: [],
|
||||
});
|
||||
const xData = ref([]);
|
||||
const yDataColumn = ref([]);
|
||||
const yDataLine = ref([]);
|
||||
|
||||
watchEffect(() => {
|
||||
props.chartData && initCharts();
|
||||
});
|
||||
|
||||
watch(
|
||||
() => props.chartData,
|
||||
() => {
|
||||
console.info('props.chartData变化', props.chartData);
|
||||
props.chartData && initCharts();
|
||||
},
|
||||
{
|
||||
deep: true,
|
||||
immediate: true,
|
||||
}
|
||||
);
|
||||
|
||||
function hexToRGBA(hex, alpha = 1) {
|
||||
let hexCode = hex.replace('#', '');
|
||||
if (hexCode.length === 3) {
|
||||
hexCode = hexCode
|
||||
.split('')
|
||||
.map((char) => char + char)
|
||||
.join('');
|
||||
}
|
||||
const r = parseInt(hexCode.slice(0, 2), 16);
|
||||
const g = parseInt(hexCode.slice(2, 4), 16);
|
||||
const b = parseInt(hexCode.slice(4, 6), 16);
|
||||
return `rgba(${r}, ${g}, ${b}, ${alpha})`;
|
||||
}
|
||||
|
||||
function setAreaStyle(color) {
|
||||
return {
|
||||
color: {
|
||||
type: 'linear',
|
||||
x: 0,
|
||||
y: 0,
|
||||
x2: 0,
|
||||
y2: 1,
|
||||
colorStops: [
|
||||
{
|
||||
offset: 0,
|
||||
color: hexToRGBA(color, 1),
|
||||
},
|
||||
{
|
||||
offset: 1,
|
||||
color: hexToRGBA(color, 0.2),
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function initCharts() {
|
||||
xData.value = props.chartData.map((item) => item.name);
|
||||
yDataColumn.value = props.chartData.map((item) => item.value1);
|
||||
yDataLine.value = props.chartData.map((item) => item.value);
|
||||
if (props.option) {
|
||||
Object.assign(option, cloneDeep(props.option));
|
||||
}
|
||||
option.series[0].data = yDataColumn.value;
|
||||
option.series[1].data = yDataLine.value;
|
||||
option.xAxis.data = xData.value;
|
||||
setOptions(option);
|
||||
startAutoPlay({
|
||||
interval: 2000,
|
||||
seriesIndex: 0,
|
||||
showTooltip: true,
|
||||
});
|
||||
getInstance()?.off('click', onClick);
|
||||
getInstance()?.on('click', onClick);
|
||||
}
|
||||
|
||||
function onClick(params) {
|
||||
emit('click', params);
|
||||
}
|
||||
|
||||
return { chartRef };
|
||||
},
|
||||
};
|
||||
</script>
|
@ -0,0 +1,294 @@
|
||||
<template>
|
||||
<div ref="chartRef" style="width: 100%; height: 170px"></div>
|
||||
</template>
|
||||
<script>
|
||||
import { ref, reactive, watchEffect } from 'vue';
|
||||
import { cloneDeep } from 'lodash';
|
||||
import { useEcharts } from '@/hooks/useEcharts';
|
||||
export default {
|
||||
name: 'CustomEchartHyalineCake',
|
||||
props: {
|
||||
chartData: {
|
||||
type: Array,
|
||||
default: () => [
|
||||
{
|
||||
name: '项目一',
|
||||
value: 60,
|
||||
},
|
||||
{
|
||||
name: '项目二',
|
||||
value: 44,
|
||||
},
|
||||
{
|
||||
name: '项目三',
|
||||
value: 32,
|
||||
},
|
||||
],
|
||||
},
|
||||
option: {
|
||||
type: Object,
|
||||
default: () => ({
|
||||
k: 1,
|
||||
opacity: '0,6',
|
||||
itemGap: 0.2,
|
||||
itemHeight: 120,
|
||||
autoItemHeight: 0,
|
||||
legendSuffix: '',
|
||||
}),
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
default: 'bar',
|
||||
},
|
||||
width: {
|
||||
type: String,
|
||||
default: '100%',
|
||||
},
|
||||
height: {
|
||||
type: String,
|
||||
default: 'calc(100vh - 78px)',
|
||||
},
|
||||
isSeries: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
emits: ['click'],
|
||||
setup(props, { emit }) {
|
||||
const chartRef = ref(null);
|
||||
const { setOptions, getInstance, startAutoPlay } = useEcharts(chartRef);
|
||||
const option = ref({});
|
||||
watchEffect(() => {
|
||||
props.chartData && initCharts();
|
||||
});
|
||||
|
||||
function initCharts() {
|
||||
if (props.option) {
|
||||
option.value = Object.assign(option, cloneDeep(props.option));
|
||||
}
|
||||
option.value = getPie3D(props.chartData, props.option.opacity);
|
||||
setOptions(option.value);
|
||||
}
|
||||
|
||||
function getParametricEquation(startRatio, endRatio, isSelected, isHovered, k, h) {
|
||||
const midRatio = (startRatio + endRatio) / 2;
|
||||
const startRadian = startRatio * Math.PI * 2;
|
||||
const endRadian = endRatio * Math.PI * 2;
|
||||
const midRadian = midRatio * Math.PI * 2;
|
||||
if (startRatio === 0 && endRatio === 1) {
|
||||
isSelected = false;
|
||||
}
|
||||
k = typeof k !== 'undefined' ? k : 1 / 3;
|
||||
const offsetX = Math.cos(midRadian) * props.option.itemGap;
|
||||
const offsetY = Math.sin(midRadian) * props.option.itemGap;
|
||||
const hoverRate = 1;
|
||||
return {
|
||||
u: {
|
||||
min: -Math.PI,
|
||||
max: Math.PI * 3,
|
||||
step: Math.PI / 32,
|
||||
},
|
||||
v: {
|
||||
min: 0,
|
||||
max: Math.PI * 2,
|
||||
step: Math.PI / 20,
|
||||
},
|
||||
|
||||
x(u, v) {
|
||||
if (u < startRadian) {
|
||||
return offsetX + Math.cos(startRadian) * (1 + Math.cos(v) * k) * hoverRate;
|
||||
}
|
||||
if (u > endRadian) {
|
||||
return offsetX + Math.cos(endRadian) * (1 + Math.cos(v) * k) * hoverRate;
|
||||
}
|
||||
return offsetX + Math.cos(u) * (1 + Math.cos(v) * k) * hoverRate;
|
||||
},
|
||||
y(u, v) {
|
||||
if (u < startRadian) {
|
||||
return offsetY + Math.sin(startRadian) * (1 + Math.cos(v) * k) * hoverRate;
|
||||
}
|
||||
if (u > endRadian) {
|
||||
return offsetY + Math.sin(endRadian) * (1 + Math.cos(v) * k) * hoverRate;
|
||||
}
|
||||
return offsetY + Math.sin(u) * (1 + Math.cos(v) * k) * hoverRate;
|
||||
},
|
||||
z(u, v) {
|
||||
if (u < -Math.PI * 0.5) {
|
||||
return Math.sin(u);
|
||||
}
|
||||
if (u > Math.PI * 2.5) {
|
||||
return Math.sin(u) * h * 0.1;
|
||||
}
|
||||
return Math.sin(v) > 0 ? 1 * h * 0.1 : -1;
|
||||
},
|
||||
};
|
||||
}
|
||||
// 生成模拟 3D 饼图的配置项
|
||||
function getPie3D(pieData) {
|
||||
const series = [];
|
||||
// 总和
|
||||
let sumValue = 0;
|
||||
let startValue = 0;
|
||||
let endValue = 0;
|
||||
const legendData = [];
|
||||
const k = props.option.k ?? 1;
|
||||
for (let i = 0; i < pieData.length; i += 1) {
|
||||
sumValue += pieData[i].value;
|
||||
|
||||
const seriesItem = {
|
||||
name: typeof pieData[i].name === 'undefined' ? `series${i}` : pieData[i].name,
|
||||
type: 'surface',
|
||||
parametric: true,
|
||||
wireframe: {
|
||||
show: false,
|
||||
},
|
||||
pieData: pieData[i],
|
||||
|
||||
itemStyle: {
|
||||
// color: colors[i], // 自定义颜色
|
||||
opacity: props.option.opacity,
|
||||
borderRadius: 300,
|
||||
borderColor: '#fff',
|
||||
borderWidth: 0,
|
||||
},
|
||||
pieStatus: {
|
||||
selected: false,
|
||||
hovered: false,
|
||||
k,
|
||||
},
|
||||
};
|
||||
if (typeof pieData[i].itemStyle !== 'undefined') {
|
||||
const { itemStyle } = pieData[i];
|
||||
typeof pieData[i].itemStyle.color !== 'undefined' ? (itemStyle.color = pieData[i].itemStyle.color) : null;
|
||||
typeof pieData[i].itemStyle.opacity !== 'undefined' ? (itemStyle.opacity = pieData[i].itemStyle.opacity) : null;
|
||||
seriesItem.itemStyle = itemStyle;
|
||||
}
|
||||
series.push(seriesItem);
|
||||
}
|
||||
for (let i = 0; i < series.length; i += 1) {
|
||||
endValue = startValue + series[i].pieData.value;
|
||||
series[i].pieData.startRatio = startValue / sumValue;
|
||||
series[i].pieData.endRatio = endValue / sumValue;
|
||||
series[i].parametricEquation = getParametricEquation(
|
||||
series[i].pieData.startRatio,
|
||||
series[i].pieData.endRatio,
|
||||
false,
|
||||
false,
|
||||
k,
|
||||
props.option.autoItemHeight > 0 ? props.option.autoItemHeight * series[i].pieData.value : props.option.itemHeight
|
||||
);
|
||||
startValue = endValue;
|
||||
legendData.push(series[i].name);
|
||||
}
|
||||
const option = Object.assign(
|
||||
{
|
||||
tooltip: {
|
||||
// tooltip样式调整添加这个类名
|
||||
className: 'custom-tooltip-container', // 自定义父容器类名
|
||||
backgroundColor: 'rgba(0,0,0,0.5)',
|
||||
borderColor: '#35d0c0',
|
||||
color: '#fff',
|
||||
position: function (point, params, dom, rect, size) {
|
||||
var x = point[0];
|
||||
var y = point[1];
|
||||
var viewWidth = size.viewSize[0];
|
||||
var viewHeight = size.viewSize[1];
|
||||
var boxWidth = size.contentSize[0];
|
||||
var boxHeight = size.contentSize[1];
|
||||
// 判断 tooltip 位置,调整其位置使其不会超出图表边界
|
||||
if (x + boxWidth > viewWidth) {
|
||||
x = x - boxWidth;
|
||||
}
|
||||
if (y + boxHeight > viewHeight) {
|
||||
y = y - boxHeight;
|
||||
}
|
||||
// 保证 tooltip 始终在图表内部
|
||||
if (x < 0) {
|
||||
x = 0;
|
||||
}
|
||||
if (y < 0) {
|
||||
y = 0;
|
||||
}
|
||||
|
||||
return [x, y];
|
||||
},
|
||||
formatter: (params) => {
|
||||
if (params.seriesName !== 'mouseoutSeries') {
|
||||
return `
|
||||
<span style="color:#FFF">
|
||||
${params.seriesName}<br/>
|
||||
<span style="display:inline-block;
|
||||
margin-right:5px;
|
||||
border-radius:10px;
|
||||
width:10px;
|
||||
height:10px;
|
||||
background-color:${params.color};"></span>
|
||||
${option.series[params.seriesIndex].pieData.value}
|
||||
</span>
|
||||
`;
|
||||
}
|
||||
return '';
|
||||
},
|
||||
},
|
||||
xAxis3D: {
|
||||
min: -1,
|
||||
max: 1,
|
||||
},
|
||||
yAxis3D: {
|
||||
min: -1,
|
||||
max: 1,
|
||||
},
|
||||
zAxis3D: {
|
||||
min: -1,
|
||||
max: 1,
|
||||
},
|
||||
grid3D: {
|
||||
show: false,
|
||||
boxHeight: 5,
|
||||
top: '0',
|
||||
left: '-20%',
|
||||
viewControl: {
|
||||
//3d效果可以放大、旋转等,请自己去查看官方配置
|
||||
alpha: 60, //角度(这个很重要 调节角度的)
|
||||
distance: 240, //调整视角到主体的距离,类似调整zoom(这是整体大小)
|
||||
rotateSensitivity: 10, //设置旋转灵敏度,为0无法旋转
|
||||
zoomSensitivity: 10, //设置缩放灵敏度,为0无法缩放
|
||||
panSensitivity: 10, //设置平移灵敏度,0无法平移
|
||||
autoRotate: true, //自动旋转
|
||||
autoRotateAfterStill: 2, //在鼠标静止操作后恢复自动旋转的时间间隔,在开启 autoRotate 后有效
|
||||
},
|
||||
},
|
||||
legend: {
|
||||
show: true,
|
||||
right: '25%',
|
||||
top: '25%',
|
||||
orient: 'vertical',
|
||||
icon: 'circle',
|
||||
itemHeight: 12,
|
||||
itemWidth: 12,
|
||||
itemGap: 10,
|
||||
textStyle: {
|
||||
color: '#fff',
|
||||
fontSize: 14,
|
||||
fontWeight: '400',
|
||||
},
|
||||
formatter: (name) => {
|
||||
if (props.chartData.length) {
|
||||
const item = props.chartData.filter((item) => item.name === name)[0];
|
||||
return `${name}`;
|
||||
}
|
||||
},
|
||||
},
|
||||
series,
|
||||
},
|
||||
props.option
|
||||
);
|
||||
return option;
|
||||
}
|
||||
function onClick(params) {
|
||||
emit('click', params);
|
||||
}
|
||||
return { chartRef };
|
||||
},
|
||||
};
|
||||
</script>
|
@ -0,0 +1,492 @@
|
||||
<template>
|
||||
<div ref="chartRef" :style="{ width: width, height: height }"></div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, watch, onMounted, nextTick } from 'vue';
|
||||
import { cloneDeep } from 'lodash';
|
||||
import { useEcharts } from '@/hooks/useEcharts';
|
||||
|
||||
defineOptions({ name: 'NewHyalineCake' });
|
||||
|
||||
// 定义组件 props
|
||||
const props = defineProps({
|
||||
chartData: {
|
||||
type: Array,
|
||||
default: () => [
|
||||
// 默认示例数据
|
||||
{ name: '项目一', value: 60 },
|
||||
{ name: '项目二', value: 44 },
|
||||
{ name: '项目三', value: 32 },
|
||||
],
|
||||
},
|
||||
option: {
|
||||
type: Object,
|
||||
default: () => ({
|
||||
// 控制内外径关系的系数,1 表示无内径(实心饼),值越小内径越大
|
||||
k: 1,
|
||||
// 扇形边缘向外偏移距离比例(用于选中效果),单位视图坐标,可调
|
||||
itemGap: 0.2,
|
||||
// 扇形高度(影响z轴拉伸程度)
|
||||
itemHeight: 120,
|
||||
// 自动计算扇形高度时使用的系数(>0时 itemHeight 失效,使用 autoItemHeight * value )
|
||||
autoItemHeight: 0,
|
||||
// 透明度设置
|
||||
opacity: 0.6,
|
||||
// 图例后缀
|
||||
legendSuffix: '',
|
||||
}),
|
||||
},
|
||||
width: {
|
||||
type: String,
|
||||
default: '100%',
|
||||
},
|
||||
height: {
|
||||
type: String,
|
||||
default: '100%',
|
||||
},
|
||||
});
|
||||
|
||||
// 声明组件触发的事件
|
||||
const emit = defineEmits(['click']);
|
||||
|
||||
// 绑定到DOM的容器引用
|
||||
const chartRef = ref(null);
|
||||
// 使用 useEcharts 钩子初始化 ECharts 实例,并获取控制方法
|
||||
const { setOptions, getInstance } = useEcharts(chartRef);
|
||||
|
||||
// 存储当前的 ECharts 配置项
|
||||
const chartOption = ref({});
|
||||
|
||||
// 参数方程函数:生成每个扇形曲面的参数方程,用于 series-surface.parametricEquation
|
||||
function getParametricEquation(startRatio, endRatio, isSelected, isHovered, k, h) {
|
||||
// 中心弧度用于计算偏移方向
|
||||
const midRatio = (startRatio + endRatio) / 2;
|
||||
const startRadian = startRatio * Math.PI * 2;
|
||||
const endRadian = endRatio * Math.PI * 2;
|
||||
const midRadian = midRatio * Math.PI * 2;
|
||||
|
||||
// 如果整个饼只剩一个扇形,则不实现选中效果
|
||||
if (startRatio === 0 && endRatio === 1) {
|
||||
isSelected = false;
|
||||
}
|
||||
// k 取默认值 1/3(环的厚度比例),如果传入 k 则使用传入值
|
||||
k = typeof k !== 'undefined' ? k : 1 / 3;
|
||||
|
||||
// 计算选中效果的偏移量(基于扇形中心角度)
|
||||
const offsetX = isSelected ? Math.cos(midRadian) * props.option.itemGap : 0;
|
||||
const offsetY = isSelected ? Math.sin(midRadian) * props.option.itemGap : 0;
|
||||
// 计算高亮时的放大比例(未高亮时为1)
|
||||
const hoverRate = isHovered ? 1.05 : 1;
|
||||
|
||||
// 返回 parametric 曲面方程
|
||||
return {
|
||||
u: {
|
||||
// u 参数控制饼的周向角度:从 -π 到 3π,可以完整绘制一圈
|
||||
min: -Math.PI,
|
||||
max: Math.PI * 3,
|
||||
step: Math.PI / 32,
|
||||
},
|
||||
v: {
|
||||
// v 从 0 - 2π 参数控制扇形的径向方向(厚度方向)
|
||||
min: 0,
|
||||
max: Math.PI * 2,
|
||||
step: Math.PI / 20,
|
||||
},
|
||||
x(u, v) {
|
||||
// 如果在起始弧度之前,保持扇形起点的半径
|
||||
if (u < startRadian) {
|
||||
return offsetX + Math.cos(startRadian) * (1 + Math.cos(v) * k) * hoverRate;
|
||||
}
|
||||
// 如果在结束弧度之后,保持扇形终点的半径
|
||||
if (u > endRadian) {
|
||||
return offsetX + Math.cos(endRadian) * (1 + Math.cos(v) * k) * hoverRate;
|
||||
}
|
||||
// 扇形中间部分正常绘制
|
||||
return offsetX + Math.cos(u) * (1 + Math.cos(v) * k) * hoverRate;
|
||||
},
|
||||
y(u, v) {
|
||||
if (u < startRadian) {
|
||||
return offsetY + Math.sin(startRadian) * (1 + Math.cos(v) * k) * hoverRate;
|
||||
}
|
||||
if (u > endRadian) {
|
||||
return offsetY + Math.sin(endRadian) * (1 + Math.cos(v) * k) * hoverRate;
|
||||
}
|
||||
return offsetY + Math.sin(u) * (1 + Math.cos(v) * k) * hoverRate;
|
||||
},
|
||||
z(u, v) {
|
||||
// 在 u < -π/2 时,防止出现不必要的翻转,直接使用基本正弦
|
||||
if (u < -Math.PI * 0.5) {
|
||||
return Math.sin(u);
|
||||
}
|
||||
// 在 u > 2.5π 时,处理扇形尾部厚度
|
||||
if (u > Math.PI * 2.5) {
|
||||
return Math.sin(u) * h * 0.1;
|
||||
}
|
||||
// 正常情况下,z 根据 v 参数控制上下表面的高度差
|
||||
// 当前图形的高度是Z根据h(每个value的值决定的)
|
||||
return Math.sin(v) > 0 ? 1 * h * 0.1 : -1;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// 生成整个 3D 饼图的配置项
|
||||
function getPie3D(pieData) {
|
||||
const series = [];
|
||||
let sumValue = 0;
|
||||
// 计算总值
|
||||
pieData.forEach((item) => {
|
||||
sumValue += item.value;
|
||||
});
|
||||
|
||||
// k 为外径厚度系数
|
||||
const k = props.option.k ?? 1;
|
||||
// 构建每个扇形的 series 数据
|
||||
pieData.forEach((dataItem, idx) => {
|
||||
const seriesItem = {
|
||||
name: dataItem.name ?? `series${idx}`,
|
||||
type: 'surface',
|
||||
parametric: true,
|
||||
wireframe: { show: false },
|
||||
pieData: dataItem,
|
||||
itemStyle: {
|
||||
opacity: props.option.opacity,
|
||||
borderRadius: 300,
|
||||
borderColor: '#fff',
|
||||
borderWidth: 0,
|
||||
},
|
||||
pieStatus: {
|
||||
selected: false,
|
||||
hovered: false,
|
||||
k,
|
||||
},
|
||||
};
|
||||
// 合并自定义样式(如果有)
|
||||
if (dataItem.itemStyle) {
|
||||
const customStyle = {};
|
||||
if (dataItem.itemStyle.color !== undefined) {
|
||||
customStyle.color = dataItem.itemStyle.color;
|
||||
}
|
||||
if (dataItem.itemStyle.opacity !== undefined) {
|
||||
customStyle.opacity = dataItem.itemStyle.opacity;
|
||||
}
|
||||
seriesItem.itemStyle = { ...seriesItem.itemStyle, ...customStyle };
|
||||
}
|
||||
series.push(seriesItem);
|
||||
});
|
||||
|
||||
// 计算每个扇形的 startRatio/endRatio 和参数方程
|
||||
let startValue = 0;
|
||||
series.forEach((serie) => {
|
||||
const endValue = startValue + serie.pieData.value;
|
||||
const startRatio = startValue / sumValue;
|
||||
const endRatio = endValue / sumValue;
|
||||
serie.pieData.startRatio = startRatio;
|
||||
serie.pieData.endRatio = endRatio;
|
||||
// 初始均为未选中未高亮
|
||||
serie.parametricEquation = getParametricEquation(
|
||||
startRatio,
|
||||
endRatio,
|
||||
false,
|
||||
true,
|
||||
k,
|
||||
// 扇形高度:根据配置自动计算或使用固定高度
|
||||
props.option.autoItemHeight > 0 ? props.option.autoItemHeight * serie.pieData.value : props.option.itemHeight
|
||||
);
|
||||
startValue = endValue;
|
||||
});
|
||||
|
||||
// 添加一个透明圆环,用于实现 hover 在扇形外面时取消高亮
|
||||
series.push({
|
||||
name: 'mouseoutSeries',
|
||||
type: 'surface',
|
||||
parametric: true,
|
||||
wireframe: { show: false },
|
||||
itemStyle: { opacity: 0 },
|
||||
parametricEquation: {
|
||||
u: { min: 0, max: Math.PI * 2, step: Math.PI / 20 },
|
||||
v: { min: 0, max: Math.PI, step: Math.PI / 20 },
|
||||
x(u, v) {
|
||||
return Math.sin(v) * Math.sin(u) + Math.sin(u);
|
||||
},
|
||||
y(u, v) {
|
||||
return Math.sin(v) * Math.cos(u) + Math.cos(u);
|
||||
},
|
||||
z(u, v) {
|
||||
return Math.cos(v) > 0 ? 0.1 : -0.1;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// 基础配置:坐标轴、视角、图例、提示框等
|
||||
const option = Object.assign(
|
||||
{
|
||||
tooltip: {
|
||||
backgroundColor: 'rgba(18, 55, 85, 0.8)',
|
||||
borderColor: '#35d0c0',
|
||||
color: '#fff',
|
||||
position: (point, params, dom, rect, size) => {
|
||||
// 动态调整 tooltip 位置,避免溢出
|
||||
let x = point[0],
|
||||
y = point[1];
|
||||
const [viewW, viewH] = size.viewSize;
|
||||
const [boxW, boxH] = size.contentSize;
|
||||
if (x + boxW > viewW) x -= boxW;
|
||||
if (y + boxH > viewH) y -= boxH;
|
||||
if (x < 0) x = 0;
|
||||
if (y < 0) y = 0;
|
||||
return [x, y];
|
||||
},
|
||||
formatter: (params) => {
|
||||
// 只对非透明环(实际扇形)显示信息
|
||||
if (params.seriesName !== 'mouseoutSeries') {
|
||||
return `
|
||||
<span style="color:#FFF">
|
||||
${params.seriesName}<br/>
|
||||
<span style="
|
||||
display:inline-block;
|
||||
margin-right:5px;
|
||||
border-radius:10px;
|
||||
width:10px;
|
||||
height:10px;
|
||||
background-color:${params.color};"></span>
|
||||
${chartOption.value.series[params.seriesIndex].pieData.value}
|
||||
</span>`;
|
||||
}
|
||||
return '';
|
||||
},
|
||||
},
|
||||
xAxis3D: { min: -1, max: 1 },
|
||||
yAxis3D: { min: -1, max: 1 },
|
||||
zAxis3D: { min: -1, max: 1 },
|
||||
grid3D: {
|
||||
show: false,
|
||||
boxHeight: 5,
|
||||
top: '0',
|
||||
left: '-20%',
|
||||
viewControl: {
|
||||
// 3D 视角设置
|
||||
alpha: 60, // 俯仰角度
|
||||
distance: 240, // 视角距离
|
||||
rotateSensitivity: 10,
|
||||
zoomSensitivity: 10,
|
||||
panSensitivity: 10,
|
||||
autoRotate: true,
|
||||
autoRotateAfterStill: 2,
|
||||
},
|
||||
},
|
||||
legend: {
|
||||
show: true,
|
||||
selectedMode: false,
|
||||
right: '5%',
|
||||
top: '25%',
|
||||
orient: 'vertical',
|
||||
icon: 'circle',
|
||||
itemHeight: 12,
|
||||
itemWidth: 12,
|
||||
itemGap: 10,
|
||||
textStyle: {
|
||||
color: '#fff',
|
||||
fontSize: 14,
|
||||
fontWeight: '400',
|
||||
},
|
||||
// 图例标签显示名称和数值
|
||||
formatter: (name) => {
|
||||
const item = props.chartData.find((d) => d.name === name);
|
||||
return item ? ` ${name} ${item.value}${props.option.legendSuffix || ''}` : name;
|
||||
},
|
||||
},
|
||||
series,
|
||||
},
|
||||
// 将外部配置项覆盖基础配置
|
||||
props.option
|
||||
);
|
||||
|
||||
return option;
|
||||
}
|
||||
|
||||
// 初始化图表
|
||||
function initChart() {
|
||||
// 生成基础的3D饼图配置
|
||||
const baseOption = getPie3D(props.chartData);
|
||||
// 合并用户配置,确保不修改原始对象
|
||||
const finalOption = Object.assign({}, baseOption, cloneDeep(props.option || {}));
|
||||
chartOption.value = finalOption;
|
||||
// 设置图表配置
|
||||
setOptions(chartOption.value);
|
||||
// 等待 DOM + ECharts 初始化完成
|
||||
nextTick(() => {
|
||||
const chart = getInstance();
|
||||
if (!chart) {
|
||||
console.warn('ECharts 实例未初始化,事件绑定失败');
|
||||
return;
|
||||
}
|
||||
|
||||
// 避免重复绑定事件
|
||||
chart.off('click');
|
||||
chart.off('mouseover');
|
||||
chart.off('globalout');
|
||||
|
||||
chart.on('click', handleClick);
|
||||
chart.on('mouseover', handleMouseover);
|
||||
chart.on('globalout', handleGlobalout);
|
||||
});
|
||||
}
|
||||
function handleClick(params) {
|
||||
// 如果点击的是透明辅助环,则忽略
|
||||
if (params.seriesName === 'mouseoutSeries') return;
|
||||
|
||||
const optionVal = chartOption.value;
|
||||
const series = optionVal.series;
|
||||
const idx = params.seriesIndex;
|
||||
|
||||
// 新的选中状态(取反)
|
||||
const isSelected = !series[idx].pieStatus.selected;
|
||||
const isHovered = series[idx].pieStatus.hovered;
|
||||
const k = series[idx].pieStatus.k;
|
||||
const startRatio = series[idx].pieData.startRatio;
|
||||
const endRatio = series[idx].pieData.endRatio;
|
||||
const h = series[idx].pieData.value;
|
||||
|
||||
// 如果之前有其他扇形被选中,取消其选中状态
|
||||
if (selectedIndex !== null && selectedIndex !== idx) {
|
||||
const prev = series[selectedIndex];
|
||||
prev.parametricEquation = getParametricEquation(
|
||||
prev.pieData.startRatio,
|
||||
prev.pieData.endRatio,
|
||||
false,
|
||||
false,
|
||||
prev.pieStatus.k,
|
||||
prev.pieData.value
|
||||
);
|
||||
prev.pieStatus.selected = false;
|
||||
selectedIndex = null;
|
||||
}
|
||||
|
||||
// 设置当前扇形的选中/取消选中状态
|
||||
series[idx].parametricEquation = getParametricEquation(startRatio, endRatio, isSelected, isHovered, k, h);
|
||||
series[idx].pieStatus.selected = isSelected;
|
||||
|
||||
// 更新记录选中的系列索引
|
||||
selectedIndex = isSelected ? idx : null;
|
||||
|
||||
// 更新图表配置
|
||||
setOptions(optionVal);
|
||||
// 触发组件 click 事件供父组件使用
|
||||
emit('click', params);
|
||||
}
|
||||
// 在函数顶部声明(确保作用域覆盖所有需要的地方)
|
||||
window.debugZValues = {
|
||||
current: null,
|
||||
history: [],
|
||||
};
|
||||
function handleMouseover(params) {
|
||||
if (params.seriesName === 'mouseoutSeries') return;
|
||||
|
||||
const chart = getInstance();
|
||||
const optionVal = chart.getOption(); // 改用实时获取最新配置
|
||||
const series = optionVal.series;
|
||||
const idx = params.seriesIndex;
|
||||
const hh = series[idx].parametricEquation.z();
|
||||
window.debugZValues.current = hh;
|
||||
window.debugZValues.history.push({
|
||||
event: 'mouseover',
|
||||
series: series[idx].name,
|
||||
zValue: hh,
|
||||
time: new Date().toISOString(),
|
||||
});
|
||||
console.log('当前Z值:', hh, '历史记录:', window.debugZValues.history);
|
||||
// 打印移入前的完整状态(调试用)
|
||||
console.log(
|
||||
'[移入] 当前所有扇形状态',
|
||||
series.map((s) => ({
|
||||
name: s.name,
|
||||
hovered: s.pieStatus?.hovered,
|
||||
selected: s.pieStatus?.selected,
|
||||
height: s.parametricEquation?.z(Math.PI, Math.PI),
|
||||
}))
|
||||
);
|
||||
|
||||
// 取消之前的高亮(确保只修改状态,不破坏参数方程)
|
||||
if (hoveredIndex !== null && hoveredIndex !== idx) {
|
||||
const prev = series[hoveredIndex];
|
||||
prev.pieStatus.hovered = false; // 仅修改状态
|
||||
prev.parametricEquation = getParametricEquation(
|
||||
// 重新生成正确方程
|
||||
prev.pieData.startRatio,
|
||||
prev.pieData.endRatio,
|
||||
prev.pieStatus.selected,
|
||||
false, // isHovered=false
|
||||
prev.pieStatus.k,
|
||||
prev.pieData.value
|
||||
);
|
||||
}
|
||||
|
||||
// 设置新高亮
|
||||
const current = series[idx];
|
||||
current.pieStatus.hovered = true;
|
||||
current.parametricEquation = getParametricEquation(
|
||||
current.pieData.startRatio,
|
||||
current.pieData.endRatio,
|
||||
current.pieStatus.selected,
|
||||
true, // isHovered=true
|
||||
current.pieStatus.k,
|
||||
current.pieData.value
|
||||
);
|
||||
|
||||
hoveredIndex = idx;
|
||||
chart.setOption({ series }); // 仅更新series部分
|
||||
}
|
||||
function handleGlobalout() {
|
||||
if (hoveredIndex !== null) {
|
||||
const chart = getInstance();
|
||||
const optionVal = chart.getOption();
|
||||
const series = optionVal.series;
|
||||
const prev = series[hoveredIndex];
|
||||
|
||||
// 打印修复前的错误状态(调试用)
|
||||
console.warn('[修复前] 异常状态', {
|
||||
name: prev.name,
|
||||
z: prev.parametricEquation.z(Math.PI, Math.PI),
|
||||
equation: prev.parametricEquation,
|
||||
});
|
||||
|
||||
// 重置状态并重新生成方程
|
||||
prev.pieStatus.hovered = false;
|
||||
prev.parametricEquation = getParametricEquation(
|
||||
prev.pieData.startRatio,
|
||||
prev.pieData.endRatio,
|
||||
prev.pieStatus.selected,
|
||||
false, // isHovered=false
|
||||
prev.pieStatus.k,
|
||||
prev.pieData.value
|
||||
);
|
||||
|
||||
hoveredIndex = null;
|
||||
chart.setOption({ series }, { replaceMerge: 'series' }); // 强制替换series
|
||||
}
|
||||
}
|
||||
// 记录当前选中和高亮的系列索引
|
||||
let selectedIndex = null;
|
||||
let hoveredIndex = null;
|
||||
|
||||
// 组件挂载后绑定事件
|
||||
onMounted(() => {
|
||||
initChart();
|
||||
});
|
||||
|
||||
// 监听数据或配置变更,重新初始化图表
|
||||
watch(
|
||||
[() => props.chartData, () => props.option],
|
||||
() => {
|
||||
if (props.chartData && props.chartData.length) {
|
||||
initChart();
|
||||
}
|
||||
},
|
||||
{ immediate: true, deep: true }
|
||||
);
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 可根据需要自定义图表容器样式 */
|
||||
</style>
|
@ -0,0 +1,78 @@
|
||||
<!-- <template>
|
||||
<div ref="chartRef" :style="{ height, width }"></div>
|
||||
</template> -->
|
||||
<template>
|
||||
<div ref="chartRef" style="width: 100%; height: 260px"></div>
|
||||
</template>
|
||||
<script>
|
||||
import { ref, reactive, watch, watchEffect } from 'vue';
|
||||
import { cloneDeep } from 'lodash';
|
||||
import { useEcharts } from '@/hooks/useEcharts';
|
||||
|
||||
export default {
|
||||
name: 'CustomEchartLineLine',
|
||||
props: {
|
||||
chartData: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
size: {
|
||||
type: Object,
|
||||
default: () => {},
|
||||
},
|
||||
option: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
width: {
|
||||
type: String,
|
||||
default: '100%',
|
||||
},
|
||||
height: {
|
||||
type: String,
|
||||
default: 'calc(100vh - 78px)',
|
||||
},
|
||||
},
|
||||
emits: ['click'],
|
||||
setup(props, { emit }) {
|
||||
const chartRef = ref(null);
|
||||
const { setOptions, getInstance, resize, startAutoPlay } = useEcharts(chartRef);
|
||||
const optionVal = reactive({});
|
||||
|
||||
watchEffect(() => {
|
||||
props.option && initCharts();
|
||||
});
|
||||
|
||||
watch(
|
||||
() => props.size,
|
||||
() => {
|
||||
resize();
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
}
|
||||
);
|
||||
|
||||
function initCharts() {
|
||||
if (props.option) {
|
||||
Object.assign(optionVal, cloneDeep(props.option));
|
||||
}
|
||||
setOptions(props.option);
|
||||
startAutoPlay({
|
||||
interval: 2000,
|
||||
seriesIndex: 0,
|
||||
showTooltip: true,
|
||||
});
|
||||
resize();
|
||||
getInstance()?.off('click', onClick);
|
||||
getInstance()?.on('click', onClick);
|
||||
}
|
||||
|
||||
function onClick(params) {
|
||||
emit('click', params);
|
||||
}
|
||||
|
||||
return { chartRef };
|
||||
},
|
||||
};
|
||||
</script>
|
@ -0,0 +1,157 @@
|
||||
<template>
|
||||
<div ref="chartRef" :style="{ height, width }"></div>
|
||||
</template>
|
||||
<script>
|
||||
import { ref, reactive, watchEffect } from 'vue';
|
||||
import { cloneDeep } from 'lodash';
|
||||
import { useEcharts } from '@/hooks/useEcharts';
|
||||
|
||||
export default {
|
||||
name: 'CustomEchartLine',
|
||||
props: {
|
||||
chartData: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
required: true,
|
||||
},
|
||||
option: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
default: 'line',
|
||||
},
|
||||
width: {
|
||||
type: String,
|
||||
default: '100%',
|
||||
},
|
||||
height: {
|
||||
type: String,
|
||||
default: 'calc(100vh - 78px)',
|
||||
},
|
||||
},
|
||||
emits: ['click'],
|
||||
setup(props, { emit }) {
|
||||
const chartRef = ref(null);
|
||||
const { setOptions, getInstance, startAutoPlay } = useEcharts(chartRef);
|
||||
const option = reactive({
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: {
|
||||
type: 'shadow',
|
||||
label: {
|
||||
show: true,
|
||||
backgroundColor: '#333',
|
||||
},
|
||||
},
|
||||
},
|
||||
legend: {
|
||||
top: 30,
|
||||
},
|
||||
grid: {
|
||||
top: 60,
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
data: [],
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
},
|
||||
series: [],
|
||||
});
|
||||
|
||||
watchEffect(() => {
|
||||
props.chartData && initCharts();
|
||||
});
|
||||
|
||||
function hexToRGBA(hex, alpha = 1) {
|
||||
let hexCode = hex.replace('#', '');
|
||||
if (hexCode.length === 3) {
|
||||
hexCode = hexCode
|
||||
.split('')
|
||||
.map((char) => char + char)
|
||||
.join('');
|
||||
}
|
||||
const r = parseInt(hexCode.slice(0, 2), 16);
|
||||
const g = parseInt(hexCode.slice(2, 4), 16);
|
||||
const b = parseInt(hexCode.slice(4, 6), 16);
|
||||
return `rgba(${r}, ${g}, ${b}, ${alpha})`;
|
||||
}
|
||||
|
||||
function setAreaStyle(color) {
|
||||
return {
|
||||
color: {
|
||||
type: 'linear',
|
||||
x: 0,
|
||||
y: 0,
|
||||
x2: 0,
|
||||
y2: 1,
|
||||
colorStops: [
|
||||
{
|
||||
offset: 0,
|
||||
color: hexToRGBA(color, 1),
|
||||
},
|
||||
{
|
||||
offset: 1,
|
||||
color: hexToRGBA(color, 0.2),
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function initCharts() {
|
||||
if (props.option) {
|
||||
Object.assign(option, cloneDeep(props.option));
|
||||
}
|
||||
let typeArr = Array.from(new Set(props.chartData.map((item) => item.type)));
|
||||
let xAxisData = Array.from(new Set(props.chartData.map((item) => item.name)));
|
||||
let seriesData = [];
|
||||
typeArr.forEach((type, index) => {
|
||||
let obj = {
|
||||
name: type,
|
||||
type: props.type,
|
||||
smooth: true,
|
||||
};
|
||||
if (props.option?.color) {
|
||||
obj.areaStyle = setAreaStyle(props.option?.color[index]);
|
||||
}
|
||||
const findItem = props.chartData.find((item) => item.type == type);
|
||||
if (findItem && findItem.color) {
|
||||
obj.color = findItem.color;
|
||||
obj.areaStyle = setAreaStyle(findItem.color[index]);
|
||||
}
|
||||
let data = [];
|
||||
xAxisData.forEach((x) => {
|
||||
let dataArr = props.chartData.filter((item) => type === item.type && item.name == x);
|
||||
if (dataArr && dataArr.length > 0) {
|
||||
data.push(dataArr[0].value);
|
||||
} else {
|
||||
data.push(null);
|
||||
}
|
||||
});
|
||||
obj['data'] = data;
|
||||
seriesData.push(obj);
|
||||
});
|
||||
option.series = seriesData;
|
||||
option.xAxis.data = xAxisData;
|
||||
setOptions(option);
|
||||
startAutoPlay({
|
||||
interval: 2000,
|
||||
seriesIndex: 0,
|
||||
showTooltip: true,
|
||||
});
|
||||
// getInstance()?.off('click', onClick);
|
||||
// getInstance()?.on('click', onClick);
|
||||
}
|
||||
|
||||
function onClick(params) {
|
||||
emit('click', params);
|
||||
}
|
||||
|
||||
return { chartRef };
|
||||
},
|
||||
};
|
||||
</script>
|
@ -0,0 +1,85 @@
|
||||
<template>
|
||||
<div ref="chartMap" style="width: 100%; height: 100%"></div>
|
||||
</template>
|
||||
<script>
|
||||
import { ref, reactive, watch, watchEffect, nextTick } from 'vue';
|
||||
import { cloneDeep } from 'lodash';
|
||||
import { useEcharts } from '@/hooks/useEcharts';
|
||||
|
||||
export default {
|
||||
name: 'CustomEchartMaps',
|
||||
props: {
|
||||
chartData: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
size: {
|
||||
type: Object,
|
||||
default: () => {},
|
||||
},
|
||||
option: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
width: {
|
||||
type: String,
|
||||
default: '100%',
|
||||
},
|
||||
geo: {
|
||||
type: Object,
|
||||
default: () => {
|
||||
return {};
|
||||
},
|
||||
},
|
||||
name: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
height: {
|
||||
type: String,
|
||||
default: 'calc(100vh - 78px)',
|
||||
},
|
||||
},
|
||||
emits: ['click'],
|
||||
setup(props, { emit }) {
|
||||
const chartMap = ref(null);
|
||||
const { setOptions, getInstance, resize, regMap, startAutoPlay, onMapClick } = useEcharts(chartMap);
|
||||
const option = reactive({});
|
||||
|
||||
watchEffect(() => {
|
||||
regMap(props.name, props.geo);
|
||||
props.chartData && initCharts();
|
||||
});
|
||||
|
||||
watch(
|
||||
() => props.size,
|
||||
() => {
|
||||
resize();
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
}
|
||||
);
|
||||
|
||||
function initCharts() {
|
||||
if (props.option) {
|
||||
Object.assign(option, cloneDeep(props.option));
|
||||
}
|
||||
|
||||
setOptions(option);
|
||||
|
||||
onMapClick((data) => {
|
||||
emit('click', data);
|
||||
});
|
||||
startAutoPlay({
|
||||
interval: 2000,
|
||||
seriesIndex: 0,
|
||||
showTooltip: true,
|
||||
});
|
||||
resize();
|
||||
}
|
||||
|
||||
return { chartMap };
|
||||
},
|
||||
};
|
||||
</script>
|
@ -0,0 +1,102 @@
|
||||
<template>
|
||||
<div ref="chartRef" style="width: 100%; height: 170px"></div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref, reactive, watchEffect } from 'vue';
|
||||
import { cloneDeep } from 'lodash';
|
||||
import { useEcharts } from '@/hooks/useEcharts';
|
||||
|
||||
export default {
|
||||
name: 'CustomEchartMixin',
|
||||
props: {
|
||||
chartData: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
option: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
width: {
|
||||
type: String,
|
||||
default: '100%',
|
||||
},
|
||||
height: {
|
||||
type: String,
|
||||
default: 'calc(100vh - 78px)',
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
const chartRef = ref(null);
|
||||
const { setOptions, startAutoPlay } = useEcharts(chartRef);
|
||||
const option = reactive({
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: {
|
||||
type: 'shadow',
|
||||
label: {
|
||||
show: true,
|
||||
backgroundColor: '#333',
|
||||
},
|
||||
},
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
data: [],
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: 'bar',
|
||||
type: 'bar',
|
||||
data: [],
|
||||
itemStyle: {
|
||||
barWidth: 10,
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
watchEffect(() => {
|
||||
props.chartData && initCharts();
|
||||
});
|
||||
|
||||
function initCharts() {
|
||||
console.log(props.option);
|
||||
if (props.option.series) {
|
||||
Object.assign(option, cloneDeep(props.option));
|
||||
} else {
|
||||
if (props.option) {
|
||||
Object.assign(option, cloneDeep(props.option));
|
||||
}
|
||||
let typeArr = Array.from(new Set(props.chartData.map((item) => item.type)));
|
||||
let xAxisData = Array.from(new Set(props.chartData.map((item) => item.name)));
|
||||
let seriesData = [];
|
||||
typeArr.forEach((type, index) => {
|
||||
const barStyle = props.option?.barStyle ?? {};
|
||||
let obj = { name: type, ...barStyle };
|
||||
let chartArr = props.chartData.filter((item) => type === item.type);
|
||||
obj['data'] = chartArr.map((item) => item.value);
|
||||
obj['type'] = chartArr[0].seriesType;
|
||||
obj['stack'] = chartArr[0].stack;
|
||||
obj['itemStyle'] = chartArr[0].itemStyle;
|
||||
seriesData.push(obj);
|
||||
});
|
||||
option.series = seriesData;
|
||||
option.xAxis.data = xAxisData;
|
||||
}
|
||||
|
||||
setOptions(option);
|
||||
startAutoPlay({
|
||||
interval: 2000,
|
||||
seriesIndex: 0,
|
||||
showTooltip: true,
|
||||
});
|
||||
}
|
||||
return { chartRef };
|
||||
},
|
||||
};
|
||||
</script>
|
@ -0,0 +1,124 @@
|
||||
<template>
|
||||
<div ref="chartRef" :style="{ height, width }"></div>
|
||||
</template>
|
||||
<script>
|
||||
import { ref, reactive, watch, watchEffect } from 'vue';
|
||||
import { cloneDeep } from 'lodash';
|
||||
import { useEcharts } from '@/hooks/useEcharts';
|
||||
|
||||
export default {
|
||||
name: 'customEchartPictorialBar',
|
||||
props: {
|
||||
chartData: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
size: {
|
||||
type: Object,
|
||||
default: () => {},
|
||||
},
|
||||
option: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
width: {
|
||||
type: String,
|
||||
default: '100%',
|
||||
},
|
||||
height: {
|
||||
type: String,
|
||||
default: 'calc(100vh - 78px)',
|
||||
},
|
||||
},
|
||||
emits: ['click'],
|
||||
setup(props, { emit }) {
|
||||
const chartRef = ref(null);
|
||||
const { setOptions, getInstance, resize, startAutoPlay } = useEcharts(chartRef);
|
||||
const option = reactive({
|
||||
grid: {
|
||||
left: '3%',
|
||||
right: '4%',
|
||||
bottom: '6%',
|
||||
top: '11%',
|
||||
containLabel: true,
|
||||
},
|
||||
tooltip: {
|
||||
formatter: '{b}',
|
||||
},
|
||||
series: [
|
||||
{
|
||||
type: 'pictorialBar',
|
||||
barCategoryGap: '40%',
|
||||
barWidth: '100%',
|
||||
symbol: 'path://M0,10 L10,10 C5.5,10 5.5,5 5,0 C4.5,5 4.5,10 0,10 z',
|
||||
data: [],
|
||||
labelLine: { show: true },
|
||||
z: 10,
|
||||
itemStyle: {
|
||||
color: {
|
||||
type: 'linear',
|
||||
x: 0,
|
||||
y: 0,
|
||||
x2: 0,
|
||||
y2: 1,
|
||||
colorStops: [
|
||||
{
|
||||
offset: 0,
|
||||
color: '#000001', // 起始颜色
|
||||
},
|
||||
{
|
||||
offset: 1,
|
||||
color: '#0175b6', // 结束颜色
|
||||
},
|
||||
],
|
||||
global: false, // 默认为 false
|
||||
},
|
||||
},
|
||||
label: {
|
||||
show: false,
|
||||
position: 'top',
|
||||
formatter: '{c}',
|
||||
color: 'white',
|
||||
fontSize: 14,
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
watchEffect(() => {
|
||||
props.chartData && initCharts();
|
||||
});
|
||||
|
||||
watch(
|
||||
() => props.size,
|
||||
() => {
|
||||
resize();
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
}
|
||||
);
|
||||
|
||||
function initCharts() {
|
||||
if (props.option) {
|
||||
Object.assign(option, cloneDeep(props.option));
|
||||
}
|
||||
setOptions(option);
|
||||
startAutoPlay({
|
||||
interval: 2000,
|
||||
seriesIndex: 0,
|
||||
showTooltip: true,
|
||||
});
|
||||
resize();
|
||||
getInstance()?.off('click', onClick);
|
||||
getInstance()?.on('click', onClick);
|
||||
}
|
||||
|
||||
function onClick(params) {
|
||||
emit('click', params);
|
||||
}
|
||||
|
||||
return { chartRef };
|
||||
},
|
||||
};
|
||||
</script>
|
@ -0,0 +1,86 @@
|
||||
<template>
|
||||
<div ref="chartRef" :style="{ height, width }"></div>
|
||||
</template>
|
||||
<script>
|
||||
import { ref, reactive, watch, watchEffect } from 'vue';
|
||||
import { cloneDeep } from 'lodash';
|
||||
import { useEcharts } from '@/hooks/useEcharts';
|
||||
|
||||
export default {
|
||||
name: 'CustomEchartPie3d',
|
||||
props: {
|
||||
chartData: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
size: {
|
||||
type: Object,
|
||||
default: () => {},
|
||||
},
|
||||
option: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
width: {
|
||||
type: String,
|
||||
default: '100%',
|
||||
},
|
||||
height: {
|
||||
type: String,
|
||||
default: 'calc(100vh - 78px)',
|
||||
},
|
||||
},
|
||||
emits: ['click'],
|
||||
setup(props, { emit }) {
|
||||
const chartRef = ref(null);
|
||||
const { setOptions, getInstance, resize, onMapClick } = useEcharts(chartRef);
|
||||
const option = reactive({
|
||||
series: [],
|
||||
});
|
||||
|
||||
watchEffect(() => {
|
||||
props.chartData && initCharts();
|
||||
});
|
||||
|
||||
watch(
|
||||
() => props.size,
|
||||
() => {
|
||||
resize();
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
}
|
||||
);
|
||||
|
||||
watch(
|
||||
() => props.option,
|
||||
() => {
|
||||
initCharts();
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
}
|
||||
);
|
||||
|
||||
function initCharts() {
|
||||
if (props.option) {
|
||||
Object.assign(option, cloneDeep(props.option));
|
||||
}
|
||||
option.series = props.chartData;
|
||||
setOptions(option);
|
||||
resize();
|
||||
onMapClick((data) => {
|
||||
emit('click', data);
|
||||
});
|
||||
// getInstance()?.off('click', onClick);
|
||||
// getInstance()?.on('click', onClick);
|
||||
}
|
||||
|
||||
function onClick(params) {
|
||||
emit('click', params);
|
||||
}
|
||||
|
||||
return { chartRef };
|
||||
},
|
||||
};
|
||||
</script>
|
@ -0,0 +1,78 @@
|
||||
<template>
|
||||
<div ref="chartRef" :style="{ height, width }"></div>
|
||||
</template>
|
||||
<script>
|
||||
import { ref, reactive, watch, watchEffect } from 'vue';
|
||||
import { cloneDeep } from 'lodash';
|
||||
import { useEcharts } from '@/hooks/useEcharts';
|
||||
|
||||
export default {
|
||||
name: 'CustomEchartPieGauge',
|
||||
props: {
|
||||
chartData: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
size: {
|
||||
type: Object,
|
||||
default: () => {},
|
||||
},
|
||||
option: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
width: {
|
||||
type: String,
|
||||
default: '100%',
|
||||
},
|
||||
height: {
|
||||
type: String,
|
||||
default: 'calc(100vh - 78px)',
|
||||
},
|
||||
},
|
||||
emits: ['click'],
|
||||
setup(props, { emit }) {
|
||||
const chartRef = ref(null);
|
||||
const { setOptions, getInstance, resize, startAutoPlay } = useEcharts(chartRef);
|
||||
const option = reactive({
|
||||
series: [],
|
||||
});
|
||||
|
||||
watchEffect(() => {
|
||||
props.chartData && initCharts();
|
||||
});
|
||||
|
||||
watch(
|
||||
() => props.size,
|
||||
() => {
|
||||
resize();
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
}
|
||||
);
|
||||
|
||||
function initCharts() {
|
||||
if (props.option) {
|
||||
Object.assign(option, cloneDeep(props.option));
|
||||
}
|
||||
option.series = props.chartData;
|
||||
setOptions(option);
|
||||
startAutoPlay({
|
||||
interval: 2000,
|
||||
seriesIndex: 0,
|
||||
showTooltip: true,
|
||||
});
|
||||
resize();
|
||||
getInstance()?.off('click', onClick);
|
||||
getInstance()?.on('click', onClick);
|
||||
}
|
||||
|
||||
function onClick(params) {
|
||||
emit('click', params);
|
||||
}
|
||||
|
||||
return { chartRef };
|
||||
},
|
||||
};
|
||||
</script>
|
@ -0,0 +1,94 @@
|
||||
<template>
|
||||
<div ref="chartRef" :style="{ height, width }"></div>
|
||||
</template>
|
||||
<script>
|
||||
import { ref, reactive, watch, watchEffect } from 'vue';
|
||||
import { cloneDeep } from 'lodash';
|
||||
import { useEcharts } from '@/hooks/useEcharts';
|
||||
|
||||
export default {
|
||||
name: 'CustomEchartPie',
|
||||
props: {
|
||||
chartData: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
size: {
|
||||
type: Object,
|
||||
default: () => {},
|
||||
},
|
||||
option: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
width: {
|
||||
type: String,
|
||||
default: '100%',
|
||||
},
|
||||
height: {
|
||||
type: String,
|
||||
default: 'calc(100vh - 78px)',
|
||||
},
|
||||
},
|
||||
emits: ['click'],
|
||||
setup(props, { emit }) {
|
||||
const chartRef = ref(null);
|
||||
const { setOptions, getInstance, resize, startAutoPlay } = useEcharts(chartRef);
|
||||
const option = reactive({
|
||||
tooltip: {
|
||||
formatter: '{b} ({c})',
|
||||
},
|
||||
series: [
|
||||
{
|
||||
type: 'pie',
|
||||
radius: '72%',
|
||||
center: ['50%', '55%'],
|
||||
data: [],
|
||||
labelLine: { show: true },
|
||||
label: {
|
||||
show: true,
|
||||
formatter: '{b} \n ({d}%)',
|
||||
color: '#B1B9D3',
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
watchEffect(() => {
|
||||
props.chartData && initCharts();
|
||||
});
|
||||
|
||||
watch(
|
||||
() => props.size,
|
||||
() => {
|
||||
resize();
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
}
|
||||
);
|
||||
|
||||
function initCharts() {
|
||||
if (props.option) {
|
||||
Object.assign(option, cloneDeep(props.option));
|
||||
}
|
||||
option.series[0].data = props.chartData;
|
||||
setOptions(option);
|
||||
startAutoPlay({
|
||||
interval: 2000,
|
||||
seriesIndex: 0,
|
||||
showTooltip: true,
|
||||
});
|
||||
resize();
|
||||
getInstance()?.off('click', onClick);
|
||||
getInstance()?.on('click', onClick);
|
||||
}
|
||||
|
||||
function onClick(params) {
|
||||
emit('click', params);
|
||||
}
|
||||
|
||||
return { chartRef };
|
||||
},
|
||||
};
|
||||
</script>
|
@ -0,0 +1,97 @@
|
||||
<template>
|
||||
<div ref="chartRef" :style="{ height, width }"></div>
|
||||
</template>
|
||||
<script>
|
||||
import { ref, reactive, watchEffect } from 'vue';
|
||||
import { cloneDeep } from 'lodash';
|
||||
import { useEcharts } from '@/hooks/useEcharts';
|
||||
|
||||
export default {
|
||||
name: 'CustomEchartRadar',
|
||||
props: {
|
||||
chartData: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
required: true,
|
||||
},
|
||||
option: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
default: 'radar',
|
||||
},
|
||||
width: {
|
||||
type: String,
|
||||
default: '100%',
|
||||
},
|
||||
height: {
|
||||
type: String,
|
||||
default: 'calc(100vh - 78px)',
|
||||
},
|
||||
},
|
||||
emits: ['click'],
|
||||
setup(props, { emit }) {
|
||||
const chartRef = ref(null);
|
||||
const { setOptions, getInstance, startAutoPlay } = useEcharts(chartRef);
|
||||
const option = reactive({
|
||||
title: {
|
||||
text: '',
|
||||
},
|
||||
legend: {},
|
||||
radar: {},
|
||||
tooltip: {},
|
||||
series: [
|
||||
{
|
||||
type: 'radar',
|
||||
data: [],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
watchEffect(() => {
|
||||
props.chartData && initCharts();
|
||||
});
|
||||
|
||||
function initCharts() {
|
||||
if (props.option) {
|
||||
Object.assign(option, cloneDeep(props.option));
|
||||
}
|
||||
let typeArr = Array.from(new Set(props.chartData.map((item) => item.type)));
|
||||
let indicator = Array.from(
|
||||
new Set(
|
||||
props.chartData.map((item) => {
|
||||
let { name, max } = item;
|
||||
return { name, max };
|
||||
})
|
||||
)
|
||||
);
|
||||
let data = [];
|
||||
typeArr.forEach((type) => {
|
||||
const radarStyle = props.option?.radarStyle ?? {};
|
||||
let obj = { name: type, ...radarStyle };
|
||||
let chartArr = props.chartData.filter((item) => type === item.type);
|
||||
obj['value'] = chartArr.map((item) => item.value);
|
||||
data.push(obj);
|
||||
});
|
||||
option.radar.indicator = indicator;
|
||||
option.series[0]['data'] = data;
|
||||
setOptions(option);
|
||||
startAutoPlay({
|
||||
interval: 2000,
|
||||
seriesIndex: 0,
|
||||
showTooltip: true,
|
||||
});
|
||||
getInstance()?.off('click', onClick);
|
||||
getInstance()?.on('click', onClick);
|
||||
}
|
||||
|
||||
function onClick(params) {
|
||||
emit('click', params);
|
||||
}
|
||||
|
||||
return { chartRef };
|
||||
},
|
||||
};
|
||||
</script>
|
@ -0,0 +1,78 @@
|
||||
<template>
|
||||
<div ref="chartRef" :style="{ height, width }"></div>
|
||||
</template>
|
||||
<script>
|
||||
import { ref, reactive, watch, watchEffect } from 'vue';
|
||||
import { cloneDeep } from 'lodash';
|
||||
import { useEcharts } from '@/hooks/useEcharts';
|
||||
|
||||
export default {
|
||||
name: 'customEchartScatterBlister',
|
||||
props: {
|
||||
chartData: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
size: {
|
||||
type: Object,
|
||||
default: () => {},
|
||||
},
|
||||
option: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
width: {
|
||||
type: String,
|
||||
default: '100%',
|
||||
},
|
||||
height: {
|
||||
type: String,
|
||||
default: 'calc(100vh - 78px)',
|
||||
},
|
||||
},
|
||||
emits: ['click'],
|
||||
setup(props, { emit }) {
|
||||
const chartRef = ref(null);
|
||||
const { setOptions, getInstance, resize, startAutoPlay } = useEcharts(chartRef);
|
||||
const option = reactive({
|
||||
series: [],
|
||||
});
|
||||
|
||||
watchEffect(() => {
|
||||
props.chartData && initCharts();
|
||||
});
|
||||
|
||||
watch(
|
||||
() => props.size,
|
||||
() => {
|
||||
resize();
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
}
|
||||
);
|
||||
|
||||
function initCharts() {
|
||||
if (props.option) {
|
||||
Object.assign(option, cloneDeep(props.option));
|
||||
}
|
||||
option.series = props.chartData;
|
||||
setOptions(option);
|
||||
startAutoPlay({
|
||||
interval: 2000,
|
||||
seriesIndex: 0,
|
||||
showTooltip: true,
|
||||
});
|
||||
resize();
|
||||
getInstance()?.off('click', onClick);
|
||||
getInstance()?.on('click', onClick);
|
||||
}
|
||||
|
||||
function onClick(params) {
|
||||
emit('click', params);
|
||||
}
|
||||
|
||||
return { chartRef };
|
||||
},
|
||||
};
|
||||
</script>
|
@ -0,0 +1,159 @@
|
||||
<template>
|
||||
<div ref="chartRef" :style="{ height, width }"></div>
|
||||
</template>
|
||||
<script>
|
||||
import { ref, reactive, watch, watchEffect } from 'vue';
|
||||
import { cloneDeep } from 'lodash';
|
||||
import { useEcharts } from '@/hooks/useEcharts';
|
||||
|
||||
export default {
|
||||
name: 'CustomEchartTriangle',
|
||||
props: {
|
||||
chartData: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
size: {
|
||||
type: Object,
|
||||
default: () => {},
|
||||
},
|
||||
option: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
width: {
|
||||
type: String,
|
||||
default: '100%',
|
||||
},
|
||||
height: {
|
||||
type: String,
|
||||
default: 'calc(100vh - 78px)',
|
||||
},
|
||||
},
|
||||
emits: ['click'],
|
||||
setup(props, { emit }) {
|
||||
const chartRef = ref(null);
|
||||
const { setOptions, getInstance, resize, startAutoPlay } = useEcharts(chartRef);
|
||||
const option = reactive({
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: {
|
||||
type: 'shadow',
|
||||
},
|
||||
backgroundColor: 'rgba(18, 55, 85, 0.8);',
|
||||
borderColor: '#35d0c0',
|
||||
formatter: (data) => {
|
||||
const params = data.data;
|
||||
let str = `<div class="custom-echarts-tips">
|
||||
<span>${params.name}</span><br/>
|
||||
<span>${params.reaVal}%</span>
|
||||
</div>`;
|
||||
return str;
|
||||
},
|
||||
},
|
||||
series: [
|
||||
{
|
||||
zlevel: 1,
|
||||
name: '漏斗图',
|
||||
type: 'funnel',
|
||||
top: '11%',
|
||||
left: 'center',
|
||||
width: '25%',
|
||||
sort: 'ascending',
|
||||
gap: 0,
|
||||
label: {
|
||||
show: true,
|
||||
position: 'outside',
|
||||
width: '200px',
|
||||
align: 'right',
|
||||
formatter: function (params) {
|
||||
if (!params.data.reaVal) return '';
|
||||
let arr = [`{a|${params.data.name}}`, `{b| ${params.data.reaVal}%}`];
|
||||
return arr.join('\n');
|
||||
},
|
||||
rich: {
|
||||
a: { color: '#fff', fontSize: '16px' },
|
||||
b: { color: '#05FCC6', fontSize: '16px', marginTop: '10px' },
|
||||
},
|
||||
verticalAlign: 'middle',
|
||||
padding: [5, 6], // 增加标签内边距
|
||||
},
|
||||
labelLine: {
|
||||
show: true,
|
||||
length: 10,
|
||||
length2: 50,
|
||||
smooth: true,
|
||||
lineStyle: {
|
||||
width: 1,
|
||||
color: '#ffffff',
|
||||
opacity: 1,
|
||||
type: 'solid',
|
||||
},
|
||||
},
|
||||
// 添加第二段折线
|
||||
// markLine: {
|
||||
// symbol: 'none', // 隐藏端点
|
||||
// lineStyle: {
|
||||
// type: 'solid',
|
||||
// color: '#fff',
|
||||
// width: 1,
|
||||
// },
|
||||
// data: [
|
||||
// // 从默认 labelLine 的终点到自定义位置
|
||||
// [
|
||||
// {
|
||||
// coord: [50, 50], // 第一段线的终点(需动态计算)
|
||||
// name: 'Label1',
|
||||
// },
|
||||
// {
|
||||
// coord: [80, 50], // 第二段线的终点
|
||||
// name: 'Label1',
|
||||
// },
|
||||
// ],
|
||||
// ],
|
||||
// },
|
||||
itemStyle: {
|
||||
show: false,
|
||||
borderColor: '#fff',
|
||||
borderWidth: 1,
|
||||
},
|
||||
emphasis: {
|
||||
label: {
|
||||
fontSize: 20,
|
||||
},
|
||||
},
|
||||
data: [],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
watchEffect(() => {
|
||||
props.chartData && initCharts();
|
||||
});
|
||||
|
||||
watch(
|
||||
() => props.size,
|
||||
() => {
|
||||
resize();
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
}
|
||||
);
|
||||
|
||||
function initCharts() {
|
||||
if (props.option) {
|
||||
Object.assign(option, cloneDeep(props.option));
|
||||
}
|
||||
option.series[0].data = props.chartData;
|
||||
setOptions(option);
|
||||
}
|
||||
|
||||
function onClick(params) {
|
||||
emit('click', params);
|
||||
}
|
||||
|
||||
return { chartRef };
|
||||
},
|
||||
};
|
||||
</script>
|
@ -0,0 +1,93 @@
|
||||
<template>
|
||||
<div ref="chartRef" style="width: 100%; height: 260px"></div>
|
||||
</template>
|
||||
<script>
|
||||
import { ref, reactive, watch, watchEffect } from 'vue';
|
||||
import { cloneDeep } from 'lodash';
|
||||
import { useEcharts } from '@/hooks/useEcharts';
|
||||
|
||||
export default {
|
||||
name: 'CustomEchartWaterDroplet',
|
||||
props: {
|
||||
chartData: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
size: {
|
||||
type: Object,
|
||||
default: () => {},
|
||||
},
|
||||
option: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
width: {
|
||||
type: String,
|
||||
default: '100%',
|
||||
},
|
||||
height: {
|
||||
type: String,
|
||||
default: 'calc(100vh - 78px)',
|
||||
},
|
||||
},
|
||||
emits: ['click'],
|
||||
setup(props, { emit }) {
|
||||
const chartRef = ref(null);
|
||||
const { setOptions, getInstance, resize, startAutoPlay } = useEcharts(chartRef);
|
||||
const option = reactive({
|
||||
tooltip: {
|
||||
formatter: '{b} ({c})',
|
||||
},
|
||||
series: [
|
||||
{
|
||||
type: 'pie',
|
||||
radius: '72%',
|
||||
center: ['50%', '55%'],
|
||||
data: [],
|
||||
labelLine: { show: true },
|
||||
label: {
|
||||
show: true,
|
||||
formatter: '{b} \n ({d}%)',
|
||||
color: '#B1B9D3',
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
watchEffect(() => {
|
||||
props.chartData && initCharts();
|
||||
});
|
||||
|
||||
watch(
|
||||
() => props.size,
|
||||
() => {
|
||||
resize();
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
}
|
||||
);
|
||||
|
||||
function initCharts() {
|
||||
if (props.option) {
|
||||
Object.assign(option, cloneDeep(props.option));
|
||||
}
|
||||
setOptions(props.option);
|
||||
startAutoPlay({
|
||||
interval: 2000,
|
||||
seriesIndex: 0,
|
||||
showTooltip: true,
|
||||
});
|
||||
resize();
|
||||
getInstance()?.off('click', onClick);
|
||||
getInstance()?.on('click', onClick);
|
||||
}
|
||||
|
||||
function onClick(params) {
|
||||
emit('click', params);
|
||||
}
|
||||
|
||||
return { chartRef };
|
||||
},
|
||||
};
|
||||
</script>
|
@ -0,0 +1,78 @@
|
||||
<template>
|
||||
<div ref="chartRef" :style="{ height, width }"></div>
|
||||
</template>
|
||||
<script>
|
||||
import { ref, reactive, watch, watchEffect } from 'vue';
|
||||
import { cloneDeep } from 'lodash';
|
||||
import { useEcharts } from '@/hooks/useEcharts';
|
||||
|
||||
export default {
|
||||
name: 'CustomEchartWordCloud',
|
||||
props: {
|
||||
chartData: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
size: {
|
||||
type: Object,
|
||||
default: () => {},
|
||||
},
|
||||
option: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
width: {
|
||||
type: String,
|
||||
default: '100%',
|
||||
},
|
||||
height: {
|
||||
type: String,
|
||||
default: 'calc(100vh - 78px)',
|
||||
},
|
||||
},
|
||||
emits: ['click'],
|
||||
setup(props, { emit }) {
|
||||
const chartRef = ref(null);
|
||||
const { setOptions, getInstance, resize, startAutoPlay } = useEcharts(chartRef);
|
||||
const option = reactive({
|
||||
series: [],
|
||||
});
|
||||
|
||||
watchEffect(() => {
|
||||
props.chartData && initCharts();
|
||||
});
|
||||
|
||||
watch(
|
||||
() => props.size,
|
||||
() => {
|
||||
resize();
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
}
|
||||
);
|
||||
|
||||
function initCharts() {
|
||||
if (props.option) {
|
||||
Object.assign(option, cloneDeep(props.option));
|
||||
}
|
||||
option.series = props.chartData;
|
||||
setOptions(option);
|
||||
startAutoPlay({
|
||||
interval: 2000,
|
||||
seriesIndex: 0,
|
||||
showTooltip: true,
|
||||
});
|
||||
resize();
|
||||
getInstance()?.off('click', onClick);
|
||||
getInstance()?.on('click', onClick);
|
||||
}
|
||||
|
||||
function onClick(params) {
|
||||
emit('click', params);
|
||||
}
|
||||
|
||||
return { chartRef };
|
||||
},
|
||||
};
|
||||
</script>
|
39
sub-operation-service/src/components/custom-iframe/index.vue
Normal file
@ -0,0 +1,39 @@
|
||||
<template>
|
||||
<iframe v-if="state.url" :src="state.url" frameborder="0" width="100%" height="100%" @load="onLoad"></iframe>
|
||||
</template>
|
||||
|
||||
<script setup name="custom-iframe">
|
||||
import { reactive, watch } from 'vue';
|
||||
import { isExternal } from '@/utils/validate';
|
||||
|
||||
const props = defineProps({
|
||||
url: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits(['on-load']);
|
||||
|
||||
const state = reactive({
|
||||
url: '',
|
||||
loaded: false,
|
||||
});
|
||||
|
||||
watch(
|
||||
() => props.url,
|
||||
(val) => {
|
||||
if (isExternal(val)) {
|
||||
state.url = val;
|
||||
}
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
}
|
||||
);
|
||||
|
||||
const onLoad = () => {
|
||||
state.loaded = true;
|
||||
emit('on-load', state.loaded);
|
||||
};
|
||||
</script>
|
@ -0,0 +1,105 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
v-model="state.visible"
|
||||
draggable
|
||||
append-to-body
|
||||
:title="title"
|
||||
width="50%"
|
||||
:close-on-click-modal="false"
|
||||
:close-on-press-escape="false"
|
||||
@close="onClose"
|
||||
>
|
||||
<div class="import-tips">
|
||||
<p>{{ tips }}</p>
|
||||
<el-button v-if="templateUrl" type="primary" icon="download" text @click="emit('on-download', templateUrl)">下载模板</el-button>
|
||||
</div>
|
||||
<el-upload ref="uploadRef" drag action="#" :show-file-list="true" accept=".xlsx,.xls" :limit="1" :http-request="onUploadExcel">
|
||||
<el-icon class="el-icon--upload"><upload-filled /></el-icon>
|
||||
<div class="el-upload__text">将文件放在此处或单击上传</div>
|
||||
<template #tip>
|
||||
<div class="el-upload__tip">仅允许导入xls、xlsx格式文件,excel文件大小小于500kb</div>
|
||||
</template>
|
||||
</el-upload>
|
||||
<template #footer>
|
||||
<el-button type="primary" @click="onConfirm"> 确定导入</el-button>
|
||||
<el-button @click="onClose"> 取消</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
<script setup name="custom-import-excel">
|
||||
import { reactive, ref, shallowRef } from 'vue';
|
||||
import { isEmpty } from '@/utils';
|
||||
const props = defineProps({
|
||||
title: {
|
||||
type: String,
|
||||
default: '文件导入',
|
||||
},
|
||||
tips: {
|
||||
type: String,
|
||||
default: '提示:导入前请先下载模板填写信息,然后再导入!',
|
||||
},
|
||||
templateUrl: {
|
||||
type: [String, URL],
|
||||
default: '',
|
||||
},
|
||||
// options: {
|
||||
// type: Object,
|
||||
// default: () => {
|
||||
// return {
|
||||
// tips: '提示:导入前请先下载模板填写信息,然后再导入!',
|
||||
// };
|
||||
// },
|
||||
// },
|
||||
});
|
||||
const emit = defineEmits(['on-confirm', 'on-close', 'on-download']);
|
||||
|
||||
const uploadRef = ref(null);
|
||||
const formDate = shallowRef(null);
|
||||
const state = reactive({
|
||||
visible: false,
|
||||
loading: false,
|
||||
});
|
||||
|
||||
const onUploadExcel = ({ file }) => {
|
||||
if (isEmpty(file.name)) return;
|
||||
formDate.value = new FormData();
|
||||
formDate.value.append('file', file);
|
||||
};
|
||||
|
||||
const onConfirm = () => {
|
||||
emit('on-confirm', formDate.value);
|
||||
};
|
||||
|
||||
const onClose = () => {
|
||||
uploadRef?.value && uploadRef.value.clearFiles();
|
||||
state.visible = false;
|
||||
};
|
||||
|
||||
defineExpose({
|
||||
show: () => {
|
||||
formDate.value = null;
|
||||
state.visible = true;
|
||||
},
|
||||
hide: () => {
|
||||
onClose();
|
||||
},
|
||||
clear: () => {
|
||||
uploadRef?.value && uploadRef.value.clearFiles();
|
||||
},
|
||||
});
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.import {
|
||||
&-tips {
|
||||
@include flex-row;
|
||||
|
||||
align-items: center;
|
||||
margin-bottom: 20px;
|
||||
font-size: 14px;
|
||||
color: #979797;
|
||||
p {
|
||||
flex: 3;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
269
sub-operation-service/src/components/custom-rank-list/index.vue
Normal file
@ -0,0 +1,269 @@
|
||||
<template>
|
||||
<div :class="`custom-rank-list rank-${type}`" :style="`color: ${textColor}`">
|
||||
<div
|
||||
v-for="(item, i) in status.rows"
|
||||
:key="item.toString() + item.scroll"
|
||||
:class="`row-item row-item-${item.ranking}`"
|
||||
:style="`height: ${status.heights[i]}px;`"
|
||||
>
|
||||
<div class="ranking-info">
|
||||
<div class="rank" :style="`color: ${color};font-size: ${indexFontSize}px`">{{ indexPrefix }}{{ item.ranking }}</div>
|
||||
<div class="info-name" :style="`font-size: ${leftFontSize}px`" v-html="item.name" />
|
||||
<div v-if="type === 'column'" class="ranking-value" :style="`color: ${textColor};font-size: ${rightFontSize}px`">
|
||||
{{ status.mergedConfig.valueFormatter ? status.mergedConfig.valueFormatter(item) : item.value }}
|
||||
{{ unit }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="ranking-column" :style="`border-color: ${borderColor ?? 'none'}`">
|
||||
<div class="inside-column" :style="`width: ${item.percent}%;background: ${color};borderRadius: ${borderRadius};`">
|
||||
<div class="shine" />
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="type === 'row'" class="ranking-value" :style="`color: ${textColor};font-size: ${rightFontSize}px`">
|
||||
{{ status.mergedConfig.valueFormatter ? status.mergedConfig.valueFormatter(item) : item.value }}
|
||||
{{ unit }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup name="custom-rank-list">
|
||||
import { onUnmounted, reactive, toRefs, watch } from 'vue';
|
||||
|
||||
const props = defineProps({
|
||||
chartConfig: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
const { w, h } = toRefs(props.chartConfig.attr);
|
||||
const { type, rowNum, unit, height, color, textColor, borderColor, borderRadius, indexPrefix, indexFontSize, leftFontSize, rightFontSize } = toRefs(
|
||||
props.chartConfig.option
|
||||
);
|
||||
|
||||
const status = reactive({
|
||||
mergedConfig: props.chartConfig.option,
|
||||
rowsData: [],
|
||||
rows: [
|
||||
{
|
||||
scroll: 0,
|
||||
ranking: 1,
|
||||
name: '',
|
||||
value: '',
|
||||
percent: 0,
|
||||
},
|
||||
],
|
||||
heights: [0],
|
||||
animationIndex: 0,
|
||||
animationHandler: 0,
|
||||
updater: 0,
|
||||
avgHeight: 0,
|
||||
});
|
||||
|
||||
const calcRowsData = () => {
|
||||
let { dataset, rowNum, sort } = status.mergedConfig;
|
||||
// @ts-ignore
|
||||
sort &&
|
||||
dataset.sort(({ value: a }, { value: b }) => {
|
||||
if (a > b) return -1;
|
||||
if (a < b) return 1;
|
||||
if (a === b) return 0;
|
||||
});
|
||||
// @ts-ignore
|
||||
const value = dataset.map(({ value }) => value);
|
||||
const min = Math.min(...value) || 0;
|
||||
// abs of min
|
||||
const minAbs = Math.abs(min);
|
||||
const max = Math.max(...value) || 0;
|
||||
// abs of max
|
||||
const maxAbs = Math.abs(max);
|
||||
const total = max + minAbs;
|
||||
dataset = dataset.map((row, i) => ({
|
||||
...row,
|
||||
ranking: i + 1,
|
||||
percent: ((row.value + minAbs) / total) * 100,
|
||||
}));
|
||||
const rowLength = dataset.length;
|
||||
if (rowLength > rowNum && rowLength < 2 * rowNum) {
|
||||
dataset = [...dataset, ...dataset];
|
||||
}
|
||||
dataset = dataset.map((d, i) => ({ ...d, scroll: i }));
|
||||
status.rowsData = dataset;
|
||||
status.rows = dataset;
|
||||
};
|
||||
|
||||
const calcHeights = (onresize = false) => {
|
||||
const { rowNum, dataset } = status.mergedConfig;
|
||||
const avgHeight = h.value / rowNum;
|
||||
status.avgHeight = avgHeight;
|
||||
|
||||
if (!onresize) status.heights = new Array(dataset.length).fill(avgHeight);
|
||||
};
|
||||
|
||||
const animation = async (start = false) => {
|
||||
let { avgHeight, animationIndex, mergedConfig, rowsData, updater } = status;
|
||||
const { isAnimation, waitTime, carousel, rowNum } = mergedConfig;
|
||||
const rowLength = rowsData.length;
|
||||
if (rowNum >= rowLength) return;
|
||||
if (start) {
|
||||
await new Promise((resolve) => setTimeout(resolve, waitTime));
|
||||
if (updater !== status.updater) return;
|
||||
}
|
||||
const animationNum = carousel === 'single' ? 1 : rowNum;
|
||||
let rows = rowsData.slice(animationIndex);
|
||||
rows.push(...rowsData.slice(0, animationIndex));
|
||||
status.rows = rows.slice(0, rowNum + 1);
|
||||
status.heights = new Array(rowLength).fill(avgHeight);
|
||||
await new Promise((resolve) => setTimeout(resolve, 300));
|
||||
if (!isAnimation) return;
|
||||
if (updater !== status.updater) return;
|
||||
status.heights.splice(0, animationNum, ...new Array(animationNum).fill(0));
|
||||
animationIndex += animationNum;
|
||||
const back = animationIndex - rowLength;
|
||||
if (back >= 0) animationIndex = back;
|
||||
|
||||
status.animationIndex = animationIndex;
|
||||
status.animationHandler = setTimeout(animation, waitTime * 1000 - 300);
|
||||
};
|
||||
|
||||
const stopAnimation = () => {
|
||||
status.updater = (status.updater + 1) % 999999;
|
||||
if (!status.animationHandler) return;
|
||||
clearTimeout(status.animationHandler);
|
||||
};
|
||||
|
||||
const onRestart = async () => {
|
||||
try {
|
||||
if (!status.mergedConfig) return;
|
||||
let { dataset, rowNum, sort } = status.mergedConfig;
|
||||
stopAnimation();
|
||||
calcRowsData();
|
||||
let flag = true;
|
||||
if (dataset.length <= rowNum) {
|
||||
flag = false;
|
||||
}
|
||||
calcHeights(flag);
|
||||
animation(flag);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
};
|
||||
|
||||
onRestart();
|
||||
|
||||
watch(
|
||||
() => w.value,
|
||||
() => {
|
||||
onRestart();
|
||||
}
|
||||
);
|
||||
|
||||
watch(
|
||||
() => h.value,
|
||||
() => {
|
||||
onRestart();
|
||||
}
|
||||
);
|
||||
|
||||
watch(
|
||||
() => rowNum.value,
|
||||
() => {
|
||||
onRestart();
|
||||
}
|
||||
);
|
||||
|
||||
// 数据更新(配置时触发)
|
||||
watch(
|
||||
() => props.chartConfig.option.dataset,
|
||||
() => {
|
||||
onRestart();
|
||||
},
|
||||
{
|
||||
deep: false,
|
||||
}
|
||||
);
|
||||
|
||||
onUnmounted(() => {
|
||||
stopAnimation();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.custom-rank-list {
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
height: v-bind('h+"px"');
|
||||
.row-item {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
overflow: hidden;
|
||||
transition: all 0.3s;
|
||||
flex-direction: v-bind('type');
|
||||
}
|
||||
.ranking-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 13px;
|
||||
flex: none;
|
||||
.rank {
|
||||
margin-right: 5px;
|
||||
}
|
||||
.info-name {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
.ranking-column {
|
||||
flex: 1;
|
||||
.inside-column {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
margin-bottom: 2px;
|
||||
height: v-bind('height+"px"');
|
||||
border-radius: 1px;
|
||||
}
|
||||
.shine {
|
||||
position: absolute;
|
||||
top: v-bind('(height/2)+"px"');
|
||||
left: 0%;
|
||||
width: 50px;
|
||||
height: 2px;
|
||||
background: radial-gradient(rgb(40 248 255) 5%, transparent 80%);
|
||||
transform: translateX(-100%);
|
||||
// animation: shine 3s ease-in-out infinite alternate;
|
||||
}
|
||||
}
|
||||
|
||||
&.rank-column {
|
||||
.ranking-info {
|
||||
width: 100%;
|
||||
}
|
||||
.ranking-column {
|
||||
margin-top: 5px;
|
||||
border-bottom: 2px solid v-bind('borderColor');
|
||||
}
|
||||
}
|
||||
|
||||
&.rank-row {
|
||||
.row-item {
|
||||
align-items: center;
|
||||
}
|
||||
.ranking-info {
|
||||
margin-right: 20px;
|
||||
}
|
||||
.ranking-value {
|
||||
margin-left: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes shine {
|
||||
80% {
|
||||
left: 0;
|
||||
transform: translateX(-100%);
|
||||
}
|
||||
100% {
|
||||
left: 100%;
|
||||
transform: translateX(0%);
|
||||
}
|
||||
}
|
||||
</style>
|
@ -0,0 +1,173 @@
|
||||
<!--
|
||||
* @Description:
|
||||
* @Author: zenghua.wang
|
||||
* @Date: 2024-03-24 11:04:52
|
||||
* @LastEditors: zenghua.wang “1048523306@qq.com”
|
||||
* @LastEditTime: 2025-01-20 09:34:23
|
||||
-->
|
||||
<template>
|
||||
<section class="rich-editor">
|
||||
<Toolbar v-show="toolbarShow" class="rich-editor-toolbar" :editor="refEditor" :default-config="options.toolbarConfig" :mode="mode" />
|
||||
<Editor
|
||||
v-model="valueHtml"
|
||||
class="rich-editor-toolbar"
|
||||
:style="styleEditor"
|
||||
:default-config="options.editorConfig"
|
||||
:mode="mode"
|
||||
@on-created="handleCreated"
|
||||
@on-change="handleChange"
|
||||
@on-destroyed="handleDestroyed"
|
||||
@on-focus="handleFocus"
|
||||
@on-blur="handleBlur"
|
||||
/>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import '@wangeditor/editor/dist/css/style.css'; // 引入 css
|
||||
import { shallowRef, ref, computed, nextTick, onBeforeUnmount, onMounted } from 'vue';
|
||||
import { Editor, Toolbar } from '@wangeditor/editor-for-vue';
|
||||
import { isEmpty } from '@/utils';
|
||||
// import { CommonUpload, UploadImageFromEditor } from '@/apis/common';
|
||||
import { imageUpload } from './upFile';
|
||||
|
||||
const { VITE_APP_OSS_URL } = import.meta.env;
|
||||
|
||||
export default {
|
||||
name: 'CustomRichEditor',
|
||||
components: { Editor, Toolbar },
|
||||
props: {
|
||||
value: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
mode: {
|
||||
type: String,
|
||||
default: 'default', //'default' 或 'simple'
|
||||
},
|
||||
readOnly: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
options: {
|
||||
type: Object,
|
||||
default: () => {
|
||||
return {
|
||||
toolbarConfig: {},
|
||||
editorConfig: {
|
||||
placeholder: '请输入内容...',
|
||||
readOnly: false,
|
||||
MENU_CONF: {
|
||||
uploadImage: {
|
||||
server: '',
|
||||
base64LimitSize: 10 * 1024, //10kb
|
||||
maxFileSize: 10 * 1024 * 1024, //10M
|
||||
maxNumberOfFiles: 10,
|
||||
allowedFileTypes: ['image/*'],
|
||||
customUpload: imageUpload,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
},
|
||||
},
|
||||
toolbarShow: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
},
|
||||
emits: ['focus', 'blur', 'change', 'update:value'],
|
||||
setup(props, cxt) {
|
||||
const refEditor = shallowRef();
|
||||
const valueHtml = ref('');
|
||||
|
||||
const styleEditor = computed(() => {
|
||||
return {
|
||||
height: props.options.contentHeight || '300px',
|
||||
};
|
||||
});
|
||||
|
||||
/**
|
||||
* 创建
|
||||
* @param {*} editor
|
||||
*/
|
||||
const handleCreated = (editor) => {
|
||||
refEditor.value = editor;
|
||||
props.readOnly ? editor.disable() : editor.enable();
|
||||
};
|
||||
/**
|
||||
* 组件内容变化
|
||||
* @param {*} editor
|
||||
*/
|
||||
const handleChange = (editor) => {
|
||||
cxt.emit('change', editor);
|
||||
cxt.emit('update:value', valueHtml.value);
|
||||
};
|
||||
/**
|
||||
* 组件销毁
|
||||
* @param {*} editor
|
||||
*/
|
||||
const handleDestroyed = (editor) => {
|
||||
valueHtml.value = '';
|
||||
};
|
||||
/**
|
||||
* 光标处于编辑区
|
||||
* @param {*} editor
|
||||
*/
|
||||
const handleFocus = (editor) => {
|
||||
cxt.emit('focus', editor);
|
||||
};
|
||||
/**
|
||||
* 光标离开编辑区
|
||||
* @param {*} editor
|
||||
*/
|
||||
const handleBlur = (editor) => {
|
||||
cxt.emit('blur', editor);
|
||||
};
|
||||
/**
|
||||
* 挂载
|
||||
*/
|
||||
onMounted(() => {
|
||||
nextTick(() => {
|
||||
if (props?.value) {
|
||||
valueHtml.value = props.value;
|
||||
}
|
||||
});
|
||||
});
|
||||
/**
|
||||
* 组件销毁时,也及时销毁编辑器
|
||||
*/
|
||||
onBeforeUnmount(() => {
|
||||
if (!refEditor?.value) return;
|
||||
refEditor.value.destroy();
|
||||
});
|
||||
|
||||
return {
|
||||
refEditor,
|
||||
valueHtml,
|
||||
styleEditor,
|
||||
handleCreated,
|
||||
handleChange,
|
||||
handleDestroyed,
|
||||
handleFocus,
|
||||
handleBlur,
|
||||
};
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.rich-editor {
|
||||
z-index: 9999;
|
||||
border: 1px solid $color-border;
|
||||
&-toolbar {
|
||||
border-bottom: 1px solid $color-border;
|
||||
:deep(.w-e-bar-divider) {
|
||||
width: 0;
|
||||
}
|
||||
}
|
||||
&-editor {
|
||||
overflow-y: hidden;
|
||||
height: 200px;
|
||||
}
|
||||
}
|
||||
</style>
|