refactor(projects): 删除废弃代码
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"generatedAt": "2026-06-01T01:55:51.875Z",
|
"generatedAt": "2026-06-05T03:08:01.803Z",
|
||||||
"description": "Frontend visible page resource whitelist for backend route/menu configuration.",
|
"description": "Frontend visible page resource whitelist for backend route/menu configuration.",
|
||||||
"rules": {
|
"rules": {
|
||||||
"directoryComponent": "layout.base",
|
"directoryComponent": "layout.base",
|
||||||
@@ -437,39 +437,6 @@
|
|||||||
"pageType": "leaf",
|
"pageType": "leaf",
|
||||||
"source": "generated"
|
"source": "generated"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"name": "personal-center_pending-approval",
|
|
||||||
"path": "/personal-center/pending-approval",
|
|
||||||
"component": "view.personal-center_pending-approval",
|
|
||||||
"title": "待我审批",
|
|
||||||
"routeTitle": "personal-center_pending-approval",
|
|
||||||
"i18nKey": "route.personal-center_pending-approval",
|
|
||||||
"icon": "mdi:check-decagram-outline",
|
|
||||||
"localIcon": null,
|
|
||||||
"order": 5,
|
|
||||||
"hideInMenu": false,
|
|
||||||
"keepAlive": true,
|
|
||||||
"activeMenu": null,
|
|
||||||
"multiTab": false,
|
|
||||||
"fixedIndexInTab": null,
|
|
||||||
"redirect": null,
|
|
||||||
"props": null,
|
|
||||||
"meta": {
|
|
||||||
"title": "待我审批",
|
|
||||||
"i18nKey": "route.personal-center_pending-approval",
|
|
||||||
"icon": "mdi:check-decagram-outline",
|
|
||||||
"localIcon": null,
|
|
||||||
"order": 5,
|
|
||||||
"keepAlive": true,
|
|
||||||
"hideInMenu": false,
|
|
||||||
"activeMenu": null,
|
|
||||||
"multiTab": false,
|
|
||||||
"fixedIndexInTab": null
|
|
||||||
},
|
|
||||||
"parentName": "personal-center",
|
|
||||||
"pageType": "leaf",
|
|
||||||
"source": "generated"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"name": "personal-center_overtime-application",
|
"name": "personal-center_overtime-application",
|
||||||
"path": "/personal-center/overtime-application",
|
"path": "/personal-center/overtime-application",
|
||||||
@@ -503,6 +470,39 @@
|
|||||||
"pageType": "leaf",
|
"pageType": "leaf",
|
||||||
"source": "generated"
|
"source": "generated"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "personal-center_pending-approval",
|
||||||
|
"path": "/personal-center/pending-approval",
|
||||||
|
"component": "view.personal-center_pending-approval",
|
||||||
|
"title": "待我审批",
|
||||||
|
"routeTitle": "personal-center_pending-approval",
|
||||||
|
"i18nKey": "route.personal-center_pending-approval",
|
||||||
|
"icon": "mdi:check-decagram-outline",
|
||||||
|
"localIcon": null,
|
||||||
|
"order": 7,
|
||||||
|
"hideInMenu": false,
|
||||||
|
"keepAlive": true,
|
||||||
|
"activeMenu": null,
|
||||||
|
"multiTab": false,
|
||||||
|
"fixedIndexInTab": null,
|
||||||
|
"redirect": null,
|
||||||
|
"props": null,
|
||||||
|
"meta": {
|
||||||
|
"title": "待我审批",
|
||||||
|
"i18nKey": "route.personal-center_pending-approval",
|
||||||
|
"icon": "mdi:check-decagram-outline",
|
||||||
|
"localIcon": null,
|
||||||
|
"order": 7,
|
||||||
|
"keepAlive": true,
|
||||||
|
"hideInMenu": false,
|
||||||
|
"activeMenu": null,
|
||||||
|
"multiTab": false,
|
||||||
|
"fixedIndexInTab": null
|
||||||
|
},
|
||||||
|
"parentName": "personal-center",
|
||||||
|
"pageType": "leaf",
|
||||||
|
"source": "generated"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "system_user",
|
"name": "system_user",
|
||||||
"path": "/system/user",
|
"path": "/system/user",
|
||||||
|
|||||||
24
package.json
24
package.json
@@ -37,61 +37,39 @@
|
|||||||
"update-pkg": "sa update-pkg"
|
"update-pkg": "sa update-pkg"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@antv/data-set": "0.11.8",
|
|
||||||
"@antv/g2": "5.4.0",
|
|
||||||
"@antv/g6": "5.0.49",
|
|
||||||
"@better-scroll/core": "2.5.1",
|
"@better-scroll/core": "2.5.1",
|
||||||
"@iconify-vue/mingcute": "^1.0.5",
|
|
||||||
"@iconify/vue": "5.0.0",
|
"@iconify/vue": "5.0.0",
|
||||||
"@sa/axios": "workspace:*",
|
"@sa/axios": "workspace:*",
|
||||||
"@sa/color": "workspace:*",
|
"@sa/color": "workspace:*",
|
||||||
"@sa/hooks": "workspace:*",
|
"@sa/hooks": "workspace:*",
|
||||||
"@sa/materials": "workspace:*",
|
"@sa/materials": "workspace:*",
|
||||||
"@sa/utils": "workspace:*",
|
"@sa/utils": "workspace:*",
|
||||||
"@visactor/vchart": "2.0.4",
|
|
||||||
"@visactor/vchart-theme": "1.12.2",
|
|
||||||
"@visactor/vtable-editors": "1.19.8",
|
|
||||||
"@visactor/vtable-gantt": "1.19.8",
|
|
||||||
"@visactor/vue-vtable": "1.19.8",
|
|
||||||
"@vueuse/components": "13.9.0",
|
|
||||||
"@vueuse/core": "13.9.0",
|
"@vueuse/core": "13.9.0",
|
||||||
"@wangeditor/editor": "^5.1.23",
|
"@wangeditor/editor": "^5.1.23",
|
||||||
"@wangeditor/editor-for-vue": "^5.1.12",
|
"@wangeditor/editor-for-vue": "^5.1.12",
|
||||||
"clipboard": "2.0.11",
|
"clipboard": "2.0.11",
|
||||||
"dayjs": "1.11.18",
|
"dayjs": "1.11.18",
|
||||||
"defu": "^6.1.4",
|
"defu": "^6.1.4",
|
||||||
"dhtmlx-gantt": "9.0.14",
|
|
||||||
"dompurify": "3.2.6",
|
"dompurify": "3.2.6",
|
||||||
"echarts": "6.0.0",
|
"echarts": "6.0.0",
|
||||||
"element-plus": "^2.11.1",
|
"element-plus": "^2.11.1",
|
||||||
"grid-layout-plus": "^1.1.1",
|
"grid-layout-plus": "^1.1.1",
|
||||||
"jsbarcode": "3.12.1",
|
|
||||||
"jsencrypt": "^3.5.4",
|
"jsencrypt": "^3.5.4",
|
||||||
"json5": "2.2.3",
|
"json5": "2.2.3",
|
||||||
"nprogress": "0.2.0",
|
"nprogress": "0.2.0",
|
||||||
"pinia": "3.0.3",
|
"pinia": "3.0.3",
|
||||||
"pinyin-pro": "3.27.0",
|
|
||||||
"print-js": "1.6.0",
|
|
||||||
"swiper": "11.2.10",
|
|
||||||
"tailwind-merge": "3.3.1",
|
"tailwind-merge": "3.3.1",
|
||||||
"typeit": "8.8.7",
|
|
||||||
"vditor": "3.11.2",
|
|
||||||
"vue": "3.5.20",
|
"vue": "3.5.20",
|
||||||
"vue-draggable-plus": "0.6.0",
|
"vue-draggable-plus": "0.6.0",
|
||||||
"vue-i18n": "11.1.11",
|
"vue-i18n": "11.1.11",
|
||||||
"vue-pdf-embed": "2.1.3",
|
"vue-router": "4.5.1"
|
||||||
"vue-router": "4.5.1",
|
|
||||||
"xgplayer": "3.0.23",
|
|
||||||
"xlsx": "0.18.5"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@amap/amap-jsapi-types": "0.0.15",
|
|
||||||
"@elegant-router/vue": "0.3.8",
|
"@elegant-router/vue": "0.3.8",
|
||||||
"@iconify/json": "2.2.380",
|
"@iconify/json": "2.2.380",
|
||||||
"@sa/scripts": "workspace:*",
|
"@sa/scripts": "workspace:*",
|
||||||
"@sa/uno-preset": "workspace:*",
|
"@sa/uno-preset": "workspace:*",
|
||||||
"@soybeanjs/eslint-config": "1.7.1",
|
"@soybeanjs/eslint-config": "1.7.1",
|
||||||
"@types/bmapgl": "0.0.7",
|
|
||||||
"@types/node": "24.3.0",
|
"@types/node": "24.3.0",
|
||||||
"@types/nprogress": "0.2.3",
|
"@types/nprogress": "0.2.3",
|
||||||
"@unocss/eslint-config": "66.5.0",
|
"@unocss/eslint-config": "66.5.0",
|
||||||
|
|||||||
@@ -1,61 +0,0 @@
|
|||||||
<script lang="ts" setup>
|
|
||||||
import { computed } from 'vue';
|
|
||||||
import { getPaletteColorByNumber } from '@sa/color';
|
|
||||||
|
|
||||||
defineOptions({ name: 'WaveBg' });
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
/** Theme color */
|
|
||||||
themeColor: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const props = defineProps<Props>();
|
|
||||||
|
|
||||||
const lightColor = computed(() => getPaletteColorByNumber(props.themeColor, 200));
|
|
||||||
const darkColor = computed(() => getPaletteColorByNumber(props.themeColor, 500));
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div class="absolute-lt z-1 size-full overflow-hidden">
|
|
||||||
<div class="absolute -right-300px -top-900px lt-sm:(-right-100px -top-1170px)">
|
|
||||||
<svg height="1337" width="1337">
|
|
||||||
<defs>
|
|
||||||
<path
|
|
||||||
id="path-1"
|
|
||||||
opacity="1"
|
|
||||||
fill-rule="evenodd"
|
|
||||||
d="M1337,668.5 C1337,1037.455193874239 1037.455193874239,1337 668.5,1337 C523.6725684305388,1337 337,1236 370.50000000000006,1094 C434.03835568300906,824.6732385973953 6.906089672974592e-14,892.6277623047779 0,668.5000000000001 C0,299.5448061257611 299.5448061257609,1.1368683772161603e-13 668.4999999999999,0 C1037.455193874239,0 1337,299.544806125761 1337,668.5Z"
|
|
||||||
/>
|
|
||||||
<linearGradient id="linearGradient-2" x1="0.79" y1="0.62" x2="0.21" y2="0.86">
|
|
||||||
<stop offset="0" :stop-color="lightColor" stop-opacity="1" />
|
|
||||||
<stop offset="1" :stop-color="darkColor" stop-opacity="1" />
|
|
||||||
</linearGradient>
|
|
||||||
</defs>
|
|
||||||
<g opacity="1">
|
|
||||||
<use xlink:href="#path-1" fill="url(#linearGradient-2)" fill-opacity="1" />
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
<div class="absolute -bottom-400px -left-200px lt-sm:(-bottom-760px -left-100px)">
|
|
||||||
<svg height="896" width="967.8852157128662">
|
|
||||||
<defs>
|
|
||||||
<path
|
|
||||||
id="path-2"
|
|
||||||
opacity="1"
|
|
||||||
fill-rule="evenodd"
|
|
||||||
d="M896,448 C1142.6325445712241,465.5747656464056 695.2579309733121,896 448,896 C200.74206902668806,896 5.684341886080802e-14,695.2579309733121 0,448.0000000000001 C0,200.74206902668806 200.74206902668791,5.684341886080802e-14 447.99999999999994,0 C695.2579309733121,0 475,418 896,448Z"
|
|
||||||
/>
|
|
||||||
<linearGradient id="linearGradient-3" x1="0.5" y1="0" x2="0.5" y2="1">
|
|
||||||
<stop offset="0" :stop-color="darkColor" stop-opacity="1" />
|
|
||||||
<stop offset="1" :stop-color="lightColor" stop-opacity="1" />
|
|
||||||
</linearGradient>
|
|
||||||
</defs>
|
|
||||||
<g opacity="1">
|
|
||||||
<use xlink:href="#path-2" fill="url(#linearGradient-3)" fill-opacity="1" />
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped></style>
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
/** baidu map sdk url */
|
|
||||||
export const BAIDU_MAP_SDK_URL = `https://api.map.baidu.com/getscript?v=3.0&ak=KSezYymXPth1DIGILRX3oYN9PxbOQQmU&services=&t=20210201100830&s=1`;
|
|
||||||
|
|
||||||
/** Amap sdk url */
|
|
||||||
export const AMAP_SDK_URL = 'https://webapi.amap.com/maps?v=2.0&key=e7bd02bd504062087e6563daf4d6721d';
|
|
||||||
|
|
||||||
/** tencent sdk url */
|
|
||||||
export const TENCENT_MAP_SDK_URL = 'https://map.qq.com/api/gljs?v=1.exp&key=A6DBZ-KXPLW-JKSRY-ONZF4-CPHY3-K6BL7';
|
|
||||||
@@ -1,158 +0,0 @@
|
|||||||
import { computed, effectScope, onScopeDispose, ref, watch } from 'vue';
|
|
||||||
import { useElementSize } from '@vueuse/core';
|
|
||||||
import VChart, { registerLiquidChart } from '@visactor/vchart';
|
|
||||||
import type { ISpec, ITheme } from '@visactor/vchart';
|
|
||||||
import light from '@visactor/vchart-theme/public/light.json';
|
|
||||||
import dark from '@visactor/vchart-theme/public/dark.json';
|
|
||||||
import { useThemeStore } from '@/store/modules/theme';
|
|
||||||
|
|
||||||
registerLiquidChart();
|
|
||||||
|
|
||||||
// register the theme
|
|
||||||
VChart.ThemeManager.registerTheme('light', light as ITheme);
|
|
||||||
VChart.ThemeManager.registerTheme('dark', dark as ITheme);
|
|
||||||
|
|
||||||
interface ChartHooks {
|
|
||||||
onRender?: (chart: VChart) => void | Promise<void>;
|
|
||||||
onUpdated?: (chart: VChart) => void | Promise<void>;
|
|
||||||
onDestroy?: (chart: VChart) => void | Promise<void>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useVChart<T extends ISpec>(specFactory: () => T, hooks: ChartHooks = {}) {
|
|
||||||
const scope = effectScope();
|
|
||||||
const themeStore = useThemeStore();
|
|
||||||
const darkMode = computed(() => themeStore.darkMode);
|
|
||||||
|
|
||||||
const domRef = ref<HTMLElement | null>(null);
|
|
||||||
const initialSize = { width: 0, height: 0 };
|
|
||||||
const { width, height } = useElementSize(domRef, initialSize);
|
|
||||||
|
|
||||||
let chart: VChart | null = null;
|
|
||||||
const spec: T = specFactory();
|
|
||||||
|
|
||||||
const { onRender, onUpdated, onDestroy } = hooks;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* whether can render chart
|
|
||||||
*
|
|
||||||
* when domRef is ready and initialSize is valid
|
|
||||||
*/
|
|
||||||
function canRender() {
|
|
||||||
return domRef.value && initialSize.width > 0 && initialSize.height > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** is chart rendered */
|
|
||||||
function isRendered() {
|
|
||||||
return Boolean(domRef.value && chart);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* update chart spec
|
|
||||||
*
|
|
||||||
* @param callback callback function
|
|
||||||
*/
|
|
||||||
async function updateSpec(callback: (opts: T, optsFactory: () => T) => ISpec = () => spec) {
|
|
||||||
if (!isRendered()) return;
|
|
||||||
|
|
||||||
const updatedOpts = callback(spec, specFactory);
|
|
||||||
|
|
||||||
Object.assign(spec, updatedOpts);
|
|
||||||
|
|
||||||
// if (isRendered()) {
|
|
||||||
// chart?.release();
|
|
||||||
// }
|
|
||||||
|
|
||||||
chart?.updateSpec({ ...updatedOpts }, true);
|
|
||||||
|
|
||||||
await onUpdated?.(chart!);
|
|
||||||
}
|
|
||||||
|
|
||||||
function setSpec(newSpec: T) {
|
|
||||||
chart?.updateSpec(newSpec);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** render chart */
|
|
||||||
async function render() {
|
|
||||||
if (!isRendered()) {
|
|
||||||
// apply the theme
|
|
||||||
if (darkMode.value) {
|
|
||||||
VChart.ThemeManager.setCurrentTheme('dark');
|
|
||||||
} else {
|
|
||||||
VChart.ThemeManager.setCurrentTheme('light');
|
|
||||||
}
|
|
||||||
|
|
||||||
chart = new VChart(spec, { dom: domRef.value as HTMLElement });
|
|
||||||
chart.renderSync();
|
|
||||||
|
|
||||||
await onRender?.(chart);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** resize chart */
|
|
||||||
function resize() {
|
|
||||||
// chart?.resize();
|
|
||||||
}
|
|
||||||
|
|
||||||
/** destroy chart */
|
|
||||||
async function destroy() {
|
|
||||||
if (!chart) return;
|
|
||||||
|
|
||||||
await onDestroy?.(chart);
|
|
||||||
chart?.release();
|
|
||||||
chart = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** change chart theme */
|
|
||||||
async function changeTheme() {
|
|
||||||
await destroy();
|
|
||||||
await render();
|
|
||||||
await onUpdated?.(chart!);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* render chart by size
|
|
||||||
*
|
|
||||||
* @param w width
|
|
||||||
* @param h height
|
|
||||||
*/
|
|
||||||
async function renderChartBySize(w: number, h: number) {
|
|
||||||
initialSize.width = w;
|
|
||||||
initialSize.height = h;
|
|
||||||
|
|
||||||
// size is abnormal, destroy chart
|
|
||||||
if (!canRender()) {
|
|
||||||
await destroy();
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// resize chart
|
|
||||||
if (isRendered()) {
|
|
||||||
resize();
|
|
||||||
}
|
|
||||||
|
|
||||||
// render chart
|
|
||||||
await render();
|
|
||||||
}
|
|
||||||
|
|
||||||
scope.run(() => {
|
|
||||||
watch([width, height], ([newWidth, newHeight]) => {
|
|
||||||
renderChartBySize(newWidth, newHeight);
|
|
||||||
});
|
|
||||||
|
|
||||||
watch(darkMode, () => {
|
|
||||||
changeTheme();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
onScopeDispose(() => {
|
|
||||||
destroy();
|
|
||||||
scope.stop();
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
domRef,
|
|
||||||
updateSpec,
|
|
||||||
setSpec
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -178,16 +178,6 @@ const local: App.I18n.Schema = {
|
|||||||
infra: 'Infra',
|
infra: 'Infra',
|
||||||
'infra_state-machine': 'State Machine',
|
'infra_state-machine': 'State Machine',
|
||||||
'infra_rd-code': 'R&D Code',
|
'infra_rd-code': 'R&D Code',
|
||||||
function: 'System Function',
|
|
||||||
function_tab: 'Tab',
|
|
||||||
'function_multi-tab': 'Multi Tab',
|
|
||||||
'function_hide-child': 'Hide Child',
|
|
||||||
'function_hide-child_one': 'Hide Child',
|
|
||||||
'function_hide-child_two': 'Two',
|
|
||||||
'function_hide-child_three': 'Three',
|
|
||||||
function_request: 'Request',
|
|
||||||
'function_toggle-auth': 'Toggle Auth',
|
|
||||||
'function_super-page': 'Super Admin Visible',
|
|
||||||
product: 'Product',
|
product: 'Product',
|
||||||
product_list: 'Product List',
|
product_list: 'Product List',
|
||||||
product_dashboard: 'Dashboard',
|
product_dashboard: 'Dashboard',
|
||||||
@@ -211,28 +201,7 @@ const local: App.I18n.Schema = {
|
|||||||
exception: 'Exception',
|
exception: 'Exception',
|
||||||
exception_403: '403',
|
exception_403: '403',
|
||||||
exception_404: '404',
|
exception_404: '404',
|
||||||
exception_500: '500',
|
exception_500: '500'
|
||||||
plugin: 'Plugin',
|
|
||||||
plugin_copy: 'Copy',
|
|
||||||
plugin_charts: 'Charts',
|
|
||||||
plugin_charts_echarts: 'ECharts',
|
|
||||||
plugin_charts_antv: 'AntV',
|
|
||||||
plugin_charts_vchart: 'VChart',
|
|
||||||
plugin_icon: 'Icon',
|
|
||||||
plugin_map: 'Map',
|
|
||||||
plugin_print: 'Print',
|
|
||||||
plugin_swiper: 'Swiper',
|
|
||||||
plugin_video: 'Video',
|
|
||||||
plugin_barcode: 'Barcode',
|
|
||||||
plugin_pinyin: 'pinyin',
|
|
||||||
plugin_excel: 'Excel',
|
|
||||||
plugin_pdf: 'PDF preview',
|
|
||||||
plugin_gantt: 'Gantt Chart',
|
|
||||||
plugin_gantt_dhtmlx: 'dhtmlxGantt',
|
|
||||||
plugin_gantt_vtable: 'VTableGantt',
|
|
||||||
plugin_typeit: 'Typeit',
|
|
||||||
plugin_tables: 'Tables',
|
|
||||||
plugin_tables_vtable: 'VTable'
|
|
||||||
},
|
},
|
||||||
page: {
|
page: {
|
||||||
login: {
|
login: {
|
||||||
@@ -328,45 +297,6 @@ const local: App.I18n.Schema = {
|
|||||||
},
|
},
|
||||||
creativity: 'Creativity'
|
creativity: 'Creativity'
|
||||||
},
|
},
|
||||||
function: {
|
|
||||||
tab: {
|
|
||||||
tabOperate: {
|
|
||||||
title: 'Tab Operation',
|
|
||||||
addTab: 'Add Tab',
|
|
||||||
addTabDesc: 'To user management page',
|
|
||||||
closeTab: 'Close Tab',
|
|
||||||
closeCurrentTab: 'Close Current Tab',
|
|
||||||
closeAboutTab: 'Close "User Management" Tab',
|
|
||||||
addMultiTab: 'Add Multi Tab',
|
|
||||||
addMultiTabDesc1: 'To MultiTab page',
|
|
||||||
addMultiTabDesc2: 'To MultiTab page(with query params)'
|
|
||||||
},
|
|
||||||
tabTitle: {
|
|
||||||
title: 'Tab Title',
|
|
||||||
changeTitle: 'Change Title',
|
|
||||||
change: 'Change',
|
|
||||||
resetTitle: 'Reset Title',
|
|
||||||
reset: 'Reset'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
multiTab: {
|
|
||||||
routeParam: 'Route Param',
|
|
||||||
backTab: 'Back function_tab'
|
|
||||||
},
|
|
||||||
toggleAuth: {
|
|
||||||
toggleAccount: 'Toggle Account',
|
|
||||||
authHook: 'Auth Hook Function `hasAuth`',
|
|
||||||
superAdminVisible: 'Super Admin Visible',
|
|
||||||
adminVisible: 'Admin Visible',
|
|
||||||
adminOrUserVisible: 'Admin and User Visible'
|
|
||||||
},
|
|
||||||
request: {
|
|
||||||
repeatedErrorOccurOnce: 'Repeated Request Error Occurs Once',
|
|
||||||
repeatedError: 'Repeated Request Error',
|
|
||||||
repeatedErrorMsg1: 'Custom Request Error 1',
|
|
||||||
repeatedErrorMsg2: 'Custom Request Error 2'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
system: {
|
system: {
|
||||||
common: {
|
common: {
|
||||||
status: {
|
status: {
|
||||||
|
|||||||
@@ -178,16 +178,6 @@ const local: App.I18n.Schema = {
|
|||||||
infra: '基础设施',
|
infra: '基础设施',
|
||||||
'infra_state-machine': '状态机管理',
|
'infra_state-machine': '状态机管理',
|
||||||
'infra_rd-code': '研发令号',
|
'infra_rd-code': '研发令号',
|
||||||
function: '系统功能',
|
|
||||||
function_tab: '标签页',
|
|
||||||
'function_multi-tab': '多标签页',
|
|
||||||
'function_hide-child': '隐藏子菜单',
|
|
||||||
'function_hide-child_one': '隐藏子菜单',
|
|
||||||
'function_hide-child_two': '菜单二',
|
|
||||||
'function_hide-child_three': '菜单三',
|
|
||||||
function_request: '请求',
|
|
||||||
'function_toggle-auth': '切换权限',
|
|
||||||
'function_super-page': '超级管理员可见',
|
|
||||||
product: '产品管理',
|
product: '产品管理',
|
||||||
product_list: '产品列表',
|
product_list: '产品列表',
|
||||||
product_dashboard: '产品仪表盘',
|
product_dashboard: '产品仪表盘',
|
||||||
@@ -211,28 +201,7 @@ const local: App.I18n.Schema = {
|
|||||||
exception: '异常页',
|
exception: '异常页',
|
||||||
exception_403: '403',
|
exception_403: '403',
|
||||||
exception_404: '404',
|
exception_404: '404',
|
||||||
exception_500: '500',
|
exception_500: '500'
|
||||||
plugin: '插件示例',
|
|
||||||
plugin_copy: '剪贴板',
|
|
||||||
plugin_charts: '图表',
|
|
||||||
plugin_charts_echarts: 'ECharts',
|
|
||||||
plugin_charts_antv: 'AntV',
|
|
||||||
plugin_charts_vchart: 'VChart',
|
|
||||||
plugin_icon: '图标',
|
|
||||||
plugin_map: '地图',
|
|
||||||
plugin_print: '打印',
|
|
||||||
plugin_swiper: 'Swiper',
|
|
||||||
plugin_video: '视频',
|
|
||||||
plugin_barcode: '条形码',
|
|
||||||
plugin_pinyin: '拼音',
|
|
||||||
plugin_excel: 'Excel',
|
|
||||||
plugin_pdf: 'PDF 预览',
|
|
||||||
plugin_gantt: '甘特图',
|
|
||||||
plugin_gantt_dhtmlx: 'dhtmlxGantt',
|
|
||||||
plugin_gantt_vtable: 'VTableGantt',
|
|
||||||
plugin_typeit: '打字机',
|
|
||||||
plugin_tables: '表格',
|
|
||||||
plugin_tables_vtable: 'VTable'
|
|
||||||
},
|
},
|
||||||
page: {
|
page: {
|
||||||
login: {
|
login: {
|
||||||
@@ -327,45 +296,6 @@ const local: App.I18n.Schema = {
|
|||||||
},
|
},
|
||||||
creativity: '创意'
|
creativity: '创意'
|
||||||
},
|
},
|
||||||
function: {
|
|
||||||
tab: {
|
|
||||||
tabOperate: {
|
|
||||||
title: '标签页操作',
|
|
||||||
addTab: '添加标签页',
|
|
||||||
addTabDesc: '跳转到用户管理页面',
|
|
||||||
closeTab: '关闭标签页',
|
|
||||||
closeCurrentTab: '关闭当前标签页',
|
|
||||||
closeAboutTab: '关闭"用户管理"标签页',
|
|
||||||
addMultiTab: '添加多标签页',
|
|
||||||
addMultiTabDesc1: '跳转到多标签页页面',
|
|
||||||
addMultiTabDesc2: '跳转到多标签页页面(带有查询参数)'
|
|
||||||
},
|
|
||||||
tabTitle: {
|
|
||||||
title: '标签页标题',
|
|
||||||
changeTitle: '修改标题',
|
|
||||||
change: '修改',
|
|
||||||
resetTitle: '重置标题',
|
|
||||||
reset: '重置'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
multiTab: {
|
|
||||||
routeParam: '路由参数',
|
|
||||||
backTab: '返回 function_tab'
|
|
||||||
},
|
|
||||||
toggleAuth: {
|
|
||||||
toggleAccount: '切换账号',
|
|
||||||
authHook: '权限钩子函数 `hasAuth`',
|
|
||||||
superAdminVisible: '超级管理员可见',
|
|
||||||
adminVisible: '管理员可见',
|
|
||||||
adminOrUserVisible: '管理员和用户可见'
|
|
||||||
},
|
|
||||||
request: {
|
|
||||||
repeatedErrorOccurOnce: '重复请求错误只出现一次',
|
|
||||||
repeatedError: '重复请求错误',
|
|
||||||
repeatedErrorMsg1: '自定义请求错误 1',
|
|
||||||
repeatedErrorMsg2: '自定义请求错误 2'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
system: {
|
system: {
|
||||||
common: {
|
common: {
|
||||||
status: {
|
status: {
|
||||||
|
|||||||
@@ -3,6 +3,3 @@ import 'element-plus/dist/index.css';
|
|||||||
import 'element-plus/theme-chalk/dark/css-vars.css';
|
import 'element-plus/theme-chalk/dark/css-vars.css';
|
||||||
import 'uno.css';
|
import 'uno.css';
|
||||||
import '../styles/css/global.css';
|
import '../styles/css/global.css';
|
||||||
import 'swiper/css';
|
|
||||||
import 'swiper/css/navigation';
|
|
||||||
import 'swiper/css/pagination';
|
|
||||||
|
|||||||
@@ -20,14 +20,6 @@ export const views: Record<LastLevelRouteKey, RouteComponent | (() => Promise<Ro
|
|||||||
500: () => import("@/views/_builtin/500/index.vue"),
|
500: () => import("@/views/_builtin/500/index.vue"),
|
||||||
"iframe-page": () => import("@/views/_builtin/iframe-page/[url].vue"),
|
"iframe-page": () => import("@/views/_builtin/iframe-page/[url].vue"),
|
||||||
login: () => import("@/views/_builtin/login/index.vue"),
|
login: () => import("@/views/_builtin/login/index.vue"),
|
||||||
"function_hide-child_one": () => import("@/views/function/hide-child/one/index.vue"),
|
|
||||||
"function_hide-child_three": () => import("@/views/function/hide-child/three/index.vue"),
|
|
||||||
"function_hide-child_two": () => import("@/views/function/hide-child/two/index.vue"),
|
|
||||||
"function_multi-tab": () => import("@/views/function/multi-tab/index.vue"),
|
|
||||||
function_request: () => import("@/views/function/request/index.vue"),
|
|
||||||
"function_super-page": () => import("@/views/function/super-page/index.vue"),
|
|
||||||
function_tab: () => import("@/views/function/tab/index.vue"),
|
|
||||||
"function_toggle-auth": () => import("@/views/function/toggle-auth/index.vue"),
|
|
||||||
"infra_rd-code": () => import("@/views/infra/rd-code/index.vue"),
|
"infra_rd-code": () => import("@/views/infra/rd-code/index.vue"),
|
||||||
"infra_state-machine": () => import("@/views/infra/state-machine/index.vue"),
|
"infra_state-machine": () => import("@/views/infra/state-machine/index.vue"),
|
||||||
"metrics_member-efficiency": () => import("@/views/metrics/member-efficiency/index.vue"),
|
"metrics_member-efficiency": () => import("@/views/metrics/member-efficiency/index.vue"),
|
||||||
@@ -41,23 +33,6 @@ export const views: Record<LastLevelRouteKey, RouteComponent | (() => Promise<Ro
|
|||||||
"personal-center_my-weekly": () => import("@/views/personal-center/my-weekly/index.vue"),
|
"personal-center_my-weekly": () => import("@/views/personal-center/my-weekly/index.vue"),
|
||||||
"personal-center_overtime-application": () => import("@/views/personal-center/overtime-application/index.vue"),
|
"personal-center_overtime-application": () => import("@/views/personal-center/overtime-application/index.vue"),
|
||||||
"personal-center_pending-approval": () => import("@/views/personal-center/pending-approval/index.vue"),
|
"personal-center_pending-approval": () => import("@/views/personal-center/pending-approval/index.vue"),
|
||||||
plugin_barcode: () => import("@/views/plugin/barcode/index.vue"),
|
|
||||||
plugin_charts_antv: () => import("@/views/plugin/charts/antv/index.vue"),
|
|
||||||
plugin_charts_echarts: () => import("@/views/plugin/charts/echarts/index.vue"),
|
|
||||||
plugin_charts_vchart: () => import("@/views/plugin/charts/vchart/index.vue"),
|
|
||||||
plugin_copy: () => import("@/views/plugin/copy/index.vue"),
|
|
||||||
plugin_excel: () => import("@/views/plugin/excel/index.vue"),
|
|
||||||
plugin_gantt_dhtmlx: () => import("@/views/plugin/gantt/dhtmlx/index.vue"),
|
|
||||||
plugin_gantt_vtable: () => import("@/views/plugin/gantt/vtable/index.vue"),
|
|
||||||
plugin_icon: () => import("@/views/plugin/icon/index.vue"),
|
|
||||||
plugin_map: () => import("@/views/plugin/map/index.vue"),
|
|
||||||
plugin_pdf: () => import("@/views/plugin/pdf/index.vue"),
|
|
||||||
plugin_pinyin: () => import("@/views/plugin/pinyin/index.vue"),
|
|
||||||
plugin_print: () => import("@/views/plugin/print/index.vue"),
|
|
||||||
plugin_swiper: () => import("@/views/plugin/swiper/index.vue"),
|
|
||||||
plugin_tables_vtable: () => import("@/views/plugin/tables/vtable/index.vue"),
|
|
||||||
plugin_typeit: () => import("@/views/plugin/typeit/index.vue"),
|
|
||||||
plugin_video: () => import("@/views/plugin/video/index.vue"),
|
|
||||||
product_dashboard: () => import("@/views/product/dashboard/index.vue"),
|
product_dashboard: () => import("@/views/product/dashboard/index.vue"),
|
||||||
product_list: () => import("@/views/product/list/index.vue"),
|
product_list: () => import("@/views/product/list/index.vue"),
|
||||||
product_requirement: () => import("@/views/product/requirement/index.vue"),
|
product_requirement: () => import("@/views/product/requirement/index.vue"),
|
||||||
|
|||||||
@@ -39,124 +39,6 @@ export const generatedRoutes: GeneratedRoute[] = [
|
|||||||
hideInMenu: true
|
hideInMenu: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: 'function',
|
|
||||||
path: '/function',
|
|
||||||
component: 'layout.base',
|
|
||||||
meta: {
|
|
||||||
title: 'function',
|
|
||||||
i18nKey: 'route.function',
|
|
||||||
icon: 'icon-park-outline:all-application',
|
|
||||||
order: 6
|
|
||||||
},
|
|
||||||
children: [
|
|
||||||
{
|
|
||||||
name: 'function_hide-child',
|
|
||||||
path: '/function/hide-child',
|
|
||||||
meta: {
|
|
||||||
title: 'function_hide-child',
|
|
||||||
i18nKey: 'route.function_hide-child',
|
|
||||||
icon: 'material-symbols:filter-list-off',
|
|
||||||
order: 2
|
|
||||||
},
|
|
||||||
redirect: '/function/hide-child/one',
|
|
||||||
children: [
|
|
||||||
{
|
|
||||||
name: 'function_hide-child_one',
|
|
||||||
path: '/function/hide-child/one',
|
|
||||||
component: 'view.function_hide-child_one',
|
|
||||||
meta: {
|
|
||||||
title: 'function_hide-child_one',
|
|
||||||
i18nKey: 'route.function_hide-child_one',
|
|
||||||
icon: 'material-symbols:filter-list-off',
|
|
||||||
hideInMenu: true,
|
|
||||||
activeMenu: 'function_hide-child'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'function_hide-child_three',
|
|
||||||
path: '/function/hide-child/three',
|
|
||||||
component: 'view.function_hide-child_three',
|
|
||||||
meta: {
|
|
||||||
title: 'function_hide-child_three',
|
|
||||||
i18nKey: 'route.function_hide-child_three',
|
|
||||||
hideInMenu: true,
|
|
||||||
activeMenu: 'function_hide-child'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'function_hide-child_two',
|
|
||||||
path: '/function/hide-child/two',
|
|
||||||
component: 'view.function_hide-child_two',
|
|
||||||
meta: {
|
|
||||||
title: 'function_hide-child_two',
|
|
||||||
i18nKey: 'route.function_hide-child_two',
|
|
||||||
hideInMenu: true,
|
|
||||||
activeMenu: 'function_hide-child'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'function_multi-tab',
|
|
||||||
path: '/function/multi-tab',
|
|
||||||
component: 'view.function_multi-tab',
|
|
||||||
meta: {
|
|
||||||
title: 'function_multi-tab',
|
|
||||||
i18nKey: 'route.function_multi-tab',
|
|
||||||
icon: 'ic:round-tab',
|
|
||||||
multiTab: true,
|
|
||||||
hideInMenu: true,
|
|
||||||
activeMenu: 'function_tab'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'function_request',
|
|
||||||
path: '/function/request',
|
|
||||||
component: 'view.function_request',
|
|
||||||
meta: {
|
|
||||||
title: 'function_request',
|
|
||||||
i18nKey: 'route.function_request',
|
|
||||||
icon: 'carbon:network-overlay',
|
|
||||||
order: 3
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'function_super-page',
|
|
||||||
path: '/function/super-page',
|
|
||||||
component: 'view.function_super-page',
|
|
||||||
meta: {
|
|
||||||
title: 'function_super-page',
|
|
||||||
i18nKey: 'route.function_super-page',
|
|
||||||
icon: 'ic:round-supervisor-account',
|
|
||||||
order: 5,
|
|
||||||
roles: ['R_SUPER']
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'function_tab',
|
|
||||||
path: '/function/tab',
|
|
||||||
component: 'view.function_tab',
|
|
||||||
meta: {
|
|
||||||
title: 'function_tab',
|
|
||||||
i18nKey: 'route.function_tab',
|
|
||||||
icon: 'ic:round-tab',
|
|
||||||
order: 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'function_toggle-auth',
|
|
||||||
path: '/function/toggle-auth',
|
|
||||||
component: 'view.function_toggle-auth',
|
|
||||||
meta: {
|
|
||||||
title: 'function_toggle-auth',
|
|
||||||
i18nKey: 'route.function_toggle-auth',
|
|
||||||
icon: 'ic:round-construction',
|
|
||||||
order: 4
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
name: 'iframe-page',
|
name: 'iframe-page',
|
||||||
path: '/iframe-page/:url',
|
path: '/iframe-page/:url',
|
||||||
@@ -377,223 +259,6 @@ export const generatedRoutes: GeneratedRoute[] = [
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: 'plugin',
|
|
||||||
path: '/plugin',
|
|
||||||
component: 'layout.base',
|
|
||||||
meta: {
|
|
||||||
title: '插件示例',
|
|
||||||
i18nKey: 'route.plugin',
|
|
||||||
order: 7,
|
|
||||||
icon: 'clarity:plugin-line'
|
|
||||||
},
|
|
||||||
children: [
|
|
||||||
{
|
|
||||||
name: 'plugin_barcode',
|
|
||||||
path: '/plugin/barcode',
|
|
||||||
component: 'view.plugin_barcode',
|
|
||||||
meta: {
|
|
||||||
title: 'plugin_barcode',
|
|
||||||
i18nKey: 'route.plugin_barcode',
|
|
||||||
icon: 'ic:round-barcode'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'plugin_charts',
|
|
||||||
path: '/plugin/charts',
|
|
||||||
meta: {
|
|
||||||
title: 'plugin_charts',
|
|
||||||
i18nKey: 'route.plugin_charts',
|
|
||||||
icon: 'mdi:chart-areaspline'
|
|
||||||
},
|
|
||||||
children: [
|
|
||||||
{
|
|
||||||
name: 'plugin_charts_antv',
|
|
||||||
path: '/plugin/charts/antv',
|
|
||||||
component: 'view.plugin_charts_antv',
|
|
||||||
meta: {
|
|
||||||
title: 'plugin_charts_antv',
|
|
||||||
i18nKey: 'route.plugin_charts_antv',
|
|
||||||
icon: 'hugeicons:flow-square'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'plugin_charts_echarts',
|
|
||||||
path: '/plugin/charts/echarts',
|
|
||||||
component: 'view.plugin_charts_echarts',
|
|
||||||
meta: {
|
|
||||||
title: 'plugin_charts_echarts',
|
|
||||||
i18nKey: 'route.plugin_charts_echarts',
|
|
||||||
icon: 'simple-icons:apacheecharts'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'plugin_charts_vchart',
|
|
||||||
path: '/plugin/charts/vchart',
|
|
||||||
component: 'view.plugin_charts_vchart',
|
|
||||||
meta: {
|
|
||||||
title: 'plugin_charts_vchart',
|
|
||||||
i18nKey: 'route.plugin_charts_vchart',
|
|
||||||
localIcon: 'visactor'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'plugin_copy',
|
|
||||||
path: '/plugin/copy',
|
|
||||||
component: 'view.plugin_copy',
|
|
||||||
meta: {
|
|
||||||
title: 'plugin_copy',
|
|
||||||
i18nKey: 'route.plugin_copy',
|
|
||||||
icon: 'mdi:clipboard-outline'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'plugin_excel',
|
|
||||||
path: '/plugin/excel',
|
|
||||||
component: 'view.plugin_excel',
|
|
||||||
meta: {
|
|
||||||
title: 'plugin_excel',
|
|
||||||
i18nKey: 'route.plugin_excel',
|
|
||||||
icon: 'ri:file-excel-2-line',
|
|
||||||
keepAlive: true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'plugin_gantt',
|
|
||||||
path: '/plugin/gantt',
|
|
||||||
meta: {
|
|
||||||
title: 'plugin_gantt',
|
|
||||||
i18nKey: 'route.plugin_gantt',
|
|
||||||
icon: 'ant-design:bar-chart-outlined'
|
|
||||||
},
|
|
||||||
children: [
|
|
||||||
{
|
|
||||||
name: 'plugin_gantt_dhtmlx',
|
|
||||||
path: '/plugin/gantt/dhtmlx',
|
|
||||||
component: 'view.plugin_gantt_dhtmlx',
|
|
||||||
meta: {
|
|
||||||
title: 'plugin_gantt_dhtmlx',
|
|
||||||
i18nKey: 'route.plugin_gantt_dhtmlx',
|
|
||||||
icon: 'gridicons:posts'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'plugin_gantt_vtable',
|
|
||||||
path: '/plugin/gantt/vtable',
|
|
||||||
component: 'view.plugin_gantt_vtable',
|
|
||||||
meta: {
|
|
||||||
title: 'plugin_gantt_vtable',
|
|
||||||
i18nKey: 'route.plugin_gantt_vtable',
|
|
||||||
localIcon: 'visactor'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'plugin_icon',
|
|
||||||
path: '/plugin/icon',
|
|
||||||
component: 'view.plugin_icon',
|
|
||||||
meta: {
|
|
||||||
title: 'plugin_icon',
|
|
||||||
i18nKey: 'route.plugin_icon',
|
|
||||||
localIcon: 'custom-icon'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'plugin_map',
|
|
||||||
path: '/plugin/map',
|
|
||||||
component: 'view.plugin_map',
|
|
||||||
meta: {
|
|
||||||
title: 'plugin_map',
|
|
||||||
i18nKey: 'route.plugin_map',
|
|
||||||
icon: 'mdi:map'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'plugin_pdf',
|
|
||||||
path: '/plugin/pdf',
|
|
||||||
component: 'view.plugin_pdf',
|
|
||||||
meta: {
|
|
||||||
title: 'plugin_pdf',
|
|
||||||
i18nKey: 'route.plugin_pdf',
|
|
||||||
icon: 'uiw:file-pdf'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'plugin_pinyin',
|
|
||||||
path: '/plugin/pinyin',
|
|
||||||
component: 'view.plugin_pinyin',
|
|
||||||
meta: {
|
|
||||||
title: 'plugin_pinyin',
|
|
||||||
i18nKey: 'route.plugin_pinyin',
|
|
||||||
icon: 'entypo-social:google-hangouts'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'plugin_print',
|
|
||||||
path: '/plugin/print',
|
|
||||||
component: 'view.plugin_print',
|
|
||||||
meta: {
|
|
||||||
title: 'plugin_print',
|
|
||||||
i18nKey: 'route.plugin_print',
|
|
||||||
icon: 'mdi:printer'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'plugin_swiper',
|
|
||||||
path: '/plugin/swiper',
|
|
||||||
component: 'view.plugin_swiper',
|
|
||||||
meta: {
|
|
||||||
title: 'plugin_swiper',
|
|
||||||
i18nKey: 'route.plugin_swiper',
|
|
||||||
icon: 'simple-icons:swiper'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'plugin_tables',
|
|
||||||
path: '/plugin/tables',
|
|
||||||
meta: {
|
|
||||||
title: 'plugin_tables',
|
|
||||||
i18nKey: 'route.plugin_tables',
|
|
||||||
icon: 'icon-park-outline:table'
|
|
||||||
},
|
|
||||||
children: [
|
|
||||||
{
|
|
||||||
name: 'plugin_tables_vtable',
|
|
||||||
path: '/plugin/tables/vtable',
|
|
||||||
component: 'view.plugin_tables_vtable',
|
|
||||||
meta: {
|
|
||||||
title: 'plugin_tables_vtable',
|
|
||||||
i18nKey: 'route.plugin_tables_vtable',
|
|
||||||
localIcon: 'visactor'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'plugin_typeit',
|
|
||||||
path: '/plugin/typeit',
|
|
||||||
component: 'view.plugin_typeit',
|
|
||||||
meta: {
|
|
||||||
title: 'plugin_typeit',
|
|
||||||
i18nKey: 'route.plugin_typeit',
|
|
||||||
icon: 'mdi:typewriter'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'plugin_video',
|
|
||||||
path: '/plugin/video',
|
|
||||||
component: 'view.plugin_video',
|
|
||||||
meta: {
|
|
||||||
title: 'plugin_video',
|
|
||||||
i18nKey: 'route.plugin_video',
|
|
||||||
icon: 'mdi:video'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
name: 'product',
|
name: 'product',
|
||||||
path: '/product',
|
path: '/product',
|
||||||
|
|||||||
@@ -170,16 +170,6 @@ const routeMap: RouteMap = {
|
|||||||
"403": "/403",
|
"403": "/403",
|
||||||
"404": "/404",
|
"404": "/404",
|
||||||
"500": "/500",
|
"500": "/500",
|
||||||
"function": "/function",
|
|
||||||
"function_hide-child": "/function/hide-child",
|
|
||||||
"function_hide-child_one": "/function/hide-child/one",
|
|
||||||
"function_hide-child_three": "/function/hide-child/three",
|
|
||||||
"function_hide-child_two": "/function/hide-child/two",
|
|
||||||
"function_multi-tab": "/function/multi-tab",
|
|
||||||
"function_request": "/function/request",
|
|
||||||
"function_super-page": "/function/super-page",
|
|
||||||
"function_tab": "/function/tab",
|
|
||||||
"function_toggle-auth": "/function/toggle-auth",
|
|
||||||
"iframe-page": "/iframe-page/:url",
|
"iframe-page": "/iframe-page/:url",
|
||||||
"infra": "/infra",
|
"infra": "/infra",
|
||||||
"infra_rd-code": "/infra/rd-code",
|
"infra_rd-code": "/infra/rd-code",
|
||||||
@@ -198,27 +188,6 @@ const routeMap: RouteMap = {
|
|||||||
"personal-center_my-weekly": "/personal-center/my-weekly",
|
"personal-center_my-weekly": "/personal-center/my-weekly",
|
||||||
"personal-center_overtime-application": "/personal-center/overtime-application",
|
"personal-center_overtime-application": "/personal-center/overtime-application",
|
||||||
"personal-center_pending-approval": "/personal-center/pending-approval",
|
"personal-center_pending-approval": "/personal-center/pending-approval",
|
||||||
"plugin": "/plugin",
|
|
||||||
"plugin_barcode": "/plugin/barcode",
|
|
||||||
"plugin_charts": "/plugin/charts",
|
|
||||||
"plugin_charts_antv": "/plugin/charts/antv",
|
|
||||||
"plugin_charts_echarts": "/plugin/charts/echarts",
|
|
||||||
"plugin_charts_vchart": "/plugin/charts/vchart",
|
|
||||||
"plugin_copy": "/plugin/copy",
|
|
||||||
"plugin_excel": "/plugin/excel",
|
|
||||||
"plugin_gantt": "/plugin/gantt",
|
|
||||||
"plugin_gantt_dhtmlx": "/plugin/gantt/dhtmlx",
|
|
||||||
"plugin_gantt_vtable": "/plugin/gantt/vtable",
|
|
||||||
"plugin_icon": "/plugin/icon",
|
|
||||||
"plugin_map": "/plugin/map",
|
|
||||||
"plugin_pdf": "/plugin/pdf",
|
|
||||||
"plugin_pinyin": "/plugin/pinyin",
|
|
||||||
"plugin_print": "/plugin/print",
|
|
||||||
"plugin_swiper": "/plugin/swiper",
|
|
||||||
"plugin_tables": "/plugin/tables",
|
|
||||||
"plugin_tables_vtable": "/plugin/tables/vtable",
|
|
||||||
"plugin_typeit": "/plugin/typeit",
|
|
||||||
"plugin_video": "/plugin/video",
|
|
||||||
"product": "/product",
|
"product": "/product",
|
||||||
"product_dashboard": "/product/dashboard",
|
"product_dashboard": "/product/dashboard",
|
||||||
"product_list": "/product/list",
|
"product_list": "/product/list",
|
||||||
|
|||||||
39
src/typings/app.d.ts
vendored
39
src/typings/app.d.ts
vendored
@@ -504,45 +504,6 @@ declare namespace App {
|
|||||||
};
|
};
|
||||||
creativity: string;
|
creativity: string;
|
||||||
};
|
};
|
||||||
function: {
|
|
||||||
tab: {
|
|
||||||
tabOperate: {
|
|
||||||
title: string;
|
|
||||||
addTab: string;
|
|
||||||
addTabDesc: string;
|
|
||||||
closeTab: string;
|
|
||||||
closeCurrentTab: string;
|
|
||||||
closeAboutTab: string;
|
|
||||||
addMultiTab: string;
|
|
||||||
addMultiTabDesc1: string;
|
|
||||||
addMultiTabDesc2: string;
|
|
||||||
};
|
|
||||||
tabTitle: {
|
|
||||||
title: string;
|
|
||||||
changeTitle: string;
|
|
||||||
change: string;
|
|
||||||
resetTitle: string;
|
|
||||||
reset: string;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
multiTab: {
|
|
||||||
routeParam: string;
|
|
||||||
backTab: string;
|
|
||||||
};
|
|
||||||
toggleAuth: {
|
|
||||||
toggleAccount: string;
|
|
||||||
authHook: string;
|
|
||||||
superAdminVisible: string;
|
|
||||||
adminVisible: string;
|
|
||||||
adminOrUserVisible: string;
|
|
||||||
};
|
|
||||||
request: {
|
|
||||||
repeatedErrorOccurOnce: string;
|
|
||||||
repeatedError: string;
|
|
||||||
repeatedErrorMsg1: string;
|
|
||||||
repeatedErrorMsg2: string;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
system: {
|
system: {
|
||||||
common: {
|
common: {
|
||||||
status: {
|
status: {
|
||||||
|
|||||||
58
src/typings/elegant-router.d.ts
vendored
58
src/typings/elegant-router.d.ts
vendored
@@ -24,16 +24,6 @@ declare module "@elegant-router/types" {
|
|||||||
"403": "/403";
|
"403": "/403";
|
||||||
"404": "/404";
|
"404": "/404";
|
||||||
"500": "/500";
|
"500": "/500";
|
||||||
"function": "/function";
|
|
||||||
"function_hide-child": "/function/hide-child";
|
|
||||||
"function_hide-child_one": "/function/hide-child/one";
|
|
||||||
"function_hide-child_three": "/function/hide-child/three";
|
|
||||||
"function_hide-child_two": "/function/hide-child/two";
|
|
||||||
"function_multi-tab": "/function/multi-tab";
|
|
||||||
"function_request": "/function/request";
|
|
||||||
"function_super-page": "/function/super-page";
|
|
||||||
"function_tab": "/function/tab";
|
|
||||||
"function_toggle-auth": "/function/toggle-auth";
|
|
||||||
"iframe-page": "/iframe-page/:url";
|
"iframe-page": "/iframe-page/:url";
|
||||||
"infra": "/infra";
|
"infra": "/infra";
|
||||||
"infra_rd-code": "/infra/rd-code";
|
"infra_rd-code": "/infra/rd-code";
|
||||||
@@ -52,27 +42,6 @@ declare module "@elegant-router/types" {
|
|||||||
"personal-center_my-weekly": "/personal-center/my-weekly";
|
"personal-center_my-weekly": "/personal-center/my-weekly";
|
||||||
"personal-center_overtime-application": "/personal-center/overtime-application";
|
"personal-center_overtime-application": "/personal-center/overtime-application";
|
||||||
"personal-center_pending-approval": "/personal-center/pending-approval";
|
"personal-center_pending-approval": "/personal-center/pending-approval";
|
||||||
"plugin": "/plugin";
|
|
||||||
"plugin_barcode": "/plugin/barcode";
|
|
||||||
"plugin_charts": "/plugin/charts";
|
|
||||||
"plugin_charts_antv": "/plugin/charts/antv";
|
|
||||||
"plugin_charts_echarts": "/plugin/charts/echarts";
|
|
||||||
"plugin_charts_vchart": "/plugin/charts/vchart";
|
|
||||||
"plugin_copy": "/plugin/copy";
|
|
||||||
"plugin_excel": "/plugin/excel";
|
|
||||||
"plugin_gantt": "/plugin/gantt";
|
|
||||||
"plugin_gantt_dhtmlx": "/plugin/gantt/dhtmlx";
|
|
||||||
"plugin_gantt_vtable": "/plugin/gantt/vtable";
|
|
||||||
"plugin_icon": "/plugin/icon";
|
|
||||||
"plugin_map": "/plugin/map";
|
|
||||||
"plugin_pdf": "/plugin/pdf";
|
|
||||||
"plugin_pinyin": "/plugin/pinyin";
|
|
||||||
"plugin_print": "/plugin/print";
|
|
||||||
"plugin_swiper": "/plugin/swiper";
|
|
||||||
"plugin_tables": "/plugin/tables";
|
|
||||||
"plugin_tables_vtable": "/plugin/tables/vtable";
|
|
||||||
"plugin_typeit": "/plugin/typeit";
|
|
||||||
"plugin_video": "/plugin/video";
|
|
||||||
"product": "/product";
|
"product": "/product";
|
||||||
"product_dashboard": "/product/dashboard";
|
"product_dashboard": "/product/dashboard";
|
||||||
"product_list": "/product/list";
|
"product_list": "/product/list";
|
||||||
@@ -135,13 +104,11 @@ declare module "@elegant-router/types" {
|
|||||||
| "403"
|
| "403"
|
||||||
| "404"
|
| "404"
|
||||||
| "500"
|
| "500"
|
||||||
| "function"
|
|
||||||
| "iframe-page"
|
| "iframe-page"
|
||||||
| "infra"
|
| "infra"
|
||||||
| "login"
|
| "login"
|
||||||
| "metrics"
|
| "metrics"
|
||||||
| "personal-center"
|
| "personal-center"
|
||||||
| "plugin"
|
|
||||||
| "product"
|
| "product"
|
||||||
| "project"
|
| "project"
|
||||||
| "system"
|
| "system"
|
||||||
@@ -169,14 +136,6 @@ declare module "@elegant-router/types" {
|
|||||||
| "500"
|
| "500"
|
||||||
| "iframe-page"
|
| "iframe-page"
|
||||||
| "login"
|
| "login"
|
||||||
| "function_hide-child_one"
|
|
||||||
| "function_hide-child_three"
|
|
||||||
| "function_hide-child_two"
|
|
||||||
| "function_multi-tab"
|
|
||||||
| "function_request"
|
|
||||||
| "function_super-page"
|
|
||||||
| "function_tab"
|
|
||||||
| "function_toggle-auth"
|
|
||||||
| "infra_rd-code"
|
| "infra_rd-code"
|
||||||
| "infra_state-machine"
|
| "infra_state-machine"
|
||||||
| "metrics_member-efficiency"
|
| "metrics_member-efficiency"
|
||||||
@@ -190,23 +149,6 @@ declare module "@elegant-router/types" {
|
|||||||
| "personal-center_my-weekly"
|
| "personal-center_my-weekly"
|
||||||
| "personal-center_overtime-application"
|
| "personal-center_overtime-application"
|
||||||
| "personal-center_pending-approval"
|
| "personal-center_pending-approval"
|
||||||
| "plugin_barcode"
|
|
||||||
| "plugin_charts_antv"
|
|
||||||
| "plugin_charts_echarts"
|
|
||||||
| "plugin_charts_vchart"
|
|
||||||
| "plugin_copy"
|
|
||||||
| "plugin_excel"
|
|
||||||
| "plugin_gantt_dhtmlx"
|
|
||||||
| "plugin_gantt_vtable"
|
|
||||||
| "plugin_icon"
|
|
||||||
| "plugin_map"
|
|
||||||
| "plugin_pdf"
|
|
||||||
| "plugin_pinyin"
|
|
||||||
| "plugin_print"
|
|
||||||
| "plugin_swiper"
|
|
||||||
| "plugin_tables_vtable"
|
|
||||||
| "plugin_typeit"
|
|
||||||
| "plugin_video"
|
|
||||||
| "product_dashboard"
|
| "product_dashboard"
|
||||||
| "product_list"
|
| "product_list"
|
||||||
| "product_requirement"
|
| "product_requirement"
|
||||||
|
|||||||
20
src/typings/package.d.ts
vendored
20
src/typings/package.d.ts
vendored
@@ -1,20 +0,0 @@
|
|||||||
/// <reference types="@amap/amap-jsapi-types" />
|
|
||||||
/// <reference types="bmapgl" />
|
|
||||||
|
|
||||||
declare namespace BMap {
|
|
||||||
class Map extends BMapGL.Map {}
|
|
||||||
class Point extends BMapGL.Point {}
|
|
||||||
}
|
|
||||||
|
|
||||||
declare const TMap: any;
|
|
||||||
|
|
||||||
interface Window {
|
|
||||||
/**
|
|
||||||
* make baidu map request under https protocol
|
|
||||||
*
|
|
||||||
* - 0: http
|
|
||||||
* - 1: https
|
|
||||||
* - 2: https
|
|
||||||
*/
|
|
||||||
HOST_TYPE: '0' | '1' | '2';
|
|
||||||
}
|
|
||||||
@@ -1,85 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import { computed } from 'vue';
|
|
||||||
import type { Component } from 'vue';
|
|
||||||
import { getPaletteColorByNumber, mixColor } from '@sa/color';
|
|
||||||
import { loginModuleRecord } from '@/constants/app';
|
|
||||||
import { useAppStore } from '@/store/modules/app';
|
|
||||||
import { useThemeStore } from '@/store/modules/theme';
|
|
||||||
import { $t } from '@/locales';
|
|
||||||
import PwdLogin from './modules/pwd-login.vue';
|
|
||||||
import ResetPwd from './modules/reset-pwd.vue';
|
|
||||||
|
|
||||||
defineOptions({ name: 'LoginPage' });
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
/** The login module */
|
|
||||||
module?: UnionKey.LoginModule;
|
|
||||||
}
|
|
||||||
|
|
||||||
const props = defineProps<Props>();
|
|
||||||
|
|
||||||
const appStore = useAppStore();
|
|
||||||
const themeStore = useThemeStore();
|
|
||||||
|
|
||||||
interface LoginModule {
|
|
||||||
label: App.I18n.I18nKey;
|
|
||||||
component: Component;
|
|
||||||
}
|
|
||||||
|
|
||||||
const moduleMap: Record<UnionKey.LoginModule, LoginModule> = {
|
|
||||||
'pwd-login': { label: loginModuleRecord['pwd-login'], component: PwdLogin },
|
|
||||||
'reset-pwd': { label: loginModuleRecord['reset-pwd'], component: ResetPwd }
|
|
||||||
};
|
|
||||||
|
|
||||||
const activeModule = computed(() => moduleMap[props.module || 'pwd-login']);
|
|
||||||
|
|
||||||
const bgThemeColor = computed(() =>
|
|
||||||
themeStore.darkMode ? getPaletteColorByNumber(themeStore.themeColor, 600) : themeStore.themeColor
|
|
||||||
);
|
|
||||||
|
|
||||||
const bgColor = computed(() => {
|
|
||||||
const COLOR_WHITE = '#ffffff';
|
|
||||||
|
|
||||||
const ratio = themeStore.darkMode ? 0.5 : 0.2;
|
|
||||||
|
|
||||||
return mixColor(COLOR_WHITE, themeStore.themeColor, ratio);
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div class="relative size-full flex-center overflow-hidden" :style="{ backgroundColor: bgColor }">
|
|
||||||
<WaveBg :theme-color="bgThemeColor" />
|
|
||||||
<ElCard class="relative z-4 w-auto rd-12px">
|
|
||||||
<div class="w-400px lt-sm:w-300px">
|
|
||||||
<header class="flex-y-center justify-between">
|
|
||||||
<SystemLogo class="text-64px text-primary lt-sm:text-48px" />
|
|
||||||
<h3 class="text-28px text-primary font-500 lt-sm:text-22px">{{ $t('system.title') }}</h3>
|
|
||||||
<div class="i-flex-col">
|
|
||||||
<ThemeSchemaSwitch
|
|
||||||
:theme-schema="themeStore.themeScheme"
|
|
||||||
:show-tooltip="false"
|
|
||||||
class="text-20px lt-sm:text-18px"
|
|
||||||
@switch="themeStore.toggleThemeScheme"
|
|
||||||
/>
|
|
||||||
<LangSwitch
|
|
||||||
v-if="themeStore.header.multilingual.visible"
|
|
||||||
:lang="appStore.locale"
|
|
||||||
:lang-options="appStore.localeOptions"
|
|
||||||
:show-tooltip="false"
|
|
||||||
@change-lang="appStore.changeLocale"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
<main class="pt-15px">
|
|
||||||
<div class="pt-15px">
|
|
||||||
<Transition :name="themeStore.page.animateMode" mode="out-in" appear>
|
|
||||||
<component :is="activeModule.component" />
|
|
||||||
</Transition>
|
|
||||||
</div>
|
|
||||||
</main>
|
|
||||||
</div>
|
|
||||||
</ElCard>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped></style>
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
<script setup lang="ts"></script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<LookForward />
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped></style>
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
<script setup lang="ts"></script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<LookForward />
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped></style>
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
<script setup lang="ts"></script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<LookForward />
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped></style>
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import { computed } from 'vue';
|
|
||||||
import { useRoute } from 'vue-router';
|
|
||||||
import { useRouterPush } from '@/hooks/common/router';
|
|
||||||
import { $t } from '@/locales';
|
|
||||||
|
|
||||||
const route = useRoute();
|
|
||||||
const { routerPushByKey } = useRouterPush();
|
|
||||||
|
|
||||||
const routeQuery = computed(() => JSON.stringify(route.query));
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div>
|
|
||||||
<LookForward>
|
|
||||||
<div>
|
|
||||||
<ElButton @click="routerPushByKey('function_tab')">{{ $t('page.function.multiTab.backTab') }}</ElButton>
|
|
||||||
<div class="py-24px">{{ $t('page.function.multiTab.routeParam') }}: {{ routeQuery }}</div>
|
|
||||||
</div>
|
|
||||||
</LookForward>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped></style>
|
|
||||||
@@ -1,57 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import { fetchCustomBackendError } from '@/service/api';
|
|
||||||
import { $t } from '@/locales';
|
|
||||||
|
|
||||||
async function logout() {
|
|
||||||
await fetchCustomBackendError('8888', $t('request.logoutMsg'));
|
|
||||||
}
|
|
||||||
|
|
||||||
async function logoutWithModal() {
|
|
||||||
await fetchCustomBackendError('7777', $t('request.logoutWithModalMsg'));
|
|
||||||
}
|
|
||||||
|
|
||||||
async function refreshToken() {
|
|
||||||
await fetchCustomBackendError('9999', $t('request.tokenExpired'));
|
|
||||||
}
|
|
||||||
|
|
||||||
async function handleRepeatedMessageError() {
|
|
||||||
await Promise.all([
|
|
||||||
fetchCustomBackendError('2222', $t('page.function.request.repeatedErrorMsg1')),
|
|
||||||
fetchCustomBackendError('2222', $t('page.function.request.repeatedErrorMsg1')),
|
|
||||||
fetchCustomBackendError('2222', $t('page.function.request.repeatedErrorMsg1')),
|
|
||||||
fetchCustomBackendError('3333', $t('page.function.request.repeatedErrorMsg2')),
|
|
||||||
fetchCustomBackendError('3333', $t('page.function.request.repeatedErrorMsg2')),
|
|
||||||
fetchCustomBackendError('3333', $t('page.function.request.repeatedErrorMsg2'))
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function handleRepeatedModalError() {
|
|
||||||
await Promise.all([
|
|
||||||
fetchCustomBackendError('7777', $t('request.logoutWithModalMsg')),
|
|
||||||
fetchCustomBackendError('7777', $t('request.logoutWithModalMsg')),
|
|
||||||
fetchCustomBackendError('7777', $t('request.logoutWithModalMsg'))
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<ElSpace direction="vertical" fill :size="16">
|
|
||||||
<ElCard :header="$t('request.logout')" class="card-wrapper">
|
|
||||||
<ElButton @click="logout">{{ $t('common.trigger') }}</ElButton>
|
|
||||||
</ElCard>
|
|
||||||
<ElCard :header="$t('request.logoutWithModal')" class="card-wrapper">
|
|
||||||
<ElButton @click="logoutWithModal">{{ $t('common.trigger') }}</ElButton>
|
|
||||||
</ElCard>
|
|
||||||
<ElCard :header="$t('request.refreshToken')" class="card-wrapper">
|
|
||||||
<ElButton @click="refreshToken">{{ $t('common.trigger') }}</ElButton>
|
|
||||||
</ElCard>
|
|
||||||
<ElCard :header="$t('page.function.request.repeatedErrorOccurOnce')" class="card-wrapper">
|
|
||||||
<ElButton @click="handleRepeatedMessageError">{{ $t('page.function.request.repeatedError') }}(Message)</ElButton>
|
|
||||||
<ElButton class="ml-12px" @click="handleRepeatedModalError">
|
|
||||||
{{ $t('page.function.request.repeatedError') }}(Modal)
|
|
||||||
</ElButton>
|
|
||||||
</ElCard>
|
|
||||||
</ElSpace>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped></style>
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
<script setup lang="ts"></script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<LookForward />
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped></style>
|
|
||||||
@@ -1,62 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import { ref } from 'vue';
|
|
||||||
import { useTabStore } from '@/store/modules/tab';
|
|
||||||
import { useRouterPush } from '@/hooks/common/router';
|
|
||||||
import { $t } from '@/locales';
|
|
||||||
|
|
||||||
defineOptions({ name: 'TabPage' });
|
|
||||||
|
|
||||||
const tabStore = useTabStore();
|
|
||||||
const { routerPushByKey } = useRouterPush();
|
|
||||||
|
|
||||||
const tabLabel = ref('');
|
|
||||||
|
|
||||||
function changeTabLabel() {
|
|
||||||
tabStore.setTabLabel(tabLabel.value);
|
|
||||||
}
|
|
||||||
|
|
||||||
function resetTabLabel() {
|
|
||||||
tabStore.resetTabLabel();
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<ElSpace direction="vertical" fill :size="16">
|
|
||||||
<ElCard :header="$t('page.function.tab.tabOperate.title')" class="card-wrapper">
|
|
||||||
<ElDivider content-position="left">{{ $t('page.function.tab.tabOperate.addTab') }}</ElDivider>
|
|
||||||
<ElButton @click="routerPushByKey('system_user')">{{ $t('page.function.tab.tabOperate.addTabDesc') }}</ElButton>
|
|
||||||
<ElDivider content-position="left">{{ $t('page.function.tab.tabOperate.closeTab') }}</ElDivider>
|
|
||||||
<ElSpace>
|
|
||||||
<ElButton @click="tabStore.removeActiveTab">
|
|
||||||
{{ $t('page.function.tab.tabOperate.closeCurrentTab') }}
|
|
||||||
</ElButton>
|
|
||||||
<ElButton @click="tabStore.removeTabByRouteName('system_user')">
|
|
||||||
{{ $t('page.function.tab.tabOperate.closeAboutTab') }}
|
|
||||||
</ElButton>
|
|
||||||
</ElSpace>
|
|
||||||
<ElDivider content-position="left">{{ $t('page.function.tab.tabOperate.addMultiTab') }}</ElDivider>
|
|
||||||
<ElSpace>
|
|
||||||
<ElButton @click="routerPushByKey('function_multi-tab')">
|
|
||||||
{{ $t('page.function.tab.tabOperate.addMultiTabDesc1') }}
|
|
||||||
</ElButton>
|
|
||||||
<ElButton @click="routerPushByKey('function_multi-tab', { query: { a: '1' } })">
|
|
||||||
{{ $t('page.function.tab.tabOperate.addMultiTabDesc2') }}
|
|
||||||
</ElButton>
|
|
||||||
</ElSpace>
|
|
||||||
</ElCard>
|
|
||||||
<ElCard :header="$t('page.function.tab.tabTitle.title')" class="card-wrapper">
|
|
||||||
<ElDivider content-position="left">{{ $t('page.function.tab.tabTitle.changeTitle') }}</ElDivider>
|
|
||||||
<ElInput v-model="tabLabel" class="max-w-240px">
|
|
||||||
<template #append>
|
|
||||||
<ElButton type="primary" @click="changeTabLabel">{{ $t('page.function.tab.tabTitle.change') }}</ElButton>
|
|
||||||
</template>
|
|
||||||
</ElInput>
|
|
||||||
<ElDivider content-position="left">{{ $t('page.function.tab.tabTitle.resetTitle') }}</ElDivider>
|
|
||||||
<ElButton type="danger" plain class="w-80px" @click="resetTabLabel">
|
|
||||||
{{ $t('page.function.tab.tabTitle.reset') }}
|
|
||||||
</ElButton>
|
|
||||||
</ElCard>
|
|
||||||
</ElSpace>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped></style>
|
|
||||||
@@ -1,99 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import { computed, ref } from 'vue';
|
|
||||||
import { useRoute } from 'vue-router';
|
|
||||||
import { useLoading } from '@sa/hooks';
|
|
||||||
import { useAppStore } from '@/store/modules/app';
|
|
||||||
import { useAuthStore } from '@/store/modules/auth';
|
|
||||||
import { useTabStore } from '@/store/modules/tab';
|
|
||||||
import { useAuth } from '@/hooks/business/auth';
|
|
||||||
import { $t } from '@/locales';
|
|
||||||
|
|
||||||
defineOptions({ name: 'ToggleAuth' });
|
|
||||||
|
|
||||||
const route = useRoute();
|
|
||||||
const appStore = useAppStore();
|
|
||||||
const authStore = useAuthStore();
|
|
||||||
const tabStore = useTabStore();
|
|
||||||
const { hasAuth } = useAuth();
|
|
||||||
const { loading, startLoading, endLoading } = useLoading();
|
|
||||||
|
|
||||||
type AccountKey = 'super' | 'admin' | 'user';
|
|
||||||
|
|
||||||
interface Account {
|
|
||||||
key: AccountKey;
|
|
||||||
label: string;
|
|
||||||
userName: string;
|
|
||||||
password: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const accounts = computed<Account[]>(() => [
|
|
||||||
{
|
|
||||||
key: 'super',
|
|
||||||
label: $t('page.login.pwdLogin.superAdmin'),
|
|
||||||
userName: 'Super',
|
|
||||||
password: '123456'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'admin',
|
|
||||||
label: $t('page.login.pwdLogin.admin'),
|
|
||||||
userName: 'Admin',
|
|
||||||
password: '123456'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'user',
|
|
||||||
label: $t('page.login.pwdLogin.user'),
|
|
||||||
userName: 'User',
|
|
||||||
password: '123456'
|
|
||||||
}
|
|
||||||
]);
|
|
||||||
|
|
||||||
const loginAccount = ref<AccountKey>('super');
|
|
||||||
|
|
||||||
async function handleToggleAccount(account: Account) {
|
|
||||||
loginAccount.value = account.key;
|
|
||||||
|
|
||||||
startLoading();
|
|
||||||
await authStore.login(account.userName, account.password, false);
|
|
||||||
tabStore.initTabStore(route);
|
|
||||||
endLoading();
|
|
||||||
appStore.reloadPage();
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<ElSpace direction="vertical" fill :size="16">
|
|
||||||
<ElCard :header="$t('route.function_toggle-auth')" class="card-wrapper">
|
|
||||||
<ElDescriptions direction="vertical" border :column="1">
|
|
||||||
<ElDescriptionsItem :label="$t('page.system.user.userRole')">
|
|
||||||
<ElSpace>
|
|
||||||
<ElTag v-for="role in authStore.userInfo.roles" :key="role">{{ role }}</ElTag>
|
|
||||||
</ElSpace>
|
|
||||||
</ElDescriptionsItem>
|
|
||||||
<ElDescriptionsItem ions-item :label="$t('page.function.toggleAuth.toggleAccount')">
|
|
||||||
<ElSpace>
|
|
||||||
<ElButton
|
|
||||||
v-for="account in accounts"
|
|
||||||
:key="account.key"
|
|
||||||
:loading="loading && loginAccount === account.key"
|
|
||||||
:disabled="loading && loginAccount !== account.key"
|
|
||||||
@click="handleToggleAccount(account)"
|
|
||||||
>
|
|
||||||
{{ account.label }}
|
|
||||||
</ElButton>
|
|
||||||
</ElSpace>
|
|
||||||
</ElDescriptionsItem>
|
|
||||||
</ElDescriptions>
|
|
||||||
</ElCard>
|
|
||||||
<ElCard :header="$t('page.function.toggleAuth.authHook')" class="card-wrapper">
|
|
||||||
<ElSpace>
|
|
||||||
<ElButton v-if="hasAuth('B_CODE1')">{{ $t('page.function.toggleAuth.superAdminVisible') }}</ElButton>
|
|
||||||
<ElButton v-if="hasAuth('B_CODE2')">{{ $t('page.function.toggleAuth.adminVisible') }}</ElButton>
|
|
||||||
<ElButton v-if="hasAuth('B_CODE3')">
|
|
||||||
{{ $t('page.function.toggleAuth.adminOrUserVisible') }}
|
|
||||||
</ElButton>
|
|
||||||
</ElSpace>
|
|
||||||
</ElCard>
|
|
||||||
</ElSpace>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped></style>
|
|
||||||
@@ -1,116 +0,0 @@
|
|||||||
<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>
|
|
||||||
@@ -1,60 +0,0 @@
|
|||||||
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 }
|
|
||||||
]
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,67 +0,0 @@
|
|||||||
<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>
|
|
||||||
@@ -1,135 +0,0 @@
|
|||||||
<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>
|
|
||||||
@@ -1,170 +0,0 @@
|
|||||||
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 };
|
|
||||||
}
|
|
||||||
@@ -1,95 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
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[];
|
|
||||||
}
|
|
||||||
@@ -1,706 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
};
|
|
||||||
@@ -1,93 +0,0 @@
|
|||||||
<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>
|
|
||||||
@@ -1,872 +0,0 @@
|
|||||||
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'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
};
|
|
||||||
@@ -1,51 +0,0 @@
|
|||||||
<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>
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
<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>
|
|
||||||
@@ -1,171 +0,0 @@
|
|||||||
<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>
|
|
||||||
@@ -1,173 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
||||||
];
|
|
||||||
@@ -1,169 +0,0 @@
|
|||||||
<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>
|
|
||||||
@@ -1,721 +0,0 @@
|
|||||||
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'
|
|
||||||
}
|
|
||||||
];
|
|
||||||
@@ -1,792 +0,0 @@
|
|||||||
<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>
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
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'
|
|
||||||
];
|
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
<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>
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
<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>
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
<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>
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
import BaiduMap from './baidu-map.vue';
|
|
||||||
import GaodeMap from './gaode-map.vue';
|
|
||||||
import TencentMap from './tencent-map.vue';
|
|
||||||
|
|
||||||
export { BaiduMap, GaodeMap, TencentMap };
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
<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>
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
<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>
|
|
||||||
@@ -1,91 +0,0 @@
|
|||||||
<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>
|
|
||||||
@@ -1,59 +0,0 @@
|
|||||||
<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>
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
<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>
|
|
||||||
@@ -1,60 +0,0 @@
|
|||||||
<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>
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,408 +0,0 @@
|
|||||||
<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>
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
<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>
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
<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>
|
|
||||||
@@ -1,767 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import { computed, ref, watch } from 'vue';
|
|
||||||
import { RDMS_OBJECT_DIRECTION_DICT_CODE } from '@/constants/dict';
|
|
||||||
import { fetchGetProduct, fetchGetProductMembers, fetchGetProductSettings } from '@/service/api';
|
|
||||||
import { useDict } from '@/hooks/business/dict';
|
|
||||||
import { useCurrentProduct } from '../shared/use-current-product';
|
|
||||||
import ProductActivityTimelinePanel from './modules/product-activity-timeline-panel.vue';
|
|
||||||
import {
|
|
||||||
buildProductHomepageBanner,
|
|
||||||
buildRequirementPoolRecentChanges,
|
|
||||||
buildRequirementPoolSummary,
|
|
||||||
getProductHomepageExtensionModules
|
|
||||||
} from './homepage';
|
|
||||||
import { productHomepageExtensionMock, productRequirementPoolMock } from './mock';
|
|
||||||
|
|
||||||
defineOptions({ name: 'ProductDashboard' });
|
|
||||||
|
|
||||||
const { currentObjectId } = useCurrentProduct();
|
|
||||||
const { getLabel: getDirectionDictLabel } = useDict(RDMS_OBJECT_DIRECTION_DICT_CODE);
|
|
||||||
|
|
||||||
const pageLoading = ref(false);
|
|
||||||
const productDetail = ref<Api.Product.Product | null>(null);
|
|
||||||
const settings = ref<Api.Product.ProductSettings | null>(null);
|
|
||||||
const members = ref<Api.Product.ProductMember[]>([]);
|
|
||||||
const latestActivityTime = ref('');
|
|
||||||
|
|
||||||
const requirementPoolSummary = computed(() => buildRequirementPoolSummary(productRequirementPoolMock.summary));
|
|
||||||
const requirementPoolRecentChanges = computed(() =>
|
|
||||||
buildRequirementPoolRecentChanges(productRequirementPoolMock.recentChanges)
|
|
||||||
);
|
|
||||||
const homepageBanner = computed(() =>
|
|
||||||
buildProductHomepageBanner({
|
|
||||||
product: productDetail.value,
|
|
||||||
settings: settings.value,
|
|
||||||
members: members.value,
|
|
||||||
requirementSummary: requirementPoolSummary.value,
|
|
||||||
latestActivityTime: latestActivityTime.value
|
|
||||||
})
|
|
||||||
);
|
|
||||||
const extensionModules = computed(() => getProductHomepageExtensionModules(productHomepageExtensionMock));
|
|
||||||
const directionLabel = computed(() => getDirectionDictLabel(homepageBanner.value.identity.directionCode, '--'));
|
|
||||||
const bannerFacts = computed(() => {
|
|
||||||
const [managerFact, roleFact] = homepageBanner.value.identity.facts;
|
|
||||||
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
label: '产品方向',
|
|
||||||
value: directionLabel.value,
|
|
||||||
fullWidth: false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: managerFact?.label || '产品经理',
|
|
||||||
value: managerFact?.value || '--',
|
|
||||||
fullWidth: false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: roleFact?.label || '角色摘要',
|
|
||||||
value: roleFact?.value || '--',
|
|
||||||
fullWidth: true
|
|
||||||
}
|
|
||||||
];
|
|
||||||
});
|
|
||||||
const bannerStatusClass = computed(() => {
|
|
||||||
const statusCode = homepageBanner.value.identity.statusCode;
|
|
||||||
|
|
||||||
return statusCode ? `product-homepage-banner--${statusCode}` : 'product-homepage-banner--default';
|
|
||||||
});
|
|
||||||
const bannerStatusWordClass = computed(() => {
|
|
||||||
const statusCode = homepageBanner.value.identity.statusCode;
|
|
||||||
|
|
||||||
return statusCode
|
|
||||||
? `product-homepage-banner__status-word--${statusCode}`
|
|
||||||
: 'product-homepage-banner__status-word--default';
|
|
||||||
});
|
|
||||||
|
|
||||||
function handleLatestActivityTimeChange(value: string) {
|
|
||||||
latestActivityTime.value = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function loadDashboardData(objectId: string) {
|
|
||||||
pageLoading.value = true;
|
|
||||||
|
|
||||||
try {
|
|
||||||
const [productResult, settingsResult, membersResult] = await Promise.all([
|
|
||||||
fetchGetProduct(objectId),
|
|
||||||
fetchGetProductSettings(objectId),
|
|
||||||
fetchGetProductMembers(objectId)
|
|
||||||
]);
|
|
||||||
|
|
||||||
productDetail.value = productResult.error ? null : productResult.data || null;
|
|
||||||
settings.value = settingsResult.error ? null : settingsResult.data || null;
|
|
||||||
members.value = membersResult.error ? [] : membersResult.data || [];
|
|
||||||
} finally {
|
|
||||||
pageLoading.value = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
watch(
|
|
||||||
() => currentObjectId.value,
|
|
||||||
async objectId => {
|
|
||||||
if (!objectId) {
|
|
||||||
productDetail.value = null;
|
|
||||||
settings.value = null;
|
|
||||||
members.value = [];
|
|
||||||
latestActivityTime.value = '';
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await loadDashboardData(objectId);
|
|
||||||
},
|
|
||||||
{ immediate: true }
|
|
||||||
);
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div v-loading="pageLoading" class="product-homepage">
|
|
||||||
<section class="product-homepage-banner" :class="bannerStatusClass">
|
|
||||||
<div class="product-homepage-banner__identity">
|
|
||||||
<div class="product-homepage-banner__title-group">
|
|
||||||
<div class="product-homepage-banner__title-main min-w-0">
|
|
||||||
<div class="product-homepage-banner__title-row">
|
|
||||||
<h1 class="product-homepage-banner__title">{{ homepageBanner.identity.name }}</h1>
|
|
||||||
<span class="product-homepage-banner__status-word" :class="bannerStatusWordClass">
|
|
||||||
{{ homepageBanner.identity.statusLabel }}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div class="product-homepage-banner__subtitle">
|
|
||||||
<span class="product-homepage-banner__code">编号 {{ homepageBanner.identity.code }}</span>
|
|
||||||
<p v-if="homepageBanner.identity.description" class="product-homepage-banner__description">
|
|
||||||
{{ homepageBanner.identity.description }}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="product-homepage-banner__facts">
|
|
||||||
<div
|
|
||||||
v-for="item in bannerFacts"
|
|
||||||
:key="item.label"
|
|
||||||
class="product-homepage-banner__fact"
|
|
||||||
:class="{ 'product-homepage-banner__fact--full': item.fullWidth }"
|
|
||||||
>
|
|
||||||
<span class="product-homepage-banner__fact-label">{{ item.label }}</span>
|
|
||||||
<strong class="product-homepage-banner__fact-value">{{ item.value }}</strong>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="product-homepage-banner__metrics">
|
|
||||||
<article v-for="item in homepageBanner.metrics" :key="item.label" class="product-homepage-banner__metric">
|
|
||||||
<span class="product-homepage-banner__metric-label">{{ item.label }}</span>
|
|
||||||
<strong class="product-homepage-banner__metric-value">{{ item.value }}</strong>
|
|
||||||
</article>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section class="product-homepage-main">
|
|
||||||
<ProductActivityTimelinePanel
|
|
||||||
:product-id="currentObjectId || ''"
|
|
||||||
@latest-time-change="handleLatestActivityTimeChange"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<div class="product-homepage-main__aside">
|
|
||||||
<ElCard class="product-homepage-panel card-wrapper">
|
|
||||||
<template #header>
|
|
||||||
<div>
|
|
||||||
<h3 class="product-homepage-panel__title">需求池管理概览</h3>
|
|
||||||
<p class="product-homepage-panel__desc">先看需求池现在的总体规模、状态结构和待处理压力。</p>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<div class="product-homepage-requirement-summary">
|
|
||||||
<div class="product-homepage-requirement-summary__metrics">
|
|
||||||
<article
|
|
||||||
v-for="item in requirementPoolSummary.metrics"
|
|
||||||
:key="item.label"
|
|
||||||
class="product-homepage-requirement-summary__metric"
|
|
||||||
>
|
|
||||||
<span class="product-homepage-requirement-summary__metric-label">{{ item.label }}</span>
|
|
||||||
<strong class="product-homepage-requirement-summary__metric-value">{{ item.value }}</strong>
|
|
||||||
</article>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="product-homepage-requirement-summary__distribution">
|
|
||||||
<div
|
|
||||||
v-for="item in requirementPoolSummary.distribution"
|
|
||||||
:key="item.label"
|
|
||||||
class="product-homepage-requirement-summary__distribution-item"
|
|
||||||
>
|
|
||||||
<span>{{ item.label }}</span>
|
|
||||||
<strong>{{ item.value }}</strong>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</ElCard>
|
|
||||||
|
|
||||||
<ElCard class="product-homepage-panel card-wrapper">
|
|
||||||
<template #header>
|
|
||||||
<div>
|
|
||||||
<h3 class="product-homepage-panel__title">需求池最近变化</h3>
|
|
||||||
<p class="product-homepage-panel__desc">承接需求新增、状态流转和关闭情况,和产品动态时间线分开表达。</p>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<div v-if="requirementPoolRecentChanges.length" class="product-homepage-requirement-changes">
|
|
||||||
<article
|
|
||||||
v-for="item in requirementPoolRecentChanges"
|
|
||||||
:key="item.id"
|
|
||||||
class="product-homepage-requirement-changes__item"
|
|
||||||
>
|
|
||||||
<div class="product-homepage-requirement-changes__meta">
|
|
||||||
<ElTag type="info" effect="plain" size="small">{{ item.actionLabel }}</ElTag>
|
|
||||||
<span class="product-homepage-requirement-changes__time">{{ item.time }}</span>
|
|
||||||
</div>
|
|
||||||
<strong class="product-homepage-requirement-changes__title">{{ item.title }}</strong>
|
|
||||||
<p class="product-homepage-requirement-changes__status">当前状态:{{ item.statusLabel }}</p>
|
|
||||||
</article>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<ElEmpty v-else description="当前暂无需求池最近变化" :image-size="72" />
|
|
||||||
</ElCard>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section class="product-homepage-extension">
|
|
||||||
<ElCard v-for="module in extensionModules" :key="module.key" class="product-homepage-panel card-wrapper">
|
|
||||||
<template #header>
|
|
||||||
<div>
|
|
||||||
<h3 class="product-homepage-panel__title">{{ module.title }}</h3>
|
|
||||||
<p class="product-homepage-panel__desc">{{ module.description }}</p>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<div class="product-homepage-extension__list">
|
|
||||||
<div v-for="item in module.items" :key="item" class="product-homepage-extension__item">
|
|
||||||
<span class="product-homepage-extension__dot" />
|
|
||||||
<span>{{ item }}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</ElCard>
|
|
||||||
</section>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.product-homepage {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.product-homepage-banner {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: minmax(0, 1.55fr) minmax(320px, 1fr);
|
|
||||||
gap: 16px;
|
|
||||||
padding: 24px;
|
|
||||||
border: 1px solid rgb(226 232 240 / 92%);
|
|
||||||
border-radius: 24px;
|
|
||||||
background:
|
|
||||||
radial-gradient(circle at top left, rgb(14 116 144 / 14%), transparent 34%),
|
|
||||||
radial-gradient(circle at bottom right, rgb(15 118 110 / 10%), transparent 28%),
|
|
||||||
linear-gradient(135deg, rgb(255 255 255 / 99%), rgb(248 250 252 / 98%));
|
|
||||||
}
|
|
||||||
|
|
||||||
.product-homepage-banner--default {
|
|
||||||
border-color: rgb(226 232 240 / 92%);
|
|
||||||
background:
|
|
||||||
radial-gradient(circle at top left, rgb(14 116 144 / 14%), transparent 34%),
|
|
||||||
radial-gradient(circle at bottom right, rgb(15 118 110 / 10%), transparent 28%),
|
|
||||||
linear-gradient(135deg, rgb(255 255 255 / 99%), rgb(248 250 252 / 98%));
|
|
||||||
}
|
|
||||||
|
|
||||||
.product-homepage-banner--active {
|
|
||||||
border-color: rgb(167 243 208 / 88%);
|
|
||||||
background:
|
|
||||||
radial-gradient(circle at top left, rgb(5 150 105 / 16%), transparent 32%),
|
|
||||||
radial-gradient(circle at bottom right, rgb(16 185 129 / 14%), transparent 26%),
|
|
||||||
linear-gradient(135deg, rgb(236 253 245 / 99%), rgb(255 255 255 / 98%));
|
|
||||||
}
|
|
||||||
|
|
||||||
.product-homepage-banner--paused {
|
|
||||||
border-color: rgb(253 230 138 / 90%);
|
|
||||||
background:
|
|
||||||
radial-gradient(circle at top left, rgb(245 158 11 / 18%), transparent 32%),
|
|
||||||
radial-gradient(circle at bottom right, rgb(251 191 36 / 16%), transparent 24%),
|
|
||||||
linear-gradient(135deg, rgb(255 251 235 / 99%), rgb(255 255 255 / 98%));
|
|
||||||
}
|
|
||||||
|
|
||||||
.product-homepage-banner--archived {
|
|
||||||
border-color: rgb(203 213 225 / 92%);
|
|
||||||
background:
|
|
||||||
radial-gradient(circle at top left, rgb(100 116 139 / 14%), transparent 34%),
|
|
||||||
radial-gradient(circle at bottom right, rgb(148 163 184 / 10%), transparent 26%),
|
|
||||||
linear-gradient(135deg, rgb(248 250 252 / 99%), rgb(255 255 255 / 98%));
|
|
||||||
}
|
|
||||||
|
|
||||||
.product-homepage-banner--abandoned {
|
|
||||||
border-color: rgb(254 205 211 / 92%);
|
|
||||||
background:
|
|
||||||
radial-gradient(circle at top left, rgb(244 63 94 / 16%), transparent 32%),
|
|
||||||
radial-gradient(circle at bottom right, rgb(251 113 133 / 14%), transparent 24%),
|
|
||||||
linear-gradient(135deg, rgb(255 241 242 / 99%), rgb(255 255 255 / 98%));
|
|
||||||
}
|
|
||||||
|
|
||||||
.product-homepage-banner__identity {
|
|
||||||
display: flex;
|
|
||||||
min-width: 0;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.product-homepage-banner__title-group {
|
|
||||||
display: flex;
|
|
||||||
align-items: flex-start;
|
|
||||||
gap: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.product-homepage-banner__title-main {
|
|
||||||
display: flex;
|
|
||||||
min-width: 0;
|
|
||||||
flex: 1;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.product-homepage-banner__title-row {
|
|
||||||
display: flex;
|
|
||||||
min-width: 0;
|
|
||||||
align-items: baseline;
|
|
||||||
gap: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.product-homepage-banner__code {
|
|
||||||
margin: 0;
|
|
||||||
color: rgb(14 116 144 / 92%);
|
|
||||||
font-size: 13px;
|
|
||||||
font-weight: 600;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.product-homepage-banner__title {
|
|
||||||
margin: 0;
|
|
||||||
color: rgb(15 23 42 / 98%);
|
|
||||||
font-size: 34px;
|
|
||||||
line-height: 1.15;
|
|
||||||
letter-spacing: -0.03em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.product-homepage-banner__subtitle {
|
|
||||||
display: flex;
|
|
||||||
min-width: 0;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
align-items: baseline;
|
|
||||||
gap: 10px 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.product-homepage-banner__description {
|
|
||||||
margin: 0;
|
|
||||||
min-width: 0;
|
|
||||||
color: rgb(71 85 105 / 94%);
|
|
||||||
font-size: 14px;
|
|
||||||
line-height: 1.8;
|
|
||||||
}
|
|
||||||
|
|
||||||
.product-homepage-banner__status-word {
|
|
||||||
flex-shrink: 0;
|
|
||||||
font-size: 26px;
|
|
||||||
font-weight: 800;
|
|
||||||
line-height: 1;
|
|
||||||
letter-spacing: 0.18em;
|
|
||||||
text-transform: uppercase;
|
|
||||||
user-select: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.product-homepage-banner__status-word--default {
|
|
||||||
color: rgb(148 163 184 / 48%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.product-homepage-banner__status-word--active {
|
|
||||||
color: transparent;
|
|
||||||
background: linear-gradient(180deg, rgb(5 150 105 / 94%), rgb(16 185 129 / 70%));
|
|
||||||
background-clip: text;
|
|
||||||
-webkit-text-fill-color: transparent;
|
|
||||||
text-shadow: 0 10px 24px rgb(5 150 105 / 16%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.product-homepage-banner__status-word--paused {
|
|
||||||
color: transparent;
|
|
||||||
background: linear-gradient(180deg, rgb(217 119 6 / 94%), rgb(245 158 11 / 70%));
|
|
||||||
background-clip: text;
|
|
||||||
-webkit-text-fill-color: transparent;
|
|
||||||
text-shadow: 0 10px 24px rgb(245 158 11 / 16%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.product-homepage-banner__status-word--archived {
|
|
||||||
color: transparent;
|
|
||||||
background: linear-gradient(180deg, rgb(71 85 105 / 92%), rgb(148 163 184 / 64%));
|
|
||||||
background-clip: text;
|
|
||||||
-webkit-text-fill-color: transparent;
|
|
||||||
text-shadow: 0 10px 24px rgb(100 116 139 / 14%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.product-homepage-banner__status-word--abandoned {
|
|
||||||
color: transparent;
|
|
||||||
background: linear-gradient(180deg, rgb(225 29 72 / 94%), rgb(251 113 133 / 68%));
|
|
||||||
background-clip: text;
|
|
||||||
-webkit-text-fill-color: transparent;
|
|
||||||
text-shadow: 0 10px 24px rgb(225 29 72 / 16%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.product-homepage-banner__facts {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
||||||
gap: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.product-homepage-banner__fact {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
gap: 10px;
|
|
||||||
min-height: 58px;
|
|
||||||
padding: 14px 16px;
|
|
||||||
border: 1px solid rgb(226 232 240 / 88%);
|
|
||||||
border-radius: 18px;
|
|
||||||
background-color: rgb(255 255 255 / 78%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.product-homepage-banner__fact--full {
|
|
||||||
grid-column: 1 / -1;
|
|
||||||
align-items: flex-start;
|
|
||||||
}
|
|
||||||
|
|
||||||
.product-homepage-banner__fact-label {
|
|
||||||
color: rgb(100 116 139 / 94%);
|
|
||||||
font-size: 13px;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.product-homepage-banner__fact-value {
|
|
||||||
color: rgb(15 23 42 / 96%);
|
|
||||||
font-size: 15px;
|
|
||||||
line-height: 1.6;
|
|
||||||
text-align: right;
|
|
||||||
}
|
|
||||||
|
|
||||||
.product-homepage-banner__fact--full .product-homepage-banner__fact-value {
|
|
||||||
max-width: 72%;
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
.product-homepage-banner__metrics {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
||||||
gap: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.product-homepage-banner__metric {
|
|
||||||
display: flex;
|
|
||||||
min-height: 112px;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: center;
|
|
||||||
gap: 16px;
|
|
||||||
padding: 18px;
|
|
||||||
border: 1px solid rgb(226 232 240 / 88%);
|
|
||||||
border-radius: 20px;
|
|
||||||
background: linear-gradient(180deg, rgb(255 255 255 / 99%), rgb(241 245 249 / 98%));
|
|
||||||
}
|
|
||||||
|
|
||||||
.product-homepage-banner__metric-label {
|
|
||||||
color: rgb(100 116 139 / 92%);
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.product-homepage-banner__metric-value {
|
|
||||||
color: rgb(15 23 42 / 98%);
|
|
||||||
font-size: 28px;
|
|
||||||
line-height: 1.1;
|
|
||||||
letter-spacing: -0.02em;
|
|
||||||
word-break: break-word;
|
|
||||||
}
|
|
||||||
|
|
||||||
.product-homepage-main {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
||||||
gap: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.product-homepage-main__aside {
|
|
||||||
display: flex;
|
|
||||||
min-width: 0;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.product-homepage-panel {
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.product-homepage-panel__title {
|
|
||||||
margin: 0;
|
|
||||||
color: rgb(15 23 42 / 98%);
|
|
||||||
font-size: 16px;
|
|
||||||
font-weight: 700;
|
|
||||||
}
|
|
||||||
|
|
||||||
.product-homepage-panel__desc {
|
|
||||||
margin: 4px 0 0;
|
|
||||||
color: rgb(100 116 139 / 92%);
|
|
||||||
font-size: 13px;
|
|
||||||
line-height: 1.7;
|
|
||||||
}
|
|
||||||
|
|
||||||
.product-homepage-timeline {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.product-homepage-timeline__item {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 20px minmax(0, 1fr);
|
|
||||||
gap: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.product-homepage-timeline__rail {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.product-homepage-timeline__dot {
|
|
||||||
width: 12px;
|
|
||||||
height: 12px;
|
|
||||||
border-radius: 999px;
|
|
||||||
margin-top: 6px;
|
|
||||||
box-shadow: 0 0 0 4px rgb(255 255 255 / 96%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.product-homepage-timeline__dot--sky {
|
|
||||||
background-color: rgb(14 165 233 / 88%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.product-homepage-timeline__dot--emerald {
|
|
||||||
background-color: rgb(5 150 105 / 88%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.product-homepage-timeline__dot--amber {
|
|
||||||
background-color: rgb(217 119 6 / 88%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.product-homepage-timeline__dot--rose {
|
|
||||||
background-color: rgb(225 29 72 / 88%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.product-homepage-timeline__dot--slate {
|
|
||||||
background-color: rgb(100 116 139 / 88%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.product-homepage-timeline__line {
|
|
||||||
flex: 1;
|
|
||||||
width: 2px;
|
|
||||||
min-height: 30px;
|
|
||||||
margin-top: 4px;
|
|
||||||
background: linear-gradient(180deg, rgb(203 213 225 / 96%), rgb(226 232 240 / 28%));
|
|
||||||
}
|
|
||||||
|
|
||||||
.product-homepage-timeline__item:last-child .product-homepage-timeline__line {
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.product-homepage-timeline__content {
|
|
||||||
padding: 12px 14px;
|
|
||||||
border: 1px solid rgb(226 232 240 / 92%);
|
|
||||||
border-radius: 16px;
|
|
||||||
background-color: rgb(255 255 255 / 98%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.product-homepage-timeline__meta {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
gap: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.product-homepage-timeline__time {
|
|
||||||
color: rgb(100 116 139 / 90%);
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.product-homepage-timeline__sentence {
|
|
||||||
margin: 6px 0 0;
|
|
||||||
color: rgb(71 85 105 / 94%);
|
|
||||||
font-size: 13px;
|
|
||||||
line-height: 1.65;
|
|
||||||
}
|
|
||||||
|
|
||||||
.product-homepage-timeline__headline {
|
|
||||||
margin-right: 6px;
|
|
||||||
color: rgb(15 23 42 / 98%);
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
.product-homepage-requirement-summary {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.product-homepage-requirement-summary__metrics {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
||||||
gap: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.product-homepage-requirement-summary__metric {
|
|
||||||
display: flex;
|
|
||||||
min-height: 100px;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: center;
|
|
||||||
gap: 14px;
|
|
||||||
padding: 14px 16px;
|
|
||||||
border-radius: 18px;
|
|
||||||
background: linear-gradient(180deg, rgb(248 250 252 / 98%), rgb(241 245 249 / 94%));
|
|
||||||
}
|
|
||||||
|
|
||||||
.product-homepage-requirement-summary__metric-label {
|
|
||||||
color: rgb(100 116 139 / 92%);
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.product-homepage-requirement-summary__metric-value {
|
|
||||||
color: rgb(15 23 42 / 98%);
|
|
||||||
font-size: 24px;
|
|
||||||
line-height: 1.1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.product-homepage-requirement-summary__distribution {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.product-homepage-requirement-summary__distribution-item {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
gap: 12px;
|
|
||||||
padding: 13px 14px;
|
|
||||||
border: 1px solid rgb(226 232 240 / 88%);
|
|
||||||
border-radius: 14px;
|
|
||||||
background-color: rgb(255 255 255 / 96%);
|
|
||||||
color: rgb(51 65 85 / 95%);
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.product-homepage-requirement-summary__distribution-item strong {
|
|
||||||
color: rgb(15 23 42 / 98%);
|
|
||||||
font-size: 18px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.product-homepage-requirement-changes {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.product-homepage-requirement-changes__item {
|
|
||||||
padding: 14px 16px;
|
|
||||||
border: 1px solid rgb(226 232 240 / 90%);
|
|
||||||
border-radius: 18px;
|
|
||||||
background-color: rgb(255 255 255 / 98%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.product-homepage-requirement-changes__meta {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
gap: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.product-homepage-requirement-changes__time {
|
|
||||||
color: rgb(100 116 139 / 90%);
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.product-homepage-requirement-changes__title {
|
|
||||||
display: block;
|
|
||||||
margin-top: 10px;
|
|
||||||
color: rgb(15 23 42 / 98%);
|
|
||||||
font-size: 15px;
|
|
||||||
line-height: 1.65;
|
|
||||||
}
|
|
||||||
|
|
||||||
.product-homepage-requirement-changes__status {
|
|
||||||
margin: 8px 0 0;
|
|
||||||
color: rgb(71 85 105 / 94%);
|
|
||||||
font-size: 13px;
|
|
||||||
line-height: 1.7;
|
|
||||||
}
|
|
||||||
|
|
||||||
.product-homepage-extension {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
|
||||||
gap: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.product-homepage-extension__list {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.product-homepage-extension__item {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 10px;
|
|
||||||
padding: 13px 14px;
|
|
||||||
border-radius: 16px;
|
|
||||||
background-color: rgb(248 250 252 / 96%);
|
|
||||||
color: rgb(51 65 85 / 95%);
|
|
||||||
font-size: 14px;
|
|
||||||
line-height: 1.7;
|
|
||||||
}
|
|
||||||
|
|
||||||
.product-homepage-extension__dot {
|
|
||||||
width: 8px;
|
|
||||||
height: 8px;
|
|
||||||
border-radius: 999px;
|
|
||||||
background-color: rgb(14 116 144 / 88%);
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (width <= 1280px) {
|
|
||||||
.product-homepage-banner,
|
|
||||||
.product-homepage-main,
|
|
||||||
.product-homepage-extension {
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (width <= 768px) {
|
|
||||||
.product-homepage-banner {
|
|
||||||
padding: 18px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.product-homepage-banner__title-row {
|
|
||||||
flex-wrap: wrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.product-homepage-banner__title {
|
|
||||||
font-size: 28px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.product-homepage-banner__status-word {
|
|
||||||
font-size: 22px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.product-homepage-banner__facts,
|
|
||||||
.product-homepage-banner__metrics,
|
|
||||||
.product-homepage-requirement-summary__metrics {
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,785 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import { computed, ref, watch } from 'vue';
|
|
||||||
import { RDMS_OBJECT_DIRECTION_DICT_CODE, RDMS_PROJECT_TYPE_DICT_CODE } from '@/constants/dict';
|
|
||||||
import { fetchGetProject, fetchGetProjectMembers, fetchGetProjectSettings } from '@/service/api';
|
|
||||||
import { useDict } from '@/hooks/business/dict';
|
|
||||||
import { useCurrentProject } from '../../shared/use-current-project';
|
|
||||||
import {
|
|
||||||
buildProjectHomepageBanner,
|
|
||||||
buildProjectHomepageTimeline,
|
|
||||||
buildProjectScheduleOverview,
|
|
||||||
buildProjectTeamOverview,
|
|
||||||
getProjectHomepageExtensionModules
|
|
||||||
} from './homepage';
|
|
||||||
import { projectHomepageExtensionMock } from './mock';
|
|
||||||
|
|
||||||
defineOptions({ name: 'ProjectOverview' });
|
|
||||||
|
|
||||||
const { currentObjectId, currentProject } = useCurrentProject();
|
|
||||||
const { getLabel: getDirectionLabel } = useDict(RDMS_OBJECT_DIRECTION_DICT_CODE);
|
|
||||||
const { getLabel: getProjectTypeLabel } = useDict(RDMS_PROJECT_TYPE_DICT_CODE);
|
|
||||||
|
|
||||||
const pageLoading = ref(false);
|
|
||||||
const projectDetail = ref<Api.Project.Project | null>(null);
|
|
||||||
const settings = ref<Api.Project.ProjectSettings | null>(null);
|
|
||||||
const members = ref<Api.Project.ProjectMember[]>([]);
|
|
||||||
const latestActivityTime = ref('');
|
|
||||||
|
|
||||||
const timelineItems = computed(() => buildProjectHomepageTimeline(projectDetail.value, settings.value, members.value));
|
|
||||||
const scheduleOverview = computed(() => buildProjectScheduleOverview(projectDetail.value));
|
|
||||||
const teamOverview = computed(() => buildProjectTeamOverview(members.value));
|
|
||||||
const homepageBanner = computed(() =>
|
|
||||||
buildProjectHomepageBanner({
|
|
||||||
project: projectDetail.value,
|
|
||||||
settings: settings.value,
|
|
||||||
members: members.value,
|
|
||||||
latestActivityTime: latestActivityTime.value
|
|
||||||
})
|
|
||||||
);
|
|
||||||
const extensionModules = computed(() => getProjectHomepageExtensionModules(projectHomepageExtensionMock));
|
|
||||||
const directionLabel = computed(() => getDirectionLabel(homepageBanner.value.identity.directionCode, '--'));
|
|
||||||
const projectTypeLabel = computed(() => getProjectTypeLabel(homepageBanner.value.identity.projectType, '--'));
|
|
||||||
const bannerFacts = computed(() => [
|
|
||||||
{
|
|
||||||
label: '项目方向',
|
|
||||||
value: directionLabel.value,
|
|
||||||
fullWidth: false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: '项目类型',
|
|
||||||
value: projectTypeLabel.value,
|
|
||||||
fullWidth: false
|
|
||||||
},
|
|
||||||
...homepageBanner.value.identity.facts
|
|
||||||
]);
|
|
||||||
const progressValue = computed(() => projectDetail.value?.progressRate ?? 0);
|
|
||||||
const bannerStatusClass = computed(() => {
|
|
||||||
const statusCode = homepageBanner.value.identity.statusCode;
|
|
||||||
|
|
||||||
return statusCode ? `project-homepage-banner--${statusCode}` : 'project-homepage-banner--default';
|
|
||||||
});
|
|
||||||
const bannerStatusWordClass = computed(() => {
|
|
||||||
const statusCode = homepageBanner.value.identity.statusCode;
|
|
||||||
|
|
||||||
return statusCode
|
|
||||||
? `project-homepage-banner__status-word--${statusCode}`
|
|
||||||
: 'project-homepage-banner__status-word--default';
|
|
||||||
});
|
|
||||||
|
|
||||||
async function loadOverviewData(objectId: string) {
|
|
||||||
pageLoading.value = true;
|
|
||||||
|
|
||||||
try {
|
|
||||||
const [projectResult, settingsResult, membersResult] = await Promise.all([
|
|
||||||
fetchGetProject(objectId),
|
|
||||||
fetchGetProjectSettings(objectId),
|
|
||||||
fetchGetProjectMembers(objectId)
|
|
||||||
]);
|
|
||||||
|
|
||||||
projectDetail.value = projectResult.error ? null : projectResult.data || null;
|
|
||||||
settings.value = settingsResult.error ? null : settingsResult.data || null;
|
|
||||||
members.value = membersResult.error ? [] : membersResult.data || [];
|
|
||||||
latestActivityTime.value = timelineItems.value[0]?.time || '';
|
|
||||||
} finally {
|
|
||||||
pageLoading.value = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
watch(
|
|
||||||
() => currentObjectId.value,
|
|
||||||
async objectId => {
|
|
||||||
if (!objectId) {
|
|
||||||
projectDetail.value = null;
|
|
||||||
settings.value = null;
|
|
||||||
members.value = [];
|
|
||||||
latestActivityTime.value = '';
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await loadOverviewData(objectId);
|
|
||||||
},
|
|
||||||
{ immediate: true }
|
|
||||||
);
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div v-loading="pageLoading" class="project-homepage">
|
|
||||||
<section class="project-homepage-banner" :class="bannerStatusClass">
|
|
||||||
<div class="project-homepage-banner__identity">
|
|
||||||
<div class="project-homepage-banner__title-group">
|
|
||||||
<div class="project-homepage-banner__title-main min-w-0">
|
|
||||||
<div class="project-homepage-banner__title-row">
|
|
||||||
<h1 class="project-homepage-banner__title">
|
|
||||||
{{ homepageBanner.identity.name || currentProject?.projectName || '--' }}
|
|
||||||
</h1>
|
|
||||||
<span class="project-homepage-banner__status-word" :class="bannerStatusWordClass">
|
|
||||||
{{ homepageBanner.identity.statusLabel }}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div class="project-homepage-banner__subtitle">
|
|
||||||
<span class="project-homepage-banner__code">编号 {{ homepageBanner.identity.code }}</span>
|
|
||||||
<p v-if="homepageBanner.identity.description" class="project-homepage-banner__description">
|
|
||||||
{{ homepageBanner.identity.description }}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="project-homepage-banner__facts">
|
|
||||||
<div
|
|
||||||
v-for="item in bannerFacts"
|
|
||||||
:key="item.label"
|
|
||||||
class="project-homepage-banner__fact"
|
|
||||||
:class="{ 'project-homepage-banner__fact--full': item.fullWidth }"
|
|
||||||
>
|
|
||||||
<span class="project-homepage-banner__fact-label">{{ item.label }}</span>
|
|
||||||
<strong class="project-homepage-banner__fact-value">{{ item.value }}</strong>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="project-homepage-banner__metrics">
|
|
||||||
<article v-for="item in homepageBanner.metrics" :key="item.label" class="project-homepage-banner__metric">
|
|
||||||
<span class="project-homepage-banner__metric-label">{{ item.label }}</span>
|
|
||||||
<strong class="project-homepage-banner__metric-value">{{ item.value }}</strong>
|
|
||||||
</article>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section class="project-homepage-main">
|
|
||||||
<ElCard class="project-homepage-panel card-wrapper">
|
|
||||||
<template #header>
|
|
||||||
<div>
|
|
||||||
<h3 class="project-homepage-panel__title">项目动态时间线</h3>
|
|
||||||
<p class="project-homepage-panel__desc">
|
|
||||||
先展示项目创建、状态动作、实际日期和团队变化,后续可替换为专用动态接口。
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<div v-if="timelineItems.length" class="project-homepage-timeline">
|
|
||||||
<article v-for="item in timelineItems" :key="item.key" class="project-homepage-timeline__item">
|
|
||||||
<div class="project-homepage-timeline__rail">
|
|
||||||
<span class="project-homepage-timeline__dot" :class="`project-homepage-timeline__dot--${item.tone}`" />
|
|
||||||
<span class="project-homepage-timeline__line" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="project-homepage-timeline__content">
|
|
||||||
<div class="project-homepage-timeline__meta">
|
|
||||||
<ElTag effect="plain" size="small">{{ item.tag }}</ElTag>
|
|
||||||
<span class="project-homepage-timeline__time">{{ item.time }}</span>
|
|
||||||
</div>
|
|
||||||
<p class="project-homepage-timeline__sentence">
|
|
||||||
<strong class="project-homepage-timeline__headline">{{ item.title }}</strong>
|
|
||||||
<span>{{ item.content }}</span>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</article>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<ElEmpty v-else description="当前暂无可展示的项目动态" :image-size="88" />
|
|
||||||
</ElCard>
|
|
||||||
|
|
||||||
<div class="project-homepage-main__aside">
|
|
||||||
<ElCard class="project-homepage-panel card-wrapper">
|
|
||||||
<template #header>
|
|
||||||
<div>
|
|
||||||
<h3 class="project-homepage-panel__title">计划进展概览</h3>
|
|
||||||
<p class="project-homepage-panel__desc">先看当前进度、计划周期和实际执行日期是否已经闭环。</p>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<div class="project-homepage-schedule">
|
|
||||||
<div class="project-homepage-schedule__progress">
|
|
||||||
<strong>{{ progressValue }}%</strong>
|
|
||||||
<ElProgress
|
|
||||||
:percentage="progressValue"
|
|
||||||
:stroke-width="8"
|
|
||||||
:show-text="false"
|
|
||||||
:color="progressValue >= 100 ? '#10b981' : progressValue >= 50 ? '#3b82f6' : '#6366f1'"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="project-homepage-summary-metrics">
|
|
||||||
<article
|
|
||||||
v-for="item in scheduleOverview.metrics"
|
|
||||||
:key="item.label"
|
|
||||||
class="project-homepage-summary-metrics__item"
|
|
||||||
>
|
|
||||||
<span class="project-homepage-summary-metrics__label">{{ item.label }}</span>
|
|
||||||
<strong class="project-homepage-summary-metrics__value">{{ item.value }}</strong>
|
|
||||||
</article>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="project-homepage-schedule__dates">
|
|
||||||
<div v-for="item in scheduleOverview.dates" :key="item.label" class="project-homepage-schedule__date">
|
|
||||||
<span>{{ item.label }}</span>
|
|
||||||
<strong>{{ item.value }}</strong>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</ElCard>
|
|
||||||
|
|
||||||
<ElCard class="project-homepage-panel card-wrapper">
|
|
||||||
<template #header>
|
|
||||||
<div>
|
|
||||||
<h3 class="project-homepage-panel__title">项目团队概览</h3>
|
|
||||||
<p class="project-homepage-panel__desc">承接当前成员规模、负责人和角色结构,和设置页团队维护分开表达。</p>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<div class="project-homepage-team">
|
|
||||||
<div class="project-homepage-summary-metrics">
|
|
||||||
<article
|
|
||||||
v-for="item in teamOverview.metrics"
|
|
||||||
:key="item.label"
|
|
||||||
class="project-homepage-summary-metrics__item"
|
|
||||||
>
|
|
||||||
<span class="project-homepage-summary-metrics__label">{{ item.label }}</span>
|
|
||||||
<strong class="project-homepage-summary-metrics__value">{{ item.value }}</strong>
|
|
||||||
</article>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-if="teamOverview.roles.length" class="project-homepage-team__roles">
|
|
||||||
<div v-for="item in teamOverview.roles" :key="item.label" class="project-homepage-team__role">
|
|
||||||
<span>{{ item.label }}</span>
|
|
||||||
<strong>{{ item.value }}</strong>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<ElEmpty v-else description="当前暂无有效团队成员" :image-size="72" />
|
|
||||||
</div>
|
|
||||||
</ElCard>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section class="project-homepage-extension">
|
|
||||||
<ElCard v-for="module in extensionModules" :key="module.key" class="project-homepage-panel card-wrapper">
|
|
||||||
<template #header>
|
|
||||||
<div>
|
|
||||||
<h3 class="project-homepage-panel__title">{{ module.title }}</h3>
|
|
||||||
<p class="project-homepage-panel__desc">{{ module.description }}</p>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<div class="project-homepage-extension__list">
|
|
||||||
<div v-for="item in module.items" :key="item" class="project-homepage-extension__item">
|
|
||||||
<span class="project-homepage-extension__dot" />
|
|
||||||
<span>{{ item }}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</ElCard>
|
|
||||||
</section>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.project-homepage {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.project-homepage-banner {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: minmax(0, 1.55fr) minmax(320px, 1fr);
|
|
||||||
gap: 16px;
|
|
||||||
padding: 24px;
|
|
||||||
border: 1px solid rgb(226 232 240 / 92%);
|
|
||||||
border-radius: 24px;
|
|
||||||
background:
|
|
||||||
radial-gradient(circle at top left, rgb(14 116 144 / 14%), transparent 34%),
|
|
||||||
radial-gradient(circle at bottom right, rgb(15 118 110 / 10%), transparent 28%),
|
|
||||||
linear-gradient(135deg, rgb(255 255 255 / 99%), rgb(248 250 252 / 98%));
|
|
||||||
}
|
|
||||||
|
|
||||||
.project-homepage-banner--default {
|
|
||||||
border-color: rgb(226 232 240 / 92%);
|
|
||||||
background:
|
|
||||||
radial-gradient(circle at top left, rgb(14 116 144 / 14%), transparent 34%),
|
|
||||||
radial-gradient(circle at bottom right, rgb(15 118 110 / 10%), transparent 28%),
|
|
||||||
linear-gradient(135deg, rgb(255 255 255 / 99%), rgb(248 250 252 / 98%));
|
|
||||||
}
|
|
||||||
|
|
||||||
.project-homepage-banner--pending,
|
|
||||||
.project-homepage-banner--archived {
|
|
||||||
border-color: rgb(203 213 225 / 92%);
|
|
||||||
background:
|
|
||||||
radial-gradient(circle at top left, rgb(100 116 139 / 14%), transparent 34%),
|
|
||||||
radial-gradient(circle at bottom right, rgb(148 163 184 / 10%), transparent 26%),
|
|
||||||
linear-gradient(135deg, rgb(248 250 252 / 99%), rgb(255 255 255 / 98%));
|
|
||||||
}
|
|
||||||
|
|
||||||
.project-homepage-banner--active,
|
|
||||||
.project-homepage-banner--completed {
|
|
||||||
border-color: rgb(167 243 208 / 88%);
|
|
||||||
background:
|
|
||||||
radial-gradient(circle at top left, rgb(5 150 105 / 16%), transparent 32%),
|
|
||||||
radial-gradient(circle at bottom right, rgb(16 185 129 / 14%), transparent 26%),
|
|
||||||
linear-gradient(135deg, rgb(236 253 245 / 99%), rgb(255 255 255 / 98%));
|
|
||||||
}
|
|
||||||
|
|
||||||
.project-homepage-banner--paused {
|
|
||||||
border-color: rgb(253 230 138 / 90%);
|
|
||||||
background:
|
|
||||||
radial-gradient(circle at top left, rgb(245 158 11 / 18%), transparent 32%),
|
|
||||||
radial-gradient(circle at bottom right, rgb(251 191 36 / 16%), transparent 24%),
|
|
||||||
linear-gradient(135deg, rgb(255 251 235 / 99%), rgb(255 255 255 / 98%));
|
|
||||||
}
|
|
||||||
|
|
||||||
.project-homepage-banner--cancelled {
|
|
||||||
border-color: rgb(254 205 211 / 92%);
|
|
||||||
background:
|
|
||||||
radial-gradient(circle at top left, rgb(244 63 94 / 16%), transparent 32%),
|
|
||||||
radial-gradient(circle at bottom right, rgb(251 113 133 / 14%), transparent 24%),
|
|
||||||
linear-gradient(135deg, rgb(255 241 242 / 99%), rgb(255 255 255 / 98%));
|
|
||||||
}
|
|
||||||
|
|
||||||
.project-homepage-banner__identity {
|
|
||||||
display: flex;
|
|
||||||
min-width: 0;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.project-homepage-banner__title-group {
|
|
||||||
display: flex;
|
|
||||||
align-items: flex-start;
|
|
||||||
gap: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.project-homepage-banner__title-main {
|
|
||||||
display: flex;
|
|
||||||
min-width: 0;
|
|
||||||
flex: 1;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.project-homepage-banner__title-row {
|
|
||||||
display: flex;
|
|
||||||
min-width: 0;
|
|
||||||
align-items: baseline;
|
|
||||||
gap: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.project-homepage-banner__code {
|
|
||||||
margin: 0;
|
|
||||||
color: rgb(14 116 144 / 92%);
|
|
||||||
font-size: 13px;
|
|
||||||
font-weight: 600;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.project-homepage-banner__title {
|
|
||||||
margin: 0;
|
|
||||||
color: rgb(15 23 42 / 98%);
|
|
||||||
font-size: 34px;
|
|
||||||
line-height: 1.15;
|
|
||||||
letter-spacing: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.project-homepage-banner__subtitle {
|
|
||||||
display: flex;
|
|
||||||
min-width: 0;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
align-items: baseline;
|
|
||||||
gap: 10px 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.project-homepage-banner__description {
|
|
||||||
margin: 0;
|
|
||||||
min-width: 0;
|
|
||||||
color: rgb(71 85 105 / 94%);
|
|
||||||
font-size: 14px;
|
|
||||||
line-height: 1.8;
|
|
||||||
}
|
|
||||||
|
|
||||||
.project-homepage-banner__status-word {
|
|
||||||
flex-shrink: 0;
|
|
||||||
font-size: 26px;
|
|
||||||
font-weight: 800;
|
|
||||||
line-height: 1;
|
|
||||||
letter-spacing: 0.18em;
|
|
||||||
text-transform: uppercase;
|
|
||||||
user-select: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.project-homepage-banner__status-word--default {
|
|
||||||
color: rgb(148 163 184 / 48%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.project-homepage-banner__status-word--pending,
|
|
||||||
.project-homepage-banner__status-word--archived {
|
|
||||||
color: transparent;
|
|
||||||
background: linear-gradient(180deg, rgb(71 85 105 / 92%), rgb(148 163 184 / 64%));
|
|
||||||
background-clip: text;
|
|
||||||
text-shadow: 0 10px 24px rgb(100 116 139 / 14%);
|
|
||||||
-webkit-text-fill-color: transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
.project-homepage-banner__status-word--active,
|
|
||||||
.project-homepage-banner__status-word--completed {
|
|
||||||
color: transparent;
|
|
||||||
background: linear-gradient(180deg, rgb(5 150 105 / 94%), rgb(16 185 129 / 70%));
|
|
||||||
background-clip: text;
|
|
||||||
text-shadow: 0 10px 24px rgb(5 150 105 / 16%);
|
|
||||||
-webkit-text-fill-color: transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
.project-homepage-banner__status-word--paused {
|
|
||||||
color: transparent;
|
|
||||||
background: linear-gradient(180deg, rgb(217 119 6 / 94%), rgb(245 158 11 / 70%));
|
|
||||||
background-clip: text;
|
|
||||||
text-shadow: 0 10px 24px rgb(245 158 11 / 16%);
|
|
||||||
-webkit-text-fill-color: transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
.project-homepage-banner__status-word--cancelled {
|
|
||||||
color: transparent;
|
|
||||||
background: linear-gradient(180deg, rgb(244 63 94 / 94%), rgb(251 113 133 / 68%));
|
|
||||||
background-clip: text;
|
|
||||||
text-shadow: 0 10px 24px rgb(244 63 94 / 16%);
|
|
||||||
-webkit-text-fill-color: transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
.project-homepage-banner__facts {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
||||||
gap: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.project-homepage-banner__fact {
|
|
||||||
display: flex;
|
|
||||||
min-height: 58px;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
gap: 10px;
|
|
||||||
padding: 14px 16px;
|
|
||||||
border: 1px solid rgb(226 232 240 / 88%);
|
|
||||||
border-radius: 18px;
|
|
||||||
background-color: rgb(255 255 255 / 78%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.project-homepage-banner__fact--full {
|
|
||||||
grid-column: 1 / -1;
|
|
||||||
align-items: flex-start;
|
|
||||||
}
|
|
||||||
|
|
||||||
.project-homepage-banner__fact-label {
|
|
||||||
color: rgb(100 116 139 / 94%);
|
|
||||||
font-size: 13px;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.project-homepage-banner__fact-value {
|
|
||||||
color: rgb(15 23 42 / 96%);
|
|
||||||
font-size: 15px;
|
|
||||||
line-height: 1.6;
|
|
||||||
text-align: right;
|
|
||||||
}
|
|
||||||
|
|
||||||
.project-homepage-banner__fact--full .project-homepage-banner__fact-value {
|
|
||||||
max-width: 72%;
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
.project-homepage-banner__metrics {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
||||||
gap: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.project-homepage-banner__metric {
|
|
||||||
display: flex;
|
|
||||||
min-height: 112px;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: center;
|
|
||||||
gap: 16px;
|
|
||||||
padding: 18px;
|
|
||||||
border: 1px solid rgb(226 232 240 / 88%);
|
|
||||||
border-radius: 20px;
|
|
||||||
background: linear-gradient(180deg, rgb(255 255 255 / 99%), rgb(241 245 249 / 98%));
|
|
||||||
}
|
|
||||||
|
|
||||||
.project-homepage-banner__metric-label {
|
|
||||||
color: rgb(100 116 139 / 92%);
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.project-homepage-banner__metric-value {
|
|
||||||
color: rgb(15 23 42 / 98%);
|
|
||||||
font-size: 28px;
|
|
||||||
line-height: 1.1;
|
|
||||||
letter-spacing: 0;
|
|
||||||
word-break: break-word;
|
|
||||||
}
|
|
||||||
|
|
||||||
.project-homepage-main {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
||||||
gap: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.project-homepage-main__aside {
|
|
||||||
display: flex;
|
|
||||||
min-width: 0;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.project-homepage-panel {
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.project-homepage-panel__title {
|
|
||||||
margin: 0;
|
|
||||||
color: rgb(15 23 42 / 98%);
|
|
||||||
font-size: 16px;
|
|
||||||
font-weight: 700;
|
|
||||||
}
|
|
||||||
|
|
||||||
.project-homepage-panel__desc {
|
|
||||||
margin: 4px 0 0;
|
|
||||||
color: rgb(100 116 139 / 92%);
|
|
||||||
font-size: 13px;
|
|
||||||
line-height: 1.7;
|
|
||||||
}
|
|
||||||
|
|
||||||
.project-homepage-timeline {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.project-homepage-timeline__item {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 20px minmax(0, 1fr);
|
|
||||||
gap: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.project-homepage-timeline__rail {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.project-homepage-timeline__dot {
|
|
||||||
width: 12px;
|
|
||||||
height: 12px;
|
|
||||||
margin-top: 6px;
|
|
||||||
border-radius: 999px;
|
|
||||||
box-shadow: 0 0 0 4px rgb(255 255 255 / 96%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.project-homepage-timeline__dot--sky {
|
|
||||||
background-color: rgb(14 165 233 / 88%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.project-homepage-timeline__dot--emerald {
|
|
||||||
background-color: rgb(5 150 105 / 88%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.project-homepage-timeline__dot--amber {
|
|
||||||
background-color: rgb(217 119 6 / 88%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.project-homepage-timeline__dot--rose {
|
|
||||||
background-color: rgb(225 29 72 / 88%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.project-homepage-timeline__dot--slate {
|
|
||||||
background-color: rgb(100 116 139 / 88%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.project-homepage-timeline__line {
|
|
||||||
flex: 1;
|
|
||||||
width: 2px;
|
|
||||||
min-height: 30px;
|
|
||||||
margin-top: 4px;
|
|
||||||
background: linear-gradient(180deg, rgb(203 213 225 / 96%), rgb(226 232 240 / 28%));
|
|
||||||
}
|
|
||||||
|
|
||||||
.project-homepage-timeline__item:last-child .project-homepage-timeline__line {
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.project-homepage-timeline__content {
|
|
||||||
padding: 12px 14px;
|
|
||||||
border: 1px solid rgb(226 232 240 / 92%);
|
|
||||||
border-radius: 16px;
|
|
||||||
background-color: rgb(255 255 255 / 98%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.project-homepage-timeline__meta {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
gap: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.project-homepage-timeline__time {
|
|
||||||
color: rgb(100 116 139 / 90%);
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.project-homepage-timeline__sentence {
|
|
||||||
margin: 6px 0 0;
|
|
||||||
color: rgb(71 85 105 / 94%);
|
|
||||||
font-size: 13px;
|
|
||||||
line-height: 1.65;
|
|
||||||
}
|
|
||||||
|
|
||||||
.project-homepage-timeline__headline {
|
|
||||||
margin-right: 6px;
|
|
||||||
color: rgb(15 23 42 / 98%);
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
.project-homepage-schedule,
|
|
||||||
.project-homepage-team {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.project-homepage-schedule__progress {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 12px;
|
|
||||||
padding: 18px;
|
|
||||||
border-radius: 18px;
|
|
||||||
background: linear-gradient(180deg, rgb(248 250 252 / 98%), rgb(241 245 249 / 94%));
|
|
||||||
}
|
|
||||||
|
|
||||||
.project-homepage-schedule__progress strong {
|
|
||||||
color: rgb(15 23 42 / 98%);
|
|
||||||
font-size: 36px;
|
|
||||||
line-height: 1.1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.project-homepage-summary-metrics {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
||||||
gap: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.project-homepage-summary-metrics__item {
|
|
||||||
display: flex;
|
|
||||||
min-height: 100px;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: center;
|
|
||||||
gap: 14px;
|
|
||||||
padding: 14px 16px;
|
|
||||||
border-radius: 18px;
|
|
||||||
background: linear-gradient(180deg, rgb(248 250 252 / 98%), rgb(241 245 249 / 94%));
|
|
||||||
}
|
|
||||||
|
|
||||||
.project-homepage-summary-metrics__label {
|
|
||||||
color: rgb(100 116 139 / 92%);
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.project-homepage-summary-metrics__value {
|
|
||||||
color: rgb(15 23 42 / 98%);
|
|
||||||
font-size: 22px;
|
|
||||||
line-height: 1.2;
|
|
||||||
word-break: break-word;
|
|
||||||
}
|
|
||||||
|
|
||||||
.project-homepage-schedule__dates,
|
|
||||||
.project-homepage-team__roles {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.project-homepage-schedule__date,
|
|
||||||
.project-homepage-team__role {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
gap: 12px;
|
|
||||||
padding: 13px 14px;
|
|
||||||
border: 1px solid rgb(226 232 240 / 88%);
|
|
||||||
border-radius: 14px;
|
|
||||||
background-color: rgb(255 255 255 / 96%);
|
|
||||||
color: rgb(51 65 85 / 95%);
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.project-homepage-schedule__date strong,
|
|
||||||
.project-homepage-team__role strong {
|
|
||||||
color: rgb(15 23 42 / 98%);
|
|
||||||
font-size: 18px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.project-homepage-extension {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
|
||||||
gap: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.project-homepage-extension__list {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.project-homepage-extension__item {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 10px;
|
|
||||||
padding: 13px 14px;
|
|
||||||
border-radius: 16px;
|
|
||||||
background-color: rgb(248 250 252 / 96%);
|
|
||||||
color: rgb(51 65 85 / 95%);
|
|
||||||
font-size: 14px;
|
|
||||||
line-height: 1.7;
|
|
||||||
}
|
|
||||||
|
|
||||||
.project-homepage-extension__dot {
|
|
||||||
width: 8px;
|
|
||||||
height: 8px;
|
|
||||||
flex-shrink: 0;
|
|
||||||
border-radius: 999px;
|
|
||||||
background-color: rgb(14 116 144 / 88%);
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (width <= 1280px) {
|
|
||||||
.project-homepage-banner,
|
|
||||||
.project-homepage-main,
|
|
||||||
.project-homepage-extension {
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (width <= 768px) {
|
|
||||||
.project-homepage-banner {
|
|
||||||
padding: 18px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.project-homepage-banner__title-row {
|
|
||||||
flex-wrap: wrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.project-homepage-banner__title {
|
|
||||||
font-size: 28px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.project-homepage-banner__status-word {
|
|
||||||
font-size: 22px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.project-homepage-banner__facts,
|
|
||||||
.project-homepage-banner__metrics,
|
|
||||||
.project-homepage-summary-metrics {
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
}
|
|
||||||
|
|
||||||
.project-homepage-banner__fact--full .project-homepage-banner__fact-value {
|
|
||||||
max-width: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
Reference in New Issue
Block a user