初始化
This commit is contained in:
116
src/views/plugin/barcode/index.vue
Normal file
116
src/views/plugin/barcode/index.vue
Normal file
@@ -0,0 +1,116 @@
|
||||
<script setup lang="ts">
|
||||
import { onMounted } from 'vue';
|
||||
import JsBarcode from 'jsbarcode';
|
||||
import type { Options } from 'jsbarcode';
|
||||
|
||||
defineOptions({ name: 'BarcodePage' });
|
||||
|
||||
const text = 'CN-RDMS';
|
||||
|
||||
interface CodeConfig {
|
||||
id: string;
|
||||
title: string;
|
||||
text: string;
|
||||
options: Options;
|
||||
}
|
||||
|
||||
const codes: CodeConfig[] = [
|
||||
{
|
||||
id: 'code39',
|
||||
title: 'CODE 39 正常尺寸',
|
||||
text: 'Hello',
|
||||
options: { format: 'code39' }
|
||||
},
|
||||
{
|
||||
id: 'code128',
|
||||
title: 'CODE 128 正常尺寸',
|
||||
text,
|
||||
options: {}
|
||||
},
|
||||
{
|
||||
id: 'ean-13',
|
||||
title: 'ENA-13 商品条形码',
|
||||
text: '1234567890128',
|
||||
options: { format: 'ean13' }
|
||||
},
|
||||
{
|
||||
id: 'upc-a',
|
||||
title: 'UPC-A 商品条形码',
|
||||
text: '123456789012',
|
||||
options: { format: 'upc' }
|
||||
},
|
||||
{
|
||||
id: 'barcode',
|
||||
title: '不一样的高度,不一样的颜色',
|
||||
text: 'Hello',
|
||||
options: {
|
||||
height: 30,
|
||||
lineColor: '#9ca3af'
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'barcode1',
|
||||
title: '加个背景色',
|
||||
text,
|
||||
options: {
|
||||
background: '#9ca3af',
|
||||
lineColor: '#ffffff'
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'barcode2',
|
||||
title: '字体好大',
|
||||
text,
|
||||
options: {
|
||||
fontSize: 40
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'barcode3',
|
||||
title: '粗狂的条码,文字离远点',
|
||||
text: 'Hi',
|
||||
options: {
|
||||
textMargin: 30,
|
||||
width: 4
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'barcode4',
|
||||
title: '字体跑上面来,还是粗体',
|
||||
text,
|
||||
options: {
|
||||
textPosition: 'top',
|
||||
fontOptions: 'bold'
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
function generateBarcode() {
|
||||
codes.forEach(code => {
|
||||
JsBarcode(`#${code.id}`, code.text, code.options);
|
||||
});
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
generateBarcode();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="overflow-hidden">
|
||||
<ElCard header="条形码" class="h-full card-wrapper">
|
||||
<ElScrollbar class="h-full">
|
||||
<ElRow :gutter="12" class="w-[calc(100%-12px)]">
|
||||
<ElCol v-for="item in codes" :key="item.id" :lg="8" :md="12" :sm="24" class="mb-24px">
|
||||
<div class="flex-col-center">
|
||||
<h3>{{ item.title }}</h3>
|
||||
<svg :id="item.id" class="h-130px" />
|
||||
</div>
|
||||
</ElCol>
|
||||
</ElRow>
|
||||
</ElScrollbar>
|
||||
</ElCard>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
60
src/views/plugin/charts/antv/data.ts
Normal file
60
src/views/plugin/charts/antv/data.ts
Normal file
@@ -0,0 +1,60 @@
|
||||
import type { CustomGraphData } from './modules/types';
|
||||
|
||||
// 日期可以自己随便设置,就是字符串展示,也可以修改为业务需要的字段
|
||||
export function getFlowData(): CustomGraphData {
|
||||
return {
|
||||
nodes: [
|
||||
{
|
||||
id: 'NS',
|
||||
name: 'Start',
|
||||
status: 'COMPLETED',
|
||||
startDate: '2024-10-01',
|
||||
endDate: '2024-10-07',
|
||||
actualStartDate: '2024-10-01',
|
||||
actualEndDate: '2024-10-07'
|
||||
},
|
||||
{
|
||||
id: 'N1',
|
||||
name: 'Node1',
|
||||
status: 'COMPLETED_EARLY',
|
||||
startDate: '2024-10-08',
|
||||
endDate: '2024-10-10',
|
||||
actualStartDate: '2024-10-08',
|
||||
actualEndDate: '2024-10-09',
|
||||
milestone: true
|
||||
},
|
||||
{
|
||||
id: 'N2',
|
||||
name: 'Node2',
|
||||
status: 'COMPLETED_EARLY',
|
||||
startDate: '2024-10-11',
|
||||
endDate: '2024-10-13',
|
||||
actualStartDate: '2024-10-11',
|
||||
actualEndDate: '2024-10-12'
|
||||
},
|
||||
{ id: 'N3', name: 'Node3', status: 'IN_PROGRESS', isDeleted: true },
|
||||
{ id: 'N4', name: 'Node4', status: 'COMPLETED_LATE' },
|
||||
{ id: 'N5', name: 'Node5', status: 'DELAYED', isDelayed: true, milestone: true },
|
||||
{ id: 'N6', name: 'Node6', status: 'PAUSED' },
|
||||
{ id: 'N7', name: 'Node7', status: 'NOT_STARTED' },
|
||||
{ id: 'N8', name: 'Node8', status: 'NOT_STARTED' },
|
||||
{ id: 'N9', name: 'End', status: 'NOT_STARTED' },
|
||||
{ id: 'NX', name: 'NodeX', status: 'NOT_STARTED', isDeleted: true }
|
||||
],
|
||||
edges: [
|
||||
{ id: 'E1', source: 'NS', target: 'N1' },
|
||||
{ id: 'E2', source: 'N1', target: 'N2' },
|
||||
{ id: 'E3', source: 'N1', target: 'N3', isDeleted: true },
|
||||
{ id: 'E4', source: 'N1', target: 'N4' },
|
||||
{ id: 'E5', source: 'N2', target: 'N5' },
|
||||
{ id: 'E6', source: 'N3', target: 'N5', isDeleted: true },
|
||||
{ id: 'E7', source: 'N4', target: 'N5' },
|
||||
{ id: 'E8', source: 'N5', target: 'N6' },
|
||||
{ id: 'E9', source: 'N6', target: 'N7' },
|
||||
{ id: 'E10', source: 'N6', target: 'N8' },
|
||||
{ id: 'E11', source: 'N7', target: 'N9' },
|
||||
{ id: 'EX', source: 'N8', target: 'N9' },
|
||||
{ id: 'EO', source: 'N5', target: 'NX', isDeleted: true }
|
||||
]
|
||||
};
|
||||
}
|
||||
67
src/views/plugin/charts/antv/index.vue
Normal file
67
src/views/plugin/charts/antv/index.vue
Normal file
@@ -0,0 +1,67 @@
|
||||
<script setup lang="tsx">
|
||||
import { computed, onMounted, ref, useTemplateRef } from 'vue';
|
||||
import type { Ref } from 'vue';
|
||||
import type { CustomBehaviorOption, IPointerEvent } from '@antv/g6';
|
||||
import AntvFlow from './modules/antv-flow.vue';
|
||||
import type { CustomGraphData } from './modules/types';
|
||||
import { getFlowData } from './data';
|
||||
|
||||
defineOptions({ name: 'AntVCharts' });
|
||||
|
||||
const antvFlowRef = useTemplateRef('antvFlowRef');
|
||||
|
||||
const flowData = ref({
|
||||
nodes: [],
|
||||
edges: []
|
||||
}) as Ref<CustomGraphData>;
|
||||
|
||||
const selectedNode = ref<string | undefined>('N2');
|
||||
|
||||
const behaviors: CustomBehaviorOption[] = [
|
||||
{
|
||||
type: 'click-select',
|
||||
enable: (event: IPointerEvent) => event.targetType === 'node',
|
||||
onClick: (event: IPointerEvent) => {
|
||||
const node = event.target as unknown as HTMLElement;
|
||||
const nodeData = flowData.value.nodes.find(item => item.id === node.id);
|
||||
selectedNode.value = nodeData?.id;
|
||||
window.$message?.success(`选中节点:[${node.id}]${nodeData?.name}`);
|
||||
}
|
||||
}
|
||||
];
|
||||
const hasNodeN = computed(() => flowData.value.nodes.some(node => node.id === 'NN'));
|
||||
|
||||
function addNode() {
|
||||
const { nodes, edges } = flowData.value;
|
||||
|
||||
nodes.push({ id: 'NN', name: 'New node', status: 'NOT_STARTED' });
|
||||
edges.push({ id: 'EN', source: 'N5', target: 'NN' });
|
||||
flowData.value = { nodes, edges };
|
||||
}
|
||||
|
||||
function removeNode(id: string) {
|
||||
const { nodes, edges } = flowData.value;
|
||||
// 删除node的同时,也需要删除包含NX的edge
|
||||
flowData.value = {
|
||||
nodes: nodes.filter(node => node.id !== id),
|
||||
edges: edges.filter(edge => edge.source !== id && edge.target !== id)
|
||||
};
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
flowData.value = getFlowData();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="h-full">
|
||||
<ElCard header="AntV G6 Next" class="h-full card-wrapper">
|
||||
<AntvFlow ref="antvFlowRef" :data="flowData" :selected="selectedNode" :behaviors="behaviors" />
|
||||
<ElDivider />
|
||||
<ElButton @click="selectedNode = 'N5'">选中节点N5(需要自行处理选中事件,不会触发元素点击)</ElButton>
|
||||
<ElButton v-if="!hasNodeN" @click="addNode">添加节点并与Node5连线</ElButton>
|
||||
<ElButton v-else @click="() => removeNode('NN')">删除新添加的节点</ElButton>
|
||||
<ElButton @click="() => removeNode('NX')">删除NodeX</ElButton>
|
||||
</ElCard>
|
||||
</div>
|
||||
</template>
|
||||
135
src/views/plugin/charts/antv/modules/antv-flow.vue
Normal file
135
src/views/plugin/charts/antv/modules/antv-flow.vue
Normal file
@@ -0,0 +1,135 @@
|
||||
<script setup lang="tsx">
|
||||
import { shallowRef, useTemplateRef, watch } from 'vue';
|
||||
import { useDebounceFn } from '@vueuse/core';
|
||||
import { vResizeObserver } from '@vueuse/components';
|
||||
import type { CustomBehaviorOption, Graph } from '@antv/g6';
|
||||
import { useAntFlow } from './antv-g6-flow';
|
||||
import { nodeStatus } from './status';
|
||||
import type { CustomGraphData } from './types';
|
||||
|
||||
defineOptions({ name: 'AntvFLow' });
|
||||
|
||||
interface Props {
|
||||
behaviors?: CustomBehaviorOption[];
|
||||
data: CustomGraphData;
|
||||
selected?: string;
|
||||
height?: string;
|
||||
autoFit?: 'view' | 'center';
|
||||
}
|
||||
|
||||
const props = defineProps<Props>();
|
||||
|
||||
const containerRef = useTemplateRef('containerRef');
|
||||
const graphRef = shallowRef<Graph | null>(null);
|
||||
|
||||
// 监听容器尺寸变化,调整画布大小为图容器大小
|
||||
const onContainerResize = useDebounceFn(() => {
|
||||
if (graphRef.value) {
|
||||
graphRef.value.resize();
|
||||
}
|
||||
}, 5);
|
||||
|
||||
async function draw() {
|
||||
if (graphRef.value) {
|
||||
graphRef.value.destroy();
|
||||
}
|
||||
const { graph } = useAntFlow({
|
||||
container: 'antv-flow',
|
||||
data: props.data,
|
||||
behaviors: props.behaviors,
|
||||
autoFit: props.autoFit
|
||||
});
|
||||
graphRef.value = graph;
|
||||
await selectNode();
|
||||
}
|
||||
|
||||
async function selectNode() {
|
||||
if (props.selected && graphRef.value) {
|
||||
try {
|
||||
await graphRef.value.setElementState(props.selected, 'selected');
|
||||
} catch {}
|
||||
}
|
||||
}
|
||||
|
||||
function zoomOut() {
|
||||
graphRef.value?.zoomBy(0.9);
|
||||
}
|
||||
|
||||
function zoomIn() {
|
||||
graphRef.value?.zoomBy(1.1);
|
||||
}
|
||||
|
||||
function resetZoom() {
|
||||
graphRef.value?.zoomTo(1);
|
||||
graphRef.value?.fitCenter();
|
||||
}
|
||||
|
||||
function fitZoom() {
|
||||
graphRef.value?.fitView();
|
||||
graphRef.value?.fitCenter();
|
||||
}
|
||||
|
||||
watch(
|
||||
[() => props.data, () => props.selected],
|
||||
() => {
|
||||
draw();
|
||||
},
|
||||
{ deep: true }
|
||||
);
|
||||
|
||||
defineExpose({ selectNode, graph: graphRef });
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="relative">
|
||||
<!-- canvas toolbar -->
|
||||
<div class="absolute left-0 right-0 z-1 flex items-center items-stretch justify-between">
|
||||
<ElButtonGroup size="small" class="bg-white!">
|
||||
<ElButton @click="zoomOut">
|
||||
<icon-mingcute:zoom-out-line />
|
||||
</ElButton>
|
||||
<ElButton @click="zoomIn">
|
||||
<icon-mingcute:zoom-in-line />
|
||||
</ElButton>
|
||||
<ElButton @click="resetZoom">
|
||||
<icon-icon-park-outline:equal-ratio />
|
||||
</ElButton>
|
||||
<ElButton @click="fitZoom">
|
||||
<icon-gg:ratio />
|
||||
</ElButton>
|
||||
</ElButtonGroup>
|
||||
<div class="flex-center gap-12px">
|
||||
<ElPopover placement="bottom-end" :width="200" :animated="false">
|
||||
<template #reference>
|
||||
<ElButton size="small" class="bg-white!">
|
||||
<icon-fe:question />
|
||||
</ElButton>
|
||||
</template>
|
||||
<div class="flex-col gap-8px">
|
||||
<div span="2" class="text-12px font-bold">节点图例</div>
|
||||
<ElRow>
|
||||
<ElCol v-for="(config, status) in nodeStatus" :key="status" :span="12" class="mb-8px flex-center">
|
||||
<ElTag size="small" round :bordered="false">
|
||||
<template #default>
|
||||
<icon-f7:flag-circle-fill v-if="status === 'MILESTONE'" :style="{ color: config.color }" />
|
||||
<icon-f7:circle-fill v-else :style="{ color: config.color }" />
|
||||
{{ config.type }}
|
||||
</template>
|
||||
</ElTag>
|
||||
</ElCol>
|
||||
</ElRow>
|
||||
</div>
|
||||
</ElPopover>
|
||||
</div>
|
||||
</div>
|
||||
<!-- canvas container -->
|
||||
<div
|
||||
id="antv-flow"
|
||||
ref="containerRef"
|
||||
v-resize-observer="onContainerResize"
|
||||
class="w-full"
|
||||
:style="{ height: props.height || '300px' }"
|
||||
@contextmenu="event => event.preventDefault()"
|
||||
></div>
|
||||
</div>
|
||||
</template>
|
||||
170
src/views/plugin/charts/antv/modules/antv-g6-flow.ts
Normal file
170
src/views/plugin/charts/antv/modules/antv-g6-flow.ts
Normal file
@@ -0,0 +1,170 @@
|
||||
import { Graph } from '@antv/g6';
|
||||
import type { CustomBehaviorOption, IPointerEvent } from '@antv/g6';
|
||||
import type { Canvas } from '@antv/g6/lib/runtime/canvas';
|
||||
import { useThemeStore } from '@/store/modules/theme';
|
||||
import { getNodeIcon, nodeStatus } from './status';
|
||||
import type { CustomEdgeData, CustomGraphData, CustomNodeData } from './types';
|
||||
|
||||
interface AntFlowConfig {
|
||||
container: string | HTMLElement | Canvas;
|
||||
data: CustomGraphData;
|
||||
behaviors?: CustomBehaviorOption[];
|
||||
autoFit?: 'view' | 'center';
|
||||
}
|
||||
|
||||
export function useAntFlow(config: AntFlowConfig) {
|
||||
const themeStore = useThemeStore();
|
||||
|
||||
const baseColor = 'rgb(158 163 171)';
|
||||
|
||||
const { container, autoFit = 'center', data, behaviors = [] } = config;
|
||||
|
||||
const graph = new Graph({
|
||||
container,
|
||||
animation: false,
|
||||
padding: 16,
|
||||
theme: 'light',
|
||||
autoFit,
|
||||
data,
|
||||
node: {
|
||||
type: 'rect',
|
||||
|
||||
style: (node: CustomNodeData) => {
|
||||
const iconS = getNodeIcon(node);
|
||||
let labelFill = '#000000';
|
||||
if (node.taskState === 'NOT_STARTED') {
|
||||
labelFill = '#787878';
|
||||
}
|
||||
|
||||
return {
|
||||
labelText: node.name as string,
|
||||
size: [120, 26],
|
||||
radius: 99,
|
||||
fill: '#FFFFFF',
|
||||
stroke: node.isDeleted ? themeStore.otherColor.error : baseColor,
|
||||
lineDash: node.isDeleted ? 4 : 0,
|
||||
lineWidth: 1,
|
||||
labelFill,
|
||||
labelX: 2,
|
||||
labelY: 2,
|
||||
labelTextBaseline: 'middle',
|
||||
labelTextAlign: 'center',
|
||||
labelLineHeight: 13,
|
||||
labelWordWrap: true,
|
||||
labelMaxWidth: 72,
|
||||
iconSrc: iconS,
|
||||
iconWidth: 16,
|
||||
iconHeight: 16,
|
||||
iconX: -45,
|
||||
labelFontSize: 12,
|
||||
labelPlacement: 'center',
|
||||
badgeLineWidth: 6,
|
||||
badgeFontSize: 8,
|
||||
badges: [
|
||||
{ text: '延期', placement: 'top', offsetY: -11, visibility: node.isDelayed ? 'visible' : 'hidden' },
|
||||
{ text: '已删除', placement: 'bottom', offsetY: 11, visibility: node.isDeleted ? 'visible' : 'hidden' }
|
||||
],
|
||||
badgePalette: [themeStore.otherColor.error, themeStore.otherColor.error],
|
||||
ports: [{ placement: 'left' }, { placement: 'right' }]
|
||||
};
|
||||
},
|
||||
state: {
|
||||
selected: {
|
||||
lineWidth: 2,
|
||||
stroke: themeStore.themeColor,
|
||||
labelFill: themeStore.themeColor,
|
||||
halo: true,
|
||||
haloStroke: themeStore.themeColor,
|
||||
haloLineWidth: 6
|
||||
},
|
||||
active: (node: CustomNodeData) => ({
|
||||
halo: true,
|
||||
haloStroke: node.isDeleted ? themeStore.otherColor.error : themeStore.themeColor,
|
||||
haloLineWidth: 6,
|
||||
zIndex: 2
|
||||
})
|
||||
}
|
||||
},
|
||||
edge: {
|
||||
type: 'cubic-horizontal',
|
||||
style: (node: CustomEdgeData) => ({
|
||||
curveOffset: 10,
|
||||
curvePosition: 0.5,
|
||||
stroke: node.isDeleted ? themeStore.otherColor.error : baseColor,
|
||||
lineDash: node.isDeleted ? 4 : 0
|
||||
}),
|
||||
state: {
|
||||
active: (node: CustomEdgeData) => ({
|
||||
lineWidth: 2,
|
||||
stroke: node.isDeleted ? themeStore.otherColor.error : themeStore.themeColor,
|
||||
halo: true,
|
||||
haloStroke: node.isDeleted ? themeStore.otherColor.error : themeStore.themeColor,
|
||||
haloLineWidth: 6,
|
||||
zIndex: 2
|
||||
})
|
||||
}
|
||||
},
|
||||
layout: {
|
||||
type: 'antv-dagre',
|
||||
rankdir: 'LR',
|
||||
ranksep: 20,
|
||||
nodesep: -20,
|
||||
controlPoints: true
|
||||
},
|
||||
behaviors: [
|
||||
{
|
||||
key: 'hover-activate',
|
||||
type: 'hover-activate',
|
||||
degree: 1,
|
||||
direction: 'both'
|
||||
},
|
||||
'drag-canvas',
|
||||
...behaviors
|
||||
],
|
||||
plugins: [
|
||||
{
|
||||
type: 'tooltip',
|
||||
enable: (event: IPointerEvent) => event.targetType === 'node',
|
||||
getContent: (_event: IPointerEvent, items?: CustomNodeData[]) => {
|
||||
let result = '<div style="display: flex; flex-direction: column; gap: 8px;">';
|
||||
|
||||
// 弹出提示可以自定义各种内容,但是这里很奇怪,有的class不跟随unocss的样式
|
||||
items?.forEach(item => {
|
||||
result += `
|
||||
<h3 style="display: flex; align-items: center; gap: 8px;">${item.name}</h3>
|
||||
<div style="display: flex;">
|
||||
<b>状态:</b>
|
||||
<div style="display: flex; gap: 4px;">
|
||||
<img src="${getNodeIcon(item)}" />
|
||||
<span style="font-weight: 400 !important;">${nodeStatus[item.status as keyof typeof nodeStatus].type}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div style="display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); column-gap: 32px; row-gap: 4px;">
|
||||
<div style="display: flex; flex-direction: column;"><div style="color: rgb(156 163 175);">预计开始</div>
|
||||
<div style="font-weight: 700;">${item.startDate || '-'}</div>
|
||||
</div>
|
||||
<div style="display: flex; flex-direction: column;">
|
||||
<div style="color: rgb(156 163 175);">预计结束</div>
|
||||
<div style="font-weight: 700;">${item.endDate || '-'}</div>
|
||||
</div>
|
||||
<div style="display: flex; flex-direction: column;">
|
||||
<div style="color: rgb(156 163 175);">实际开始</div>
|
||||
<div style="font-weight: 700;">${item.actualStartDate || '-'}</div>
|
||||
</div>
|
||||
<div style="display: flex; flex-direction: column;">
|
||||
<div style="color: rgb(156 163 175);">实际结束</div>
|
||||
<div style="font-weight: 700;">${item.actualEndDate || '-'}</div>
|
||||
</div>
|
||||
`;
|
||||
});
|
||||
|
||||
result += '</div>';
|
||||
return result;
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
graph.render();
|
||||
|
||||
return { graph };
|
||||
}
|
||||
95
src/views/plugin/charts/antv/modules/status.ts
Normal file
95
src/views/plugin/charts/antv/modules/status.ts
Normal file
@@ -0,0 +1,95 @@
|
||||
import { h } from 'vue';
|
||||
import { ElTag } from 'element-plus';
|
||||
import type { TagProps } from 'element-plus';
|
||||
import type { CustomNodeData, NodeStatus } from './types';
|
||||
|
||||
interface NodeStatusConfig {
|
||||
type: string;
|
||||
color: string;
|
||||
textColor: string;
|
||||
base64: string;
|
||||
flag64: string;
|
||||
}
|
||||
|
||||
export const nodeStatus: Record<NodeStatus, NodeStatusConfig> = {
|
||||
MILESTONE: {
|
||||
type: '里程碑',
|
||||
color: '#5b5b5b',
|
||||
textColor: '',
|
||||
base64: '',
|
||||
flag64: ''
|
||||
},
|
||||
NOT_STARTED: {
|
||||
type: '未开始',
|
||||
color: '#CCCDD0',
|
||||
textColor: '#5b5b5b',
|
||||
base64: `data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxZW0iIGhlaWdodD0iMWVtIiB2aWV3Qm94PSIwIDAgNTYgNTYiPjxwYXRoIGZpbGw9IiNDQ0NERDAiIGQ9Ik0yOCA1MS45MDZjMTMuMDU1IDAgMjMuOTA2LTEwLjgyOCAyMy45MDYtMjMuOTA2YzAtMTMuMDU1LTEwLjg3NS0yMy45MDYtMjMuOTMtMjMuOTA2QzE0Ljg5OSA0LjA5NCA0LjA5NSAxNC45NDUgNC4wOTUgMjhjMCAxMy4wNzggMTAuODI4IDIzLjkwNiAyMy45MDYgMjMuOTA2Ii8+PC9zdmc+`,
|
||||
flag64: `data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxZW0iIGhlaWdodD0iMWVtIiB2aWV3Qm94PSIwIDAgNTYgNTYiPjxwYXRoIGZpbGw9IiNDQ0NERDAiIGQ9Ik0yOCA1MS45MDZjMTMuMDU1IDAgMjMuOTA2LTEwLjg1MSAyMy45MDYtMjMuOTA2YzAtMTMuMDc4LTEwLjg3NS0yMy45MDYtMjMuOTMtMjMuOTA2QzE0Ljg5OSA0LjA5NCA0LjA5NSAxNC45MjIgNC4wOTUgMjhjMCAxMy4wNTUgMTAuODI4IDIzLjkwNiAyMy45MDYgMjMuOTA2bS05LjA3LTExLjExYy0uNTg2IDAtMS4xMDItLjUxNS0xLjEwMi0xLjA3N1YxOS44MmMwLTEuMDA4LjQ5Mi0xLjc1OCAxLjQ1My0yLjE4Yy44NDQtLjM3NCAxLjU3LS41ODUgMy4zNzUtLjU4NWM0LjI0MiAwIDYuODkgMi4wODYgMTAuODc1IDIuMDg2YzEuOTIyIDAgMi45My0uNDkzIDMuNTQtLjQ5M2MuNzk2IDAgMS40MDYuNDkzIDEuNDA2IDEuMTcydjExLjU1NWMwIDEuMDU1LS40NDYgMS43MzQtMS40NTQgMi4xOGMtLjg2Ny4zOTgtMS42MTcuNjA5LTMuMzc1LjYwOWMtNC4wNzggMC02LjY4LTIuMDYyLTEwLjg3NS0yLjA2MmMtMS40MyAwLTIuMzY3LjI4LTIuNzg5LjQ2OHY3LjE0OWMwIC41ODYtLjQ0NSAxLjA3OC0xLjA1NCAxLjA3OCIvPjwvc3ZnPg==`
|
||||
},
|
||||
DELAYED: {
|
||||
type: '已延期',
|
||||
color: '#B81111',
|
||||
textColor: '#dccbcb',
|
||||
base64: `data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxZW0iIGhlaWdodD0iMWVtIiB2aWV3Qm94PSIwIDAgNTYgNTYiPjxwYXRoIGZpbGw9IiNCODExMTEiIGQ9Ik0yOCA1MS45MDZjMTMuMDU1IDAgMjMuOTA2LTEwLjgyOCAyMy45MDYtMjMuOTA2YzAtMTMuMDU1LTEwLjg3NS0yMy45MDYtMjMuOTMtMjMuOTA2QzE0Ljg5OSA0LjA5NCA0LjA5NSAxNC45NDUgNC4wOTUgMjhjMCAxMy4wNzggMTAuODI4IDIzLjkwNiAyMy45MDYgMjMuOTA2Ii8+PC9zdmc+`,
|
||||
flag64: `data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxZW0iIGhlaWdodD0iMWVtIiB2aWV3Qm94PSIwIDAgNTYgNTYiPjxwYXRoIGZpbGw9IiNCODExMTEiIGQ9Ik0yOCA1MS45MDZjMTMuMDU1IDAgMjMuOTA2LTEwLjg1MSAyMy45MDYtMjMuOTA2YzAtMTMuMDc4LTEwLjg3NS0yMy45MDYtMjMuOTMtMjMuOTA2QzE0Ljg5OSA0LjA5NCA0LjA5NSAxNC45MjIgNC4wOTUgMjhjMCAxMy4wNTUgMTAuODI4IDIzLjkwNiAyMy45MDYgMjMuOTA2bS05LjA3LTExLjExYy0uNTg2IDAtMS4xMDItLjUxNS0xLjEwMi0xLjA3N1YxOS44MmMwLTEuMDA4LjQ5Mi0xLjc1OCAxLjQ1My0yLjE4Yy44NDQtLjM3NCAxLjU3LS41ODUgMy4zNzUtLjU4NWM0LjI0MiAwIDYuODkgMi4wODYgMTAuODc1IDIuMDg2YzEuOTIyIDAgMi45My0uNDkzIDMuNTQtLjQ5M2MuNzk2IDAgMS40MDYuNDkzIDEuNDA2IDEuMTcydjExLjU1NWMwIDEuMDU1LS40NDYgMS43MzQtMS40NTQgMi4xOGMtLjg2Ny4zOTgtMS42MTcuNjA5LTMuMzc1LjYwOWMtNC4wNzggMC02LjY4LTIuMDYyLTEwLjg3NS0yLjA2MmMtMS40MyAwLTIuMzY3LjI4LTIuNzg5LjQ2OHY3LjE0OWMwIC41ODYtLjQ0NSAxLjA3OC0xLjA1NCAxLjA3OCIvPjwvc3ZnPg==`
|
||||
},
|
||||
PAUSED: {
|
||||
type: '已暂停',
|
||||
color: '#0E42D2',
|
||||
textColor: '#dae0f0',
|
||||
base64: `data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxZW0iIGhlaWdodD0iMWVtIiB2aWV3Qm94PSIwIDAgNTYgNTYiPjxwYXRoIGZpbGw9IiMwRTQyRDIiIGQ9Ik0yOCA1MS45MDZjMTMuMDU1IDAgMjMuOTA2LTEwLjgyOCAyMy45MDYtMjMuOTA2YzAtMTMuMDU1LTEwLjg3NS0yMy45MDYtMjMuOTMtMjMuOTA2QzE0Ljg5OSA0LjA5NCA0LjA5NSAxNC45NDUgNC4wOTUgMjhjMCAxMy4wNzggMTAuODI4IDIzLjkwNiAyMy45MDYgMjMuOTA2Ii8+PC9zdmc+`,
|
||||
flag64: `data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxZW0iIGhlaWdodD0iMWVtIiB2aWV3Qm94PSIwIDAgNTYgNTYiPjxwYXRoIGZpbGw9IiMwRTQyRDIiIGQ9Ik0yOCA1MS45MDZjMTMuMDU1IDAgMjMuOTA2LTEwLjg1MSAyMy45MDYtMjMuOTA2YzAtMTMuMDc4LTEwLjg3NS0yMy45MDYtMjMuOTMtMjMuOTA2QzE0Ljg5OSA0LjA5NCA0LjA5NSAxNC45MjIgNC4wOTUgMjhjMCAxMy4wNTUgMTAuODI4IDIzLjkwNiAyMy45MDYgMjMuOTA2bS05LjA3LTExLjExYy0uNTg2IDAtMS4xMDItLjUxNS0xLjEwMi0xLjA3N1YxOS44MmMwLTEuMDA4LjQ5Mi0xLjc1OCAxLjQ1My0yLjE4Yy44NDQtLjM3NCAxLjU3LS41ODUgMy4zNzUtLjU4NWM0LjI0MiAwIDYuODkgMi4wODYgMTAuODc1IDIuMDg2YzEuOTIyIDAgMi45My0uNDkzIDMuNTQtLjQ5M2MuNzk2IDAgMS40MDYuNDkzIDEuNDA2IDEuMTcydjExLjU1NWMwIDEuMDU1LS40NDYgMS43MzQtMS40NTQgMi4xOGMtLjg2Ny4zOTgtMS42MTcuNjA5LTMuMzc1LjYwOWMtNC4wNzggMC02LjY4LTIuMDYyLTEwLjg3NS0yLjA2MmMtMS40MyAwLTIuMzY3LjI4LTIuNzg5LjQ2OHY3LjE0OWMwIC41ODYtLjQ0NSAxLjA3OC0xLjA1NCAxLjA3OCIvPjwvc3ZnPg==`
|
||||
},
|
||||
IN_PROGRESS: {
|
||||
type: '进行中',
|
||||
color: '#E1BE0D',
|
||||
textColor: '#4f4304',
|
||||
base64: `data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxZW0iIGhlaWdodD0iMWVtIiB2aWV3Qm94PSIwIDAgNTYgNTYiPjxwYXRoIGZpbGw9IiNFMUJFMEQiIGQ9Ik0yOCA1MS45MDZjMTMuMDU1IDAgMjMuOTA2LTEwLjgyOCAyMy45MDYtMjMuOTA2YzAtMTMuMDU1LTEwLjg3NS0yMy45MDYtMjMuOTMtMjMuOTA2QzE0Ljg5OSA0LjA5NCA0LjA5NSAxNC45NDUgNC4wOTUgMjhjMCAxMy4wNzggMTAuODI4IDIzLjkwNiAyMy45MDYgMjMuOTA2Ii8+PC9zdmc+`,
|
||||
flag64: `data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxZW0iIGhlaWdodD0iMWVtIiB2aWV3Qm94PSIwIDAgNTYgNTYiPjxwYXRoIGZpbGw9IiNFMUJFMEQiIGQ9Ik0yOCA1MS45MDZjMTMuMDU1IDAgMjMuOTA2LTEwLjg1MSAyMy45MDYtMjMuOTA2YzAtMTMuMDc4LTEwLjg3NS0yMy45MDYtMjMuOTMtMjMuOTA2QzE0Ljg5OSA0LjA5NCA0LjA5NSAxNC45MjIgNC4wOTUgMjhjMCAxMy4wNTUgMTAuODI4IDIzLjkwNiAyMy45MDYgMjMuOTA2bS05LjA3LTExLjExYy0uNTg2IDAtMS4xMDItLjUxNS0xLjEwMi0xLjA3N1YxOS44MmMwLTEuMDA4LjQ5Mi0xLjc1OCAxLjQ1My0yLjE4Yy44NDQtLjM3NCAxLjU3LS41ODUgMy4zNzUtLjU4NWM0LjI0MiAwIDYuODkgMi4wODYgMTAuODc1IDIuMDg2YzEuOTIyIDAgMi45My0uNDkzIDMuNTQtLjQ5M2MuNzk2IDAgMS40MDYuNDkzIDEuNDA2IDEuMTcydjExLjU1NWMwIDEuMDU1LS40NDYgMS43MzQtMS40NTQgMi4xOGMtLjg2Ny4zOTgtMS42MTcuNjA5LTMuMzc1LjYwOWMtNC4wNzggMC02LjY4LTIuMDYyLTEwLjg3NS0yLjA2MmMtMS40MyAwLTIuMzY3LjI4LTIuNzg5LjQ2OHY3LjE0OWMwIC41ODYtLjQ0NSAxLjA3OC0xLjA1NCAxLjA3OCIvPjwvc3ZnPg==`
|
||||
},
|
||||
COMPLETED: {
|
||||
type: '已完成',
|
||||
color: '#33C73D',
|
||||
textColor: '#084e0c',
|
||||
base64: `data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxZW0iIGhlaWdodD0iMWVtIiB2aWV3Qm94PSIwIDAgNTYgNTYiPjxwYXRoIGZpbGw9IiMzM0M3M0QiIGQ9Ik0yOCA1MS45MDZjMTMuMDU1IDAgMjMuOTA2LTEwLjgyOCAyMy45MDYtMjMuOTA2YzAtMTMuMDU1LTEwLjg3NS0yMy45MDYtMjMuOTMtMjMuOTA2QzE0Ljg5OSA0LjA5NCA0LjA5NSAxNC45NDUgNC4wOTUgMjhjMCAxMy4wNzggMTAuODI4IDIzLjkwNiAyMy45MDYgMjMuOTA2Ii8+PC9zdmc+`,
|
||||
flag64: `data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxZW0iIGhlaWdodD0iMWVtIiB2aWV3Qm94PSIwIDAgNTYgNTYiPjxwYXRoIGZpbGw9IiMzM0M3M0QiIGQ9Ik0yOCA1MS45MDZjMTMuMDU1IDAgMjMuOTA2LTEwLjg1MSAyMy45MDYtMjMuOTA2YzAtMTMuMDc4LTEwLjg3NS0yMy45MDYtMjMuOTMtMjMuOTA2QzE0Ljg5OSA0LjA5NCA0LjA5NSAxNC45MjIgNC4wOTUgMjhjMCAxMy4wNTUgMTAuODI4IDIzLjkwNiAyMy45MDYgMjMuOTA2bS05LjA3LTExLjExYy0uNTg2IDAtMS4xMDItLjUxNS0xLjEwMi0xLjA3N1YxOS44MmMwLTEuMDA4LjQ5Mi0xLjc1OCAxLjQ1My0yLjE4Yy44NDQtLjM3NCAxLjU3LS41ODUgMy4zNzUtLjU4NWM0LjI0MiAwIDYuODkgMi4wODYgMTAuODc1IDIuMDg2YzEuOTIyIDAgMi45My0uNDkzIDMuNTQtLjQ5M2MuNzk2IDAgMS40MDYuNDkzIDEuNDA2IDEuMTcydjExLjU1NWMwIDEuMDU1LS40NDYgMS43MzQtMS40NTQgMi4xOGMtLjg2Ny4zOTgtMS42MTcuNjA5LTMuMzc1LjYwOWMtNC4wNzggMC02LjY4LTIuMDYyLTEwLjg3NS0yLjA2MmMtMS40MyAwLTIuMzY3LjI4LTIuNzg5LjQ2OHY3LjE0OWMwIC41ODYtLjQ0NSAxLjA3OC0xLjA1NCAxLjA3OCIvPjwvc3ZnPg==`
|
||||
},
|
||||
COMPLETED_EARLY: {
|
||||
type: '提前完成',
|
||||
color: '#CCFF99',
|
||||
textColor: '#42681d',
|
||||
base64: `data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxZW0iIGhlaWdodD0iMWVtIiB2aWV3Qm94PSIwIDAgNTYgNTYiPjxwYXRoIGZpbGw9IiNDQ0ZGOTkiIGQ9Ik0yOCA1MS45MDZjMTMuMDU1IDAgMjMuOTA2LTEwLjgyOCAyMy45MDYtMjMuOTA2YzAtMTMuMDU1LTEwLjg3NS0yMy45MDYtMjMuOTMtMjMuOTA2QzE0Ljg5OSA0LjA5NCA0LjA5NSAxNC45NDUgNC4wOTUgMjhjMCAxMy4wNzggMTAuODI4IDIzLjkwNiAyMy45MDYgMjMuOTA2Ii8+PC9zdmc+`,
|
||||
flag64: `data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxZW0iIGhlaWdodD0iMWVtIiB2aWV3Qm94PSIwIDAgNTYgNTYiPjxwYXRoIGZpbGw9IiNDQ0ZGOTkiIGQ9Ik0yOCA1MS45MDZjMTMuMDU1IDAgMjMuOTA2LTEwLjg1MSAyMy45MDYtMjMuOTA2YzAtMTMuMDc4LTEwLjg3NS0yMy45MDYtMjMuOTMtMjMuOTA2QzE0Ljg5OSA0LjA5NCA0LjA5NSAxNC45MjIgNC4wOTUgMjhjMCAxMy4wNTUgMTAuODI4IDIzLjkwNiAyMy45MDYgMjMuOTA2bS05LjA3LTExLjExYy0uNTg2IDAtMS4xMDItLjUxNS0xLjEwMi0xLjA3N1YxOS44MmMwLTEuMDA4LjQ5Mi0xLjc1OCAxLjQ1My0yLjE4Yy44NDQtLjM3NCAxLjU3LS41ODUgMy4zNzUtLjU4NWM0LjI0MiAwIDYuODkgMi4wODYgMTAuODc1IDIuMDg2YzEuOTIyIDAgMi45My0uNDkzIDMuNTQtLjQ5M2MuNzk2IDAgMS40MDYuNDkzIDEuNDA2IDEuMTcydjExLjU1NWMwIDEuMDU1LS40NDYgMS43MzQtMS40NTQgMi4xOGMtLjg2Ny4zOTgtMS42MTcuNjA5LTMuMzc1LjYwOWMtNC4wNzggMC02LjY4LTIuMDYyLTEwLjg3NS0yLjA2MmMtMS40MyAwLTIuMzY3LjI4LTIuNzg5LjQ2OHY3LjE0OWMwIC41ODYtLjQ0NSAxLjA3OC0xLjA1NCAxLjA3OCIvPjwvc3ZnPg==`
|
||||
},
|
||||
COMPLETED_LATE: {
|
||||
type: '延期完成',
|
||||
color: '#CC6699',
|
||||
textColor: '#4b092a',
|
||||
base64: `data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxZW0iIGhlaWdodD0iMWVtIiB2aWV3Qm94PSIwIDAgNTYgNTYiPjxwYXRoIGZpbGw9IiNDQzY2OTkiIGQ9Ik0yOCA1MS45MDZjMTMuMDU1IDAgMjMuOTA2LTEwLjgyOCAyMy45MDYtMjMuOTA2YzAtMTMuMDU1LTEwLjg3NS0yMy45MDYtMjMuOTMtMjMuOTA2QzE0Ljg5OSA0LjA5NCA0LjA5NSAxNC45NDUgNC4wOTUgMjhjMCAxMy4wNzggMTAuODI4IDIzLjkwNiAyMy45MDYgMjMuOTA2Ii8+PC9zdmc+`,
|
||||
flag64: `data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxZW0iIGhlaWdodD0iMWVtIiB2aWV3Qm94PSIwIDAgNTYgNTYiPjxwYXRoIGZpbGw9IiNDQzY2OTkiIGQ9Ik0yOCA1MS45MDZjMTMuMDU1IDAgMjMuOTA2LTEwLjg1MSAyMy45MDYtMjMuOTA2YzAtMTMuMDc4LTEwLjg3NS0yMy45MDYtMjMuOTMtMjMuOTA2QzE0Ljg5OSA0LjA5NCA0LjA5NSAxNC45MjIgNC4wOTUgMjhjMCAxMy4wNTUgMTAuODI4IDIzLjkwNiAyMy45MDYgMjMuOTA2bS05LjA3LTExLjExYy0uNTg2IDAtMS4xMDItLjUxNS0xLjEwMi0xLjA3N1YxOS44MmMwLTEuMDA4LjQ5Mi0xLjc1OCAxLjQ1My0yLjE4Yy44NDQtLjM3NCAxLjU3LS41ODUgMy4zNzUtLjU4NWM0LjI0MiAwIDYuODkgMi4wODYgMTAuODc1IDIuMDg2YzEuOTIyIDAgMi45My0uNDkzIDMuNTQtLjQ5M2MuNzk2IDAgMS40MDYuNDkzIDEuNDA2IDEuMTcydjExLjU1NWMwIDEuMDU1LS40NDYgMS43MzQtMS40NTQgMi4xOGMtLjg2Ny4zOTgtMS42MTcuNjA5LTMuMzc1LjYwOWMtNC4wNzggMC02LjY4LTIuMDYyLTEwLjg3NS0yLjA2MmMtMS40MyAwLTIuMzY3LjI4LTIuNzg5LjQ2OHY3LjE0OWMwIC41ODYtLjQ0NSAxLjA3OC0xLjA1NCAxLjA3OCIvPjwvc3ZnPg==`
|
||||
}
|
||||
};
|
||||
|
||||
export function getNodeIcon(node: CustomNodeData) {
|
||||
if (!node.status) return '';
|
||||
|
||||
const type = node.milestone ? 'flag64' : 'base64';
|
||||
|
||||
return nodeStatus[node.status][type];
|
||||
}
|
||||
|
||||
export function getNodeStatusTag(state: NodeStatus, tagProperty?: TagProps) {
|
||||
const { color, type } = nodeStatus[state] || {};
|
||||
|
||||
return h(
|
||||
ElTag,
|
||||
{
|
||||
color,
|
||||
size: 'small',
|
||||
...tagProperty
|
||||
},
|
||||
{
|
||||
default: () => type
|
||||
}
|
||||
);
|
||||
}
|
||||
28
src/views/plugin/charts/antv/modules/types.ts
Normal file
28
src/views/plugin/charts/antv/modules/types.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import type { EdgeData, GraphData, NodeData } from '@antv/g6';
|
||||
|
||||
export type NodeStatus =
|
||||
| 'MILESTONE'
|
||||
| 'NOT_STARTED'
|
||||
| 'DELAYED'
|
||||
| 'PAUSED'
|
||||
| 'IN_PROGRESS'
|
||||
| 'COMPLETED'
|
||||
| 'COMPLETED_EARLY'
|
||||
| 'COMPLETED_LATE';
|
||||
|
||||
export interface CustomNodeData extends NodeData {
|
||||
isDelayed?: boolean;
|
||||
isDeleted?: boolean;
|
||||
milestone?: boolean;
|
||||
status?: NodeStatus;
|
||||
}
|
||||
|
||||
export interface CustomEdgeData extends EdgeData {
|
||||
isDelayed?: boolean;
|
||||
isDeleted?: boolean;
|
||||
}
|
||||
|
||||
export interface CustomGraphData extends GraphData {
|
||||
nodes: CustomNodeData[];
|
||||
edges: CustomEdgeData[];
|
||||
}
|
||||
706
src/views/plugin/charts/echarts/data.ts
Normal file
706
src/views/plugin/charts/echarts/data.ts
Normal file
@@ -0,0 +1,706 @@
|
||||
import { graphic } from 'echarts';
|
||||
import type { ScatterSeriesOption } from 'echarts/charts';
|
||||
import type { SingleAxisComponentOption, TitleComponentOption } from 'echarts/components';
|
||||
import type { ECOption } from '@/hooks/common/echarts';
|
||||
|
||||
export const pieOptions: ECOption = {
|
||||
legend: {},
|
||||
toolbox: {
|
||||
show: true,
|
||||
feature: {
|
||||
mark: { show: true },
|
||||
dataView: { show: true, readOnly: false },
|
||||
restore: { show: true },
|
||||
saveAsImage: { show: true }
|
||||
}
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: 'Nightingale Chart',
|
||||
type: 'pie',
|
||||
radius: [50, 150],
|
||||
center: ['50%', '50%'],
|
||||
roseType: 'area',
|
||||
itemStyle: {
|
||||
borderRadius: 8
|
||||
},
|
||||
data: [
|
||||
{ value: 40, name: 'rose 1' },
|
||||
{ value: 38, name: 'rose 2' },
|
||||
{ value: 32, name: 'rose 3' },
|
||||
{ value: 30, name: 'rose 4' },
|
||||
{ value: 28, name: 'rose 5' },
|
||||
{ value: 26, name: 'rose 6' },
|
||||
{ value: 22, name: 'rose 7' },
|
||||
{ value: 18, name: 'rose 8' }
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
export const lineOptions: ECOption = {
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: {
|
||||
type: 'cross',
|
||||
label: {
|
||||
backgroundColor: '#6a7985'
|
||||
}
|
||||
}
|
||||
},
|
||||
title: {
|
||||
text: 'Stacked Line'
|
||||
},
|
||||
legend: {
|
||||
data: ['Email', 'Union Ads', 'Video Ads', 'Direct', 'Search Engine']
|
||||
},
|
||||
grid: {
|
||||
left: '3%',
|
||||
right: '4%',
|
||||
bottom: '3%',
|
||||
containLabel: true
|
||||
},
|
||||
toolbox: {
|
||||
feature: {
|
||||
saveAsImage: {}
|
||||
}
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
boundaryGap: false,
|
||||
data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value'
|
||||
},
|
||||
series: [
|
||||
{
|
||||
color: '#37a2da',
|
||||
name: 'Email',
|
||||
type: 'line',
|
||||
smooth: true,
|
||||
stack: 'Total',
|
||||
areaStyle: {
|
||||
color: {
|
||||
type: 'linear',
|
||||
x: 0,
|
||||
y: 0,
|
||||
x2: 0,
|
||||
y2: 1,
|
||||
colorStops: [
|
||||
{
|
||||
offset: 0.25,
|
||||
color: '#37a2da'
|
||||
},
|
||||
{
|
||||
offset: 1,
|
||||
color: '#fff'
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
emphasis: {
|
||||
focus: 'series'
|
||||
},
|
||||
data: [120, 132, 101, 134, 90, 230, 210]
|
||||
},
|
||||
{
|
||||
color: '#9fe6b8',
|
||||
name: 'Union Ads',
|
||||
type: 'line',
|
||||
smooth: true,
|
||||
stack: 'Total',
|
||||
areaStyle: {
|
||||
color: {
|
||||
type: 'linear',
|
||||
x: 0,
|
||||
y: 0,
|
||||
x2: 0,
|
||||
y2: 1,
|
||||
colorStops: [
|
||||
{
|
||||
offset: 0.25,
|
||||
color: '#9fe6b8'
|
||||
},
|
||||
{
|
||||
offset: 1,
|
||||
color: '#fff'
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
emphasis: {
|
||||
focus: 'series'
|
||||
},
|
||||
data: [220, 182, 191, 234, 290, 330, 310]
|
||||
},
|
||||
{
|
||||
color: '#fedb5c',
|
||||
name: 'Video Ads',
|
||||
type: 'line',
|
||||
smooth: true,
|
||||
stack: 'Total',
|
||||
areaStyle: {
|
||||
color: {
|
||||
type: 'linear',
|
||||
x: 0,
|
||||
y: 0,
|
||||
x2: 0,
|
||||
y2: 1,
|
||||
colorStops: [
|
||||
{
|
||||
offset: 0.25,
|
||||
color: '#fedb5c'
|
||||
},
|
||||
{
|
||||
offset: 1,
|
||||
color: '#fff'
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
emphasis: {
|
||||
focus: 'series'
|
||||
},
|
||||
data: [150, 232, 201, 154, 190, 330, 410]
|
||||
},
|
||||
{
|
||||
color: '#fb7293',
|
||||
name: 'Direct',
|
||||
type: 'line',
|
||||
smooth: true,
|
||||
stack: 'Total',
|
||||
areaStyle: {
|
||||
color: {
|
||||
type: 'linear',
|
||||
x: 0,
|
||||
y: 0,
|
||||
x2: 0,
|
||||
y2: 1,
|
||||
colorStops: [
|
||||
{
|
||||
offset: 0.25,
|
||||
color: '#fb7293'
|
||||
},
|
||||
{
|
||||
offset: 1,
|
||||
color: '#fff'
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
emphasis: {
|
||||
focus: 'series'
|
||||
},
|
||||
data: [320, 332, 301, 334, 390, 330, 320]
|
||||
},
|
||||
{
|
||||
color: '#e7bcf3',
|
||||
name: 'Search Engine',
|
||||
type: 'line',
|
||||
smooth: true,
|
||||
stack: 'Total',
|
||||
areaStyle: {
|
||||
color: {
|
||||
type: 'linear',
|
||||
x: 0,
|
||||
y: 0,
|
||||
x2: 0,
|
||||
y2: 1,
|
||||
colorStops: [
|
||||
{
|
||||
offset: 0.25,
|
||||
color: '#e7bcf3'
|
||||
},
|
||||
{
|
||||
offset: 1,
|
||||
color: '#fff'
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
emphasis: {
|
||||
focus: 'series'
|
||||
},
|
||||
data: [820, 932, 901, 934, 1290, 1330, 1320]
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
export const barOptions: ECOption = {
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: {
|
||||
type: 'cross',
|
||||
label: {
|
||||
backgroundColor: '#6a7985'
|
||||
}
|
||||
}
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value'
|
||||
},
|
||||
series: [
|
||||
{
|
||||
data: [120, 200, 150, 80, 70, 110, 130],
|
||||
type: 'bar',
|
||||
color: '#8378ea',
|
||||
showBackground: true,
|
||||
barGap: 100,
|
||||
itemStyle: {
|
||||
borderRadius: [40, 40, 0, 0]
|
||||
},
|
||||
backgroundStyle: {
|
||||
color: 'rgba(180, 180, 180, 0.2)'
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
export function getPictorialBarOption(): ECOption {
|
||||
const category: string[] = [];
|
||||
let dottedBase = Number(new Date());
|
||||
const lineData: number[] = [];
|
||||
const barData: number[] = [];
|
||||
|
||||
for (let i = 0; i < 20; i += 1) {
|
||||
const date = new Date((dottedBase += 3600 * 24 * 1000));
|
||||
category.push([date.getFullYear(), date.getMonth() + 1, date.getDate()].join('-'));
|
||||
const b = Math.random() * 200;
|
||||
const d = Math.random() * 200;
|
||||
barData.push(b);
|
||||
lineData.push(d + b);
|
||||
}
|
||||
|
||||
const options: ECOption = {
|
||||
backgroundColor: '#0f375f',
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: {
|
||||
type: 'shadow'
|
||||
}
|
||||
},
|
||||
legend: {
|
||||
data: ['line', 'bar'],
|
||||
textStyle: {
|
||||
color: '#ccc'
|
||||
}
|
||||
},
|
||||
xAxis: {
|
||||
data: category,
|
||||
axisLine: {
|
||||
lineStyle: {
|
||||
color: '#ccc'
|
||||
}
|
||||
}
|
||||
},
|
||||
yAxis: {
|
||||
splitLine: { show: false },
|
||||
axisLine: {
|
||||
lineStyle: {
|
||||
color: '#ccc'
|
||||
}
|
||||
}
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: 'line',
|
||||
type: 'line',
|
||||
smooth: true,
|
||||
showAllSymbol: true,
|
||||
symbol: 'emptyCircle',
|
||||
symbolSize: 15,
|
||||
data: lineData
|
||||
},
|
||||
{
|
||||
name: 'bar',
|
||||
type: 'bar',
|
||||
barWidth: 10,
|
||||
itemStyle: {
|
||||
borderRadius: 5,
|
||||
color: new graphic.LinearGradient(0, 0, 0, 1, [
|
||||
{ offset: 0, color: '#14c8d4' },
|
||||
{ offset: 1, color: '#43eec6' }
|
||||
])
|
||||
},
|
||||
data: barData
|
||||
},
|
||||
{
|
||||
name: 'line',
|
||||
type: 'bar',
|
||||
barGap: '-100%',
|
||||
barWidth: 10,
|
||||
itemStyle: {
|
||||
color: new graphic.LinearGradient(0, 0, 0, 1, [
|
||||
{ offset: 0, color: 'rgba(20,200,212,0.5)' },
|
||||
{ offset: 0.2, color: 'rgba(20,200,212,0.2)' },
|
||||
{ offset: 1, color: 'rgba(20,200,212,0)' }
|
||||
])
|
||||
},
|
||||
z: -12,
|
||||
data: lineData
|
||||
},
|
||||
{
|
||||
name: 'dotted',
|
||||
type: 'pictorialBar',
|
||||
symbol: 'rect',
|
||||
itemStyle: {
|
||||
color: '#0f375f'
|
||||
},
|
||||
symbolRepeat: true,
|
||||
symbolSize: [12, 4],
|
||||
symbolMargin: 1,
|
||||
z: -10,
|
||||
data: lineData
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
export function getScatterOption() {
|
||||
// prettier-ignore
|
||||
const hours = ['12a', '1a', '2a', '3a', '4a', '5a', '6a', '7a', '8a', '9a','10a','11a', '12p', '1p', '2p', '3p', '4p', '5p', '6p', '7p', '8p', '9p', '10p', '11p'];
|
||||
|
||||
// prettier-ignore
|
||||
const days = ['Saturday', 'Friday', 'Thursday', 'Wednesday', 'Tuesday', 'Monday', 'Sunday'];
|
||||
|
||||
// prettier-ignore
|
||||
const data: [number, number, number][] = [[0,0,5],[0,1,1],[0,2,0],[0,3,0],[0,4,0],[0,5,0],[0,6,0],[0,7,0],[0,8,0],[0,9,0],[0,10,0],[0,11,2],[0,12,4],[0,13,1],[0,14,1],[0,15,3],[0,16,4],[0,17,6],[0,18,4],[0,19,4],[0,20,3],[0,21,3],[0,22,2],[0,23,5],[1,0,7],[1,1,0],[1,2,0],[1,3,0],[1,4,0],[1,5,0],[1,6,0],[1,7,0],[1,8,0],[1,9,0],[1,10,5],[1,11,2],[1,12,2],[1,13,6],[1,14,9],[1,15,11],[1,16,6],[1,17,7],[1,18,8],[1,19,12],[1,20,5],[1,21,5],[1,22,7],[1,23,2],[2,0,1],[2,1,1],[2,2,0],[2,3,0],[2,4,0],[2,5,0],[2,6,0],[2,7,0],[2,8,0],[2,9,0],[2,10,3],[2,11,2],[2,12,1],[2,13,9],[2,14,8],[2,15,10],[2,16,6],[2,17,5],[2,18,5],[2,19,5],[2,20,7],[2,21,4],[2,22,2],[2,23,4],[3,0,7],[3,1,3],[3,2,0],[3,3,0],[3,4,0],[3,5,0],[3,6,0],[3,7,0],[3,8,1],[3,9,0],[3,10,5],[3,11,4],[3,12,7],[3,13,14],[3,14,13],[3,15,12],[3,16,9],[3,17,5],[3,18,5],[3,19,10],[3,20,6],[3,21,4],[3,22,4],[3,23,1],[4,0,1],[4,1,3],[4,2,0],[4,3,0],[4,4,0],[4,5,1],[4,6,0],[4,7,0],[4,8,0],[4,9,2],[4,10,4],[4,11,4],[4,12,2],[4,13,4],[4,14,4],[4,15,14],[4,16,12],[4,17,1],[4,18,8],[4,19,5],[4,20,3],[4,21,7],[4,22,3],[4,23,0],[5,0,2],[5,1,1],[5,2,0],[5,3,3],[5,4,0],[5,5,0],[5,6,0],[5,7,0],[5,8,2],[5,9,0],[5,10,4],[5,11,1],[5,12,5],[5,13,10],[5,14,5],[5,15,7],[5,16,11],[5,17,6],[5,18,0],[5,19,5],[5,20,3],[5,21,4],[5,22,2],[5,23,0],[6,0,1],[6,1,0],[6,2,0],[6,3,0],[6,4,0],[6,5,0],[6,6,0],[6,7,0],[6,8,0],[6,9,0],[6,10,1],[6,11,0],[6,12,2],[6,13,1],[6,14,3],[6,15,4],[6,16,0],[6,17,0],[6,18,0],[6,19,0],[6,20,1],[6,21,2],[6,22,2],[6,23,6]];
|
||||
|
||||
const title: TitleComponentOption[] = [];
|
||||
const singleAxis: SingleAxisComponentOption[] = [];
|
||||
const series: ScatterSeriesOption[] = [];
|
||||
|
||||
days.forEach((day, idx) => {
|
||||
title.push({
|
||||
textBaseline: 'middle',
|
||||
top: `${((idx + 0.5) * 100) / 7}%`,
|
||||
text: day
|
||||
});
|
||||
singleAxis.push({
|
||||
left: 150,
|
||||
type: 'category',
|
||||
boundaryGap: false,
|
||||
data: hours,
|
||||
top: `${(idx * 100) / 7 + 5}%`,
|
||||
height: `${100 / 7 - 10}%`,
|
||||
axisLabel: {
|
||||
interval: 2
|
||||
}
|
||||
});
|
||||
series.push({
|
||||
singleAxisIndex: idx,
|
||||
coordinateSystem: 'singleAxis',
|
||||
type: 'scatter',
|
||||
data: [],
|
||||
symbolSize(dataItem) {
|
||||
return dataItem[1] * 4;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
data.forEach(dataItem => {
|
||||
(series as any)[dataItem[0]].data.push([dataItem[1], dataItem[2]]);
|
||||
});
|
||||
|
||||
const option: ECOption = {
|
||||
tooltip: {
|
||||
position: 'top'
|
||||
},
|
||||
title,
|
||||
singleAxis,
|
||||
series: series as any
|
||||
};
|
||||
|
||||
return option;
|
||||
}
|
||||
|
||||
export const radarOptions: ECOption = {
|
||||
title: {
|
||||
text: 'Multiple Radar'
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'axis'
|
||||
},
|
||||
legend: {
|
||||
left: 'center',
|
||||
data: ['A Software', 'A Phone', 'Another Phone', 'Precipitation', 'Evaporation']
|
||||
},
|
||||
radar: [
|
||||
{
|
||||
indicator: [
|
||||
{ name: 'Brand', max: 100 },
|
||||
{ name: 'Content', max: 100 },
|
||||
{ name: 'Usability', max: 100 },
|
||||
{ name: 'Function', max: 100 }
|
||||
],
|
||||
center: ['25%', '40%'],
|
||||
radius: 80
|
||||
},
|
||||
{
|
||||
indicator: [
|
||||
{ name: 'Look', max: 100 },
|
||||
{ name: 'Photo', max: 100 },
|
||||
{ name: 'System', max: 100 },
|
||||
{ name: 'Performance', max: 100 },
|
||||
{ name: 'Screen', max: 100 }
|
||||
],
|
||||
radius: 80,
|
||||
center: ['50%', '60%']
|
||||
},
|
||||
{
|
||||
indicator: (() => {
|
||||
const res = [];
|
||||
for (let i = 1; i <= 12; i += 1) {
|
||||
res.push({ name: `${i}月`, max: 100 });
|
||||
}
|
||||
return res;
|
||||
})(),
|
||||
center: ['75%', '40%'],
|
||||
radius: 80
|
||||
}
|
||||
],
|
||||
series: [
|
||||
{
|
||||
type: 'radar',
|
||||
tooltip: {
|
||||
trigger: 'item'
|
||||
},
|
||||
areaStyle: {},
|
||||
data: [
|
||||
{
|
||||
value: [60, 73, 85, 40],
|
||||
name: 'A Software'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
type: 'radar',
|
||||
radarIndex: 1,
|
||||
areaStyle: {},
|
||||
data: [
|
||||
{
|
||||
value: [85, 90, 90, 95, 95],
|
||||
name: 'A Phone'
|
||||
},
|
||||
{
|
||||
value: [95, 80, 95, 90, 93],
|
||||
name: 'Another Phone'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
type: 'radar',
|
||||
radarIndex: 2,
|
||||
areaStyle: {},
|
||||
data: [
|
||||
{
|
||||
name: 'Precipitation',
|
||||
value: [2.6, 5.9, 9.0, 26.4, 28.7, 70.7, 75.6, 82.2, 48.7, 18.8, 6.0, 2.3]
|
||||
},
|
||||
{
|
||||
name: 'Evaporation',
|
||||
value: [2.0, 4.9, 7.0, 23.2, 25.6, 76.7, 35.6, 62.2, 32.6, 20.0, 6.4, 3.3]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
export const gaugeOptions: ECOption = {
|
||||
series: [
|
||||
{
|
||||
name: 'hour',
|
||||
type: 'gauge',
|
||||
startAngle: 90,
|
||||
endAngle: -270,
|
||||
min: 0,
|
||||
max: 12,
|
||||
splitNumber: 12,
|
||||
clockwise: true,
|
||||
axisLine: {
|
||||
lineStyle: {
|
||||
width: 15,
|
||||
color: [[1, 'rgba(0,0,0,0.7)']],
|
||||
shadowColor: 'rgba(0, 0, 0, 0.5)',
|
||||
shadowBlur: 15
|
||||
}
|
||||
},
|
||||
splitLine: {
|
||||
lineStyle: {
|
||||
shadowColor: 'rgba(0, 0, 0, 0.3)',
|
||||
shadowBlur: 3,
|
||||
shadowOffsetX: 1,
|
||||
shadowOffsetY: 2
|
||||
}
|
||||
},
|
||||
axisLabel: {
|
||||
fontSize: 50,
|
||||
distance: 25,
|
||||
formatter(value) {
|
||||
if (value === 0) {
|
||||
return '';
|
||||
}
|
||||
return `${value}`;
|
||||
}
|
||||
},
|
||||
anchor: {
|
||||
show: true,
|
||||
icon: 'path://M532.8,70.8C532.8,70.8,532.8,70.8,532.8,70.8L532.8,70.8C532.7,70.8,532.8,70.8,532.8,70.8z M456.1,49.6c-2.2-6.2-8.1-10.6-15-10.6h-37.5v10.6h37.5l0,0c2.9,0,5.3,2.4,5.3,5.3c0,2.9-2.4,5.3-5.3,5.3v0h-22.5c-1.5,0.1-3,0.4-4.3,0.9c-4.5,1.6-8.1,5.2-9.7,9.8c-0.6,1.7-0.9,3.4-0.9,5.3v16h10.6v-16l0,0l0,0c0-2.7,2.1-5,4.7-5.3h10.3l10.4,21.2h11.8l-10.4-21.2h0c6.9,0,12.8-4.4,15-10.6c0.6-1.7,0.9-3.5,0.9-5.3C457,53,456.7,51.2,456.1,49.6z M388.9,92.1h11.3L381,39h-3.6h-11.3L346.8,92v0h11.3l3.9-10.7h7.3h7.7l3.9-10.6h-7.7h-7.3l7.7-21.2v0L388.9,92.1z M301,38.9h-10.6v53.1H301V70.8h28.4l3.7-10.6H301V38.9zM333.2,38.9v10.6v10.7v31.9h10.6V38.9H333.2z M249.5,81.4L249.5,81.4L249.5,81.4c-2.9,0-5.3-2.4-5.3-5.3h0V54.9h0l0,0c0-2.9,2.4-5.3,5.3-5.3l0,0l0,0h33.6l3.9-10.6h-37.5c-1.9,0-3.6,0.3-5.3,0.9c-4.5,1.6-8.1,5.2-9.7,9.7c-0.6,1.7-0.9,3.5-0.9,5.3l0,0v21.3c0,1.9,0.3,3.6,0.9,5.3c1.6,4.5,5.2,8.1,9.7,9.7c1.7,0.6,3.5,0.9,5.3,0.9h33.6l3.9-10.6H249.5z M176.8,38.9v10.6h49.6l3.9-10.6H176.8z M192.7,81.4L192.7,81.4L192.7,81.4c-2.9,0-5.3-2.4-5.3-5.3l0,0v-5.3h38.9l3.9-10.6h-53.4v10.6v5.3l0,0c0,1.9,0.3,3.6,0.9,5.3c1.6,4.5,5.2,8.1,9.7,9.7c1.7,0.6,3.4,0.9,5.3,0.9h23.4h10.2l3.9-10.6l0,0H192.7z M460.1,38.9v10.6h21.4v42.5h10.6V49.6h17.5l3.8-10.6H460.1z M541.6,68.2c-0.2,0.1-0.4,0.3-0.7,0.4C541.1,68.4,541.4,68.3,541.6,68.2L541.6,68.2z M554.3,60.2h-21.6v0l0,0c-2.9,0-5.3-2.4-5.3-5.3c0-2.9,2.4-5.3,5.3-5.3l0,0l0,0h33.6l3.8-10.6h-37.5l0,0c-6.9,0-12.8,4.4-15,10.6c-0.6,1.7-0.9,3.5-0.9,5.3c0,1.9,0.3,3.7,0.9,5.3c2.2,6.2,8.1,10.6,15,10.6h21.6l0,0c2.9,0,5.3,2.4,5.3,5.3c0,2.9-2.4,5.3-5.3,5.3l0,0h-37.5v10.6h37.5c6.9,0,12.8-4.4,15-10.6c0.6-1.7,0.9-3.5,0.9-5.3c0-1.9-0.3-3.7-0.9-5.3C567.2,64.6,561.3,60.2,554.3,60.2z',
|
||||
showAbove: false,
|
||||
offsetCenter: [0, '-35%'],
|
||||
size: 120,
|
||||
keepAspect: true,
|
||||
itemStyle: {
|
||||
color: '#707177'
|
||||
}
|
||||
},
|
||||
pointer: {
|
||||
icon: 'path://M2.9,0.7L2.9,0.7c1.4,0,2.6,1.2,2.6,2.6v115c0,1.4-1.2,2.6-2.6,2.6l0,0c-1.4,0-2.6-1.2-2.6-2.6V3.3C0.3,1.9,1.4,0.7,2.9,0.7z',
|
||||
width: 12,
|
||||
length: '55%',
|
||||
offsetCenter: [0, '8%'],
|
||||
itemStyle: {
|
||||
color: '#C0911F',
|
||||
shadowColor: 'rgba(0, 0, 0, 0.3)',
|
||||
shadowBlur: 8,
|
||||
shadowOffsetX: 2,
|
||||
shadowOffsetY: 4
|
||||
}
|
||||
},
|
||||
detail: {
|
||||
show: false
|
||||
},
|
||||
title: {
|
||||
offsetCenter: [0, '30%']
|
||||
},
|
||||
data: [
|
||||
{
|
||||
value: 0
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'minute',
|
||||
type: 'gauge',
|
||||
startAngle: 90,
|
||||
endAngle: -270,
|
||||
min: 0,
|
||||
max: 60,
|
||||
clockwise: true,
|
||||
axisLine: {
|
||||
show: false
|
||||
},
|
||||
splitLine: {
|
||||
show: false
|
||||
},
|
||||
axisTick: {
|
||||
show: false
|
||||
},
|
||||
axisLabel: {
|
||||
show: false
|
||||
},
|
||||
pointer: {
|
||||
icon: 'path://M2.9,0.7L2.9,0.7c1.4,0,2.6,1.2,2.6,2.6v115c0,1.4-1.2,2.6-2.6,2.6l0,0c-1.4,0-2.6-1.2-2.6-2.6V3.3C0.3,1.9,1.4,0.7,2.9,0.7z',
|
||||
width: 8,
|
||||
length: '70%',
|
||||
offsetCenter: [0, '8%'],
|
||||
itemStyle: {
|
||||
color: '#C0911F',
|
||||
shadowColor: 'rgba(0, 0, 0, 0.3)',
|
||||
shadowBlur: 8,
|
||||
shadowOffsetX: 2,
|
||||
shadowOffsetY: 4
|
||||
}
|
||||
},
|
||||
anchor: {
|
||||
show: true,
|
||||
size: 20,
|
||||
showAbove: false,
|
||||
itemStyle: {
|
||||
borderWidth: 15,
|
||||
borderColor: '#C0911F',
|
||||
shadowColor: 'rgba(0, 0, 0, 0.3)',
|
||||
shadowBlur: 8,
|
||||
shadowOffsetX: 2,
|
||||
shadowOffsetY: 4
|
||||
}
|
||||
},
|
||||
detail: {
|
||||
show: false
|
||||
},
|
||||
title: {
|
||||
offsetCenter: ['0%', '-40%']
|
||||
},
|
||||
data: [
|
||||
{
|
||||
value: 0
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'second',
|
||||
type: 'gauge',
|
||||
startAngle: 90,
|
||||
endAngle: -270,
|
||||
min: 0,
|
||||
max: 60,
|
||||
animationEasingUpdate: 'bounceOut',
|
||||
clockwise: true,
|
||||
axisLine: {
|
||||
show: false
|
||||
},
|
||||
splitLine: {
|
||||
show: false
|
||||
},
|
||||
axisTick: {
|
||||
show: false
|
||||
},
|
||||
axisLabel: {
|
||||
show: false
|
||||
},
|
||||
pointer: {
|
||||
icon: 'path://M2.9,0.7L2.9,0.7c1.4,0,2.6,1.2,2.6,2.6v115c0,1.4-1.2,2.6-2.6,2.6l0,0c-1.4,0-2.6-1.2-2.6-2.6V3.3C0.3,1.9,1.4,0.7,2.9,0.7z',
|
||||
width: 4,
|
||||
length: '85%',
|
||||
offsetCenter: [0, '8%'],
|
||||
itemStyle: {
|
||||
color: '#C0911F',
|
||||
shadowColor: 'rgba(0, 0, 0, 0.3)',
|
||||
shadowBlur: 8,
|
||||
shadowOffsetX: 2,
|
||||
shadowOffsetY: 4
|
||||
}
|
||||
},
|
||||
anchor: {
|
||||
show: true,
|
||||
size: 15,
|
||||
showAbove: true,
|
||||
itemStyle: {
|
||||
color: '#C0911F',
|
||||
shadowColor: 'rgba(0, 0, 0, 0.3)',
|
||||
shadowBlur: 8,
|
||||
shadowOffsetX: 2,
|
||||
shadowOffsetY: 4
|
||||
}
|
||||
},
|
||||
detail: {
|
||||
show: false
|
||||
},
|
||||
title: {
|
||||
offsetCenter: ['0%', '-40%']
|
||||
},
|
||||
data: [
|
||||
{
|
||||
value: 0
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
93
src/views/plugin/charts/echarts/index.vue
Normal file
93
src/views/plugin/charts/echarts/index.vue
Normal file
@@ -0,0 +1,93 @@
|
||||
<script setup lang="ts">
|
||||
import { onUnmounted } from 'vue';
|
||||
import { useEcharts } from '@/hooks/common/echarts';
|
||||
import {
|
||||
barOptions,
|
||||
gaugeOptions,
|
||||
getPictorialBarOption,
|
||||
getScatterOption,
|
||||
lineOptions,
|
||||
pieOptions,
|
||||
radarOptions
|
||||
} from './data';
|
||||
|
||||
defineOptions({ name: 'EchartsDemo' });
|
||||
|
||||
const { domRef: pieRef } = useEcharts(() => pieOptions, { onRender() {} });
|
||||
const { domRef: lineRef } = useEcharts(() => lineOptions, { onRender() {} });
|
||||
const { domRef: barRef } = useEcharts(() => barOptions, { onRender() {} });
|
||||
const { domRef: pictorialBarRef } = useEcharts(() => getPictorialBarOption(), { onRender() {} });
|
||||
const { domRef: radarRef } = useEcharts(() => radarOptions, { onRender() {} });
|
||||
const { domRef: scatterRef } = useEcharts(() => getScatterOption(), { onRender() {} });
|
||||
const { domRef: gaugeRef, setOptions: setGaugeOptions } = useEcharts(() => gaugeOptions, { onRender() {} });
|
||||
|
||||
let intervalId: NodeJS.Timeout;
|
||||
|
||||
function initGaugeChart() {
|
||||
intervalId = setInterval(() => {
|
||||
const date = new Date();
|
||||
const second = date.getSeconds();
|
||||
const minute = date.getMinutes() + second / 60;
|
||||
const hour = (date.getHours() % 12) + minute / 60;
|
||||
|
||||
setGaugeOptions({
|
||||
animationDurationUpdate: 300,
|
||||
series: [
|
||||
{
|
||||
name: 'hour',
|
||||
animation: hour !== 0,
|
||||
data: [{ value: hour }]
|
||||
},
|
||||
{
|
||||
name: 'minute',
|
||||
animation: minute !== 0,
|
||||
data: [{ value: minute }]
|
||||
},
|
||||
{
|
||||
animation: second !== 0,
|
||||
name: 'second',
|
||||
data: [{ value: second }]
|
||||
}
|
||||
]
|
||||
});
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
function clearGaugeChart() {
|
||||
clearInterval(intervalId);
|
||||
}
|
||||
|
||||
initGaugeChart();
|
||||
|
||||
onUnmounted(() => {
|
||||
clearGaugeChart();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ElSpace fill :size="16">
|
||||
<ElCard class="card-wrapper">
|
||||
<div ref="pieRef" class="h-400px" />
|
||||
</ElCard>
|
||||
<ElCard class="card-wrapper">
|
||||
<div ref="lineRef" class="h-400px" />
|
||||
</ElCard>
|
||||
<ElCard class="card-wrapper">
|
||||
<div ref="barRef" class="h-400px" />
|
||||
</ElCard>
|
||||
<ElCard class="card-wrapper">
|
||||
<div ref="radarRef" class="h-400px"></div>
|
||||
</ElCard>
|
||||
<ElCard class="card-wrapper">
|
||||
<div ref="scatterRef" class="h-600px"></div>
|
||||
</ElCard>
|
||||
<ElCard class="card-wrapper">
|
||||
<div ref="pictorialBarRef" class="h-600px" />
|
||||
</ElCard>
|
||||
<ElCard class="card-wrapper">
|
||||
<div ref="gaugeRef" class="h-640px" />
|
||||
</ElCard>
|
||||
</ElSpace>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
872
src/views/plugin/charts/vchart/data.ts
Normal file
872
src/views/plugin/charts/vchart/data.ts
Normal file
@@ -0,0 +1,872 @@
|
||||
import type {
|
||||
IAnimationConfig,
|
||||
IAreaChartSpec,
|
||||
IBarChartSpec,
|
||||
ICircularProgressChartSpec,
|
||||
IHistogramChartSpec,
|
||||
IIndicatorSpec,
|
||||
ILiquidChartSpec,
|
||||
IWordCloudChartSpec
|
||||
} from '@visactor/vchart';
|
||||
|
||||
export const shapeWordCloudSpec: IWordCloudChartSpec = {
|
||||
type: 'wordCloud',
|
||||
maskShape: 'https://lf9-dp-fe-cms-tos.byteorg.com/obj/bit-cloud/log.jpeg',
|
||||
nameField: 'challenge_name',
|
||||
valueField: 'sum_count',
|
||||
seriesField: 'challenge_name',
|
||||
data: [
|
||||
{
|
||||
name: 'data',
|
||||
values: [
|
||||
{
|
||||
challenge_name: '刘浩存',
|
||||
sum_count: 957
|
||||
},
|
||||
{
|
||||
challenge_name: '刘昊然',
|
||||
sum_count: 942
|
||||
},
|
||||
{
|
||||
challenge_name: '喜欢',
|
||||
sum_count: 842
|
||||
},
|
||||
{
|
||||
challenge_name: '真的',
|
||||
sum_count: 828
|
||||
},
|
||||
{
|
||||
challenge_name: '四海',
|
||||
sum_count: 665
|
||||
},
|
||||
{
|
||||
challenge_name: '好看',
|
||||
sum_count: 627
|
||||
},
|
||||
{
|
||||
challenge_name: '评论',
|
||||
sum_count: 574
|
||||
},
|
||||
{
|
||||
challenge_name: '好像',
|
||||
sum_count: 564
|
||||
},
|
||||
{
|
||||
challenge_name: '沈腾',
|
||||
sum_count: 554
|
||||
},
|
||||
{
|
||||
challenge_name: '不像',
|
||||
sum_count: 540
|
||||
},
|
||||
{
|
||||
challenge_name: '多少钱',
|
||||
sum_count: 513
|
||||
},
|
||||
{
|
||||
challenge_name: '韩寒',
|
||||
sum_count: 513
|
||||
},
|
||||
{
|
||||
challenge_name: '不知道',
|
||||
sum_count: 499
|
||||
},
|
||||
{
|
||||
challenge_name: '感觉',
|
||||
sum_count: 499
|
||||
},
|
||||
{
|
||||
challenge_name: '尹正',
|
||||
sum_count: 495
|
||||
},
|
||||
{
|
||||
challenge_name: '不看',
|
||||
sum_count: 487
|
||||
},
|
||||
{
|
||||
challenge_name: '奥特之父',
|
||||
sum_count: 484
|
||||
},
|
||||
{
|
||||
challenge_name: '阿姨',
|
||||
sum_count: 482
|
||||
},
|
||||
{
|
||||
challenge_name: '支持',
|
||||
sum_count: 482
|
||||
},
|
||||
{
|
||||
challenge_name: '父母',
|
||||
sum_count: 479
|
||||
},
|
||||
{
|
||||
challenge_name: '一条',
|
||||
sum_count: 462
|
||||
},
|
||||
{
|
||||
challenge_name: '女主',
|
||||
sum_count: 456
|
||||
},
|
||||
{
|
||||
challenge_name: '确实',
|
||||
sum_count: 456
|
||||
},
|
||||
{
|
||||
challenge_name: '票房',
|
||||
sum_count: 456
|
||||
},
|
||||
{
|
||||
challenge_name: '无语',
|
||||
sum_count: 443
|
||||
},
|
||||
{
|
||||
challenge_name: '干干净净',
|
||||
sum_count: 443
|
||||
},
|
||||
{
|
||||
challenge_name: '为啥',
|
||||
sum_count: 426
|
||||
},
|
||||
{
|
||||
challenge_name: '爱情',
|
||||
sum_count: 425
|
||||
},
|
||||
{
|
||||
challenge_name: '喜剧',
|
||||
sum_count: 422
|
||||
},
|
||||
{
|
||||
challenge_name: '春节',
|
||||
sum_count: 414
|
||||
},
|
||||
{
|
||||
challenge_name: '剧情',
|
||||
sum_count: 414
|
||||
},
|
||||
{
|
||||
challenge_name: '人生',
|
||||
sum_count: 409
|
||||
},
|
||||
{
|
||||
challenge_name: '风格',
|
||||
sum_count: 408
|
||||
},
|
||||
{
|
||||
challenge_name: '演员',
|
||||
sum_count: 403
|
||||
},
|
||||
{
|
||||
challenge_name: '成长',
|
||||
sum_count: 403
|
||||
},
|
||||
{
|
||||
challenge_name: '玩意',
|
||||
sum_count: 402
|
||||
},
|
||||
{
|
||||
challenge_name: '文学',
|
||||
sum_count: 397
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
export const circularProgressTickSpec: ICircularProgressChartSpec & { indicator: IIndicatorSpec } = {
|
||||
type: 'circularProgress',
|
||||
data: [
|
||||
{
|
||||
id: 'id0',
|
||||
values: [
|
||||
{
|
||||
type: 'Tradition Industries',
|
||||
value: 0.795,
|
||||
text: '79.5%'
|
||||
},
|
||||
{
|
||||
type: 'Business Companies',
|
||||
value: 0.5,
|
||||
text: '50%'
|
||||
},
|
||||
{
|
||||
type: 'Customer-facing Companies',
|
||||
value: 0.25,
|
||||
text: '25%'
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
color: ['rgb(255, 222, 0)', 'rgb(171, 205, 5)', 'rgb(0, 154, 68)'],
|
||||
valueField: 'value',
|
||||
categoryField: 'type',
|
||||
seriesField: 'type',
|
||||
radius: 0.8,
|
||||
innerRadius: 0.4,
|
||||
tickMask: {
|
||||
visible: true,
|
||||
angle: 10,
|
||||
offsetAngle: 0,
|
||||
forceAlign: true,
|
||||
style: {
|
||||
cornerRadius: 15
|
||||
}
|
||||
},
|
||||
axes: [
|
||||
{
|
||||
visible: false,
|
||||
type: 'linear',
|
||||
orient: 'angle'
|
||||
},
|
||||
{
|
||||
visible: false,
|
||||
type: 'band',
|
||||
orient: 'radius'
|
||||
}
|
||||
],
|
||||
indicator: {
|
||||
visible: true,
|
||||
trigger: 'hover',
|
||||
title: {
|
||||
visible: true,
|
||||
field: 'type',
|
||||
autoLimit: true,
|
||||
style: {
|
||||
fontSize: 20,
|
||||
fill: 'black'
|
||||
}
|
||||
},
|
||||
content: [
|
||||
{
|
||||
visible: true,
|
||||
field: 'text',
|
||||
style: {
|
||||
fontSize: 16,
|
||||
fill: 'gray'
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
legends: {
|
||||
visible: true,
|
||||
orient: 'bottom',
|
||||
title: {
|
||||
visible: false
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const liquidChartSmartInvertSpec: ILiquidChartSpec & { indicator: IIndicatorSpec } = {
|
||||
type: 'liquid',
|
||||
valueField: 'value',
|
||||
data: {
|
||||
id: 'data',
|
||||
values: [
|
||||
{
|
||||
value: 0.8
|
||||
}
|
||||
]
|
||||
},
|
||||
maskShape: 'drop', // 水滴
|
||||
// maskShape: 'circle',
|
||||
// maskShape: 'star',
|
||||
indicatorSmartInvert: true,
|
||||
indicator: {
|
||||
visible: true,
|
||||
title: {
|
||||
visible: true,
|
||||
style: {
|
||||
text: '进度'
|
||||
}
|
||||
},
|
||||
content: [
|
||||
{
|
||||
visible: true,
|
||||
style: {
|
||||
fill: 'black',
|
||||
text: '80%'
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
liquidBackground: {
|
||||
style: {
|
||||
fill: 'blue'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const goldenMedals: Record<number, any[]> = {
|
||||
2000: [
|
||||
{ country: 'USA', value: 37 },
|
||||
{ country: 'Russia', value: 32 },
|
||||
{ country: 'China', value: 28 },
|
||||
{ country: 'Australia', value: 16 },
|
||||
{ country: 'Germany', value: 13 },
|
||||
{ country: 'France', value: 13 },
|
||||
{ country: 'Italy', value: 13 },
|
||||
{ country: 'Netherlands', value: 12 },
|
||||
{ country: 'Cuba', value: 11 },
|
||||
{ country: 'U.K.', value: 11 }
|
||||
],
|
||||
2004: [
|
||||
{ country: 'USA', value: 36 },
|
||||
{ country: 'China', value: 32 },
|
||||
{ country: 'Russia', value: 28 },
|
||||
{ country: 'Australia', value: 17 },
|
||||
{ country: 'Japan', value: 16 },
|
||||
{ country: 'Germany', value: 13 },
|
||||
{ country: 'France', value: 11 },
|
||||
{ country: 'Italy', value: 10 },
|
||||
{ country: 'South Korea', value: 9 },
|
||||
{ country: 'U.K.', value: 9 }
|
||||
],
|
||||
2008: [
|
||||
{ country: 'China', value: 48 },
|
||||
{ country: 'USA', value: 36 },
|
||||
{ country: 'Russia', value: 24 },
|
||||
{ country: 'U.K.', value: 19 },
|
||||
{ country: 'Germany', value: 16 },
|
||||
{ country: 'Australia', value: 14 },
|
||||
{ country: 'South Korea', value: 13 },
|
||||
{ country: 'Japan', value: 9 },
|
||||
{ country: 'Italy', value: 8 },
|
||||
{ country: 'France', value: 7 }
|
||||
],
|
||||
2012: [
|
||||
{ country: 'USA', value: 46 },
|
||||
{ country: 'China', value: 39 },
|
||||
{ country: 'U.K.', value: 29 },
|
||||
{ country: 'Russia', value: 19 },
|
||||
{ country: 'South Korea', value: 13 },
|
||||
{ country: 'Germany', value: 11 },
|
||||
{ country: 'France', value: 11 },
|
||||
{ country: 'Australia', value: 8 },
|
||||
{ country: 'Italy', value: 8 },
|
||||
{ country: 'Hungary', value: 8 }
|
||||
],
|
||||
2016: [
|
||||
{ country: 'USA', value: 46 },
|
||||
{ country: 'U.K.', value: 27 },
|
||||
{ country: 'China', value: 26 },
|
||||
{ country: 'Russia', value: 19 },
|
||||
{ country: 'Germany', value: 17 },
|
||||
{ country: 'Japan', value: 12 },
|
||||
{ country: 'France', value: 10 },
|
||||
{ country: 'South Korea', value: 9 },
|
||||
{ country: 'Italy', value: 8 },
|
||||
{ country: 'Australia', value: 8 }
|
||||
],
|
||||
2020: [
|
||||
{ country: 'USA', value: 39 },
|
||||
{ country: 'China', value: 38 },
|
||||
{ country: 'Japan', value: 27 },
|
||||
{ country: 'U.K.', value: 22 },
|
||||
{ country: 'Russian Olympic Committee', value: 20 },
|
||||
{ country: 'Australia', value: 17 },
|
||||
{ country: 'Netherlands', value: 10 },
|
||||
{ country: 'France', value: 10 },
|
||||
{ country: 'Germany', value: 10 },
|
||||
{ country: 'Italy', value: 10 }
|
||||
]
|
||||
};
|
||||
|
||||
const colors = {
|
||||
China: '#d62728',
|
||||
USA: '#1664FF',
|
||||
Russia: '#B2CFFF',
|
||||
'U.K.': '#1AC6FF',
|
||||
Australia: '#94EFFF',
|
||||
Japan: '#FF8A00',
|
||||
Cuba: '#FFCE7A',
|
||||
Germany: '#3CC780',
|
||||
France: '#B9EDCD',
|
||||
Italy: '#7442D4',
|
||||
'South Korea': '#DDC5FA',
|
||||
'Russian Olympic Committee': '#B2CFFF',
|
||||
Netherlands: '#FFC400',
|
||||
Hungary: '#FAE878'
|
||||
};
|
||||
|
||||
const dataSpecs = Object.keys(goldenMedals).map(year => {
|
||||
return {
|
||||
data: [
|
||||
{
|
||||
id: 'id',
|
||||
values: (goldenMedals[year as unknown as number] as any)
|
||||
.sort((a: any, b: any) => b.value - a.value)
|
||||
.map((v: any) => {
|
||||
return { ...v, fill: (colors as any)[v.country] };
|
||||
})
|
||||
},
|
||||
{
|
||||
id: 'year',
|
||||
values: [{ year }]
|
||||
}
|
||||
]
|
||||
};
|
||||
});
|
||||
const duration = 1000;
|
||||
const exchangeDuration = 600;
|
||||
|
||||
export const rankingBarSpec: IBarChartSpec = {
|
||||
type: 'bar',
|
||||
padding: {
|
||||
top: 12,
|
||||
right: 100,
|
||||
bottom: 12
|
||||
},
|
||||
data: dataSpecs[0].data,
|
||||
direction: 'horizontal',
|
||||
yField: 'country',
|
||||
xField: 'value',
|
||||
seriesField: 'country',
|
||||
bar: {
|
||||
style: {
|
||||
fill: (datum: any) => datum.fill
|
||||
}
|
||||
},
|
||||
axes: [
|
||||
{
|
||||
animation: true,
|
||||
orient: 'bottom',
|
||||
type: 'linear',
|
||||
visible: true,
|
||||
max: 50,
|
||||
grid: {
|
||||
visible: true
|
||||
}
|
||||
},
|
||||
{
|
||||
animation: true,
|
||||
id: 'axis-left',
|
||||
orient: 'left',
|
||||
width: 130,
|
||||
tick: { visible: false },
|
||||
label: { visible: true },
|
||||
type: 'band'
|
||||
}
|
||||
],
|
||||
title: {
|
||||
visible: true,
|
||||
text: 'Top 10 Olympic Gold Medals by Country Since 2000'
|
||||
},
|
||||
animationUpdate: {
|
||||
bar: [
|
||||
{
|
||||
type: 'update',
|
||||
options: { excludeChannels: ['y'] },
|
||||
easing: 'linear',
|
||||
duration
|
||||
},
|
||||
{
|
||||
channel: ['y'],
|
||||
easing: 'circInOut',
|
||||
duration: exchangeDuration
|
||||
}
|
||||
],
|
||||
axis: {
|
||||
duration: exchangeDuration,
|
||||
easing: 'circInOut'
|
||||
}
|
||||
} as Record<string, IAnimationConfig>,
|
||||
animationEnter: {
|
||||
bar: [
|
||||
{
|
||||
type: 'moveIn',
|
||||
duration: exchangeDuration,
|
||||
easing: 'circInOut',
|
||||
options: {
|
||||
direction: 'y',
|
||||
orient: 'negative'
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
animationExit: {
|
||||
bar: [
|
||||
{
|
||||
type: 'fadeOut',
|
||||
duration: exchangeDuration
|
||||
}
|
||||
]
|
||||
},
|
||||
customMark: [
|
||||
{
|
||||
type: 'text',
|
||||
dataId: 'year',
|
||||
style: {
|
||||
textBaseline: 'bottom',
|
||||
fontSize: 200,
|
||||
textAlign: 'right',
|
||||
fontFamily: 'PingFang SC',
|
||||
fontWeight: 600,
|
||||
text: (datum: any) => datum.year,
|
||||
x: (_datum: any, ctx: any) => {
|
||||
return ctx.vchart.getChart().getCanvasRect()?.width - 50;
|
||||
},
|
||||
y: (_datum: any, ctx: any) => {
|
||||
return ctx.vchart.getChart().getCanvasRect()?.height - 50;
|
||||
},
|
||||
fill: 'grey',
|
||||
fillOpacity: 0.5
|
||||
}
|
||||
}
|
||||
],
|
||||
player: {
|
||||
type: 'continuous',
|
||||
orient: 'bottom',
|
||||
auto: true,
|
||||
loop: true,
|
||||
dx: 80,
|
||||
position: 'middle',
|
||||
interval: duration,
|
||||
specs: dataSpecs,
|
||||
slider: {
|
||||
railStyle: {
|
||||
height: 6
|
||||
}
|
||||
},
|
||||
controller: {
|
||||
backward: {
|
||||
style: {
|
||||
size: 12
|
||||
}
|
||||
},
|
||||
forward: {
|
||||
style: {
|
||||
size: 12
|
||||
}
|
||||
},
|
||||
start: {
|
||||
order: 1,
|
||||
position: 'end'
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const stackedDashAreaSpec: IAreaChartSpec = {
|
||||
type: 'area',
|
||||
data: {
|
||||
values: [
|
||||
{ month: 'Jan', country: 'Africa', value: 4229 },
|
||||
{ month: 'Jan', country: 'EU', value: 4376 },
|
||||
{ month: 'Jan', country: 'China', value: 3054 },
|
||||
{ month: 'Jan', country: 'USA', value: 12814 },
|
||||
{ month: 'Feb', country: 'Africa', value: 3932 },
|
||||
{ month: 'Feb', country: 'EU', value: 3987 },
|
||||
{ month: 'Feb', country: 'China', value: 5067 },
|
||||
{ month: 'Feb', country: 'USA', value: 13012 },
|
||||
{ month: 'Mar', country: 'Africa', value: 5221 },
|
||||
{ month: 'Mar', country: 'EU', value: 3574 },
|
||||
{ month: 'Mar', country: 'China', value: 7004 },
|
||||
{ month: 'Mar', country: 'USA', value: 11624 },
|
||||
{ month: 'Apr', country: 'Africa', value: 9256 },
|
||||
{ month: 'Apr', country: 'EU', value: 4376 },
|
||||
{ month: 'Apr', country: 'China', value: 9054 },
|
||||
{ month: 'Apr', country: 'USA', value: 8814 },
|
||||
{ month: 'May', country: 'Africa', value: 3308 },
|
||||
{ month: 'May', country: 'EU', value: 4572 },
|
||||
{ month: 'May', country: 'China', value: 12043 },
|
||||
{ month: 'May', country: 'USA', value: 12998 },
|
||||
{ month: 'Jun', country: 'Africa', value: 5432 },
|
||||
{ month: 'Jun', country: 'EU', value: 3417 },
|
||||
{ month: 'Jun', country: 'China', value: 15067 },
|
||||
{ month: 'Jun', country: 'USA', value: 12321 },
|
||||
{ month: 'Jul', country: 'Africa', value: 13701 },
|
||||
{ month: 'Jul', country: 'EU', value: 5231 },
|
||||
{ month: 'Jul', country: 'China', value: 10119 },
|
||||
{ month: 'Jul', country: 'USA', value: 10342 },
|
||||
{ month: 'Aug', country: 'Africa', value: 4008, forecast: true },
|
||||
{ month: 'Aug', country: 'EU', value: 4572, forecast: true },
|
||||
{ month: 'Aug', country: 'China', value: 12043, forecast: true },
|
||||
{ month: 'Aug', country: 'USA', value: 22998, forecast: true },
|
||||
{ month: 'Sept', country: 'Africa', value: 18712, forecast: true },
|
||||
{ month: 'Sept', country: 'EU', value: 6134, forecast: true },
|
||||
{ month: 'Sept', country: 'China', value: 10419, forecast: true },
|
||||
{ month: 'Sept', country: 'USA', value: 11261, forecast: true }
|
||||
]
|
||||
},
|
||||
stack: true,
|
||||
xField: 'month',
|
||||
yField: 'value',
|
||||
seriesField: 'country',
|
||||
point: {
|
||||
style: {
|
||||
size: 0
|
||||
},
|
||||
state: {
|
||||
dimension_hover: {
|
||||
size: 10,
|
||||
outerBorder: {
|
||||
distance: 0,
|
||||
lineWidth: 6,
|
||||
strokeOpacity: 0.2
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
line: {
|
||||
style: {
|
||||
// Configure the lineDash attribute based on the forecast field value of the data
|
||||
lineDash: (data: any) => {
|
||||
if (data.forecast) {
|
||||
return [5, 5];
|
||||
}
|
||||
return [0];
|
||||
}
|
||||
}
|
||||
},
|
||||
area: {
|
||||
style: {
|
||||
fillOpacity: 0.5,
|
||||
textureColor: '#fff',
|
||||
textureSize: 14,
|
||||
// Configure the texture attribute based on the forecast field value of the data
|
||||
texture: (data: any) => {
|
||||
if (data.forecast) {
|
||||
return 'bias-rl';
|
||||
}
|
||||
return '';
|
||||
}
|
||||
}
|
||||
},
|
||||
legends: [{ visible: true, position: 'middle', orient: 'bottom' }],
|
||||
crosshair: {
|
||||
xField: {
|
||||
visible: true,
|
||||
line: {
|
||||
type: 'line'
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const barMarkPointSpec: IBarChartSpec = {
|
||||
type: 'bar',
|
||||
height: 300,
|
||||
data: [
|
||||
{
|
||||
id: 'barData',
|
||||
values: [
|
||||
{ time: '10:20', cost: 2 },
|
||||
{ time: '10:30', cost: 1 },
|
||||
{ time: '10:40', cost: 1 },
|
||||
{ time: '10:50', cost: 2 },
|
||||
{ time: '11:00', cost: 2 },
|
||||
{ time: '11:10', cost: 2 },
|
||||
{ time: '11:20', cost: 1 },
|
||||
{ time: '11:30', cost: 1 },
|
||||
{ time: '11:40', cost: 2 },
|
||||
{ time: '11:50', cost: 1 }
|
||||
]
|
||||
}
|
||||
],
|
||||
xField: 'time',
|
||||
yField: 'cost',
|
||||
crosshair: {
|
||||
xField: {
|
||||
visible: true,
|
||||
line: {
|
||||
type: 'rect',
|
||||
style: {
|
||||
fill: 'rgb(85,208,93)',
|
||||
fillOpacity: 0.1
|
||||
}
|
||||
},
|
||||
bindingAxesIndex: [1],
|
||||
defaultSelect: {
|
||||
axisIndex: 1,
|
||||
datum: '10:20'
|
||||
}
|
||||
}
|
||||
},
|
||||
label: {
|
||||
visible: true,
|
||||
animation: false,
|
||||
formatMethod: (datum: any) => `${datum}分钟`,
|
||||
style: {
|
||||
fill: 'rgb(155,155,155)'
|
||||
}
|
||||
},
|
||||
bar: {
|
||||
style: {
|
||||
fill: 'rgb(85,208,93)',
|
||||
cornerRadius: [4, 4, 0, 0],
|
||||
width: 30
|
||||
}
|
||||
},
|
||||
markPoint: [
|
||||
{
|
||||
coordinate: {
|
||||
time: '10:20',
|
||||
cost: 2
|
||||
},
|
||||
itemContent: {
|
||||
type: 'text',
|
||||
// autoRotate: false,
|
||||
offsetY: -10,
|
||||
text: {
|
||||
dy: 14,
|
||||
text: '2分钟',
|
||||
style: {
|
||||
fill: 'white',
|
||||
fontSize: 14
|
||||
},
|
||||
labelBackground: {
|
||||
padding: [5, 10, 5, 10],
|
||||
style: {
|
||||
fill: '#000',
|
||||
cornerRadius: 5
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
itemLine: {
|
||||
endSymbol: {
|
||||
visible: true,
|
||||
style: {
|
||||
angle: Math.PI,
|
||||
scaleY: 0.4,
|
||||
fill: '#000',
|
||||
dy: 4,
|
||||
stroke: '#000'
|
||||
}
|
||||
},
|
||||
startSymbol: { visible: false },
|
||||
line: {
|
||||
style: {
|
||||
visible: false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
animationUpdate: false,
|
||||
axes: [
|
||||
{
|
||||
orient: 'left',
|
||||
max: 10,
|
||||
label: { visible: false },
|
||||
grid: {
|
||||
style: { lineDash: [4, 4] }
|
||||
}
|
||||
},
|
||||
{
|
||||
orient: 'bottom',
|
||||
label: {
|
||||
formatMethod: (datum: any) => {
|
||||
return datum === '10:20' ? '当前' : datum;
|
||||
},
|
||||
style: (datum: any) => {
|
||||
return {
|
||||
fontSize: datum === '10:20' ? 14 : 12,
|
||||
fill: datum === '10:20' ? 'black' : 'grey'
|
||||
};
|
||||
}
|
||||
},
|
||||
paddingOuter: 0.5,
|
||||
paddingInner: 0,
|
||||
grid: {
|
||||
visible: true,
|
||||
alignWithLabel: false,
|
||||
style: { lineDash: [4, 4] }
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
export const histogramDifferentBinSpec: IHistogramChartSpec = {
|
||||
type: 'histogram',
|
||||
xField: 'from',
|
||||
x2Field: 'to',
|
||||
yField: 'profit',
|
||||
seriesField: 'type',
|
||||
bar: {
|
||||
style: {
|
||||
stroke: 'white',
|
||||
lineWidth: 1
|
||||
}
|
||||
},
|
||||
title: {
|
||||
text: 'Profit',
|
||||
textStyle: {
|
||||
align: 'center',
|
||||
height: 50,
|
||||
lineWidth: 3,
|
||||
fill: '#333',
|
||||
fontSize: 25,
|
||||
fontFamily: 'Times New Roman'
|
||||
}
|
||||
},
|
||||
tooltip: {
|
||||
visible: true,
|
||||
mark: {
|
||||
title: {
|
||||
key: 'title',
|
||||
value: 'profit'
|
||||
},
|
||||
content: [
|
||||
{
|
||||
key: (datum?: Record<string, any>) => `${datum?.from}~${datum?.to}`,
|
||||
value: (datum?: Record<string, any>) => datum?.profit
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
axes: [
|
||||
{
|
||||
orient: 'bottom',
|
||||
nice: false
|
||||
}
|
||||
],
|
||||
data: [
|
||||
{
|
||||
name: 'data1',
|
||||
values: [
|
||||
{
|
||||
from: 0,
|
||||
to: 10,
|
||||
profit: 2,
|
||||
type: 'A'
|
||||
},
|
||||
{
|
||||
from: 10,
|
||||
to: 16,
|
||||
profit: 3,
|
||||
type: 'B'
|
||||
},
|
||||
{
|
||||
from: 16,
|
||||
to: 18,
|
||||
profit: 15,
|
||||
type: 'C'
|
||||
},
|
||||
{
|
||||
from: 18,
|
||||
to: 26,
|
||||
profit: 12,
|
||||
type: 'D'
|
||||
},
|
||||
{
|
||||
from: 26,
|
||||
to: 32,
|
||||
profit: 22,
|
||||
type: 'E'
|
||||
},
|
||||
{
|
||||
from: 32,
|
||||
to: 56,
|
||||
profit: 7,
|
||||
type: 'F'
|
||||
},
|
||||
{
|
||||
from: 56,
|
||||
to: 62,
|
||||
profit: 17,
|
||||
type: 'G'
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
51
src/views/plugin/charts/vchart/index.vue
Normal file
51
src/views/plugin/charts/vchart/index.vue
Normal file
@@ -0,0 +1,51 @@
|
||||
<script setup lang="ts">
|
||||
import { useVChart } from '@/hooks/common/vchart';
|
||||
import {
|
||||
barMarkPointSpec,
|
||||
circularProgressTickSpec,
|
||||
histogramDifferentBinSpec,
|
||||
liquidChartSmartInvertSpec,
|
||||
rankingBarSpec,
|
||||
shapeWordCloudSpec,
|
||||
stackedDashAreaSpec
|
||||
} from './data';
|
||||
|
||||
const { domRef: stackedDashAreaRef } = useVChart(() => stackedDashAreaSpec);
|
||||
const { domRef: barMarkPointRef } = useVChart(() => barMarkPointSpec);
|
||||
const { domRef: histogramDifferentBinRef } = useVChart(() => histogramDifferentBinSpec);
|
||||
const { domRef: rankingBarRef } = useVChart(() => rankingBarSpec);
|
||||
const { domRef: shapeWordCloudRef } = useVChart(() => shapeWordCloudSpec);
|
||||
const { domRef: circularProgressTickRef } = useVChart(() => circularProgressTickSpec);
|
||||
const { domRef: liquidChartSmartInvertRef } = useVChart(() => liquidChartSmartInvertSpec);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ElSpace direction="vertical" fill :size="16">
|
||||
<ElCard header="VChart" class="h-full card-wrapper">
|
||||
<WebSiteLink label="More Demos: " link="https://www.visactor.com/vchart/example" />
|
||||
</ElCard>
|
||||
<ElCard header="Stacked Dash Area Chart" class="h-full card-wrapper">
|
||||
<div ref="stackedDashAreaRef" class="h-400px" />
|
||||
</ElCard>
|
||||
<ElCard header="Bar Mark Point Chart" class="h-full card-wrapper">
|
||||
<div ref="barMarkPointRef" class="h-400px" />
|
||||
</ElCard>
|
||||
<ElCard header="Histogram Different Bin Chart" class="h-full card-wrapper">
|
||||
<div ref="histogramDifferentBinRef" class="h-400px" />
|
||||
</ElCard>
|
||||
<ElCard header="Ranking Bar Chart" class="h-full card-wrapper">
|
||||
<div ref="rankingBarRef" class="h-400px" />
|
||||
</ElCard>
|
||||
<ElCard header="Circular Progress Tick Chart" class="h-full card-wrapper">
|
||||
<div ref="circularProgressTickRef" class="h-400px" />
|
||||
</ElCard>
|
||||
<ElCard header="Liquid Chart Smart Invert Chart" class="h-full card-wrapper">
|
||||
<div ref="liquidChartSmartInvertRef" class="h-400px" />
|
||||
</ElCard>
|
||||
<ElCard header="Shape Word Cloud Chart" class="h-full card-wrapper">
|
||||
<div ref="shapeWordCloudRef" class="h-400px" />
|
||||
</ElCard>
|
||||
</ElSpace>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
37
src/views/plugin/copy/index.vue
Normal file
37
src/views/plugin/copy/index.vue
Normal file
@@ -0,0 +1,37 @@
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import { useClipboard } from '@vueuse/core';
|
||||
|
||||
defineOptions({ name: 'CopyPage' });
|
||||
|
||||
const { copy, isSupported } = useClipboard();
|
||||
|
||||
const source = ref('');
|
||||
|
||||
async function handleCopy() {
|
||||
if (!isSupported) {
|
||||
window.$message?.error('您的浏览器不支持Clipboard API');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!source.value) {
|
||||
window.$message?.error('请输入要复制的内容');
|
||||
return;
|
||||
}
|
||||
|
||||
await copy(source.value);
|
||||
window.$message?.success(`复制成功:${source.value}`);
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="h-full">
|
||||
<ElCard header="文本复制" class="h-full card-wrapper">
|
||||
<ElInput v-model="source" placeholder="请输入要复制的内容吧">
|
||||
<template #append>
|
||||
<ElButton type="primary" @click="handleCopy">复制</ElButton>
|
||||
</template>
|
||||
</ElInput>
|
||||
</ElCard>
|
||||
</div>
|
||||
</template>
|
||||
52
src/views/plugin/editor/markdown/index.vue
Normal file
52
src/views/plugin/editor/markdown/index.vue
Normal file
@@ -0,0 +1,52 @@
|
||||
<script setup lang="ts">
|
||||
import { onMounted, onUnmounted, ref, watch } from 'vue';
|
||||
import Vditor from 'vditor';
|
||||
import 'vditor/dist/index.css';
|
||||
import { useThemeStore } from '@/store/modules/theme';
|
||||
|
||||
defineOptions({ name: 'MarkdownPage' });
|
||||
|
||||
const theme = useThemeStore();
|
||||
|
||||
const vditor = ref<Vditor>();
|
||||
const domRef = ref<HTMLElement>();
|
||||
|
||||
function renderVditor() {
|
||||
if (!domRef.value) return;
|
||||
vditor.value = new Vditor(domRef.value, {
|
||||
minHeight: 400,
|
||||
theme: theme.darkMode ? 'dark' : 'classic',
|
||||
icon: 'material',
|
||||
cache: { enable: false }
|
||||
});
|
||||
}
|
||||
|
||||
const stopHandle = watch(
|
||||
() => theme.darkMode,
|
||||
newValue => {
|
||||
const themeMode = newValue ? 'dark' : 'classic';
|
||||
vditor.value?.setTheme(themeMode);
|
||||
}
|
||||
);
|
||||
|
||||
onMounted(() => {
|
||||
renderVditor();
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
stopHandle();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="h-full">
|
||||
<ElCard header="markdown插件" class="card-wrapper">
|
||||
<div ref="domRef"></div>
|
||||
<template #footer>
|
||||
<GithubLink link="https://github.com/Vanessa219/vditor" />
|
||||
</template>
|
||||
</ElCard>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
47
src/views/plugin/editor/quill/index.vue
Normal file
47
src/views/plugin/editor/quill/index.vue
Normal file
@@ -0,0 +1,47 @@
|
||||
<script setup lang="ts">
|
||||
import { onMounted, ref } from 'vue';
|
||||
import WangEditor from 'wangeditor';
|
||||
|
||||
defineOptions({ name: 'QuillPage' });
|
||||
|
||||
const editor = ref<WangEditor>();
|
||||
const domRef = ref<HTMLElement>();
|
||||
|
||||
function renderWangEditor() {
|
||||
editor.value = new WangEditor(domRef.value);
|
||||
setEditorConfig();
|
||||
editor.value.create();
|
||||
}
|
||||
|
||||
function setEditorConfig() {
|
||||
if (editor.value?.config?.zIndex) {
|
||||
editor.value.config.zIndex = 10;
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
renderWangEditor();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="h-full">
|
||||
<ElCard header="富文本插件" class="card-wrapper">
|
||||
<div ref="domRef" class="bg-white dark:bg-dark"></div>
|
||||
<template #footer>
|
||||
<GithubLink link="https://github.com/wangeditor-team/wangEditor" />
|
||||
</template>
|
||||
</ElCard>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
:deep(.w-e-toolbar) {
|
||||
background: inherit !important;
|
||||
border-color: var(--el-border-color) !important;
|
||||
}
|
||||
:deep(.w-e-text-container) {
|
||||
background: inherit;
|
||||
border-color: var(--el-border-color) !important;
|
||||
}
|
||||
</style>
|
||||
171
src/views/plugin/excel/index.vue
Normal file
171
src/views/plugin/excel/index.vue
Normal file
@@ -0,0 +1,171 @@
|
||||
<script setup lang="tsx">
|
||||
import { reactive } from 'vue';
|
||||
import { ElButton, ElTag } from 'element-plus';
|
||||
import type { FlatResponseData } from '@sa/axios';
|
||||
import { utils, writeFile } from 'xlsx';
|
||||
import { commonStatusRecord, userGenderRecord } from '@/constants/business';
|
||||
import { fetchGetUserList } from '@/service/api';
|
||||
import { useUIPaginatedTable } from '@/hooks/common/table';
|
||||
import { $t } from '@/locales';
|
||||
|
||||
defineOptions({ name: 'ExcelPage' });
|
||||
|
||||
const searchParams: Api.SystemManage.UserSearchParams = reactive({
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
status: undefined,
|
||||
username: undefined,
|
||||
mobile: undefined,
|
||||
deptId: undefined,
|
||||
roleId: undefined
|
||||
});
|
||||
|
||||
const { columns, data, loading } = useUIPaginatedTable<
|
||||
FlatResponseData<any, Api.SystemManage.UserList>,
|
||||
Api.SystemManage.User
|
||||
>({
|
||||
api: () => fetchGetUserList(searchParams),
|
||||
transform: response => {
|
||||
if (!response.error) {
|
||||
return {
|
||||
data: response.data.list,
|
||||
pageNum: searchParams.pageNo ?? 1,
|
||||
pageSize: searchParams.pageSize ?? 10,
|
||||
total: response.data.total
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
data: [],
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
total: 0
|
||||
};
|
||||
},
|
||||
onPaginationParamsChange: params => {
|
||||
searchParams.pageNo = params.currentPage;
|
||||
searchParams.pageSize = params.pageSize;
|
||||
},
|
||||
columns: () => [
|
||||
{ type: 'selection', width: 48 },
|
||||
{ type: 'index', label: $t('common.index'), width: 64 },
|
||||
{ prop: 'username', label: $t('page.system.user.userName'), minWidth: 100 },
|
||||
{
|
||||
prop: 'sex',
|
||||
label: $t('page.system.user.userGender'),
|
||||
width: 100,
|
||||
formatter: row => {
|
||||
const tagMap: Record<Api.SystemManage.UserGender, UI.ThemeColor> = {
|
||||
0: 'info',
|
||||
1: 'primary',
|
||||
2: 'danger'
|
||||
};
|
||||
const value = row.sex ?? 0;
|
||||
|
||||
const label = $t(userGenderRecord[value]);
|
||||
|
||||
return <ElTag type={tagMap[value]}>{label}</ElTag>;
|
||||
}
|
||||
},
|
||||
{ prop: 'nickname', label: $t('page.system.user.nickName'), minWidth: 100 },
|
||||
{ prop: 'mobile', label: $t('page.system.user.userPhone'), width: 120 },
|
||||
{ prop: 'email', label: $t('page.system.user.userEmail'), minWidth: 200 },
|
||||
{
|
||||
prop: 'status',
|
||||
label: $t('page.system.user.userStatus'),
|
||||
width: 100,
|
||||
formatter: row => {
|
||||
const tagMap: Record<Api.SystemManage.CommonStatus, UI.ThemeColor> = {
|
||||
0: 'success',
|
||||
1: 'warning'
|
||||
};
|
||||
|
||||
const label = $t(commonStatusRecord[row.status]);
|
||||
|
||||
return <ElTag type={tagMap[row.status]}>{label}</ElTag>;
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
function exportExcel() {
|
||||
const exportColumns = columns.value.slice(2);
|
||||
|
||||
const excelList = data.value.map(item => exportColumns.map(col => getTableValue(col, item)));
|
||||
|
||||
const titleList = exportColumns.map(col => (isTableColumnHasTitle(col) && col.label) || undefined);
|
||||
|
||||
excelList.unshift(titleList);
|
||||
|
||||
const workBook = utils.book_new();
|
||||
|
||||
const workSheet = utils.aoa_to_sheet(excelList);
|
||||
|
||||
workSheet['!cols'] = exportColumns.map(item => ({
|
||||
width: Math.round(Number(item.width) / 10 || 20)
|
||||
}));
|
||||
|
||||
utils.book_append_sheet(workBook, workSheet, '用户列表');
|
||||
|
||||
writeFile(workBook, '用户数据.xlsx');
|
||||
}
|
||||
|
||||
function getTableValue(col: UI.TableColumn<Api.SystemManage.User>, item: Api.SystemManage.User) {
|
||||
if (!isTableColumnHasKey(col)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const { prop } = col;
|
||||
|
||||
if (prop === 'operate' || prop === undefined) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if (prop === 'status') {
|
||||
return $t(commonStatusRecord[item.status]);
|
||||
}
|
||||
|
||||
if (prop === 'sex') {
|
||||
return $t(userGenderRecord[item.sex ?? 0]);
|
||||
}
|
||||
|
||||
if (prop in item) {
|
||||
return item[prop as keyof Api.SystemManage.User];
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
function isTableColumnHasKey<T>(column: UI.TableColumn<T>): boolean {
|
||||
return Boolean((column as UI.TableColumnWithKey<T>).prop);
|
||||
}
|
||||
|
||||
function isTableColumnHasTitle<T>(column: UI.TableColumn<T>): boolean {
|
||||
return Boolean((column as UI.TableColumnWithKey<T>).label);
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="min-h-500px flex-col-stretch gap-16px overflow-hidden lt-sm:overflow-auto">
|
||||
<ElCard class="card-wrapper sm:flex-1-hidden">
|
||||
<template #header>
|
||||
<div class="flex items-center justify-between">
|
||||
<p>Excel导出</p>
|
||||
<ElButton plain type="primary" @click="exportExcel">
|
||||
<template #icon>
|
||||
<icon-file-icons:microsoft-excel class="text-icon" />
|
||||
</template>
|
||||
导出excel
|
||||
</ElButton>
|
||||
</div>
|
||||
</template>
|
||||
<div class="h-[calc(100%-50px)]">
|
||||
<ElTable v-loading="loading" height="100%" border class="sm:h-full" :data="data" row-key="id">
|
||||
<ElTableColumn v-for="col in columns" :key="col.prop" v-bind="col" />
|
||||
</ElTable>
|
||||
</div>
|
||||
</ElCard>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
173
src/views/plugin/gantt/dhtmlx/data.ts
Normal file
173
src/views/plugin/gantt/dhtmlx/data.ts
Normal file
@@ -0,0 +1,173 @@
|
||||
import type { Task } from 'dhtmlx-gantt';
|
||||
|
||||
export const ganttTasks: Task[] = [
|
||||
{
|
||||
id: 11,
|
||||
text: 'CN-RDMS 架构设计',
|
||||
type: 'project',
|
||||
progress: 0,
|
||||
open: true,
|
||||
start_date: new Date('2024-01-10 00:00'),
|
||||
duration: 12,
|
||||
parent: 0
|
||||
},
|
||||
{
|
||||
id: 12,
|
||||
text: '测试版本',
|
||||
start_date: new Date('2024-03-20 00:00'),
|
||||
type: 'project',
|
||||
duration: 5,
|
||||
render: 'split',
|
||||
parent: '11',
|
||||
progress: 0,
|
||||
open: true
|
||||
},
|
||||
{
|
||||
id: 99,
|
||||
text: '测试版本1 发布',
|
||||
start_date: new Date('2024-03-20 00:00'),
|
||||
end_date: new Date('2024-03-25 00:00'),
|
||||
parent: '12',
|
||||
progress: 0,
|
||||
open: true
|
||||
},
|
||||
{
|
||||
id: 98,
|
||||
text: '测试版本2 发布',
|
||||
start_date: new Date('2024-03-26 00:00'),
|
||||
duration: 4,
|
||||
parent: '12',
|
||||
progress: 0,
|
||||
open: true
|
||||
},
|
||||
{
|
||||
id: 97,
|
||||
text: '测试版本3 发布',
|
||||
start_date: new Date('2024-03-31 00:00'),
|
||||
duration: 10,
|
||||
parent: '12',
|
||||
progress: 0,
|
||||
open: true
|
||||
},
|
||||
{
|
||||
id: 13,
|
||||
text: '1.0 版本',
|
||||
start_date: new Date('2024-03-31 00:00'),
|
||||
type: 'project',
|
||||
render: 'split',
|
||||
parent: '11',
|
||||
progress: 0.5,
|
||||
open: false,
|
||||
duration: 11
|
||||
},
|
||||
{
|
||||
id: 17,
|
||||
text: '1.0正式发布',
|
||||
start_date: new Date('2024-03-31 00:00'),
|
||||
end_date: new Date('2024-04-03 00:00'),
|
||||
parent: '13',
|
||||
progress: 0,
|
||||
open: true
|
||||
},
|
||||
{
|
||||
id: 18,
|
||||
text: '1.0.1 版本',
|
||||
start_date: new Date('2024-04-03 00:00'),
|
||||
duration: 5,
|
||||
parent: '13',
|
||||
progress: 0,
|
||||
open: true
|
||||
},
|
||||
{
|
||||
id: 19,
|
||||
text: '1.0.2 版本',
|
||||
start_date: new Date('2024-04-08 00:00'),
|
||||
duration: 6,
|
||||
parent: '13',
|
||||
progress: 0,
|
||||
open: true
|
||||
},
|
||||
{
|
||||
id: 20,
|
||||
text: '1.0.3 版本',
|
||||
start_date: new Date('2024-04-16 00:00'),
|
||||
duration: 8,
|
||||
parent: '13',
|
||||
progress: 0,
|
||||
open: true
|
||||
},
|
||||
{
|
||||
id: 31,
|
||||
text: '1.0.4 版本',
|
||||
start_date: new Date('2024-04-17 00:00'),
|
||||
duration: 8,
|
||||
parent: '13',
|
||||
progress: 0,
|
||||
open: true
|
||||
},
|
||||
{
|
||||
id: 32,
|
||||
text: '1.0.5 版本',
|
||||
start_date: new Date('2024-04-26 00:00'),
|
||||
duration: 9,
|
||||
parent: '13',
|
||||
progress: 0,
|
||||
open: true
|
||||
},
|
||||
{
|
||||
id: 33,
|
||||
text: '1.0.9 版本',
|
||||
start_date: new Date('2024-05-05 00:00'),
|
||||
duration: 2,
|
||||
parent: '13',
|
||||
progress: 0,
|
||||
open: true
|
||||
},
|
||||
{
|
||||
id: 14,
|
||||
text: '1.1 版本',
|
||||
start_date: new Date('2024-05-07 00:00'),
|
||||
duration: 30,
|
||||
parent: '11',
|
||||
progress: 0,
|
||||
open: true
|
||||
},
|
||||
{
|
||||
id: 15,
|
||||
text: '1.2 版本',
|
||||
start_date: new Date('2024-06-06 00:00'),
|
||||
duration: 46,
|
||||
parent: '11',
|
||||
progress: 0,
|
||||
open: true
|
||||
},
|
||||
{
|
||||
id: 16,
|
||||
text: '1.3版本',
|
||||
type: 'project',
|
||||
render: 'split',
|
||||
parent: '11',
|
||||
progress: 0,
|
||||
open: true,
|
||||
start_date: new Date('2024-07-22 00:00'),
|
||||
duration: 11
|
||||
},
|
||||
{
|
||||
id: 21,
|
||||
text: '1.3.1版本',
|
||||
start_date: new Date('2024-07-22 00:00'),
|
||||
duration: 7,
|
||||
parent: '16',
|
||||
progress: 0,
|
||||
open: true
|
||||
},
|
||||
{
|
||||
id: 22,
|
||||
text: '1.3.2版本',
|
||||
start_date: new Date('2024-07-29 00:00'),
|
||||
duration: 7,
|
||||
parent: '16',
|
||||
progress: 0,
|
||||
open: true
|
||||
}
|
||||
];
|
||||
169
src/views/plugin/gantt/dhtmlx/index.vue
Normal file
169
src/views/plugin/gantt/dhtmlx/index.vue
Normal file
@@ -0,0 +1,169 @@
|
||||
<script setup lang="tsx">
|
||||
import { onMounted, shallowRef } from 'vue';
|
||||
import { gantt } from 'dhtmlx-gantt';
|
||||
import type { GanttConfigOptions, ZoomLevel } from 'dhtmlx-gantt';
|
||||
import 'dhtmlx-gantt/codebase/dhtmlxgantt.css';
|
||||
import { ganttTasks } from './data';
|
||||
|
||||
defineOptions({ name: 'GanttPage' });
|
||||
|
||||
const ganttRef = shallowRef<HTMLElement>();
|
||||
|
||||
type TimeType = 'day' | 'week' | 'month' | 'quarter' | 'year';
|
||||
|
||||
const timeType = shallowRef<TimeType>('quarter');
|
||||
|
||||
interface TimeData {
|
||||
label: string;
|
||||
value: TimeType;
|
||||
}
|
||||
|
||||
const data: TimeData[] = [
|
||||
{ label: '天', value: 'day' },
|
||||
{ label: '周', value: 'week' },
|
||||
{ label: '月', value: 'month' },
|
||||
{ label: '季', value: 'quarter' },
|
||||
{ label: '年', value: 'year' }
|
||||
];
|
||||
|
||||
function initGantt() {
|
||||
if (!ganttRef.value) return;
|
||||
|
||||
const config: Partial<GanttConfigOptions> = {
|
||||
grid_width: 350,
|
||||
add_column: false,
|
||||
autofit: false,
|
||||
row_height: 60,
|
||||
bar_height: 34,
|
||||
auto_types: true,
|
||||
xml_date: '%Y-%m-%d',
|
||||
columns: [
|
||||
{ name: 'text', label: '项目名称', tree: true, width: '*' },
|
||||
{ name: 'start_date', label: '开始时间', align: 'center', width: 150 }
|
||||
]
|
||||
};
|
||||
|
||||
Object.assign(gantt.config, config);
|
||||
|
||||
gantt.i18n.setLocale('cn');
|
||||
gantt.init(ganttRef.value);
|
||||
gantt.parse({ data: ganttTasks });
|
||||
|
||||
const zoomLevels: ZoomLevel[] = [
|
||||
{
|
||||
name: 'day',
|
||||
scale_height: 60,
|
||||
scales: [{ unit: 'day', step: 1, format: '%d %M' }]
|
||||
},
|
||||
{
|
||||
name: 'week',
|
||||
scale_height: 60,
|
||||
scales: [
|
||||
{
|
||||
unit: 'week',
|
||||
step: 1,
|
||||
format(date: Date) {
|
||||
const dateToStr = gantt.date.date_to_str('%m-%d');
|
||||
const endDate = gantt.date.add(date, -6, 'day'); // 第几周
|
||||
return `${dateToStr(endDate)} 至 ${dateToStr(date)}`;
|
||||
}
|
||||
},
|
||||
{
|
||||
unit: 'day',
|
||||
step: 1,
|
||||
format: '%d',
|
||||
css(date: Date) {
|
||||
if (date.getDay() === 0 || date.getDay() === 6) {
|
||||
return 'day-item weekend weekend-border-bottom';
|
||||
}
|
||||
return 'day-item';
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'month',
|
||||
scale_height: 60,
|
||||
min_column_width: 18,
|
||||
scales: [
|
||||
{ unit: 'month', format: '%Y-%m' },
|
||||
{
|
||||
unit: 'day',
|
||||
step: 1,
|
||||
format: '%d',
|
||||
css(date: Date) {
|
||||
if (date.getDay() === 0 || date.getDay() === 6) {
|
||||
return 'day-item weekend weekend-border-bottom';
|
||||
}
|
||||
return 'day-item';
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'quarter',
|
||||
height: 60,
|
||||
min_column_width: 110,
|
||||
scales: [
|
||||
{
|
||||
unit: 'quarter',
|
||||
step: 1,
|
||||
format(date: Date) {
|
||||
const yearStr = `${new Date(date).getFullYear()}年`;
|
||||
const dateToStr = gantt.date.date_to_str('%M');
|
||||
const endDate = gantt.date.add(gantt.date.add(date, 3, 'month'), -1, 'day');
|
||||
return `${yearStr + dateToStr(date)} - ${dateToStr(endDate)}`;
|
||||
}
|
||||
},
|
||||
{
|
||||
unit: 'week',
|
||||
step: 1,
|
||||
format(date: Date) {
|
||||
const dateToStr = gantt.date.date_to_str('%m-%d');
|
||||
const endDate = gantt.date.add(date, 6, 'day');
|
||||
return `${dateToStr(date)} 至 ${dateToStr(endDate)}`;
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'year',
|
||||
scale_height: 50,
|
||||
min_column_width: 150,
|
||||
scales: [
|
||||
{ unit: 'year', step: 1, format: '%Y年' },
|
||||
{ unit: 'month', format: '%Y-%m' }
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
gantt.ext.zoom.init({ levels: zoomLevels });
|
||||
gantt.ext.zoom.setLevel(timeType.value);
|
||||
}
|
||||
|
||||
function changeTime(value: string | number) {
|
||||
timeType.value = value as TimeType;
|
||||
gantt.ext.zoom.setLevel(value);
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
initGantt();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="overflow-hidden lt-sm:overflow-auto">
|
||||
<ElCard header="甘特图演示" content-class="overflow-y-hidden overflow-x-auto" class="h-full card-wrapper">
|
||||
<template #header>
|
||||
<div class="flex items-center justify-between">
|
||||
<p>甘特图演示</p>
|
||||
<ElSegmented v-model="timeType" :options="data" @change="changeTime" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div ref="ganttRef" class="size-full min-w-800px"></div>
|
||||
</ElCard>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss"></style>
|
||||
721
src/views/plugin/gantt/vtable/data.ts
Normal file
721
src/views/plugin/gantt/vtable/data.ts
Normal file
@@ -0,0 +1,721 @@
|
||||
export const basicGanttRecords = [
|
||||
{
|
||||
id: 1,
|
||||
title: 'Software Development',
|
||||
developer: 'liufangfang.jane@bytedance.com',
|
||||
start: '2024-07-24',
|
||||
end: '2024-08-15',
|
||||
progress: 31,
|
||||
priority: 'P0',
|
||||
children: [
|
||||
{
|
||||
id: 2,
|
||||
title: 'Project Feature Review',
|
||||
developer: 'liufangfang.jane@bytedance.com',
|
||||
start: '2024-07-24',
|
||||
end: '2024-07-24',
|
||||
progress: 60,
|
||||
priority: 'P0'
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
title: 'Determine project scope',
|
||||
developer: 'liufangfang.jane@bytedance.com',
|
||||
start: '2024-07-25',
|
||||
end: '2024-07-26',
|
||||
progress: 100,
|
||||
priority: 'P1'
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
title: 'Project Create',
|
||||
developer: 'liufangfang.jane@bytedance.com',
|
||||
start: '2024-07-27',
|
||||
end: '2024-07-26',
|
||||
progress: 100,
|
||||
priority: 'P1'
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
title: 'Develop feature 1',
|
||||
developer: 'liufangfang.jane@bytedance.com',
|
||||
start: '2024-08-01',
|
||||
end: '2024-08-15',
|
||||
progress: 0,
|
||||
priority: 'P1'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
title: 'Scope',
|
||||
developer: 'liufangfang.jane@bytedance.com',
|
||||
start: '2024-07-24',
|
||||
end: '2024-08-01',
|
||||
progress: 60,
|
||||
priority: 'P0'
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
title: 'Determine project scope',
|
||||
developer: 'liufangfang.jane@bytedance.com',
|
||||
start: '2024-07-24',
|
||||
end: '2024-08-04',
|
||||
progress: 100,
|
||||
priority: 'P1',
|
||||
children: [
|
||||
{
|
||||
id: 1,
|
||||
title: 'Software Development',
|
||||
developer: 'liufangfang.jane@bytedance.com',
|
||||
start: '2024-08-01',
|
||||
end: '2024-08-01',
|
||||
progress: 90,
|
||||
priority: 'P0'
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
title: 'Software Development',
|
||||
developer: 'liufangfang.jane@bytedance.com',
|
||||
start: '2024-07-30',
|
||||
end: '2024-08-04',
|
||||
progress: 31,
|
||||
priority: 'P0'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
title: 'Scope',
|
||||
developer: 'liufangfang.jane@bytedance.com',
|
||||
start: '2024.07.26',
|
||||
end: '2024.07.08',
|
||||
progress: 60,
|
||||
priority: 'P0'
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
title: 'Determine project scope',
|
||||
developer: 'liufangfang.jane@bytedance.com',
|
||||
start: '2024-07-29',
|
||||
end: '2024-07-31',
|
||||
progress: 100,
|
||||
priority: 'P1'
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
title: 'Software Development',
|
||||
developer: 'liufangfang.jane@bytedance.com',
|
||||
start: '07.24.2024',
|
||||
end: '08.04.2024',
|
||||
progress: 31,
|
||||
priority: 'P0'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
title: 'Scope',
|
||||
developer: 'liufangfang.jane@bytedance.com',
|
||||
start: '2024-07-16',
|
||||
end: '2024-07-18',
|
||||
progress: 60,
|
||||
priority: 'P0'
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
title: 'Determine project scope',
|
||||
developer: 'liufangfang.jane@bytedance.com',
|
||||
start: '2024-08-09',
|
||||
end: '2024-09-11',
|
||||
progress: 100,
|
||||
priority: 'P1'
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
{
|
||||
id: 1,
|
||||
title: 'Software Development',
|
||||
developer: 'liufangfang.jane@bytedance.com',
|
||||
start: '2024-07-24',
|
||||
end: '2024-08-04',
|
||||
progress: 31,
|
||||
priority: 'P0'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
title: 'Scope',
|
||||
developer: 'liufangfang.jane@bytedance.com',
|
||||
start: '2024-07-26',
|
||||
end: '2024-07-28',
|
||||
progress: 60,
|
||||
priority: 'P0',
|
||||
children: [
|
||||
{
|
||||
id: 3,
|
||||
title: 'Determine project scope',
|
||||
developer: 'liufangfang.jane@bytedance.com',
|
||||
start: '2024-07-29',
|
||||
end: '2024-07-31',
|
||||
progress: 100,
|
||||
priority: 'P1',
|
||||
children: [
|
||||
{
|
||||
id: 1,
|
||||
title: 'Software Development',
|
||||
developer: 'liufangfang.jane@bytedance.com',
|
||||
start: '2024-07-24',
|
||||
end: '2024-08-04',
|
||||
progress: 31,
|
||||
priority: 'P0'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
title: 'Scope',
|
||||
developer: 'liufangfang.jane@bytedance.com',
|
||||
start: '2024-07-26',
|
||||
end: '2024-07-28',
|
||||
progress: 60,
|
||||
priority: 'P0'
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
title: 'Determine project scope',
|
||||
developer: 'liufangfang.jane@bytedance.com',
|
||||
start: '2024-07-29',
|
||||
end: '2024-07-31',
|
||||
progress: 100,
|
||||
priority: 'P1'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
title: 'Software Development',
|
||||
developer: 'liufangfang.jane@bytedance.com',
|
||||
start: '2024-07-24',
|
||||
end: '2024-08-04',
|
||||
progress: 31,
|
||||
priority: 'P0',
|
||||
children: [
|
||||
{
|
||||
id: 1,
|
||||
title: 'Software Development',
|
||||
developer: 'liufangfang.jane@bytedance.com',
|
||||
start: '2024-07-24',
|
||||
end: '2024-08-04',
|
||||
progress: 31,
|
||||
priority: 'P0'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
title: 'Scope',
|
||||
developer: 'liufangfang.jane@bytedance.com',
|
||||
start: '2024-07-06',
|
||||
end: '2024-07-08',
|
||||
progress: 60,
|
||||
priority: 'P0'
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
title: 'Determine project scope',
|
||||
developer: 'liufangfang.jane@bytedance.com',
|
||||
start: '2024-07-29',
|
||||
end: '2024-07-31',
|
||||
progress: 100,
|
||||
priority: 'P1'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
title: 'Scope',
|
||||
developer: 'liufangfang.jane@bytedance.com',
|
||||
start: '2024-07-26',
|
||||
end: '2024-07-28',
|
||||
progress: 60,
|
||||
priority: 'P0'
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
title: 'Determine project scope',
|
||||
developer: 'liufangfang.jane@bytedance.com',
|
||||
start: '2024-07-29',
|
||||
end: '2024-07-31',
|
||||
progress: 100,
|
||||
priority: 'P1',
|
||||
children: [
|
||||
{
|
||||
id: 1,
|
||||
title: 'Software Development',
|
||||
developer: 'liufangfang.jane@bytedance.com',
|
||||
start: '2024-07-24',
|
||||
end: '2024-08-04',
|
||||
progress: 31,
|
||||
priority: 'P0'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
title: 'Scope',
|
||||
developer: 'liufangfang.jane@bytedance.com',
|
||||
start: '2024-07-06',
|
||||
end: '2024-07-08',
|
||||
progress: 60,
|
||||
priority: 'P0'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
title: 'Software Development',
|
||||
developer: 'liufangfang.jane@bytedance.com',
|
||||
start: '2024-07-24',
|
||||
end: '2024-08-04',
|
||||
progress: 31,
|
||||
priority: 'P0'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
title: 'Scope',
|
||||
developer: 'liufangfang.jane@bytedance.com',
|
||||
start: '2024-07-26',
|
||||
end: '2024-07-28',
|
||||
progress: 60,
|
||||
priority: 'P0'
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
title: 'Determine project scope',
|
||||
developer: 'liufangfang.jane@bytedance.com',
|
||||
start: '2024-07-29',
|
||||
end: '2024-07-31',
|
||||
progress: 100,
|
||||
priority: 'P1'
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
{
|
||||
id: 3,
|
||||
title: 'Determine project scope',
|
||||
developer: 'liufangfang.jane@bytedance.com',
|
||||
start: '2024-07-29',
|
||||
end: '2024-07-31',
|
||||
progress: 100,
|
||||
priority: 'P1',
|
||||
children: [
|
||||
{
|
||||
id: 1,
|
||||
title: 'Software Development',
|
||||
developer: 'liufangfang.jane@bytedance.com',
|
||||
start: '2024-07-24',
|
||||
end: '2024-08-04',
|
||||
progress: 31,
|
||||
priority: 'P0'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
title: 'Scope',
|
||||
developer: 'liufangfang.jane@bytedance.com',
|
||||
start: '2024-07-23',
|
||||
end: '2024-07-28',
|
||||
progress: 60,
|
||||
priority: 'P0'
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
title: 'Determine project scope',
|
||||
developer: 'liufangfang.jane@bytedance.com',
|
||||
start: '2024-07-29',
|
||||
end: '2024-07-31',
|
||||
progress: 100,
|
||||
priority: 'P1'
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
title: 'Software Development',
|
||||
developer: 'liufangfang.jane@bytedance.com',
|
||||
start: '2024-07-30',
|
||||
end: '2024-08-14',
|
||||
progress: 31,
|
||||
priority: 'P0'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
title: 'Scope',
|
||||
developer: 'liufangfang.jane@bytedance.com',
|
||||
start: '2024-07-24',
|
||||
end: '2024-08-04',
|
||||
progress: 60,
|
||||
priority: 'P0'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
title: 'Software Development',
|
||||
developer: 'liufangfang.jane@bytedance.com',
|
||||
start: '2024-07-24',
|
||||
end: '2024-08-04',
|
||||
progress: 31,
|
||||
priority: 'P0',
|
||||
children: [
|
||||
{
|
||||
id: 3,
|
||||
title: 'Determine project scope',
|
||||
developer: 'liufangfang.jane@bytedance.com',
|
||||
start: '2024-07-24',
|
||||
end: '2024-08-04',
|
||||
progress: 100,
|
||||
priority: 'P1'
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
title: 'Software Development',
|
||||
developer: 'liufangfang.jane@bytedance.com',
|
||||
start: '2024-08-04',
|
||||
end: '2024-08-04',
|
||||
progress: 90,
|
||||
priority: 'P0'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
title: 'Scope',
|
||||
developer: 'liufangfang.jane@bytedance.com',
|
||||
start: '07/24/2024',
|
||||
end: '08/04/2024',
|
||||
progress: 60,
|
||||
priority: 'P0'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
title: 'Scope',
|
||||
developer: 'liufangfang.jane@bytedance.com',
|
||||
start: '2024-07-27',
|
||||
end: '2024-07-28',
|
||||
progress: 60,
|
||||
priority: 'P0'
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
title: 'Determine project scope',
|
||||
developer: 'liufangfang.jane@bytedance.com',
|
||||
start: '2024-07-29',
|
||||
end: '2024-07-31',
|
||||
progress: 100,
|
||||
priority: 'P1',
|
||||
children: [
|
||||
{
|
||||
id: 1,
|
||||
title: 'Software Development',
|
||||
developer: 'liufangfang.jane@bytedance.com',
|
||||
start: '07.24.2024',
|
||||
end: '08.04.2024',
|
||||
progress: 31,
|
||||
priority: 'P0'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
title: 'Scope',
|
||||
developer: 'liufangfang.jane@bytedance.com',
|
||||
start: '2024-07-26',
|
||||
end: '2024-07-28',
|
||||
progress: 60,
|
||||
priority: 'P0'
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
title: 'Determine project scope',
|
||||
developer: 'liufangfang.jane@bytedance.com',
|
||||
start: '2024-08-09',
|
||||
end: '2024-09-11',
|
||||
progress: 100,
|
||||
priority: 'P1'
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
title: 'Software Development',
|
||||
developer: 'liufangfang.jane@bytedance.com',
|
||||
start: '2024-07-24',
|
||||
end: '2024-08-04',
|
||||
progress: 31,
|
||||
priority: 'P0'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
title: 'Scope',
|
||||
developer: 'liufangfang.jane@bytedance.com',
|
||||
start: '2024-07-26',
|
||||
end: '2024-07-28',
|
||||
progress: 60,
|
||||
priority: 'P0'
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
title: 'Determine project scope',
|
||||
developer: 'liufangfang.jane@bytedance.com',
|
||||
start: '2024-07-29',
|
||||
end: '2024-07-31',
|
||||
progress: 100,
|
||||
priority: 'P1'
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
title: 'Software Development',
|
||||
developer: 'liufangfang.jane@bytedance.com',
|
||||
start: '2024-07-24',
|
||||
end: '2024-08-04',
|
||||
progress: 31,
|
||||
priority: 'P0'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
title: 'Scope',
|
||||
developer: 'liufangfang.jane@bytedance.com',
|
||||
start: '2024-07-26',
|
||||
end: '2024-07-28',
|
||||
progress: 60,
|
||||
priority: 'P0'
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
title: 'Determine project scope',
|
||||
developer: 'liufangfang.jane@bytedance.com',
|
||||
start: '2024-07-29',
|
||||
end: '2024-07-31',
|
||||
progress: 100,
|
||||
priority: 'P1'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
title: 'Software Development',
|
||||
developer: 'liufangfang.jane@bytedance.com',
|
||||
start: '2024-07-24',
|
||||
end: '2024-08-04',
|
||||
progress: 31,
|
||||
priority: 'P0',
|
||||
children: [
|
||||
{
|
||||
id: 3,
|
||||
title: 'Determine project scope',
|
||||
developer: 'liufangfang.jane@bytedance.com',
|
||||
start: '2024-07-24',
|
||||
end: '2024-08-04',
|
||||
progress: 100,
|
||||
priority: 'P1'
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
title: 'Software Development',
|
||||
developer: 'liufangfang.jane@bytedance.com',
|
||||
start: '2024-07-24',
|
||||
end: '2024-08-04',
|
||||
progress: 31,
|
||||
priority: 'P0'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
title: 'Scope',
|
||||
developer: 'liufangfang.jane@bytedance.com',
|
||||
start: '2024.07.06',
|
||||
end: '2024.07.08',
|
||||
progress: 60,
|
||||
priority: 'P0'
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
title: 'Determine project scope',
|
||||
developer: 'liufangfang.jane@bytedance.com',
|
||||
start: '2024-07-29',
|
||||
end: '2024-07-31',
|
||||
progress: 100,
|
||||
priority: 'P1'
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
export const linkGanttRecords = [
|
||||
{
|
||||
id: 1,
|
||||
title: 'Software Development',
|
||||
developer: 'liufangfang.jane@bytedance.com',
|
||||
start: '2024-07-15',
|
||||
end: '2024-07-16',
|
||||
progress: 31,
|
||||
priority: 'P0'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
title: 'Scope',
|
||||
developer: 'liufangfang.jane@bytedance.com',
|
||||
start: '2024-07-16',
|
||||
end: '2024-07-17',
|
||||
progress: 60,
|
||||
priority: 'P0'
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
title: 'Software Development',
|
||||
developer: 'liufangfang.jane@bytedance.com',
|
||||
start: '2024-07-18',
|
||||
end: '2024-07-19',
|
||||
progress: 90,
|
||||
priority: 'P0'
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
title: 'Determine project scope',
|
||||
developer: 'liufangfang.jane@bytedance.com',
|
||||
start: '2024/07/17',
|
||||
end: '2024/07/18',
|
||||
progress: 100,
|
||||
priority: 'P1'
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
title: 'Scope',
|
||||
developer: 'liufangfang.jane@bytedance.com',
|
||||
start: '07/19/2024',
|
||||
end: '07/20/2024',
|
||||
progress: 60,
|
||||
priority: 'P0'
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
title: 'Determine project scope',
|
||||
developer: 'liufangfang.jane@bytedance.com',
|
||||
start: '2024-07-24',
|
||||
end: '2024-08-04',
|
||||
progress: 100,
|
||||
priority: 'P1'
|
||||
},
|
||||
{
|
||||
id: 7,
|
||||
title: 'Software Development',
|
||||
developer: 'liufangfang.jane@bytedance.com',
|
||||
start: '2024-07-24',
|
||||
end: '2024-08-04',
|
||||
progress: 31,
|
||||
priority: 'P0'
|
||||
},
|
||||
{
|
||||
id: 8,
|
||||
title: 'Scope',
|
||||
developer: 'liufangfang.jane@bytedance.com',
|
||||
start: '2024.07.06',
|
||||
end: '2024.07.08',
|
||||
progress: 60,
|
||||
priority: 'P0'
|
||||
},
|
||||
{
|
||||
id: 9,
|
||||
title: 'Determine project scope',
|
||||
developer: 'liufangfang.jane@bytedance.com',
|
||||
start: '2024/07/09',
|
||||
end: '2024/07/11',
|
||||
progress: 100,
|
||||
priority: 'P1'
|
||||
},
|
||||
{
|
||||
id: 10,
|
||||
title: 'Software Development',
|
||||
developer: 'liufangfang.jane@bytedance.com',
|
||||
start: '07.24.2024',
|
||||
end: '08.04.2024',
|
||||
progress: 31,
|
||||
priority: 'P0'
|
||||
},
|
||||
|
||||
{
|
||||
id: 11,
|
||||
title: 'Software Development',
|
||||
developer: 'liufangfang.jane@bytedance.com',
|
||||
start: '2024-07-24',
|
||||
end: '2024-08-04',
|
||||
progress: 31,
|
||||
priority: 'P0'
|
||||
},
|
||||
{
|
||||
id: 12,
|
||||
title: 'Scope',
|
||||
developer: 'liufangfang.jane@bytedance.com',
|
||||
start: '2024-07-06',
|
||||
end: '2024-07-08',
|
||||
progress: 60,
|
||||
priority: 'P0'
|
||||
},
|
||||
{
|
||||
id: 13,
|
||||
title: 'Determine project scope',
|
||||
developer: 'liufangfang.jane@bytedance.com',
|
||||
start: '2024-07-09',
|
||||
end: '2024-07-11',
|
||||
progress: 100,
|
||||
priority: 'P1'
|
||||
}
|
||||
];
|
||||
|
||||
export const customGanttRecords = [
|
||||
{
|
||||
id: 1,
|
||||
title: 'Project Task 1',
|
||||
developer: 'bear.xiong',
|
||||
avatar: 'https://lf9-dp-fe-cms-tos.byteorg.com/obj/bit-cloud/VTable/custom-render/bear.jpg',
|
||||
start: '2024-07-24',
|
||||
end: '2024-07-26',
|
||||
progress: 31,
|
||||
priority: 'P0'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
title: 'Project Task 2',
|
||||
developer: 'wolf.lang',
|
||||
avatar: 'https://lf9-dp-fe-cms-tos.byteorg.com/obj/bit-cloud/VTable/custom-render/wolf.jpg',
|
||||
start: '07/25/2024',
|
||||
end: '07/28/2024',
|
||||
progress: 60,
|
||||
priority: 'P0'
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
title: 'Project Task 3',
|
||||
developer: 'rabbit.tu',
|
||||
avatar: 'https://lf9-dp-fe-cms-tos.byteorg.com/obj/bit-cloud/VTable/custom-render/rabbit.jpg',
|
||||
start: '2024-07-28',
|
||||
end: '2024-08-01',
|
||||
progress: 100,
|
||||
priority: 'P1'
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
title: 'Project Task 4',
|
||||
developer: 'cat.mao',
|
||||
avatar: 'https://lf9-dp-fe-cms-tos.byteorg.com/obj/bit-cloud/VTable/custom-render/cat.jpg',
|
||||
start: '2024-07-31',
|
||||
end: '2024-08-03',
|
||||
progress: 31,
|
||||
priority: 'P0'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
title: 'Project Task 5',
|
||||
developer: 'bird.niao',
|
||||
avatar: 'https://lf9-dp-fe-cms-tos.byteorg.com/obj/bit-cloud/VTable/custom-render/bird.jpeg',
|
||||
start: '2024-08-02',
|
||||
end: '2024-08-04',
|
||||
progress: 60,
|
||||
priority: 'P0'
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
title: 'Project Task 6',
|
||||
developer: 'flower.hua',
|
||||
avatar: 'https://lf9-dp-fe-cms-tos.byteorg.com/obj/bit-cloud/VTable/custom-render/flower.jpg',
|
||||
start: '2024-08-03',
|
||||
end: '2024-08-10',
|
||||
progress: 100,
|
||||
priority: 'P1'
|
||||
}
|
||||
];
|
||||
792
src/views/plugin/gantt/vtable/index.vue
Normal file
792
src/views/plugin/gantt/vtable/index.vue
Normal file
@@ -0,0 +1,792 @@
|
||||
<script setup lang="tsx">
|
||||
import { onMounted, onUnmounted, shallowRef, watch } from 'vue';
|
||||
import * as VTableGantt from '@visactor/vtable-gantt';
|
||||
import * as VTable_editors from '@visactor/vtable-editors';
|
||||
import { useThemeStore } from '@/store/modules/theme';
|
||||
import { basicGanttRecords, customGanttRecords, linkGanttRecords } from './data';
|
||||
|
||||
const theme = useThemeStore();
|
||||
|
||||
const input_editor = new VTable_editors.InputEditor();
|
||||
const date_input_editor = new VTable_editors.DateInputEditor();
|
||||
VTableGantt.VTable.register.editor('input', input_editor);
|
||||
VTableGantt.VTable.register.editor('date-input', date_input_editor);
|
||||
|
||||
const basicGanttDomRef = shallowRef<HTMLElement>();
|
||||
const linkGanttDomRef = shallowRef<HTMLElement>();
|
||||
const customGanttDomRef = shallowRef<HTMLElement>();
|
||||
|
||||
const basicGanttInstance = shallowRef<VTableGantt.Gantt>();
|
||||
const linkGanttInstance = shallowRef<VTableGantt.Gantt>();
|
||||
const customGanttInstance = shallowRef<VTableGantt.Gantt>();
|
||||
|
||||
const basicGanttColumns = [
|
||||
{
|
||||
field: 'title',
|
||||
title: 'title',
|
||||
width: 'auto',
|
||||
sort: true,
|
||||
tree: true,
|
||||
editor: 'input'
|
||||
},
|
||||
{
|
||||
field: 'start',
|
||||
title: 'start',
|
||||
width: 'auto',
|
||||
sort: true,
|
||||
editor: 'date-input'
|
||||
},
|
||||
{
|
||||
field: 'end',
|
||||
title: 'end',
|
||||
width: 'auto',
|
||||
sort: true,
|
||||
editor: 'date-input'
|
||||
},
|
||||
{
|
||||
field: 'priority',
|
||||
title: 'priority',
|
||||
width: 'auto',
|
||||
sort: true,
|
||||
editor: 'input'
|
||||
},
|
||||
{
|
||||
field: 'progress',
|
||||
title: 'progress',
|
||||
width: 'auto',
|
||||
sort: true,
|
||||
headerStyle: {
|
||||
borderColor: '#e1e4e8'
|
||||
},
|
||||
style: {
|
||||
borderColor: '#e1e4e8',
|
||||
color: 'green'
|
||||
},
|
||||
editor: 'input'
|
||||
}
|
||||
];
|
||||
const basicGanttOption: VTableGantt.GanttConstructorOptions = {
|
||||
overscrollBehavior: 'none',
|
||||
records: basicGanttRecords,
|
||||
taskListTable: {
|
||||
columns: basicGanttColumns,
|
||||
tableWidth: 250,
|
||||
minTableWidth: 100,
|
||||
maxTableWidth: 600
|
||||
// rightFrozenColCount: 1
|
||||
},
|
||||
frame: {
|
||||
outerFrameStyle: {
|
||||
borderLineWidth: 2,
|
||||
borderColor: '#e1e4e8',
|
||||
cornerRadius: 8
|
||||
},
|
||||
verticalSplitLine: {
|
||||
lineColor: '#e1e4e8',
|
||||
lineWidth: 3
|
||||
},
|
||||
horizontalSplitLine: {
|
||||
lineColor: '#e1e4e8',
|
||||
lineWidth: 3
|
||||
},
|
||||
verticalSplitLineMoveable: true,
|
||||
verticalSplitLineHighlight: {
|
||||
lineColor: 'green',
|
||||
lineWidth: 3
|
||||
}
|
||||
},
|
||||
grid: {
|
||||
// backgroundColor: 'gray',
|
||||
verticalLine: {
|
||||
lineWidth: 1,
|
||||
lineColor: '#e1e4e8'
|
||||
},
|
||||
horizontalLine: {
|
||||
lineWidth: 1,
|
||||
lineColor: '#e1e4e8'
|
||||
}
|
||||
},
|
||||
headerRowHeight: 40,
|
||||
rowHeight: 40,
|
||||
taskBar: {
|
||||
startDateField: 'start',
|
||||
endDateField: 'end',
|
||||
progressField: 'progress',
|
||||
// resizable: false,
|
||||
moveable: true,
|
||||
hoverBarStyle: {
|
||||
barOverlayColor: 'rgba(99, 144, 0, 0.4)'
|
||||
},
|
||||
labelText: '{title} {progress}%',
|
||||
labelTextStyle: {
|
||||
// padding: 2,
|
||||
fontFamily: 'Arial',
|
||||
fontSize: 16,
|
||||
textAlign: 'left',
|
||||
textOverflow: 'ellipsis'
|
||||
},
|
||||
barStyle: {
|
||||
width: 20,
|
||||
/** 任务条的颜色 */
|
||||
barColor: '#ee8800',
|
||||
/** 已完成部分任务条的颜色 */
|
||||
completedBarColor: '#91e8e0',
|
||||
/** 任务条的圆角 */
|
||||
cornerRadius: 8
|
||||
}
|
||||
},
|
||||
timelineHeader: {
|
||||
colWidth: 100,
|
||||
backgroundColor: '#EEF1F5',
|
||||
horizontalLine: {
|
||||
lineWidth: 1,
|
||||
lineColor: '#e1e4e8'
|
||||
},
|
||||
verticalLine: {
|
||||
lineWidth: 1,
|
||||
lineColor: '#e1e4e8'
|
||||
},
|
||||
scales: [
|
||||
{
|
||||
unit: 'week',
|
||||
step: 1,
|
||||
startOfWeek: 'sunday',
|
||||
format(date: any) {
|
||||
return `Week ${date.dateIndex}`;
|
||||
},
|
||||
style: {
|
||||
fontSize: 20,
|
||||
fontWeight: 'bold',
|
||||
color: 'white',
|
||||
strokeColor: 'black',
|
||||
textAlign: 'right',
|
||||
textBaseline: 'bottom',
|
||||
textStick: true
|
||||
// padding: [0, 30, 0, 20]
|
||||
}
|
||||
},
|
||||
{
|
||||
unit: 'day',
|
||||
step: 1,
|
||||
format(date: any) {
|
||||
return date.dateIndex.toString();
|
||||
},
|
||||
style: {
|
||||
fontSize: 20,
|
||||
fontWeight: 'bold',
|
||||
color: 'white',
|
||||
strokeColor: 'black',
|
||||
textAlign: 'right',
|
||||
textBaseline: 'bottom'
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
markLine: [
|
||||
{
|
||||
date: '2024-07-28',
|
||||
style: {
|
||||
lineWidth: 1,
|
||||
lineColor: 'blue',
|
||||
lineDash: [8, 4]
|
||||
}
|
||||
},
|
||||
{
|
||||
date: '2024-08-17',
|
||||
style: {
|
||||
lineWidth: 2,
|
||||
lineColor: 'red',
|
||||
lineDash: [8, 4]
|
||||
}
|
||||
}
|
||||
],
|
||||
rowSeriesNumber: {
|
||||
title: '行号',
|
||||
dragOrder: true
|
||||
},
|
||||
scrollStyle: {
|
||||
scrollRailColor: 'RGBA(246,246,246,0.5)',
|
||||
visible: 'scrolling',
|
||||
width: 6,
|
||||
scrollSliderCornerRadius: 2,
|
||||
scrollSliderColor: '#5cb85c'
|
||||
}
|
||||
};
|
||||
|
||||
const linkGanttColumns = [
|
||||
{
|
||||
field: 'title',
|
||||
title: 'title',
|
||||
width: 'auto',
|
||||
tree: true
|
||||
},
|
||||
{
|
||||
field: 'start',
|
||||
title: 'start',
|
||||
width: 'auto',
|
||||
editor: 'date-input'
|
||||
},
|
||||
{
|
||||
field: 'end',
|
||||
title: 'end',
|
||||
width: 'auto',
|
||||
editor: 'date-input'
|
||||
},
|
||||
{
|
||||
field: 'priority',
|
||||
title: 'priority',
|
||||
width: 'auto',
|
||||
editor: 'input'
|
||||
},
|
||||
{
|
||||
field: 'progress',
|
||||
title: 'progress',
|
||||
width: 'auto',
|
||||
headerStyle: {
|
||||
borderColor: '#e1e4e8'
|
||||
},
|
||||
style: {
|
||||
borderColor: '#e1e4e8',
|
||||
color: 'green'
|
||||
},
|
||||
editor: 'input'
|
||||
}
|
||||
];
|
||||
const linkGanttOption: VTableGantt.GanttConstructorOptions = {
|
||||
records: linkGanttRecords,
|
||||
taskListTable: {
|
||||
columns: linkGanttColumns,
|
||||
tableWidth: 400,
|
||||
minTableWidth: 100,
|
||||
maxTableWidth: 600
|
||||
},
|
||||
dependency: {
|
||||
links: [
|
||||
{
|
||||
type: VTableGantt.TYPES.DependencyType.FinishToStart,
|
||||
linkedFromTaskKey: 1,
|
||||
linkedToTaskKey: 2
|
||||
},
|
||||
{
|
||||
type: VTableGantt.TYPES.DependencyType.StartToFinish,
|
||||
linkedFromTaskKey: 2,
|
||||
linkedToTaskKey: 3
|
||||
},
|
||||
{
|
||||
type: VTableGantt.TYPES.DependencyType.StartToStart,
|
||||
linkedFromTaskKey: 3,
|
||||
linkedToTaskKey: 4
|
||||
},
|
||||
{
|
||||
type: VTableGantt.TYPES.DependencyType.FinishToFinish,
|
||||
linkedFromTaskKey: 4,
|
||||
linkedToTaskKey: 5
|
||||
}
|
||||
],
|
||||
// linkSelectable: false,
|
||||
linkSelectedLineStyle: {
|
||||
shadowBlur: 5, // 阴影宽度
|
||||
shadowColor: 'red',
|
||||
lineColor: 'red',
|
||||
lineWidth: 1
|
||||
}
|
||||
},
|
||||
frame: {
|
||||
verticalSplitLineMoveable: true,
|
||||
outerFrameStyle: {
|
||||
borderLineWidth: 2,
|
||||
// borderColor: 'red',
|
||||
cornerRadius: 8
|
||||
},
|
||||
verticalSplitLine: {
|
||||
lineWidth: 3,
|
||||
lineColor: '#e1e4e8'
|
||||
},
|
||||
verticalSplitLineHighlight: {
|
||||
lineColor: 'green',
|
||||
lineWidth: 3
|
||||
}
|
||||
},
|
||||
grid: {
|
||||
// backgroundColor: 'gray',
|
||||
verticalLine: {
|
||||
lineWidth: 1,
|
||||
lineColor: '#e1e4e8'
|
||||
},
|
||||
horizontalLine: {
|
||||
lineWidth: 1,
|
||||
lineColor: '#e1e4e8'
|
||||
}
|
||||
},
|
||||
headerRowHeight: 60,
|
||||
rowHeight: 40,
|
||||
|
||||
taskBar: {
|
||||
startDateField: 'start',
|
||||
endDateField: 'end',
|
||||
progressField: 'progress',
|
||||
labelText: '{title} {progress}%',
|
||||
labelTextStyle: {
|
||||
fontFamily: 'Arial',
|
||||
fontSize: 16,
|
||||
textAlign: 'left'
|
||||
},
|
||||
barStyle: {
|
||||
width: 20,
|
||||
/** 任务条的颜色 */
|
||||
barColor: '#ee8800',
|
||||
/** 已完成部分任务条的颜色 */
|
||||
completedBarColor: '#91e8e0',
|
||||
/** 任务条的圆角 */
|
||||
cornerRadius: 10
|
||||
},
|
||||
selectedBarStyle: {
|
||||
shadowBlur: 5, // 阴影宽度
|
||||
shadowOffsetX: 0, // x方向偏移
|
||||
shadowOffsetY: 0, // Y方向偏移
|
||||
shadowColor: 'black', // 阴影颜色
|
||||
borderColor: 'red', // 边框颜色
|
||||
borderLineWidth: 1 // 边框宽度
|
||||
}
|
||||
},
|
||||
timelineHeader: {
|
||||
verticalLine: {
|
||||
lineWidth: 1,
|
||||
lineColor: '#e1e4e8'
|
||||
},
|
||||
horizontalLine: {
|
||||
lineWidth: 1,
|
||||
lineColor: '#e1e4e8'
|
||||
},
|
||||
backgroundColor: '#EEF1F5',
|
||||
colWidth: 60,
|
||||
scales: [
|
||||
{
|
||||
unit: 'week',
|
||||
step: 1,
|
||||
startOfWeek: 'sunday',
|
||||
format(date: any) {
|
||||
return `Week ${date.dateIndex}`;
|
||||
},
|
||||
style: {
|
||||
fontSize: 20,
|
||||
fontWeight: 'bold',
|
||||
color: 'red'
|
||||
}
|
||||
},
|
||||
{
|
||||
unit: 'day',
|
||||
step: 1,
|
||||
format(date: any) {
|
||||
return date.dateIndex.toString();
|
||||
},
|
||||
style: {
|
||||
fontSize: 20,
|
||||
fontWeight: 'bold',
|
||||
color: 'red'
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
minDate: '2024-07-14',
|
||||
maxDate: '2024-10-15',
|
||||
|
||||
rowSeriesNumber: {
|
||||
title: '行号',
|
||||
dragOrder: true
|
||||
},
|
||||
scrollStyle: {
|
||||
visible: 'scrolling'
|
||||
},
|
||||
overscrollBehavior: 'none'
|
||||
};
|
||||
|
||||
const barColors0 = ['#aecde6', '#c6a49a', '#ffb582', '#eec1de', '#b3d9b3', '#cccccc', '#e59a9c', '#d9d1a5', '#c9bede'];
|
||||
const barColors = ['#1f77b4', '#8c564b', '#ff7f0e', '#e377c2', '#2ca02c', '#7f7f7f', '#d62728', '#bcbd22', '#9467bd'];
|
||||
const customGanttColumns: VTableGantt.ColumnsDefine = [
|
||||
{
|
||||
field: 'title',
|
||||
title: 'TASK',
|
||||
width: '200',
|
||||
headerStyle: {
|
||||
textAlign: 'center',
|
||||
fontSize: 20,
|
||||
fontWeight: 'bold'
|
||||
// color: 'black',
|
||||
// bgColor: '#f0f0fb'
|
||||
},
|
||||
style: {
|
||||
// bgColor: '#f0f0fb'
|
||||
},
|
||||
customLayout: (args: any) => {
|
||||
const { table, row, col, rect } = args;
|
||||
const taskRecord = table.getCellOriginRecord(col, row);
|
||||
const { height, width } = rect ?? table.getCellRect(col, row);
|
||||
const container = new VTableGantt.VRender.Group({
|
||||
y: 10,
|
||||
x: 20,
|
||||
height: height - 20,
|
||||
width: width - 40,
|
||||
fill: '#ddd',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
cornerRadius: 30
|
||||
});
|
||||
|
||||
const developer = new VTableGantt.VRender.Text({
|
||||
text: taskRecord.developer,
|
||||
fontSize: 16,
|
||||
fontFamily: 'sans-serif',
|
||||
fill: barColors[args.row],
|
||||
fontWeight: 'bold',
|
||||
maxLineWidth: width - 120,
|
||||
boundsPadding: [10, 0, 0, 0],
|
||||
alignSelf: 'center'
|
||||
});
|
||||
container.add(developer);
|
||||
|
||||
const days = new VTableGantt.VRender.Text({
|
||||
text: `${VTableGantt.tools.formatDate(new Date(taskRecord.start), 'mm/dd')}-${VTableGantt.tools.formatDate(
|
||||
new Date(taskRecord.end),
|
||||
'mm/dd'
|
||||
)}`,
|
||||
fontSize: 12,
|
||||
fontFamily: 'sans-serif',
|
||||
fontWeight: 'bold',
|
||||
fill: 'black',
|
||||
boundsPadding: [10, 0, 0, 0],
|
||||
alignSelf: 'center'
|
||||
});
|
||||
container.add(days);
|
||||
|
||||
return {
|
||||
rootContainer: container,
|
||||
expectedWidth: 160
|
||||
};
|
||||
}
|
||||
}
|
||||
];
|
||||
const customGanttOption: VTableGantt.GanttConstructorOptions = {
|
||||
records: customGanttRecords,
|
||||
taskListTable: {
|
||||
columns: customGanttColumns,
|
||||
tableWidth: 'auto'
|
||||
},
|
||||
frame: {
|
||||
outerFrameStyle: {
|
||||
borderLineWidth: 2,
|
||||
borderColor: '#E1E4E8',
|
||||
cornerRadius: 8
|
||||
}
|
||||
// verticalSplitLineHighlight: {
|
||||
// lineColor: 'green',
|
||||
// lineWidth: 3
|
||||
// }
|
||||
},
|
||||
grid: {
|
||||
// backgroundColor: '#f0f0fb',
|
||||
// vertical: {
|
||||
// lineWidth: 1,
|
||||
// lineColor: '#e1e4e8'
|
||||
// },
|
||||
horizontalLine: {
|
||||
lineWidth: 2,
|
||||
lineColor: '#d5d9ee'
|
||||
}
|
||||
},
|
||||
headerRowHeight: 60,
|
||||
rowHeight: 80,
|
||||
taskBar: {
|
||||
startDateField: 'start',
|
||||
endDateField: 'end',
|
||||
progressField: 'progress',
|
||||
barStyle: { width: 60 },
|
||||
customLayout: (args: any) => {
|
||||
const colorLength = barColors.length;
|
||||
const { width, height, index, taskDays, progress, taskRecord } = args;
|
||||
const container = new VTableGantt.VRender.Group({
|
||||
width,
|
||||
height,
|
||||
cornerRadius: 30,
|
||||
fill: {
|
||||
gradient: 'linear',
|
||||
x0: 0,
|
||||
y0: 0,
|
||||
x1: 1,
|
||||
y1: 0,
|
||||
stops: [
|
||||
{
|
||||
offset: 0,
|
||||
color: barColors0[index % colorLength]
|
||||
},
|
||||
{
|
||||
offset: 0.5,
|
||||
color: barColors[index % colorLength]
|
||||
},
|
||||
{
|
||||
offset: 1,
|
||||
color: barColors0[index % colorLength]
|
||||
}
|
||||
]
|
||||
},
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
flexWrap: 'nowrap'
|
||||
});
|
||||
const containerLeft = new VTableGantt.VRender.Group({
|
||||
height,
|
||||
width: 60,
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-around'
|
||||
// fill: 'red'
|
||||
});
|
||||
container.add(containerLeft as any);
|
||||
|
||||
const avatar = new VTableGantt.VRender.Image({
|
||||
width: 50,
|
||||
height: 50,
|
||||
image: taskRecord.avatar,
|
||||
cornerRadius: 25
|
||||
});
|
||||
containerLeft.add(avatar);
|
||||
const containerCenter = new VTableGantt.VRender.Group({
|
||||
height,
|
||||
width: width - 120,
|
||||
display: 'flex',
|
||||
flexDirection: 'column'
|
||||
// alignItems: 'left'
|
||||
});
|
||||
container.add(containerCenter as any);
|
||||
|
||||
const developer = new VTableGantt.VRender.Text({
|
||||
text: taskRecord.developer,
|
||||
fontSize: 16,
|
||||
fontFamily: 'sans-serif',
|
||||
fill: 'white',
|
||||
fontWeight: 'bold',
|
||||
maxLineWidth: width - 120,
|
||||
boundsPadding: [10, 0, 0, 0]
|
||||
});
|
||||
containerCenter.add(developer);
|
||||
|
||||
const days = new VTableGantt.VRender.Text({
|
||||
text: `${taskDays}天`,
|
||||
fontSize: 13,
|
||||
fontFamily: 'sans-serif',
|
||||
fill: 'white',
|
||||
boundsPadding: [10, 0, 0, 0]
|
||||
});
|
||||
containerCenter.add(days);
|
||||
|
||||
if (width >= 120) {
|
||||
const containerRight = new VTableGantt.VRender.Group({
|
||||
cornerRadius: 20,
|
||||
fill: 'white',
|
||||
height: 40,
|
||||
width: 40,
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center', // 垂直方向居中对齐
|
||||
boundsPadding: [10, 0, 0, 0]
|
||||
});
|
||||
container.add(containerRight as any);
|
||||
|
||||
const progressText = new VTableGantt.VRender.Text({
|
||||
text: `${progress}%`,
|
||||
fontSize: 12,
|
||||
fontFamily: 'sans-serif',
|
||||
fill: 'black',
|
||||
alignSelf: 'center',
|
||||
fontWeight: 'bold',
|
||||
maxLineWidth: (width - 60) / 2,
|
||||
boundsPadding: [0, 0, 0, 0]
|
||||
});
|
||||
containerRight.add(progressText);
|
||||
}
|
||||
return {
|
||||
rootContainer: container
|
||||
// renderDefaultBar: true
|
||||
// renderDefaultText: true
|
||||
};
|
||||
},
|
||||
hoverBarStyle: {
|
||||
cornerRadius: 30
|
||||
}
|
||||
},
|
||||
timelineHeader: {
|
||||
backgroundColor: '#f0f0fb',
|
||||
colWidth: 80,
|
||||
// verticalLine: {
|
||||
// lineColor: 'red',
|
||||
// lineWidth: 1,
|
||||
// lineDash: [4, 2]
|
||||
// },
|
||||
// horizontalLine: {
|
||||
// lineColor: 'green',
|
||||
// lineWidth: 1,
|
||||
// lineDash: [4, 2]
|
||||
// },
|
||||
scales: [
|
||||
{
|
||||
unit: 'day',
|
||||
step: 1,
|
||||
format(date: any) {
|
||||
return date.dateIndex.toString();
|
||||
},
|
||||
customLayout: (args: any) => {
|
||||
const { width, height, startDate, dateIndex } = args;
|
||||
const container = new VTableGantt.VRender.Group({
|
||||
width,
|
||||
height,
|
||||
// fill: '#f0f0fb',
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
flexWrap: 'nowrap'
|
||||
});
|
||||
const containerLeft = new VTableGantt.VRender.Group({
|
||||
height,
|
||||
width: 30,
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-around'
|
||||
// fill: 'red'
|
||||
});
|
||||
container.add(containerLeft as any);
|
||||
|
||||
const avatar = new VTableGantt.VRender.Image({
|
||||
width: 20,
|
||||
height: 30,
|
||||
image:
|
||||
'<svg t="1724675965803" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4299" width="200" height="200"><path d="M53.085678 141.319468C23.790257 141.319468 0 165.035326 0 194.34775L0 918.084273C0 947.295126 23.796789 971.112572 53.085678 971.112572L970.914322 971.112572C1000.209743 971.112572 1024 947.396696 1024 918.084273L1024 194.34775C1024 165.136896 1000.203211 141.319468 970.914322 141.319468L776.827586 141.319468 812.137931 176.629813 812.137931 88.275862C812.137931 68.774506 796.328942 52.965517 776.827586 52.965517 757.32623 52.965517 741.517241 68.774506 741.517241 88.275862L741.517241 176.629813 741.517241 211.940158 776.827586 211.940158 970.914322 211.940158C961.186763 211.940158 953.37931 204.125926 953.37931 194.34775L953.37931 918.084273C953.37931 908.344373 961.25643 900.491882 970.914322 900.491882L53.085678 900.491882C62.813237 900.491882 70.62069 908.306097 70.62069 918.084273L70.62069 194.34775C70.62069 204.087649 62.74357 211.940158 53.085678 211.940158L247.172414 211.940158C266.67377 211.940158 282.482759 196.131169 282.482759 176.629813 282.482759 157.128439 266.67377 141.319468 247.172414 141.319468L53.085678 141.319468ZM211.862069 176.629813C211.862069 196.131169 227.671058 211.940158 247.172414 211.940158 266.67377 211.940158 282.482759 196.131169 282.482759 176.629813L282.482759 88.275862C282.482759 68.774506 266.67377 52.965517 247.172414 52.965517 227.671058 52.965517 211.862069 68.774506 211.862069 88.275862L211.862069 176.629813ZM1024 353.181537 1024 317.871192 988.689655 317.871192 35.310345 317.871192 0 317.871192 0 353.181537 0 441.457399C0 460.958755 15.808989 476.767744 35.310345 476.767744 54.811701 476.767744 70.62069 460.958755 70.62069 441.457399L70.62069 353.181537 35.310345 388.491882 988.689655 388.491882 953.37931 353.181537 953.37931 441.457399C953.37931 460.958755 969.188299 476.767744 988.689655 476.767744 1008.191011 476.767744 1024 460.958755 1024 441.457399L1024 353.181537ZM776.937913 582.62069C796.439287 582.62069 812.248258 566.811701 812.248258 547.310345 812.248258 527.808989 796.439287 512 776.937913 512L247.172414 512C227.671058 512 211.862069 527.808989 211.862069 547.310345 211.862069 566.811701 227.671058 582.62069 247.172414 582.62069L776.937913 582.62069ZM247.172414 688.551724C227.671058 688.551724 211.862069 704.360713 211.862069 723.862069 211.862069 743.363425 227.671058 759.172414 247.172414 759.172414L600.386189 759.172414C619.887563 759.172414 635.696534 743.363425 635.696534 723.862069 635.696534 704.360713 619.887563 688.551724 600.386189 688.551724L247.172414 688.551724ZM776.827586 211.940158 741.517241 176.629813 741.517241 247.328574C741.517241 266.829948 757.32623 282.638919 776.827586 282.638919 796.328942 282.638919 812.137931 266.829948 812.137931 247.328574L812.137931 176.629813 812.137931 141.319468 776.827586 141.319468 247.172414 141.319468C227.671058 141.319468 211.862069 157.128439 211.862069 176.629813 211.862069 196.131169 227.671058 211.940158 247.172414 211.940158L776.827586 211.940158ZM282.482759 176.629813C282.482759 157.128439 266.67377 141.319468 247.172414 141.319468 227.671058 141.319468 211.862069 157.128439 211.862069 176.629813L211.862069 247.328574C211.862069 266.829948 227.671058 282.638919 247.172414 282.638919 266.67377 282.638919 282.482759 266.829948 282.482759 247.328574L282.482759 176.629813Z" fill="#389BFF" p-id="4300"></path></svg>'
|
||||
});
|
||||
containerLeft.add(avatar);
|
||||
|
||||
const containerCenter = new VTableGantt.VRender.Group({
|
||||
height,
|
||||
width: width - 30,
|
||||
display: 'flex',
|
||||
flexDirection: 'column'
|
||||
// alignItems: 'left'
|
||||
});
|
||||
container.add(containerCenter as any);
|
||||
const dayNumber = new VTableGantt.VRender.Text({
|
||||
text: String(dateIndex).padStart(2, '0'),
|
||||
fontSize: 20,
|
||||
fontWeight: 'bold',
|
||||
fontFamily: 'sans-serif',
|
||||
fill: '#777',
|
||||
textAlign: 'right',
|
||||
maxLineWidth: width - 30,
|
||||
boundsPadding: [15, 0, 0, 0]
|
||||
});
|
||||
containerCenter.add(dayNumber);
|
||||
|
||||
const weekDay = new VTableGantt.VRender.Text({
|
||||
text: VTableGantt.tools.getWeekday(startDate, 'short').toLocaleUpperCase(),
|
||||
fontSize: 12,
|
||||
fontFamily: 'sans-serif',
|
||||
fill: '#777',
|
||||
boundsPadding: [0, 0, 0, 0]
|
||||
});
|
||||
containerCenter.add(weekDay);
|
||||
return {
|
||||
rootContainer: container
|
||||
// renderDefaultText: true
|
||||
};
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
minDate: '2024-07-20',
|
||||
maxDate: '2024-08-15',
|
||||
markLine: [
|
||||
{
|
||||
date: '2024-07-29',
|
||||
style: {
|
||||
lineWidth: 1,
|
||||
lineColor: 'blue',
|
||||
lineDash: [8, 4]
|
||||
}
|
||||
},
|
||||
{
|
||||
date: '2024-08-17',
|
||||
style: {
|
||||
lineWidth: 2,
|
||||
lineColor: 'red',
|
||||
lineDash: [8, 4]
|
||||
}
|
||||
}
|
||||
],
|
||||
scrollStyle: {
|
||||
scrollRailColor: 'RGBA(246,246,246,0.5)',
|
||||
visible: 'focus',
|
||||
width: 6,
|
||||
scrollSliderCornerRadius: 2,
|
||||
scrollSliderColor: '#5cb85c'
|
||||
}
|
||||
};
|
||||
|
||||
function initVTableGantt() {
|
||||
basicGanttInstance.value = new VTableGantt.Gantt(basicGanttDomRef.value as HTMLElement, getOption(basicGanttOption));
|
||||
linkGanttInstance.value = new VTableGantt.Gantt(linkGanttDomRef.value as HTMLElement, getOption(linkGanttOption));
|
||||
customGanttInstance.value = new VTableGantt.Gantt(
|
||||
customGanttDomRef.value as HTMLElement,
|
||||
getOption(customGanttOption)
|
||||
);
|
||||
}
|
||||
|
||||
function getOption(option: VTableGantt.GanttConstructorOptions) {
|
||||
const isDark = theme.darkMode;
|
||||
if (isDark) {
|
||||
option.taskListTable!.theme = VTableGantt.VTable.themes.DARK;
|
||||
option.timelineHeader.backgroundColor = '#212121';
|
||||
option.underlayBackgroundColor = '#000';
|
||||
} else {
|
||||
option.taskListTable!.theme = VTableGantt.VTable.themes.DEFAULT;
|
||||
option.timelineHeader.backgroundColor = '#f0f0fb';
|
||||
option.underlayBackgroundColor = '#fff';
|
||||
}
|
||||
|
||||
return option;
|
||||
}
|
||||
|
||||
const stopHandle = watch(
|
||||
() => theme.darkMode,
|
||||
_newValue => {
|
||||
basicGanttInstance.value?.release();
|
||||
linkGanttInstance.value?.release();
|
||||
customGanttInstance.value?.release();
|
||||
|
||||
initVTableGantt();
|
||||
}
|
||||
);
|
||||
|
||||
onMounted(() => {
|
||||
initVTableGantt();
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
stopHandle();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ElSpace direction="vertical" fill :size="16">
|
||||
<ElCard header="VTableGantt" class="h-full card-wrapper">
|
||||
<WebSiteLink label="More Demos: " link="https://www.visactor.com/vtable/example" />
|
||||
</ElCard>
|
||||
<ElCard class="h-full card-wrapper">
|
||||
<div ref="basicGanttDomRef" class="relative h-400px"></div>
|
||||
</ElCard>
|
||||
<ElCard class="h-full card-wrapper">
|
||||
<div ref="linkGanttDomRef" class="relative h-400px"></div>
|
||||
</ElCard>
|
||||
<ElCard class="h-full card-wrapper">
|
||||
<div ref="customGanttDomRef" class="relative h-400px"></div>
|
||||
</ElCard>
|
||||
</ElSpace>
|
||||
</template>
|
||||
32
src/views/plugin/icon/icons.ts
Normal file
32
src/views/plugin/icon/icons.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
export const icons = [
|
||||
'mdi:emoticon',
|
||||
'mdi:ab-testing',
|
||||
'ph:alarm',
|
||||
'ph:android-logo',
|
||||
'ph:align-bottom',
|
||||
'ph:archive-box-light',
|
||||
'uil:basketball',
|
||||
'uil:brightness-plus',
|
||||
'uil:capture',
|
||||
'mdi:apps-box',
|
||||
'mdi:alert',
|
||||
'mdi:airballoon',
|
||||
'mdi:airplane-edit',
|
||||
'mdi:alpha-f-box-outline',
|
||||
'mdi:arm-flex-outline',
|
||||
'ic:baseline-10mp',
|
||||
'ic:baseline-access-time',
|
||||
'ic:baseline-brightness-4',
|
||||
'ic:baseline-brightness-5',
|
||||
'ic:baseline-credit-card',
|
||||
'ic:baseline-filter-1',
|
||||
'ic:baseline-filter-2',
|
||||
'ic:baseline-filter-3',
|
||||
'ic:baseline-filter-4',
|
||||
'ic:baseline-filter-5',
|
||||
'ic:baseline-filter-6',
|
||||
'ic:baseline-filter-7',
|
||||
'ic:baseline-filter-8',
|
||||
'ic:baseline-filter-9',
|
||||
'ic:baseline-filter-9-plus'
|
||||
];
|
||||
53
src/views/plugin/icon/index.vue
Normal file
53
src/views/plugin/icon/index.vue
Normal file
@@ -0,0 +1,53 @@
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import { icons } from './icons';
|
||||
|
||||
defineOptions({ name: 'IconPage' });
|
||||
|
||||
const selectValue = ref('');
|
||||
|
||||
const localIcons = ['custom-icon', 'activity', 'at-sign', 'cast', 'chrome', 'copy', 'wind'];
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="h-full">
|
||||
<ElCard header="Icon组件示例" class="card-wrapper">
|
||||
<div class="grid grid-cols-10">
|
||||
<template v-for="item in icons" :key="item">
|
||||
<div class="mt-5px flex-x-center">
|
||||
<SvgIcon :icon="item" class="text-30px" />
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<div class="mt-50px">
|
||||
<h1 class="mb-20px text-18px font-500">Icon图标选择器</h1>
|
||||
<CustomIconSelect v-model:value="selectValue" :icons="icons" />
|
||||
</div>
|
||||
<template #footer>
|
||||
<WebSiteLink label="iconify地址:" link="https://icones.js.org/" class="mt-10px" />
|
||||
</template>
|
||||
</ElCard>
|
||||
<ElCard header="自定义图标示例" class="mt-10px card-wrapper">
|
||||
<div class="pb-12px text-16px">
|
||||
在src/assets/svg-icon文件夹下的svg文件,通过在template里面以 icon-local-{文件名} 直接渲染,
|
||||
其中icon-local为.env文件里的 VITE_ICON_LOCAL_PREFIX
|
||||
</div>
|
||||
<div class="grid grid-cols-10">
|
||||
<div class="mt-5px flex-x-center">
|
||||
<icon-local-activity class="text-40px text-success" />
|
||||
</div>
|
||||
<div class="mt-5px flex-x-center">
|
||||
<icon-local-cast class="text-20px text-error" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="py-12px text-16px">通过SvgIcon组件动态渲染, 菜单通过meta的localIcon属性渲染自定义图标</div>
|
||||
<div class="grid grid-cols-10">
|
||||
<div v-for="(fileName, index) in localIcons" :key="index" class="mt-5px flex-x-center">
|
||||
<SvgIcon :local-icon="fileName" class="text-30px text-primary" />
|
||||
</div>
|
||||
</div>
|
||||
</ElCard>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
32
src/views/plugin/map/components/baidu-map.vue
Normal file
32
src/views/plugin/map/components/baidu-map.vue
Normal file
@@ -0,0 +1,32 @@
|
||||
<script setup lang="ts">
|
||||
import { onMounted, ref } from 'vue';
|
||||
import { useScriptTag } from '@vueuse/core';
|
||||
import { BAIDU_MAP_SDK_URL } from '@/constants/map-sdk';
|
||||
|
||||
defineOptions({ name: 'BaiduMap' });
|
||||
|
||||
window.HOST_TYPE = '2';
|
||||
|
||||
const { load } = useScriptTag(BAIDU_MAP_SDK_URL);
|
||||
|
||||
const domRef = ref<HTMLDivElement>();
|
||||
|
||||
async function renderMap() {
|
||||
await load(true);
|
||||
if (!domRef.value) return;
|
||||
const map = new BMap.Map(domRef.value);
|
||||
const point = new BMap.Point(114.05834626586915, 22.546789983033168);
|
||||
map.centerAndZoom(point, 15);
|
||||
map.enableScrollWheelZoom();
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
renderMap();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div ref="domRef" class="h-full w-full"></div>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
32
src/views/plugin/map/components/gaode-map.vue
Normal file
32
src/views/plugin/map/components/gaode-map.vue
Normal file
@@ -0,0 +1,32 @@
|
||||
<script setup lang="ts">
|
||||
import { onMounted, ref } from 'vue';
|
||||
import { useScriptTag } from '@vueuse/core';
|
||||
import { AMAP_SDK_URL } from '@/constants/map-sdk';
|
||||
|
||||
defineOptions({ name: 'GaodeMap' });
|
||||
|
||||
const { load } = useScriptTag(AMAP_SDK_URL);
|
||||
|
||||
const domRef = ref<HTMLDivElement>();
|
||||
|
||||
async function renderMap() {
|
||||
await load(true);
|
||||
if (!domRef.value) return;
|
||||
const map = new AMap.Map(domRef.value, {
|
||||
zoom: 11,
|
||||
center: [114.05834626586915, 22.546789983033168],
|
||||
viewMode: '3D'
|
||||
});
|
||||
map.getCenter();
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
renderMap();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div ref="domRef" class="h-full w-full"></div>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
5
src/views/plugin/map/components/index.ts
Normal file
5
src/views/plugin/map/components/index.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import BaiduMap from './baidu-map.vue';
|
||||
import GaodeMap from './gaode-map.vue';
|
||||
import TencentMap from './tencent-map.vue';
|
||||
|
||||
export { BaiduMap, GaodeMap, TencentMap };
|
||||
32
src/views/plugin/map/components/tencent-map.vue
Normal file
32
src/views/plugin/map/components/tencent-map.vue
Normal file
@@ -0,0 +1,32 @@
|
||||
<script setup lang="ts">
|
||||
import { onMounted, ref } from 'vue';
|
||||
import { useScriptTag } from '@vueuse/core';
|
||||
import { TENCENT_MAP_SDK_URL } from '@/constants/map-sdk';
|
||||
|
||||
defineOptions({ name: 'TencentMap' });
|
||||
|
||||
const { load } = useScriptTag(TENCENT_MAP_SDK_URL);
|
||||
|
||||
const domRef = ref<HTMLDivElement | null>(null);
|
||||
|
||||
async function renderMap() {
|
||||
await load(true);
|
||||
if (!domRef.value) return;
|
||||
// eslint-disable-next-line no-new
|
||||
new TMap.Map(domRef.value, {
|
||||
center: new TMap.LatLng(39.98412, 116.307484),
|
||||
zoom: 11,
|
||||
viewMode: '3D'
|
||||
});
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
renderMap();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div ref="domRef" class="h-full w-full"></div>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
42
src/views/plugin/map/index.vue
Normal file
42
src/views/plugin/map/index.vue
Normal file
@@ -0,0 +1,42 @@
|
||||
<script setup lang="ts">
|
||||
import { type Component, ref } from 'vue';
|
||||
import { BaiduMap, GaodeMap, TencentMap } from './components';
|
||||
|
||||
defineOptions({ name: 'MapComp' });
|
||||
|
||||
interface Map {
|
||||
id: string;
|
||||
label: string;
|
||||
component: Component;
|
||||
}
|
||||
|
||||
const maps: Map[] = [
|
||||
{ id: 'gaode', label: '高德地图', component: GaodeMap },
|
||||
{ id: 'tencent', label: '腾讯地图', component: TencentMap },
|
||||
{ id: 'baidu', label: '百度地图', component: BaiduMap }
|
||||
];
|
||||
|
||||
const activeMap = ref(maps[0].id);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="h-full">
|
||||
<ElCard header="地图插件" class="h-full" content-style="overflow:hidden">
|
||||
<ElTabs class="h-full">
|
||||
<ElTabPane
|
||||
v-for="item in maps"
|
||||
:key="item.id"
|
||||
v-model="activeMap"
|
||||
class="h-full"
|
||||
:value="item.id"
|
||||
:label="item.label"
|
||||
lazy
|
||||
>
|
||||
<component :is="item.component" />
|
||||
</ElTabPane>
|
||||
</ElTabs>
|
||||
</ElCard>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
91
src/views/plugin/pdf/index.vue
Normal file
91
src/views/plugin/pdf/index.vue
Normal file
@@ -0,0 +1,91 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, shallowRef } from 'vue';
|
||||
import VuePdfEmbed from 'vue-pdf-embed';
|
||||
import { useLoading } from '@sa/hooks';
|
||||
|
||||
defineOptions({ name: 'PdfPage' });
|
||||
|
||||
const { loading, endLoading } = useLoading(true);
|
||||
|
||||
const pdfRef = shallowRef<InstanceType<typeof VuePdfEmbed> | null>(null);
|
||||
const source = `https://xiaoxian521.github.io/hyperlink/pdf/Cookie%E5%92%8CSession%E5%8C%BA%E5%88%AB%E7%94%A8%E6%B3%95.pdf`;
|
||||
|
||||
const showAllPages = ref(false);
|
||||
const currentPage = ref<number>(1);
|
||||
const pageCount = ref(1);
|
||||
|
||||
function onPdfRendered() {
|
||||
endLoading();
|
||||
|
||||
if (pdfRef.value?.doc) {
|
||||
pageCount.value = pdfRef.value.doc.numPages;
|
||||
}
|
||||
}
|
||||
|
||||
function showAllPagesChange() {
|
||||
currentPage.value = 1;
|
||||
}
|
||||
|
||||
const rotations = [0, 90, 180, 270];
|
||||
const currentRotation = ref(0);
|
||||
|
||||
function handleRotate() {
|
||||
currentRotation.value = (currentRotation.value + 1) % 4;
|
||||
}
|
||||
|
||||
async function handlePrint() {
|
||||
await pdfRef.value?.print(undefined, 'test.pdf', true);
|
||||
}
|
||||
|
||||
async function handleDownload() {
|
||||
await pdfRef.value?.download('test.pdf');
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="overflow-hidden">
|
||||
<ElCard header="PDF 预览" class="h-full card-wrapper" content-class="overflow-hidden">
|
||||
<div class="h-[calc(100%-30px)] flex-col-stretch">
|
||||
<GithubLink link="https://github.com/hrynko/vue-pdf-embed" />
|
||||
<WebSiteLink label="文档地址:" link="https://www.npmjs.com/package/vue-pdf-embed" />
|
||||
<div class="flex-y-center justify-end gap-12px">
|
||||
<ElCheckbox v-model="showAllPages" @change="showAllPagesChange">显示所有页面</ElCheckbox>
|
||||
<ButtonIcon tooltip-content="旋转90度" @click="handleRotate">
|
||||
<icon-material-symbols-light:rotate-90-degrees-ccw-outline-rounded />
|
||||
</ButtonIcon>
|
||||
<ButtonIcon tooltip-content="打印" @click="handlePrint">
|
||||
<icon-mdi:printer />
|
||||
</ButtonIcon>
|
||||
<ButtonIcon tooltip-content="下载" @click="handleDownload">
|
||||
<icon-charm:download />
|
||||
</ButtonIcon>
|
||||
</div>
|
||||
<ElScrollbar class="flex-1-hidden">
|
||||
<NSkeleton v-if="loading" size="small" class="mt-12px" text :repeat="12" />
|
||||
<VuePdfEmbed
|
||||
ref="pdfRef"
|
||||
class="container overflow-auto"
|
||||
:class="{ 'h-0': loading }"
|
||||
:rotation="rotations[currentRotation]"
|
||||
:page="currentPage"
|
||||
:source="source"
|
||||
@rendered="onPdfRendered"
|
||||
/>
|
||||
</ElScrollbar>
|
||||
<div class="flex-y-center justify-between">
|
||||
<div v-if="showAllPages" class="text-18px font-medium">共{{ pageCount }}页</div>
|
||||
<ElPagination
|
||||
v-else
|
||||
key="pdf-page"
|
||||
layout="prev, pager, next"
|
||||
background
|
||||
:page-count="pageCount"
|
||||
@current-change="currentPage = $event"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</ElCard>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
59
src/views/plugin/pinyin/index.vue
Normal file
59
src/views/plugin/pinyin/index.vue
Normal file
@@ -0,0 +1,59 @@
|
||||
<script setup lang="ts">
|
||||
import { onMounted, ref } from 'vue';
|
||||
import { html } from 'pinyin-pro';
|
||||
import domPurify from 'dompurify';
|
||||
|
||||
defineOptions({ name: 'PinyinPage' });
|
||||
|
||||
const domRef = ref<HTMLElement | null>(null);
|
||||
const domRef2 = ref<HTMLElement | null>(null);
|
||||
const domRef3 = ref<HTMLElement | null>(null);
|
||||
|
||||
function renderHtml() {
|
||||
if (!domRef.value || !domRef2.value || !domRef3.value) return;
|
||||
|
||||
const text = 'CN-RDMS 是灿能电力内部使用的研发管理系统前端项目';
|
||||
|
||||
const code = domPurify.sanitize(html(text));
|
||||
const code2 = domPurify.sanitize(html(text, { toneType: 'none' }));
|
||||
|
||||
domRef.value.innerHTML = code;
|
||||
domRef2.value.innerHTML = code2;
|
||||
domRef3.value.innerHTML = code;
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
renderHtml();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<ElCard header="pinyin 插件" class="h-full card-wrapper">
|
||||
<ElSpace :vertical="true">
|
||||
<GithubLink link="https://github.com/zh-lx/pinyin-pro" />
|
||||
<WebSiteLink label="文档地址:" link="https://pinyin-pro.cn/" />
|
||||
</ElSpace>
|
||||
<ElDivider content-position="left">常规使用</ElDivider>
|
||||
<p ref="domRef" class="text-18px"></p>
|
||||
<ElDivider content-position="left">不带音调</ElDivider>
|
||||
<p ref="domRef2" class="text-18px"></p>
|
||||
<ElDivider content-position="left">自定义样式</ElDivider>
|
||||
<p ref="domRef3" class="custom-style text-18px"></p>
|
||||
</ElCard>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.custom-style {
|
||||
:deep(.py-result-item) {
|
||||
.py-chinese-item {
|
||||
--uno: text-primary;
|
||||
}
|
||||
|
||||
.py-pinyin-item {
|
||||
--uno: text-error;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
41
src/views/plugin/print/index.vue
Normal file
41
src/views/plugin/print/index.vue
Normal file
@@ -0,0 +1,41 @@
|
||||
<script lang="ts" setup>
|
||||
import printJS from 'print-js';
|
||||
|
||||
defineOptions({ name: 'PrintPage' });
|
||||
|
||||
function printTable() {
|
||||
printJS({
|
||||
printable: [
|
||||
{ name: 'CN-RDMS', wechat: 'internal', remark: '内部演示数据' },
|
||||
{ name: 'CN-RDMS', wechat: 'internal', remark: '内部演示数据' }
|
||||
],
|
||||
properties: ['name', 'wechat', 'remark'],
|
||||
type: 'json'
|
||||
});
|
||||
}
|
||||
function printImage() {
|
||||
printJS({
|
||||
printable: [
|
||||
'https://i.loli.net/2021/11/24/1J6REWXiHomU2kM.jpg',
|
||||
'https://i.loli.net/2021/11/24/1J6REWXiHomU2kM.jpg'
|
||||
],
|
||||
type: 'image',
|
||||
header: 'Multiple Images',
|
||||
imageStyle: 'width:100%;'
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="h-full">
|
||||
<ElCard header="打印" class="card-wrapper">
|
||||
<ElButton type="primary" class="mr-10px" @click="printTable">打印表格</ElButton>
|
||||
<ElButton type="primary" @click="printImage">打印图片</ElButton>
|
||||
<template #footer>
|
||||
<GithubLink label="printJS:" link="https://github.com/crabbly/Print.js" class="mt-10px" />
|
||||
</template>
|
||||
</ElCard>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
60
src/views/plugin/swiper/index.vue
Normal file
60
src/views/plugin/swiper/index.vue
Normal file
@@ -0,0 +1,60 @@
|
||||
<script setup lang="ts">
|
||||
import SwiperCore from 'swiper';
|
||||
import { Navigation, Pagination } from 'swiper/modules';
|
||||
import { Swiper, SwiperSlide } from 'swiper/vue';
|
||||
import type { SwiperOptions } from 'swiper/types';
|
||||
|
||||
defineOptions({ name: 'SwiperComp' });
|
||||
|
||||
type SwiperExampleOptions = Pick<
|
||||
SwiperOptions,
|
||||
'navigation' | 'pagination' | 'scrollbar' | 'slidesPerView' | 'slidesPerGroup' | 'spaceBetween' | 'direction' | 'loop'
|
||||
>;
|
||||
|
||||
interface SwiperExample {
|
||||
id: number;
|
||||
label: string;
|
||||
options: Partial<SwiperExampleOptions>;
|
||||
}
|
||||
|
||||
SwiperCore.use([Navigation, Pagination]);
|
||||
|
||||
const swiperExample: SwiperExample[] = [
|
||||
{ id: 0, label: 'Default', options: {} },
|
||||
{ id: 1, label: 'Navigation', options: { navigation: true } },
|
||||
{ id: 2, label: 'Pagination', options: { pagination: true } },
|
||||
{ id: 3, label: 'Pagination dynamic', options: { pagination: { dynamicBullets: true } } },
|
||||
{ id: 4, label: 'Pagination progress', options: { navigation: true, pagination: { type: 'progressbar' } } },
|
||||
{ id: 5, label: 'Pagination fraction', options: { navigation: true, pagination: { type: 'fraction' } } },
|
||||
{ id: 6, label: 'Slides per view', options: { pagination: { clickable: true }, slidesPerView: 3, spaceBetween: 30 } },
|
||||
{ id: 7, label: 'Infinite loop', options: { navigation: true, pagination: { clickable: true }, loop: true } }
|
||||
];
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<ElCard header="Swiper插件" class="card-wrapper">
|
||||
<ElSpace :vertical="true">
|
||||
<GithubLink link="https://github.com/nolimits4web/swiper" />
|
||||
<WebSiteLink label="vue3版文档地址:" link="https://swiperjs.com/vue" />
|
||||
<WebSiteLink label="插件demo地址:" link="https://swiperjs.com/demos" />
|
||||
</ElSpace>
|
||||
<ElSpace class="w-full" direction="vertical">
|
||||
<div v-for="item in swiperExample" :key="item.id" class="w-full">
|
||||
<h3 class="py-24px text-24px font-bold">{{ item.label }}</h3>
|
||||
<Swiper v-bind="item.options">
|
||||
<SwiperSlide v-for="i in 5" :key="i">
|
||||
<div class="h-240px w-full flex-center border-1px border-#999 text-18px font-bold">Slide{{ i }}</div>
|
||||
</SwiperSlide>
|
||||
</Swiper>
|
||||
</div>
|
||||
</ElSpace>
|
||||
</ElCard>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
:deep(.el-space__item) {
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
1044
src/views/plugin/tables/vtable/data.ts
Normal file
1044
src/views/plugin/tables/vtable/data.ts
Normal file
File diff suppressed because it is too large
Load Diff
408
src/views/plugin/tables/vtable/index.vue
Normal file
408
src/views/plugin/tables/vtable/index.vue
Normal file
@@ -0,0 +1,408 @@
|
||||
<script setup lang="tsx">
|
||||
import { computed, onMounted, ref } from 'vue';
|
||||
import {
|
||||
Group,
|
||||
Image,
|
||||
ListColumn,
|
||||
ListTable,
|
||||
Menu,
|
||||
PivotChart,
|
||||
PivotColumnDimension,
|
||||
PivotCorner,
|
||||
PivotIndicator,
|
||||
PivotRowDimension,
|
||||
PivotTable,
|
||||
Tag,
|
||||
Text,
|
||||
VTable,
|
||||
registerChartModule
|
||||
} from '@visactor/vue-vtable';
|
||||
import VChart from '@visactor/vchart';
|
||||
import { useThemeStore } from '@/store/modules/theme';
|
||||
import { customListRecords, listTableRecords, pivotChartColumns, pivotChartIndicators, pivotChartRows } from './data';
|
||||
|
||||
registerChartModule('vchart', VChart);
|
||||
const titleColorPool = ['#3370ff', '#34c724', '#ff9f1a', '#ff4050', '#1f2329'];
|
||||
|
||||
const themeStore = useThemeStore();
|
||||
|
||||
// list table
|
||||
const listTableRef = ref(null);
|
||||
const listOptions = computed(() => {
|
||||
const options = {
|
||||
theme: themeStore.darkMode ? VTable.themes.DARK : VTable.themes.DEFAULT
|
||||
};
|
||||
return options;
|
||||
});
|
||||
const listRecords = ref<Record<string, string | number>[]>(listTableRecords);
|
||||
|
||||
// group table
|
||||
const groupTableRef = ref(null);
|
||||
const groupOptions = computed(() => {
|
||||
const options = {
|
||||
groupBy: ['Category', 'Sub-Category'],
|
||||
theme: (themeStore.darkMode ? VTable.themes.DARK : VTable.themes.DEFAULT).extends({
|
||||
groupTitleStyle: {
|
||||
fontWeight: 'bold',
|
||||
bgColor: (args: any) => {
|
||||
const { col, row, table } = args;
|
||||
const index = table.getGroupTitleLevel(col, row);
|
||||
if (index !== undefined) {
|
||||
return titleColorPool[index % titleColorPool.length] as string;
|
||||
}
|
||||
return 'white';
|
||||
}
|
||||
}
|
||||
})
|
||||
};
|
||||
return options;
|
||||
});
|
||||
const groupRecords = ref<Record<string, string | number>[]>(listTableRecords);
|
||||
|
||||
// pivot table
|
||||
const pivotTableRef = ref(null);
|
||||
const pivotTableOptions = computed(() => {
|
||||
return {
|
||||
tooltip: {
|
||||
isShowOverflowTextTooltip: true
|
||||
},
|
||||
dataConfig: {
|
||||
sortRules: [
|
||||
{
|
||||
sortField: 'Category',
|
||||
sortBy: ['Office Supplies', 'Technology', 'Furniture']
|
||||
}
|
||||
]
|
||||
},
|
||||
widthMode: 'standard',
|
||||
theme: themeStore.darkMode ? VTable.themes.DARK : VTable.themes.DEFAULT,
|
||||
emptyTip: {
|
||||
text: 'no data records'
|
||||
}
|
||||
};
|
||||
});
|
||||
const pivotTableIndicators = ref([
|
||||
{
|
||||
indicatorKey: 'Quantity',
|
||||
title: 'Quantity',
|
||||
width: 'auto',
|
||||
showSort: false,
|
||||
headerStyle: { fontWeight: 'normal' },
|
||||
style: {
|
||||
padding: [16, 28, 16, 28],
|
||||
color(args: any) {
|
||||
return args.dataValue >= 0 ? 'black' : 'red';
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
indicatorKey: 'Sales',
|
||||
title: 'Sales',
|
||||
width: 'auto',
|
||||
showSort: false,
|
||||
headerStyle: { fontWeight: 'normal' },
|
||||
format: (rec: string) => `$${Number(rec).toFixed(2)}`,
|
||||
style: {
|
||||
padding: [16, 28, 16, 28],
|
||||
color(args: any) {
|
||||
return args.dataValue >= 0 ? 'black' : 'red';
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
indicatorKey: 'Profit',
|
||||
title: 'Profit',
|
||||
width: 'auto',
|
||||
showSort: false,
|
||||
headerStyle: { fontWeight: 'normal' },
|
||||
format: (rec: string) => `$${Number(rec).toFixed(2)}`,
|
||||
style: {
|
||||
padding: [16, 28, 16, 28],
|
||||
color(args: any) {
|
||||
return args.dataValue >= 0 ? 'black' : 'red';
|
||||
}
|
||||
}
|
||||
}
|
||||
]);
|
||||
const pivotTableRows = ref([
|
||||
{
|
||||
dimensionKey: 'City',
|
||||
title: 'City',
|
||||
headerStyle: { textStick: true },
|
||||
width: 'auto'
|
||||
}
|
||||
]);
|
||||
const pivotTableRecords = ref([]);
|
||||
|
||||
// pivot chart
|
||||
const pivotChartRef = ref(null);
|
||||
const pivotChartOptions = computed(() => {
|
||||
return {
|
||||
rows: pivotChartRows,
|
||||
columns: pivotChartColumns,
|
||||
indicators: pivotChartIndicators,
|
||||
indicatorsAsCol: false,
|
||||
defaultRowHeight: 200,
|
||||
defaultHeaderRowHeight: 50,
|
||||
defaultColWidth: 280,
|
||||
defaultHeaderColWidth: 100,
|
||||
indicatorTitle: '指标',
|
||||
autoWrapText: true,
|
||||
corner: {
|
||||
titleOnDimension: 'row',
|
||||
headerStyle: { autoWrapText: true }
|
||||
},
|
||||
legends: {
|
||||
orient: 'bottom',
|
||||
type: 'discrete',
|
||||
data: [
|
||||
{ label: 'Consumer-Quantity', shape: { fill: '#2E62F1', symbolType: 'circle' } },
|
||||
{ label: 'Consumer-Quantity', shape: { fill: '#4DC36A', symbolType: 'square' } },
|
||||
{ label: 'Home Office-Quantity', shape: { fill: '#FF8406', symbolType: 'square' } },
|
||||
{ label: 'Consumer-Sales', shape: { fill: '#FFCC00', symbolType: 'square' } },
|
||||
{ label: 'Consumer-Sales', shape: { fill: '#4F44CF', symbolType: 'square' } },
|
||||
{ label: 'Home Office-Sales', shape: { fill: '#5AC8FA', symbolType: 'square' } },
|
||||
{ label: 'Consumer-Profit', shape: { fill: '#003A8C', symbolType: 'square' } },
|
||||
{ label: 'Consumer-Profit', shape: { fill: '#B08AE2', symbolType: 'square' } },
|
||||
{ label: 'Home Office-Profit', shape: { fill: '#FF6341', symbolType: 'square' } }
|
||||
]
|
||||
},
|
||||
theme: (themeStore.darkMode ? VTable.themes.DARK : VTable.themes.DEFAULT).extends({
|
||||
bodyStyle: { borderColor: 'gray', borderLineWidth: [1, 0, 0, 1] },
|
||||
headerStyle: { borderColor: 'gray', borderLineWidth: [0, 0, 1, 1], hover: { cellBgColor: '#CCE0FF' } },
|
||||
rowHeaderStyle: { borderColor: 'gray', borderLineWidth: [1, 1, 0, 0], hover: { cellBgColor: '#CCE0FF' } },
|
||||
cornerHeaderStyle: { borderColor: 'gray', borderLineWidth: [0, 1, 1, 0], hover: { cellBgColor: '' } },
|
||||
cornerRightTopCellStyle: { borderColor: 'gray', borderLineWidth: [0, 0, 1, 1], hover: { cellBgColor: '' } },
|
||||
cornerLeftBottomCellStyle: { borderColor: 'gray', borderLineWidth: [1, 1, 0, 0], hover: { cellBgColor: '' } },
|
||||
cornerRightBottomCellStyle: { borderColor: 'gray', borderLineWidth: [1, 0, 0, 1], hover: { cellBgColor: '' } },
|
||||
rightFrozenStyle: { borderColor: 'gray', borderLineWidth: [1, 0, 1, 1], hover: { cellBgColor: '' } },
|
||||
bottomFrozenStyle: { borderColor: 'gray', borderLineWidth: [1, 1, 0, 1], hover: { cellBgColor: '' } },
|
||||
selectionStyle: { cellBgColor: '', cellBorderColor: '' },
|
||||
frameStyle: { borderLineWidth: 0 }
|
||||
}),
|
||||
emptyTip: {
|
||||
text: 'no data records'
|
||||
}
|
||||
};
|
||||
});
|
||||
const pivotChartRecords = ref([] as any);
|
||||
const handleLegendItemClick = (args: { value: any }) => {
|
||||
(pivotChartRef?.value as any)?.vTableInstance.updateFilterRules([
|
||||
{
|
||||
filterKey: 'Segment-Indicator',
|
||||
filteredValues: args.value
|
||||
}
|
||||
]);
|
||||
};
|
||||
|
||||
// custom layout list table
|
||||
const customLayoutListTableRef = ref(null);
|
||||
const customLayoutListTableOptions = computed(() => {
|
||||
return {
|
||||
defaultRowHeight: 80,
|
||||
theme: themeStore.darkMode ? VTable.themes.DARK : VTable.themes.DEFAULT
|
||||
};
|
||||
});
|
||||
const customLayoutListTableRecords = ref(customListRecords);
|
||||
const customLayoutListTableColumnStyle = ref({ fontFamily: 'Arial', fontSize: 12, fontWeight: 'bold' });
|
||||
|
||||
onMounted(() => {
|
||||
// pivot tablt records
|
||||
fetch('https://lf9-dp-fe-cms-tos.byteorg.com/obj/bit-cloud/VTable/North_American_Superstore_Pivot_data.json')
|
||||
.then(res => res.json())
|
||||
.then(jsonData => {
|
||||
// update record
|
||||
pivotTableRecords.value = jsonData;
|
||||
});
|
||||
|
||||
// pivot chart records
|
||||
fetch('https://lf9-dp-fe-cms-tos.byteorg.com/obj/bit-cloud/VTable/North_American_Superstore_Pivot_Chart_data.json')
|
||||
.then(res => res.json())
|
||||
.then(data => {
|
||||
// update record
|
||||
pivotChartRecords.value = data;
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="h-full">
|
||||
<ElSpace fill direction="vertical" class="mb-16px w-full" :size="16">
|
||||
<ElCard header="List Table" class="h-full w-2/3 card-wrapper">
|
||||
<ListTable ref="listTableRef" :options="listOptions" :records="listRecords" height="400px">
|
||||
<ListColumn field="Order ID" title="Order ID" width="auto" />
|
||||
<ListColumn field="Customer ID" title="Customer ID" width="auto" />
|
||||
<ListColumn field="Product Name" title="Product Name" width="auto" />
|
||||
<ListColumn field="Category" title="Category" width="auto" />
|
||||
<ListColumn field="Sub-Category" title="Sub-Category" width="auto" />
|
||||
<ListColumn field="Region" title="Region" width="auto" />
|
||||
<ListColumn field="City" title="City" width="auto" />
|
||||
<ListColumn field="Order Date" title="Order Date" width="auto" />
|
||||
<ListColumn field="Quantity" title="Quantity" width="auto" />
|
||||
<ListColumn field="Sales" title="Sales" width="auto" />
|
||||
<ListColumn field="Profit" title="Profit" width="auto" />
|
||||
</ListTable>
|
||||
</ElCard>
|
||||
|
||||
<ElCard header="Group Table" class="h-full w-2/3 card-wrapper">
|
||||
<ListTable ref="groupTableRef" :options="groupOptions" :records="groupRecords" height="400px">
|
||||
<ListColumn field="Order ID" title="Order ID" width="auto" />
|
||||
<ListColumn field="Customer ID" title="Customer ID" width="auto" />
|
||||
<ListColumn field="Product Name" title="Product Name" width="auto" />
|
||||
<ListColumn field="Category" title="Category" width="auto" />
|
||||
<ListColumn field="Sub-Category" title="Sub-Category" width="auto" />
|
||||
<ListColumn field="Region" title="Region" width="auto" />
|
||||
<ListColumn field="City" title="City" width="auto" />
|
||||
<ListColumn field="Order Date" title="Order Date" width="auto" />
|
||||
<ListColumn field="Quantity" title="Quantity" width="auto" />
|
||||
<ListColumn field="Sales" title="Sales" width="auto" />
|
||||
<ListColumn field="Profit" title="Profit" width="auto" />
|
||||
</ListTable>
|
||||
</ElCard>
|
||||
|
||||
<ElCard header="Pivot Table" class="h-full w-2/3 card-wrapper">
|
||||
<PivotTable ref="pivotTableRef" :options="pivotTableOptions" :records="pivotTableRecords" height="400px">
|
||||
<PivotColumnDimension
|
||||
title="Category"
|
||||
dimension-key="Category"
|
||||
:header-style="{ textStick: true }"
|
||||
width="auto"
|
||||
/>
|
||||
<PivotRowDimension
|
||||
v-for="(row, index) in pivotTableRows"
|
||||
:key="index"
|
||||
:dimension-key="row.dimensionKey"
|
||||
:title="row.title"
|
||||
:header-style="row.headerStyle"
|
||||
:width="row.width"
|
||||
/>
|
||||
<PivotIndicator
|
||||
v-for="(indicator, index) in pivotTableIndicators"
|
||||
:key="index"
|
||||
:indicator-key="indicator.indicatorKey"
|
||||
:title="indicator.title"
|
||||
:width="indicator.width"
|
||||
:show-sort="indicator.showSort"
|
||||
:header-style="indicator.headerStyle"
|
||||
:format="indicator.format"
|
||||
:style="indicator.style"
|
||||
/>
|
||||
<PivotCorner title-on-dimension="row" />
|
||||
<Menu menu-type="html" :context-menu-items="['copy', 'paste', 'delete', '...']" />
|
||||
</PivotTable>
|
||||
</ElCard>
|
||||
|
||||
<ElCard header="Pivot Chart" class="h-full w-2/3 card-wrapper">
|
||||
<PivotChart
|
||||
ref="pivotChartRef"
|
||||
:options="pivotChartOptions"
|
||||
:records="pivotChartRecords"
|
||||
height="800px"
|
||||
@on-legend-item-click="handleLegendItemClick"
|
||||
/>
|
||||
</ElCard>
|
||||
|
||||
<ElCard header="Custom Component" class="h-full w-2/3 card-wrapper">
|
||||
<ListTable
|
||||
ref="customLayoutListTableRef"
|
||||
:options="customLayoutListTableOptions"
|
||||
:records="customLayoutListTableRecords"
|
||||
height="400px"
|
||||
>
|
||||
<!-- Order Number Column -->
|
||||
<ListColumn field="bloggerId" title="Order Number" width="100" />
|
||||
|
||||
<!-- Anchor Nickname Column with Custom Layout -->
|
||||
<ListColumn field="bloggerName" title="Anchor Nickname" :width="330">
|
||||
<template #customLayout="{ record, height, width }">
|
||||
<Group :height="height" :width="width" display="flex" flex-direction="row" flex-wrap="nowrap">
|
||||
<!-- Avatar Group -->
|
||||
<Group
|
||||
:height="height"
|
||||
:width="60"
|
||||
display="flex"
|
||||
flex-direction="column"
|
||||
align-items="center"
|
||||
justify-content="space-around"
|
||||
fill="red"
|
||||
:opacity="0.1"
|
||||
>
|
||||
<Image id="icon0" :width="50" :height="50" :image="record.bloggerAvatar" :corner-radius="25" />
|
||||
</Group>
|
||||
<!-- Blogger Info Group -->
|
||||
<Group :height="height" :width="width - 60" display="flex" flex-direction="column" flex-wrap="nowrap">
|
||||
<Group
|
||||
:height="height / 2"
|
||||
:width="width - 60"
|
||||
display="flex"
|
||||
flex-wrap="wrap"
|
||||
align-items="center"
|
||||
fill="orange"
|
||||
:opacity="0.1"
|
||||
>
|
||||
<Text
|
||||
:text="record.bloggerName"
|
||||
:font-size="13"
|
||||
font-family="sans-serif"
|
||||
fill="black"
|
||||
:bounds-padding="[0, 0, 0, 10]"
|
||||
/>
|
||||
<Image
|
||||
id="location"
|
||||
image="https://lf9-dp-fe-cms-tos.byteorg.com/obj/bit-cloud/VTable/location.svg"
|
||||
:width="15"
|
||||
:height="15"
|
||||
:bounds-padding="[0, 0, 0, 10]"
|
||||
cursor="pointer"
|
||||
/>
|
||||
<Text :text="record.city" :font-size="11" font-family="sans-serif" fill="#6f7070" />
|
||||
</Group>
|
||||
<!-- Tags Group -->
|
||||
<Group
|
||||
:height="height / 2"
|
||||
:width="width - 60"
|
||||
display="flex"
|
||||
align-items="center"
|
||||
fill="yellow"
|
||||
:opacity="0.1"
|
||||
>
|
||||
<Tag
|
||||
v-for="tag in record?.tags"
|
||||
:key="tag"
|
||||
:text="tag"
|
||||
:text-style="{ fontSize: 10, fontFamily: 'sans-serif', fill: 'rgb(51, 101, 238)' }"
|
||||
:panel="{ visible: true, fill: '#f4f4f2', cornerRadius: 5 }"
|
||||
:space="5"
|
||||
:bounds-padding="[0, 0, 0, 5]"
|
||||
/>
|
||||
</Group>
|
||||
</Group>
|
||||
</Group>
|
||||
</template>
|
||||
</ListColumn>
|
||||
|
||||
<!-- Other Columns -->
|
||||
<ListColumn
|
||||
field="fansCount"
|
||||
title="Fans Count"
|
||||
width="120"
|
||||
:field-format="rec => rec.fansCount + 'w'"
|
||||
:style="customLayoutListTableColumnStyle"
|
||||
/>
|
||||
<ListColumn field="worksCount" title="Works Count" :style="customLayoutListTableColumnStyle" width="135" />
|
||||
<ListColumn
|
||||
field="viewCount"
|
||||
title="View Count"
|
||||
width="120"
|
||||
:field-format="rec => rec.viewCount + 'w'"
|
||||
:style="customLayoutListTableColumnStyle"
|
||||
/>
|
||||
</ListTable>
|
||||
</ElCard>
|
||||
|
||||
<ElCard class="h-full w-2/3 card-wrapper">
|
||||
<WebSiteLink label="More VTable Demos: " link="https://www.visactor.com/vtable/example" />
|
||||
</ElCard>
|
||||
</ElSpace>
|
||||
</div>
|
||||
</template>
|
||||
44
src/views/plugin/typeit/index.vue
Normal file
44
src/views/plugin/typeit/index.vue
Normal file
@@ -0,0 +1,44 @@
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, shallowRef } from 'vue';
|
||||
import TypeIt from 'typeit';
|
||||
import type { Options } from 'typeit';
|
||||
import type { El } from 'typeit/dist/types';
|
||||
|
||||
defineOptions({ name: 'TypeIt' });
|
||||
|
||||
const textRef = shallowRef<El>();
|
||||
|
||||
function init() {
|
||||
if (!textRef.value) return;
|
||||
|
||||
const options: Options = {
|
||||
strings: 'CN-RDMS 是灿能电力内部使用的研发管理系统前端项目',
|
||||
lifeLike: true,
|
||||
speed: 120,
|
||||
loop: true
|
||||
};
|
||||
|
||||
const initTypeIt = new TypeIt(textRef.value, options);
|
||||
|
||||
initTypeIt.go();
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
init();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<ElCard header="打字机 插件" class="h-full card-wrapper">
|
||||
<ElSpace direction="vertical">
|
||||
<GithubLink link="https://github.com/alexmacarthur/typeit" />
|
||||
<WebSiteLink label="文档地址:" link="https://www.typeitjs.com/docs/vanilla/usage/" />
|
||||
</ElSpace>
|
||||
<ElDivider content-position="left">基本示例</ElDivider>
|
||||
<span ref="textRef" class="text-18px"></span>
|
||||
</ElCard>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
43
src/views/plugin/video/index.vue
Normal file
43
src/views/plugin/video/index.vue
Normal file
@@ -0,0 +1,43 @@
|
||||
<script setup lang="ts">
|
||||
import { onMounted, onUnmounted, ref } from 'vue';
|
||||
import Player from 'xgplayer';
|
||||
import 'xgplayer/dist/index.min.css';
|
||||
|
||||
defineOptions({ name: 'VideoComp' });
|
||||
|
||||
const domRef = ref<HTMLElement>();
|
||||
const player = ref<Player>();
|
||||
|
||||
function renderXgPlayer() {
|
||||
if (!domRef.value) return;
|
||||
const url = 'https://lf9-cdn-tos.bytecdntp.com/cdn/expire-1-M/byted-player-videos/1.0.0/xgplayer-demo.mp4';
|
||||
player.value = new Player({
|
||||
el: domRef.value,
|
||||
url,
|
||||
playbackRate: [0.5, 0.75, 1, 1.5, 2]
|
||||
});
|
||||
}
|
||||
function destroyXgPlayer() {
|
||||
player.value?.destroy();
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
renderXgPlayer();
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
destroyXgPlayer();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<ElCard header="视频播放器插件" class="h-full card-wrapper">
|
||||
<div class="flex-center">
|
||||
<div ref="domRef" class="h-auto w-full shadow-md"></div>
|
||||
</div>
|
||||
</ElCard>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
Reference in New Issue
Block a user