chore: 调整工作台页面样式-ui稿一致
This commit is contained in:
@@ -0,0 +1,66 @@
|
||||
<script lang="ts" setup>
|
||||
import type { EchartsUIType } from '@vben/plugins/echarts';
|
||||
|
||||
import { onMounted, onUnmounted, ref, watch } from 'vue';
|
||||
|
||||
import { EchartsUI, useEcharts } from '@vben/plugins/echarts';
|
||||
|
||||
interface Props {
|
||||
/** 图表配置选项 */
|
||||
options: any;
|
||||
/** 图表透明度 */
|
||||
opacity?: number;
|
||||
/** 图表容器底部内边距 */
|
||||
paddingBottom?: string;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
opacity: 0.6,
|
||||
paddingBottom: '1.5rem',
|
||||
});
|
||||
|
||||
const chartRef = ref<EchartsUIType>();
|
||||
const { renderEcharts } = useEcharts(chartRef);
|
||||
|
||||
// 初始化图表
|
||||
function initChart() {
|
||||
if (props.options) {
|
||||
renderEcharts(props.options);
|
||||
}
|
||||
}
|
||||
|
||||
// 监听配置变化
|
||||
watch(
|
||||
() => props.options,
|
||||
() => {
|
||||
initChart();
|
||||
},
|
||||
{ deep: true },
|
||||
);
|
||||
|
||||
// 窗口大小变化时调整图表
|
||||
const handleResize = () => {
|
||||
if (chartRef.value) {
|
||||
chartRef.value.resize?.();
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
initChart();
|
||||
window.addEventListener('resize', handleResize);
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
window.removeEventListener('resize', handleResize);
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="absolute inset-x-0 bottom-0 top-0 z-0 overflow-visible rounded-lg"
|
||||
:style="{ paddingBottom: paddingBottom }"
|
||||
>
|
||||
<EchartsUI ref="chartRef" :style="{ opacity: opacity }" class="h-full w-full" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
2
apps/web-antd/src/components/background-chart/index.ts
Normal file
2
apps/web-antd/src/components/background-chart/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export { default as BackgroundChart } from './BackgroundChart.vue';
|
||||
|
||||
@@ -1,14 +1,23 @@
|
||||
<script lang="ts" setup>
|
||||
import type { EchartsUIType } from '@vben/plugins/echarts';
|
||||
|
||||
import { computed, onMounted, ref } from 'vue';
|
||||
import { computed, ref } from 'vue';
|
||||
|
||||
import { GlassCard } from '@vben/common-ui';
|
||||
import { IconifyIcon } from '@vben/icons';
|
||||
import { EchartsUI, useEcharts } from '@vben/plugins/echarts';
|
||||
|
||||
import { Button, Dropdown, message, Modal, Tag } from 'ant-design-vue';
|
||||
|
||||
import { BackgroundChart } from '../../../components/background-chart';
|
||||
import { createBackgroundChartOptions } from './utils/chart-options';
|
||||
import {
|
||||
aiCardClasses,
|
||||
buttonClasses,
|
||||
cardContentClasses,
|
||||
modalClasses,
|
||||
statCardClasses,
|
||||
taskListClasses,
|
||||
textShadowClasses,
|
||||
} from './utils/styles';
|
||||
|
||||
// 机器人图片路径
|
||||
const robotImage = '/images/Image_robot.png';
|
||||
|
||||
@@ -172,121 +181,40 @@ const filteredTasks = computed(() => {
|
||||
return tasks;
|
||||
});
|
||||
|
||||
// --- 图表引用 ---
|
||||
const flowChartRef = ref<EchartsUIType>();
|
||||
const workOrderChartRef = ref<EchartsUIType>();
|
||||
const { renderEcharts: renderFlowChart } = useEcharts(flowChartRef);
|
||||
const { renderEcharts: renderWorkOrderChart } = useEcharts(workOrderChartRef);
|
||||
|
||||
// --- 初始化图表 ---
|
||||
onMounted(() => {
|
||||
initFlowChart();
|
||||
initWorkOrderChart();
|
||||
});
|
||||
|
||||
// 初始化客流图表
|
||||
function initFlowChart() {
|
||||
const options: any = {
|
||||
grid: {
|
||||
left: 0,
|
||||
right: 0,
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category' as const,
|
||||
data: flowData.map((item) => item.time),
|
||||
show: false,
|
||||
boundaryGap: false,
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value' as const,
|
||||
show: false,
|
||||
},
|
||||
series: [
|
||||
{
|
||||
type: 'line' as const,
|
||||
data: flowData.map((item) => item.value),
|
||||
smooth: true,
|
||||
areaStyle: {
|
||||
color: {
|
||||
type: 'linear',
|
||||
x: 0,
|
||||
y: 0,
|
||||
x2: 0,
|
||||
y2: 1,
|
||||
colorStops: [
|
||||
{ offset: 0, color: 'rgba(255, 160, 10, 0.4)' },
|
||||
{ offset: 1, color: 'rgba(255, 160, 10, 0)' },
|
||||
],
|
||||
},
|
||||
},
|
||||
lineStyle: {
|
||||
color: '#FFA00A',
|
||||
width: 4,
|
||||
},
|
||||
symbol: 'none',
|
||||
symbolSize: 0,
|
||||
},
|
||||
// --- 图表配置 ---
|
||||
const flowChartOptions = computed(() =>
|
||||
createBackgroundChartOptions({
|
||||
xAxisData: flowData.map((item) => item.time),
|
||||
yAxisData: flowData.map((item) => item.value),
|
||||
seriesName: '客流',
|
||||
lineColor: '#FFA00A',
|
||||
areaColor: [
|
||||
'rgba(255, 160, 10, 0.25)',
|
||||
'rgba(255, 160, 10, 0.15)',
|
||||
'rgba(255, 160, 10, 0)',
|
||||
],
|
||||
tooltip: {
|
||||
show: false,
|
||||
yAxisFormatter: (value: number) => {
|
||||
if (value >= 1000) {
|
||||
return `${(value / 1000).toFixed(1)}k`;
|
||||
}
|
||||
return value.toString();
|
||||
},
|
||||
};
|
||||
renderFlowChart(options);
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
// 初始化工单趋势图表
|
||||
function initWorkOrderChart() {
|
||||
const options: any = {
|
||||
grid: {
|
||||
left: 0,
|
||||
right: 0,
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category' as const,
|
||||
data: workOrderTrend.map((item) => item.time),
|
||||
show: false,
|
||||
boundaryGap: false,
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value' as const,
|
||||
show: false,
|
||||
},
|
||||
series: [
|
||||
{
|
||||
type: 'line' as const,
|
||||
data: workOrderTrend.map((item) => item.value),
|
||||
smooth: true,
|
||||
areaStyle: {
|
||||
color: {
|
||||
type: 'linear',
|
||||
x: 0,
|
||||
y: 0,
|
||||
x2: 0,
|
||||
y2: 1,
|
||||
colorStops: [
|
||||
{ offset: 0, color: 'rgba(59, 130, 246, 0.4)' },
|
||||
{ offset: 1, color: 'rgba(59, 130, 246, 0)' },
|
||||
],
|
||||
},
|
||||
},
|
||||
lineStyle: {
|
||||
color: '#3B82F6',
|
||||
width: 4,
|
||||
},
|
||||
symbol: 'none',
|
||||
symbolSize: 0,
|
||||
},
|
||||
const workOrderChartOptions = computed(() =>
|
||||
createBackgroundChartOptions({
|
||||
xAxisData: workOrderTrend.map((item) => item.time),
|
||||
yAxisData: workOrderTrend.map((item) => item.value),
|
||||
seriesName: '工单',
|
||||
lineColor: '#3B82F6',
|
||||
areaColor: [
|
||||
'rgba(59, 130, 246, 0.25)',
|
||||
'rgba(59, 130, 246, 0.15)',
|
||||
'rgba(59, 130, 246, 0)',
|
||||
],
|
||||
tooltip: {
|
||||
show: false,
|
||||
},
|
||||
};
|
||||
renderWorkOrderChart(options);
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
// --- 交互方法 ---
|
||||
function handleTaskComplete(taskId: number) {
|
||||
@@ -375,24 +303,15 @@ function getColorClass(color: string, type: 'bg' | 'text') {
|
||||
<!-- 卡片1: 实时客流监测 -->
|
||||
<GlassCard class="relative flex h-[35%] flex-col overflow-hidden p-6">
|
||||
<div class="relative z-10 flex items-start justify-between">
|
||||
<div>
|
||||
<h3
|
||||
class="text-sm font-semibold uppercase tracking-wide text-slate-500"
|
||||
>
|
||||
实时客流监测
|
||||
</h3>
|
||||
<div class="mt-1 flex items-baseline gap-2">
|
||||
<span class="text-4xl font-bold text-slate-800">2,450</span>
|
||||
<Tag color="success" class="text-xs font-bold">+12%</Tag>
|
||||
<div :class="cardContentClasses.container">
|
||||
<h3 :class="cardContentClasses.title">实时客流监测</h3>
|
||||
<div :class="cardContentClasses.numberContainer">
|
||||
<span :class="cardContentClasses.largeNumber">2,450</span>
|
||||
<Tag color="success" :class="`text-xs font-bold ${textShadowClasses.tag}`">+12%</Tag>
|
||||
</div>
|
||||
<p class="mt-1 text-xs text-slate-400">预计高峰时间: 14:00</p>
|
||||
<p :class="cardContentClasses.description">预计高峰时间: 14:00</p>
|
||||
</div>
|
||||
<Button
|
||||
type="text"
|
||||
shape="circle"
|
||||
class="text-slate-400 hover:text-orange-600"
|
||||
@click="handleRefreshStats"
|
||||
>
|
||||
<Button type="text" shape="circle" :class="buttonClasses.refresh" @click="handleRefreshStats">
|
||||
<template #icon>
|
||||
<IconifyIcon icon="ant-design:more-outlined" class="text-base" />
|
||||
</template>
|
||||
@@ -400,36 +319,23 @@ function getColorClass(color: string, type: 'bg' | 'text') {
|
||||
</div>
|
||||
|
||||
<!-- 背景图表 -->
|
||||
<div
|
||||
class="pointer-events-none absolute inset-x-0 bottom-0 z-0 h-[70%] w-full opacity-80"
|
||||
>
|
||||
<EchartsUI ref="flowChartRef" class="h-full w-full" />
|
||||
</div>
|
||||
<BackgroundChart :options="flowChartOptions" :opacity="0.6" />
|
||||
</GlassCard>
|
||||
|
||||
<!-- 卡片2: 紧急待办事项 -->
|
||||
<GlassCard class="flex min-h-0 flex-1 flex-col overflow-hidden p-0">
|
||||
<div
|
||||
class="flex shrink-0 items-center justify-between border-b border-white/40 p-5"
|
||||
>
|
||||
<h3 class="flex items-center gap-2 font-bold text-slate-800">
|
||||
<span class="h-2 w-2 animate-pulse rounded-full bg-red-500"></span>
|
||||
<GlassCard :class="taskListClasses.card">
|
||||
<div :class="taskListClasses.header">
|
||||
<h3 :class="taskListClasses.title">
|
||||
<span :class="taskListClasses.titleDot"></span>
|
||||
紧急待办事项
|
||||
</h3>
|
||||
<Dropdown>
|
||||
<Button
|
||||
type="text"
|
||||
size="small"
|
||||
class="text-xs font-medium text-slate-400 hover:text-orange-600"
|
||||
>
|
||||
<Button type="text" size="small" :class="taskListClasses.filterButton">
|
||||
筛选
|
||||
<IconifyIcon
|
||||
icon="ant-design:filter-outlined"
|
||||
class="ml-1 text-xs"
|
||||
/>
|
||||
<IconifyIcon icon="ant-design:filter-outlined" class="ml-1 text-xs" />
|
||||
</Button>
|
||||
<template #overlay>
|
||||
<div class="min-w-[120px] rounded-lg bg-white p-2 shadow-lg">
|
||||
<div :class="taskListClasses.filterMenu">
|
||||
<div
|
||||
v-for="option in [
|
||||
{ label: '全部', value: 'all' },
|
||||
@@ -438,11 +344,10 @@ function getColorClass(color: string, type: 'bg' | 'text') {
|
||||
{ label: 'P2 普通', value: 'P2' },
|
||||
]"
|
||||
:key="option.value"
|
||||
class="cursor-pointer rounded px-3 py-2 text-sm hover:bg-gray-50"
|
||||
:class="{
|
||||
'bg-orange-50 text-orange-600':
|
||||
priorityFilter === option.value,
|
||||
}"
|
||||
:class="[
|
||||
taskListClasses.filterMenuItem,
|
||||
priorityFilter === option.value && taskListClasses.filterMenuItemActive,
|
||||
]"
|
||||
@click="handleFilterChange(option.value as any)"
|
||||
>
|
||||
{{ option.label }}
|
||||
@@ -452,16 +357,10 @@ function getColorClass(color: string, type: 'bg' | 'text') {
|
||||
</Dropdown>
|
||||
</div>
|
||||
|
||||
<div class="flex-1 overflow-y-auto px-2">
|
||||
<div
|
||||
v-if="filteredTasks.length === 0"
|
||||
class="flex h-full items-center justify-center text-slate-400"
|
||||
>
|
||||
<div class="text-center">
|
||||
<IconifyIcon
|
||||
icon="ant-design:check-circle-outlined"
|
||||
class="mb-2 text-4xl"
|
||||
/>
|
||||
<div :class="taskListClasses.listContainer">
|
||||
<div v-if="filteredTasks.length === 0" :class="taskListClasses.emptyState">
|
||||
<div :class="taskListClasses.emptyContent">
|
||||
<IconifyIcon icon="ant-design:check-circle-outlined" :class="taskListClasses.emptyIcon" />
|
||||
<p>暂无待办任务</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -469,14 +368,13 @@ function getColorClass(color: string, type: 'bg' | 'text') {
|
||||
<div
|
||||
v-for="task in filteredTasks"
|
||||
:key="task.id"
|
||||
class="group cursor-pointer rounded-lg border-b border-slate-50 p-3 transition-colors last:border-0 hover:bg-orange-50/40"
|
||||
:class="taskListClasses.taskItem"
|
||||
@click="handleTaskDetail(task)"
|
||||
>
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex min-w-0 flex-1 items-center gap-3">
|
||||
<div :class="taskListClasses.taskContent">
|
||||
<div :class="taskListClasses.taskLeft">
|
||||
<div
|
||||
class="flex h-7 w-7 items-center justify-center rounded-lg shadow-sm transition-transform group-hover:scale-110"
|
||||
:class="getPriorityConfig(task.priority).bg"
|
||||
:class="[taskListClasses.taskIcon, getPriorityConfig(task.priority).bg]"
|
||||
>
|
||||
<IconifyIcon
|
||||
icon="ant-design:clock-circle-outlined"
|
||||
@@ -484,69 +382,53 @@ function getColorClass(color: string, type: 'bg' | 'text') {
|
||||
:class="[getPriorityConfig(task.priority).text]"
|
||||
/>
|
||||
</div>
|
||||
<div class="min-w-0 flex-1">
|
||||
<div class="truncate font-semibold text-slate-700">
|
||||
{{ task.title }}
|
||||
</div>
|
||||
<div
|
||||
class="mt-1 flex items-center gap-3 text-xs text-slate-500"
|
||||
>
|
||||
<div :class="taskListClasses.taskInfo">
|
||||
<div :class="taskListClasses.taskTitle">{{ task.title }}</div>
|
||||
<div :class="taskListClasses.taskMeta">
|
||||
<span>{{ task.location }}</span>
|
||||
<span v-if="task.createTime"
|
||||
>• {{ task.createTime }}</span
|
||||
>
|
||||
<span v-if="task.createTime">• {{ task.createTime }}</span>
|
||||
<span v-if="task.assignee">• {{ task.assignee }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<Tag
|
||||
:color="getPriorityConfig(task.priority).color"
|
||||
class="border px-1.5 py-0.5 text-[10px] font-bold uppercase tracking-wide"
|
||||
:class="[getPriorityConfig(task.priority).border]"
|
||||
:class="[taskListClasses.taskPriorityTag, getPriorityConfig(task.priority).border]"
|
||||
>
|
||||
{{ task.priority }}
|
||||
</Tag>
|
||||
</div>
|
||||
<div class="ml-2 flex items-center gap-1">
|
||||
<div :class="taskListClasses.taskActions">
|
||||
<Button
|
||||
type="text"
|
||||
size="small"
|
||||
shape="circle"
|
||||
class="h-7 w-7 bg-slate-50 text-slate-400 hover:bg-green-500 hover:text-white"
|
||||
:class="[buttonClasses.circleIcon, buttonClasses.complete]"
|
||||
@click.stop="handleTaskComplete(task.id)"
|
||||
>
|
||||
<template #icon>
|
||||
<IconifyIcon
|
||||
icon="ant-design:check-circle-outlined"
|
||||
class="text-xs"
|
||||
/>
|
||||
<IconifyIcon icon="ant-design:check-circle-outlined" class="text-xs" />
|
||||
</template>
|
||||
</Button>
|
||||
<Button
|
||||
type="text"
|
||||
size="small"
|
||||
shape="circle"
|
||||
class="h-7 w-7 bg-slate-50 text-slate-400 hover:bg-orange-500 hover:text-white"
|
||||
:class="[buttonClasses.circleIcon, buttonClasses.view]"
|
||||
@click.stop="handleTaskDetail(task)"
|
||||
>
|
||||
<template #icon>
|
||||
<IconifyIcon
|
||||
icon="ant-design:eye-outlined"
|
||||
class="text-xs"
|
||||
/>
|
||||
<IconifyIcon icon="ant-design:eye-outlined" class="text-xs" />
|
||||
</template>
|
||||
</Button>
|
||||
<Button
|
||||
type="text"
|
||||
size="small"
|
||||
shape="circle"
|
||||
class="h-7 w-7 bg-slate-50 text-slate-400 hover:bg-red-500 hover:text-white"
|
||||
:class="[buttonClasses.circleIcon, buttonClasses.delete]"
|
||||
@click.stop="handleTaskDelete(task.id)"
|
||||
>
|
||||
<template #icon>
|
||||
<IconifyIcon
|
||||
icon="ant-design:delete-outlined"
|
||||
class="text-xs"
|
||||
/>
|
||||
<IconifyIcon icon="ant-design:delete-outlined" class="text-xs" />
|
||||
</template>
|
||||
</Button>
|
||||
</div>
|
||||
@@ -558,86 +440,58 @@ function getColorClass(color: string, type: 'bg' | 'text') {
|
||||
</div>
|
||||
|
||||
<!-- --- 右侧列 (40%) --- -->
|
||||
<div class="flex h-full w-[40%] flex-none flex-col gap-5">
|
||||
<div class="flex h-full w-[40%] flex-none flex-col gap-5" style="padding-right: 1.25rem;">
|
||||
<!-- 卡片3: 工单趋势分析 -->
|
||||
<GlassCard class="relative flex h-[35%] flex-col overflow-hidden p-6">
|
||||
<div class="relative z-10 mb-2 flex items-start justify-between">
|
||||
<div>
|
||||
<h3
|
||||
class="text-sm font-semibold uppercase tracking-wide text-slate-500"
|
||||
>
|
||||
工单趋势分析
|
||||
</h3>
|
||||
<div class="mt-1 flex items-baseline gap-2">
|
||||
<span class="text-4xl font-bold text-slate-800">89</span>
|
||||
<Tag color="processing" class="text-xs font-bold">+5 新增</Tag>
|
||||
<div :class="cardContentClasses.container">
|
||||
<h3 :class="cardContentClasses.title">工单趋势分析</h3>
|
||||
<div :class="cardContentClasses.numberContainer">
|
||||
<span :class="cardContentClasses.largeNumber">89</span>
|
||||
<Tag color="processing" :class="`text-xs font-bold ${textShadowClasses.tag}`">+5 新增</Tag>
|
||||
</div>
|
||||
</div>
|
||||
<Button
|
||||
type="text"
|
||||
size="small"
|
||||
class="rounded-lg bg-white/50 px-2 py-1 text-xs font-medium hover:text-blue-600"
|
||||
@click="message.info('查看全部工单')"
|
||||
>
|
||||
<Button type="text" size="small" :class="buttonClasses.viewAll" @click="message.info('查看全部工单')">
|
||||
查看全部
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<!-- 背景图表 -->
|
||||
<div
|
||||
class="pointer-events-none absolute inset-x-0 bottom-0 z-0 h-[65%] w-full opacity-80"
|
||||
>
|
||||
<EchartsUI ref="workOrderChartRef" class="h-full w-full" />
|
||||
</div>
|
||||
<BackgroundChart :options="workOrderChartOptions" :opacity="0.6" />
|
||||
</GlassCard>
|
||||
|
||||
<!-- 卡片4: 统计网格 + AI卡片 -->
|
||||
<div class="flex min-h-0 flex-1 flex-col gap-4">
|
||||
<!-- 2x2 统计网格 -->
|
||||
<div class="grid shrink-0 grid-cols-2 gap-3">
|
||||
<div :class="statCardClasses.container">
|
||||
<GlassCard
|
||||
v-for="(stat, idx) in stats"
|
||||
:key="idx"
|
||||
class="glass-card glass-border glass-shadow glass-highlight flex cursor-pointer items-center gap-3 rounded-[2rem] p-3 transition-colors hover:bg-white/60"
|
||||
:class="statCardClasses.card"
|
||||
@click="handleStatCardClick(stat)"
|
||||
>
|
||||
<div
|
||||
class="flex h-9 w-9 shrink-0 items-center justify-center rounded-xl text-lg"
|
||||
:class="[
|
||||
statCardClasses.iconContainer,
|
||||
getColorClass(stat.color, 'bg'),
|
||||
getColorClass(stat.color, 'text'),
|
||||
]"
|
||||
>
|
||||
<IconifyIcon :icon="stat.icon" class="text-lg" />
|
||||
</div>
|
||||
<div class="min-w-0">
|
||||
<p
|
||||
class="truncate text-[10px] font-bold uppercase text-slate-400"
|
||||
>
|
||||
{{ stat.label }}
|
||||
</p>
|
||||
<p class="mt-0.5 text-lg font-bold leading-none text-slate-800">
|
||||
{{ stat.value }}
|
||||
</p>
|
||||
<div :class="statCardClasses.content">
|
||||
<p :class="statCardClasses.label">{{ stat.label }}</p>
|
||||
<p :class="statCardClasses.value">{{ stat.value }}</p>
|
||||
</div>
|
||||
</GlassCard>
|
||||
</div>
|
||||
|
||||
<!-- AI助手卡片 -->
|
||||
<GlassCard
|
||||
class="group relative flex min-h-[140px] flex-1 items-center overflow-hidden border-0 !bg-gradient-to-br from-orange-400 to-amber-300 p-0 shadow-lg"
|
||||
>
|
||||
<GlassCard :class="aiCardClasses.container">
|
||||
<!-- 神经网络背景图案 -->
|
||||
<div class="pointer-events-none absolute inset-0 opacity-20">
|
||||
<div :class="aiCardClasses.background">
|
||||
<svg width="100%" height="100%">
|
||||
<pattern
|
||||
id="grid"
|
||||
x="0"
|
||||
y="0"
|
||||
width="20"
|
||||
height="20"
|
||||
patternUnits="userSpaceOnUse"
|
||||
>
|
||||
<pattern id="grid" x="0" y="0" width="20" height="20" patternUnits="userSpaceOnUse">
|
||||
<circle cx="1" cy="1" r="1" fill="white" />
|
||||
</pattern>
|
||||
<rect width="100%" height="100%" fill="url(#grid)" />
|
||||
@@ -652,47 +506,23 @@ function getColorClass(color: string, type: 'bg' | 'text') {
|
||||
</div>
|
||||
|
||||
<!-- 左侧内容 -->
|
||||
<div
|
||||
class="relative z-10 flex h-full w-[60%] flex-col justify-center py-4 pl-8 pr-2"
|
||||
>
|
||||
<div :class="aiCardClasses.leftContent">
|
||||
<div class="mb-2 flex items-center gap-2">
|
||||
<span
|
||||
class="flex h-6 w-6 items-center justify-center rounded-lg bg-white/20 shadow-sm backdrop-blur-sm"
|
||||
>
|
||||
<IconifyIcon
|
||||
icon="ant-design:robot-outlined"
|
||||
class="text-xs text-white"
|
||||
/>
|
||||
<span :class="aiCardClasses.iconContainer">
|
||||
<IconifyIcon icon="ant-design:robot-outlined" class="text-xs text-white" />
|
||||
</span>
|
||||
<span class="text-[10px] font-bold uppercase tracking-widest text-white/90">AI Copilot</span>
|
||||
<span :class="aiCardClasses.label">AI Copilot</span>
|
||||
</div>
|
||||
<h3
|
||||
class="mb-4 whitespace-nowrap text-lg font-bold leading-tight text-white drop-shadow-sm"
|
||||
>
|
||||
需要我帮你做什么吗?
|
||||
</h3>
|
||||
<Button
|
||||
type="primary"
|
||||
class="flex w-fit items-center gap-2 rounded-full bg-white px-6 py-2.5 text-sm font-bold text-amber-600 shadow-md shadow-orange-900/10 transition-all hover:scale-105 hover:shadow-lg"
|
||||
@click="message.info('打开AI助手')"
|
||||
>
|
||||
<h3 :class="aiCardClasses.title">需要我帮你做什么吗?</h3>
|
||||
<Button type="primary" :class="aiCardClasses.button" @click="message.info('打开AI助手')">
|
||||
使用AI助手
|
||||
<IconifyIcon
|
||||
icon="ant-design:robot-outlined"
|
||||
class="text-base text-amber-400 transition-transform group-hover:rotate-12"
|
||||
/>
|
||||
<IconifyIcon icon="ant-design:robot-outlined" :class="aiCardClasses.buttonIcon" />
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<!-- 右侧图片 -->
|
||||
<div
|
||||
class="pointer-events-none absolute bottom-0 right-[-10px] top-0 z-20 flex w-[45%] items-center justify-center"
|
||||
>
|
||||
<img
|
||||
:src="robotImage"
|
||||
alt="AI Robot"
|
||||
class="h-full w-full object-contain"
|
||||
/>
|
||||
<div :class="aiCardClasses.imageContainer">
|
||||
<img :src="robotImage" alt="AI Robot" :class="aiCardClasses.image" />
|
||||
</div>
|
||||
</GlassCard>
|
||||
</div>
|
||||
@@ -705,41 +535,30 @@ function getColorClass(color: string, type: 'bg' | 'text') {
|
||||
:footer="null"
|
||||
width="600px"
|
||||
>
|
||||
<div v-if="selectedTask" class="space-y-4">
|
||||
<div v-if="selectedTask" :class="modalClasses.field">
|
||||
<div>
|
||||
<div class="mb-1 text-sm text-slate-400">任务标题</div>
|
||||
<div class="text-lg font-bold text-slate-800">
|
||||
{{ selectedTask.title }}
|
||||
</div>
|
||||
<div :class="modalClasses.label">任务标题</div>
|
||||
<div :class="modalClasses.titleValue">{{ selectedTask.title }}</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="mb-1 text-sm text-slate-400">位置</div>
|
||||
<div class="text-base text-slate-700">
|
||||
{{ selectedTask.location }}
|
||||
</div>
|
||||
<div :class="modalClasses.label">位置</div>
|
||||
<div :class="modalClasses.value">{{ selectedTask.location }}</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="mb-1 text-sm text-slate-400">优先级</div>
|
||||
<Tag
|
||||
:color="getPriorityConfig(selectedTask.priority).color"
|
||||
class="px-2 py-1 text-xs font-bold uppercase"
|
||||
>
|
||||
<div :class="modalClasses.label">优先级</div>
|
||||
<Tag :color="getPriorityConfig(selectedTask.priority).color" class="px-2 py-1 text-xs font-bold uppercase">
|
||||
{{ selectedTask.priority }}
|
||||
</Tag>
|
||||
</div>
|
||||
<div v-if="selectedTask.assignee">
|
||||
<div class="mb-1 text-sm text-slate-400">负责人</div>
|
||||
<div class="text-base text-slate-700">
|
||||
{{ selectedTask.assignee }}
|
||||
</div>
|
||||
<div :class="modalClasses.label">负责人</div>
|
||||
<div :class="modalClasses.value">{{ selectedTask.assignee }}</div>
|
||||
</div>
|
||||
<div v-if="selectedTask.createTime">
|
||||
<div class="mb-1 text-sm text-slate-400">创建时间</div>
|
||||
<div class="text-base text-slate-700">
|
||||
{{ selectedTask.createTime }}
|
||||
</div>
|
||||
<div :class="modalClasses.label">创建时间</div>
|
||||
<div :class="modalClasses.value">{{ selectedTask.createTime }}</div>
|
||||
</div>
|
||||
<div class="flex gap-2 pt-4">
|
||||
<div :class="modalClasses.actions">
|
||||
<Button
|
||||
type="primary"
|
||||
@click="
|
||||
|
||||
@@ -0,0 +1,138 @@
|
||||
/**
|
||||
* 创建背景图表的通用配置
|
||||
*/
|
||||
export function createBackgroundChartOptions(config: {
|
||||
xAxisData: string[];
|
||||
yAxisData: number[];
|
||||
seriesName: string;
|
||||
lineColor: string;
|
||||
areaColor: string[];
|
||||
yAxisFormatter?: (value: number) => string;
|
||||
}) {
|
||||
const {
|
||||
xAxisData,
|
||||
yAxisData,
|
||||
seriesName,
|
||||
lineColor,
|
||||
areaColor,
|
||||
yAxisFormatter,
|
||||
} = config;
|
||||
|
||||
return {
|
||||
grid: {
|
||||
left: '4%',
|
||||
right: '2%',
|
||||
top: '5%',
|
||||
bottom: '15%',
|
||||
containLabel: true,
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category' as const,
|
||||
data: xAxisData,
|
||||
show: true,
|
||||
boundaryGap: false,
|
||||
axisLine: {
|
||||
show: false,
|
||||
},
|
||||
axisTick: {
|
||||
show: false,
|
||||
},
|
||||
axisLabel: {
|
||||
show: true,
|
||||
color: 'rgba(148, 163, 184, 0.6)',
|
||||
fontSize: 10,
|
||||
fontWeight: 400,
|
||||
margin: 6,
|
||||
interval: 0,
|
||||
rotate: 0,
|
||||
},
|
||||
splitLine: {
|
||||
show: false,
|
||||
},
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value' as const,
|
||||
show: true,
|
||||
axisLine: {
|
||||
show: false,
|
||||
},
|
||||
axisTick: {
|
||||
show: false,
|
||||
},
|
||||
axisLabel: {
|
||||
show: true,
|
||||
color: 'rgba(148, 163, 184, 0.6)',
|
||||
fontSize: 10,
|
||||
fontWeight: 400,
|
||||
margin: 6,
|
||||
formatter: yAxisFormatter,
|
||||
},
|
||||
splitLine: {
|
||||
show: true,
|
||||
lineStyle: {
|
||||
color: 'rgba(148, 163, 184, 0.1)',
|
||||
type: 'dashed',
|
||||
width: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: seriesName,
|
||||
type: 'line' as const,
|
||||
data: yAxisData,
|
||||
smooth: true,
|
||||
areaStyle: {
|
||||
color: {
|
||||
type: 'linear',
|
||||
x: 0,
|
||||
y: 0,
|
||||
x2: 0,
|
||||
y2: 1,
|
||||
colorStops: [
|
||||
{ offset: 0, color: areaColor[0] },
|
||||
{ offset: 0.5, color: areaColor[1] },
|
||||
{ offset: 1, color: areaColor[2] },
|
||||
],
|
||||
},
|
||||
},
|
||||
lineStyle: {
|
||||
color: lineColor,
|
||||
width: 4,
|
||||
},
|
||||
symbol: 'none',
|
||||
symbolSize: 0,
|
||||
},
|
||||
],
|
||||
tooltip: {
|
||||
show: true,
|
||||
trigger: 'axis',
|
||||
axisPointer: {
|
||||
type: 'line',
|
||||
lineStyle: {
|
||||
color: 'rgba(148, 163, 184, 0.3)',
|
||||
width: 1,
|
||||
},
|
||||
},
|
||||
backgroundColor: 'rgba(255, 255, 255, 0.95)',
|
||||
borderColor: 'rgba(148, 163, 184, 0.2)',
|
||||
borderWidth: 1,
|
||||
textStyle: {
|
||||
color: '#475569',
|
||||
fontSize: 12,
|
||||
},
|
||||
padding: [8, 12],
|
||||
formatter: (params: any) => {
|
||||
const param = params[0];
|
||||
return `
|
||||
<div style="margin-bottom: 4px; font-weight: 600;">${param.name}</div>
|
||||
<div style="display: flex; align-items: center; gap: 8px;">
|
||||
<span style="display: inline-block; width: 10px; height: 10px; background-color: ${param.color}; border-radius: 50%;"></span>
|
||||
<span>${param.seriesName || '数值'}: <strong>${param.value}</strong></span>
|
||||
</div>
|
||||
`;
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
156
apps/web-antd/src/views/dashboard/workspace/utils/styles.ts
Normal file
156
apps/web-antd/src/views/dashboard/workspace/utils/styles.ts
Normal file
@@ -0,0 +1,156 @@
|
||||
/**
|
||||
* 文字阴影样式类名
|
||||
*/
|
||||
export const textShadowClasses = {
|
||||
/** 标题阴影 */
|
||||
title: 'drop-shadow-[0_2px_16px_rgba(255,255,255,0.95),0_0_8px_rgba(255,255,255,0.8)]',
|
||||
/** 大数字阴影 */
|
||||
largeNumber: 'drop-shadow-[0_2px_20px_rgba(255,255,255,0.98),0_0_12px_rgba(255,255,255,0.9)]',
|
||||
/** 标签阴影 */
|
||||
tag: 'drop-shadow-[0_1px_8px_rgba(255,255,255,0.9),0_0_4px_rgba(255,255,255,0.8)]',
|
||||
/** 小文字阴影 */
|
||||
smallText: 'drop-shadow-[0_1px_8px_rgba(255,255,255,0.85),0_0_4px_rgba(255,255,255,0.7)]',
|
||||
};
|
||||
|
||||
/**
|
||||
* 卡片内容样式类名
|
||||
*/
|
||||
export const cardContentClasses = {
|
||||
/** 内容容器 */
|
||||
container: 'relative',
|
||||
/** 标题 */
|
||||
title: `text-sm font-semibold uppercase tracking-wide text-slate-500 ${textShadowClasses.title}`,
|
||||
/** 数字容器 */
|
||||
numberContainer: 'mt-1 flex items-baseline gap-2',
|
||||
/** 大数字 */
|
||||
largeNumber: `text-4xl font-bold text-slate-800 ${textShadowClasses.largeNumber}`,
|
||||
/** 描述文字 */
|
||||
description: `mt-1 text-xs text-slate-400 ${textShadowClasses.smallText}`,
|
||||
};
|
||||
|
||||
/**
|
||||
* 任务列表样式类名
|
||||
*/
|
||||
export const taskListClasses = {
|
||||
/** 任务卡片容器 */
|
||||
card: 'flex min-h-0 flex-1 flex-col overflow-hidden p-0',
|
||||
/** 任务列表头部 */
|
||||
header: 'flex shrink-0 items-center justify-between border-b border-white/40 p-5',
|
||||
/** 任务列表标题 */
|
||||
title: 'flex items-center gap-2 font-bold text-slate-800',
|
||||
/** 任务列表标题指示点 */
|
||||
titleDot: 'h-2 w-2 animate-pulse rounded-full bg-red-500',
|
||||
/** 筛选按钮 */
|
||||
filterButton: 'text-xs font-medium text-slate-400 hover:text-orange-600',
|
||||
/** 筛选下拉菜单 */
|
||||
filterMenu: 'min-w-[120px] rounded-lg bg-white p-2 shadow-lg',
|
||||
/** 筛选菜单项 */
|
||||
filterMenuItem: 'cursor-pointer rounded px-3 py-2 text-sm hover:bg-gray-50',
|
||||
/** 筛选菜单项激活状态 */
|
||||
filterMenuItemActive: 'bg-orange-50 text-orange-600',
|
||||
/** 任务列表容器 */
|
||||
listContainer: 'flex-1 overflow-y-auto px-2',
|
||||
/** 空状态容器 */
|
||||
emptyState: 'flex h-full items-center justify-center text-slate-400',
|
||||
/** 空状态内容 */
|
||||
emptyContent: 'text-center',
|
||||
/** 空状态图标 */
|
||||
emptyIcon: 'mb-2 text-4xl',
|
||||
/** 任务项容器 */
|
||||
taskItem: 'group cursor-pointer rounded-lg border-b border-slate-50 p-3 transition-colors last:border-0 hover:bg-orange-50/40',
|
||||
/** 任务项内容 */
|
||||
taskContent: 'flex items-center justify-between',
|
||||
/** 任务左侧内容 */
|
||||
taskLeft: 'flex min-w-0 flex-1 items-center gap-3',
|
||||
/** 任务图标容器 */
|
||||
taskIcon: 'flex h-7 w-7 items-center justify-center rounded-lg shadow-sm transition-transform group-hover:scale-110',
|
||||
/** 任务信息容器 */
|
||||
taskInfo: 'min-w-0 flex-1',
|
||||
/** 任务标题 */
|
||||
taskTitle: 'truncate font-semibold text-slate-700',
|
||||
/** 任务元信息 */
|
||||
taskMeta: 'mt-1 flex items-center gap-3 text-xs text-slate-500',
|
||||
/** 任务操作按钮组 */
|
||||
taskActions: 'ml-2 flex items-center gap-1',
|
||||
/** 任务优先级标签 */
|
||||
taskPriorityTag: 'border px-1.5 py-0.5 text-[10px] font-bold uppercase tracking-wide',
|
||||
};
|
||||
|
||||
/**
|
||||
* 按钮样式类名
|
||||
*/
|
||||
export const buttonClasses = {
|
||||
/** 圆形图标按钮基础样式 */
|
||||
circleIcon: 'h-7 w-7 bg-slate-50 text-slate-400',
|
||||
/** 完成按钮 */
|
||||
complete: 'hover:bg-green-500 hover:text-white',
|
||||
/** 查看按钮 */
|
||||
view: 'hover:bg-orange-500 hover:text-white',
|
||||
/** 删除按钮 */
|
||||
delete: 'hover:bg-red-500 hover:text-white',
|
||||
/** 刷新按钮 */
|
||||
refresh: 'text-slate-400 hover:text-orange-600',
|
||||
/** 查看全部按钮 */
|
||||
viewAll: 'rounded-lg bg-white/50 px-2 py-1 text-xs font-medium hover:text-blue-600',
|
||||
};
|
||||
|
||||
/**
|
||||
* 统计卡片样式类名
|
||||
*/
|
||||
export const statCardClasses = {
|
||||
/** 统计卡片容器 */
|
||||
container: 'grid shrink-0 grid-cols-2 gap-3',
|
||||
/** 统计卡片 */
|
||||
card: 'glass-card glass-border glass-shadow glass-highlight flex cursor-pointer items-center gap-3 rounded-[2rem] p-3 transition-colors hover:bg-white/60',
|
||||
/** 图标容器 */
|
||||
iconContainer: 'flex h-9 w-9 shrink-0 items-center justify-center rounded-xl text-lg',
|
||||
/** 内容容器 */
|
||||
content: 'min-w-0',
|
||||
/** 标签文字 */
|
||||
label: 'truncate text-[10px] font-bold uppercase text-slate-400',
|
||||
/** 数值 */
|
||||
value: 'mt-0.5 text-lg font-bold leading-none text-slate-800',
|
||||
};
|
||||
|
||||
/**
|
||||
* AI助手卡片样式类名
|
||||
*/
|
||||
export const aiCardClasses = {
|
||||
/** AI卡片容器 */
|
||||
container: 'group relative flex min-h-[140px] flex-1 items-center overflow-hidden border-0 !bg-gradient-to-br from-orange-400 to-amber-300 p-0 shadow-lg',
|
||||
/** 背景图案容器 */
|
||||
background: 'pointer-events-none absolute inset-0 opacity-20',
|
||||
/** 左侧内容容器 */
|
||||
leftContent: 'relative z-10 flex h-full w-[60%] flex-col justify-center py-4 pl-8 pr-2',
|
||||
/** 图标容器 */
|
||||
iconContainer: 'flex h-6 w-6 items-center justify-center rounded-lg bg-white/20 shadow-sm backdrop-blur-sm',
|
||||
/** 标签文字 */
|
||||
label: 'text-[10px] font-bold uppercase tracking-widest text-white/90',
|
||||
/** 标题 */
|
||||
title: 'mb-4 whitespace-nowrap text-lg font-bold leading-tight text-white drop-shadow-sm',
|
||||
/** 按钮 */
|
||||
button: 'flex w-fit items-center gap-2 rounded-full bg-white px-6 py-2.5 text-sm font-bold text-amber-600 shadow-md shadow-orange-900/10 transition-all hover:scale-105 hover:shadow-lg',
|
||||
/** 按钮图标 */
|
||||
buttonIcon: 'text-base text-amber-400 transition-transform group-hover:rotate-12',
|
||||
/** 右侧图片容器 */
|
||||
imageContainer: 'pointer-events-none absolute bottom-0 right-[-10px] top-0 z-20 flex w-[45%] items-center justify-center',
|
||||
/** 图片 */
|
||||
image: 'h-full w-full object-contain',
|
||||
};
|
||||
|
||||
/**
|
||||
* 模态框样式类名
|
||||
*/
|
||||
export const modalClasses = {
|
||||
/** 字段容器 */
|
||||
field: 'space-y-4',
|
||||
/** 字段标签 */
|
||||
label: 'mb-1 text-sm text-slate-400',
|
||||
/** 字段值 */
|
||||
value: 'text-base text-slate-700',
|
||||
/** 标题值 */
|
||||
titleValue: 'text-lg font-bold text-slate-800',
|
||||
/** 操作按钮组 */
|
||||
actions: 'flex gap-2 pt-4',
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user