|
@@ -1,95 +1,82 @@
|
|
<template>
|
|
<template>
|
|
- <div class="chart-edit-container">
|
|
|
|
- <!-- 图表类型选择 -->
|
|
|
|
- <div class="chart-type-selector">
|
|
|
|
- <div class="chart-type-item"
|
|
|
|
- :class="{ active: selectedChartType === 'line' }"
|
|
|
|
- @click="selectedChartType = 'line'">
|
|
|
|
- <i class="el-icon-data-line"></i>
|
|
|
|
- <span>折线图</span>
|
|
|
|
- </div>
|
|
|
|
- <div class="chart-type-item"
|
|
|
|
- :class="{ active: selectedChartType === 'bar' }"
|
|
|
|
- @click="selectedChartType = 'bar'">
|
|
|
|
- <i class="el-icon-data-board"></i>
|
|
|
|
- <span>柱状图</span>
|
|
|
|
- </div>
|
|
|
|
- <div class="chart-type-item"
|
|
|
|
- :class="{ active: selectedChartType === 'pie' }"
|
|
|
|
- @click="selectedChartType = 'pie'">
|
|
|
|
- <i class="el-icon-pie-chart"></i>
|
|
|
|
- <span>饼图</span>
|
|
|
|
- </div>
|
|
|
|
- </div>
|
|
|
|
-
|
|
|
|
- <!-- 主要内容区域 -->
|
|
|
|
- <div class="chart-content">
|
|
|
|
- <!-- 左侧图表列表 -->
|
|
|
|
- <div class="chart-list-panel">
|
|
|
|
- <div class="search-box">
|
|
|
|
- <el-input
|
|
|
|
- v-model="searchKeyword"
|
|
|
|
- placeholder="搜索图表名称"
|
|
|
|
- prefix-icon="el-icon-search"
|
|
|
|
- clearable
|
|
|
|
- @input="filterCharts"
|
|
|
|
- />
|
|
|
|
- </div>
|
|
|
|
- <div class="chart-list" v-loading="chartListLoading">
|
|
|
|
- <div
|
|
|
|
- v-for="chart in filteredCharts"
|
|
|
|
- :key="chart.id"
|
|
|
|
- class="chart-item"
|
|
|
|
- :class="{ active: selectedChart?.id === chart.id }"
|
|
|
|
- @click="selectChart(chart)"
|
|
|
|
- >
|
|
|
|
- <div class="chart-icon">
|
|
|
|
- <i class="el-icon-data-analysis"></i>
|
|
|
|
|
|
+ <el-container>
|
|
|
|
+ <el-main>
|
|
|
|
+ <div class="chart-edit-modal">
|
|
|
|
+ <!-- 主要内容区域 -->
|
|
|
|
+ <div class="main-content">
|
|
|
|
+ <!-- 左侧图表列表 -->
|
|
|
|
+ <div class="chart-list-section">
|
|
|
|
+ <div class="list-header">
|
|
|
|
+ <h4>选择数据维度</h4>
|
|
|
|
+ <div class="search-box">
|
|
|
|
+ <el-input v-model="searchKeyword" placeholder="搜索图表名称" prefix-icon="Search" clearable
|
|
|
|
+ @input="filterCharts" />
|
|
|
|
+ </div>
|
|
</div>
|
|
</div>
|
|
- <div class="chart-info">
|
|
|
|
- <div class="chart-name">{{ chart.name }}</div>
|
|
|
|
- <div class="chart-memo">{{ chart.memo }}</div>
|
|
|
|
|
|
+
|
|
|
|
+ <div class="chart-list" v-loading="chartListLoading">
|
|
|
|
+ <div v-for="chart in filteredCharts" :key="chart.id" class="chart-item"
|
|
|
|
+ :class="{ active: selectedCharts.includes(chart.id) }" @click="toggleChart(chart)">
|
|
|
|
+ <el-checkbox :model-value="selectedCharts.includes(chart.id)" @change="toggleChart(chart)"
|
|
|
|
+ class="chart-checkbox" />
|
|
|
|
+ <div class="chart-info">
|
|
|
|
+ <div class="chart-name">{{ chart.name }}</div>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+
|
|
|
|
+ <div v-if="!filteredCharts.length" class="empty-state">
|
|
|
|
+ <el-icon class="empty-icon">
|
|
|
|
+ <Search />
|
|
|
|
+ </el-icon>
|
|
|
|
+ <div class="empty-text">{{ searchKeyword ? '未找到匹配的图表' : '暂无可选图表' }}</div>
|
|
|
|
+ </div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
- </div>
|
|
|
|
- </div>
|
|
|
|
|
|
|
|
- <!-- 右侧预览区域 -->
|
|
|
|
- <div class="chart-preview-panel">
|
|
|
|
- <div v-if="!selectedChart" class="empty-preview">
|
|
|
|
- <i class="el-icon-data-analysis"></i>
|
|
|
|
- <p>请选择左侧图表查看预览</p>
|
|
|
|
- </div>
|
|
|
|
- <div v-else class="preview-content">
|
|
|
|
- <div class="preview-header">
|
|
|
|
- <h3>{{ selectedChart.name }}</h3>
|
|
|
|
- <p>{{ selectedChart.memo }}</p>
|
|
|
|
- </div>
|
|
|
|
- <div class="preview-chart">
|
|
|
|
- <!-- 这里可以放置图表预览组件 -->
|
|
|
|
- <div class="chart-placeholder">
|
|
|
|
- <i class="el-icon-data-line" v-if="selectedChartType === 'line'"></i>
|
|
|
|
- <i class="el-icon-data-board" v-else-if="selectedChartType === 'bar'"></i>
|
|
|
|
- <i class="el-icon-pie-chart" v-else-if="selectedChartType === 'pie'"></i>
|
|
|
|
- <p>{{ selectedChartType === 'line' ? '折线图' : selectedChartType === 'bar' ? '柱状图' : '饼图' }}预览</p>
|
|
|
|
|
|
+ <!-- 右侧预览区域 -->
|
|
|
|
+ <div class="preview-section">
|
|
|
|
+
|
|
|
|
+ <div v-if="selectedCharts.length === 0" class="empty-preview">
|
|
|
|
+ <el-icon class="empty-icon">
|
|
|
|
+ <TrendCharts />
|
|
|
|
+ </el-icon>
|
|
|
|
+ <div class="empty-text">请选择左侧数据维度进行预览</div>
|
|
|
|
+ <div class="empty-desc">选择后将显示对应的图表预览效果</div>
|
|
|
|
+ </div>
|
|
|
|
+
|
|
|
|
+ <div v-else class="preview-content">
|
|
|
|
+ <div class="chart-preview">
|
|
|
|
+ <report-chart :type="'line'" :data="currentChartData" :height="300" :show-date-picker="false"
|
|
|
|
+ :title="lastSelectedChart?.name" />
|
|
|
|
+ </div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
+
|
|
|
|
+ <!-- 底部操作按钮 -->
|
|
|
|
+ <el-footer class="sa-footer--submit">
|
|
|
|
+ <el-button @click="cancel">取消</el-button>
|
|
|
|
+ <el-button type="primary" @click="confirm" :disabled="selectedCharts.length === 0">
|
|
|
|
+ <el-icon>
|
|
|
|
+ <Plus />
|
|
|
|
+ </el-icon>
|
|
|
|
+ 确认添加 ({{ selectedCharts.length }})
|
|
|
|
+ </el-button>
|
|
|
|
+ </el-footer>
|
|
</div>
|
|
</div>
|
|
- </div>
|
|
|
|
-
|
|
|
|
- <!-- 底部操作按钮 -->
|
|
|
|
- <div class="chart-actions">
|
|
|
|
- <el-button @click="cancel">取消</el-button>
|
|
|
|
- <el-button type="primary" @click="confirm" :disabled="!selectedChart">确认</el-button>
|
|
|
|
- </div>
|
|
|
|
- </div>
|
|
|
|
|
|
+ </el-main>
|
|
|
|
+ </el-container>
|
|
</template>
|
|
</template>
|
|
|
|
|
|
<script setup>
|
|
<script setup>
|
|
-import { computed, onMounted, ref } from 'vue';
|
|
|
|
|
|
+import { ref, computed, onMounted, nextTick } from 'vue';
|
|
import { ElMessage } from 'element-plus';
|
|
import { ElMessage } from 'element-plus';
|
|
|
|
+import {
|
|
|
|
+ Search,
|
|
|
|
+ Plus
|
|
|
|
+} from '@element-plus/icons-vue';
|
|
import { api } from '../../data.service.js';
|
|
import { api } from '../../data.service.js';
|
|
|
|
+import ReportChart from './report-chart.vue';
|
|
|
|
|
|
const props = defineProps({
|
|
const props = defineProps({
|
|
modal: {
|
|
modal: {
|
|
@@ -103,18 +90,45 @@ const emit = defineEmits(['modalCallBack']);
|
|
// 响应式数据
|
|
// 响应式数据
|
|
const chartListLoading = ref(false);
|
|
const chartListLoading = ref(false);
|
|
const chartList = ref([]);
|
|
const chartList = ref([]);
|
|
-const selectedChart = ref(null);
|
|
|
|
-const selectedChartType = ref('line'); // 默认选择折线图
|
|
|
|
|
|
+const selectedCharts = ref([]); // 改为多选数组
|
|
|
|
+const lastSelectedChart = ref(null); // 最后选中的图表用于预览
|
|
|
|
+
|
|
const searchKeyword = ref('');
|
|
const searchKeyword = ref('');
|
|
|
|
|
|
|
|
+// 根据图表名称生成对应的模拟数据
|
|
|
|
+const generateMockData = (chartName) => {
|
|
|
|
+ const dates = ['2025-08-28', '2025-08-29', '2025-08-30', '2025-08-31', '2025-09-01', '2025-09-02', '2025-09-03'];
|
|
|
|
+
|
|
|
|
+ // 根据图表名称生成不同的数据模式
|
|
|
|
+ const dataPatterns = {
|
|
|
|
+ '商城停留时间统计': () => dates.map((date, index) => ({ x: date, y: Math.floor(Math.random() * 60) + 30, series: '停留时间(分钟)' })),
|
|
|
|
+ '种子统计': () => dates.map((date, index) => ({ x: date, y: Math.floor(Math.random() * 1000) + 500, series: '种子数量' })),
|
|
|
|
+ '装饰统计': () => dates.map((date, index) => ({ x: date, y: Math.floor(Math.random() * 200) + 100, series: '装饰销量' })),
|
|
|
|
+ '虫子统计': () => dates.map((date, index) => ({ x: date, y: Math.floor(Math.random() * 50) + 10, series: '虫子数量' })),
|
|
|
|
+ '次日留存': () => dates.map((date, index) => ({ x: date, y: Math.floor(Math.random() * 30) + 60, series: '留存率(%)' })),
|
|
|
|
+ '7日留存': () => dates.map((date, index) => ({ x: date, y: Math.floor(Math.random() * 20) + 40, series: '留存率(%)' })),
|
|
|
|
+ '入账笔数': () => dates.map((date, index) => ({ x: date, y: Math.floor(Math.random() * 500) + 200, series: '笔数' }))
|
|
|
|
+ };
|
|
|
|
+
|
|
|
|
+ // 如果有对应的数据模式,使用它;否则使用默认模式
|
|
|
|
+ const generator = dataPatterns[chartName] || (() => dates.map((date, index) => ({ x: date, y: Math.floor(Math.random() * 1000) + 100, series: chartName || '默认数据' })));
|
|
|
|
+ return generator();
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+// 当前图表数据(根据选中的图表动态生成)
|
|
|
|
+const currentChartData = computed(() => {
|
|
|
|
+ if (!lastSelectedChart.value) return [];
|
|
|
|
+ return generateMockData(lastSelectedChart.value.name);
|
|
|
|
+});
|
|
|
|
+
|
|
// 过滤后的图表列表
|
|
// 过滤后的图表列表
|
|
const filteredCharts = computed(() => {
|
|
const filteredCharts = computed(() => {
|
|
if (!searchKeyword.value) {
|
|
if (!searchKeyword.value) {
|
|
return chartList.value;
|
|
return chartList.value;
|
|
}
|
|
}
|
|
- return chartList.value.filter(chart =>
|
|
|
|
|
|
+ return chartList.value.filter(chart =>
|
|
chart.name.toLowerCase().includes(searchKeyword.value.toLowerCase()) ||
|
|
chart.name.toLowerCase().includes(searchKeyword.value.toLowerCase()) ||
|
|
- chart.memo.toLowerCase().includes(searchKeyword.value.toLowerCase())
|
|
|
|
|
|
+ (chart.memo && chart.memo.toLowerCase().includes(searchKeyword.value.toLowerCase()))
|
|
);
|
|
);
|
|
});
|
|
});
|
|
|
|
|
|
@@ -127,7 +141,7 @@ async function getChartList() {
|
|
ElMessage.error('缺少看板ID参数');
|
|
ElMessage.error('缺少看板ID参数');
|
|
return;
|
|
return;
|
|
}
|
|
}
|
|
-
|
|
|
|
|
|
+
|
|
const { code, data } = await api.report.spectaculars.addView(spectId);
|
|
const { code, data } = await api.report.spectaculars.addView(spectId);
|
|
if (code === '200') {
|
|
if (code === '200') {
|
|
chartList.value = data || [];
|
|
chartList.value = data || [];
|
|
@@ -142,9 +156,21 @@ async function getChartList() {
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
-// 选择图表
|
|
|
|
-function selectChart(chart) {
|
|
|
|
- selectedChart.value = chart;
|
|
|
|
|
|
+// 切换图表选择状态
|
|
|
|
+function toggleChart(chart) {
|
|
|
|
+ const index = selectedCharts.value.indexOf(chart.id);
|
|
|
|
+ if (index > -1) {
|
|
|
|
+ selectedCharts.value.splice(index, 1);
|
|
|
|
+ // 如果取消选择的是当前预览的图表,更新预览
|
|
|
|
+ if (lastSelectedChart.value?.id === chart.id) {
|
|
|
|
+ lastSelectedChart.value = selectedCharts.value.length > 0
|
|
|
|
+ ? chartList.value.find(c => c.id === selectedCharts.value[selectedCharts.value.length - 1])
|
|
|
|
+ : null;
|
|
|
|
+ }
|
|
|
|
+ } else {
|
|
|
|
+ selectedCharts.value.push(chart.id);
|
|
|
|
+ lastSelectedChart.value = chart; // 更新预览图表
|
|
|
|
+ }
|
|
}
|
|
}
|
|
|
|
|
|
// 过滤图表
|
|
// 过滤图表
|
|
@@ -159,8 +185,8 @@ function cancel() {
|
|
|
|
|
|
// 确认添加图表
|
|
// 确认添加图表
|
|
async function confirm() {
|
|
async function confirm() {
|
|
- if (!selectedChart.value) {
|
|
|
|
- ElMessage.warning('请选择一个图表');
|
|
|
|
|
|
+ if (selectedCharts.value.length === 0) {
|
|
|
|
+ ElMessage.warning('请选择至少一个图表');
|
|
return;
|
|
return;
|
|
}
|
|
}
|
|
|
|
|
|
@@ -168,17 +194,15 @@ async function confirm() {
|
|
const spectId = props.modal.params.spectId;
|
|
const spectId = props.modal.params.spectId;
|
|
const submitData = {
|
|
const submitData = {
|
|
id: spectId,
|
|
id: spectId,
|
|
- dimensions: [
|
|
|
|
- {
|
|
|
|
- dimensionId: selectedChart.value.id,
|
|
|
|
- viewType: selectedChartType.value
|
|
|
|
- }
|
|
|
|
- ]
|
|
|
|
|
|
+ dimensions: selectedCharts.value.map(chartId => ({
|
|
|
|
+ dimensionId: chartId,
|
|
|
|
+ viewType: 'line'
|
|
|
|
+ }))
|
|
};
|
|
};
|
|
|
|
|
|
- const { code } = await api.report.spectaculars.dimensions.add(submitData);
|
|
|
|
|
|
+ const { code } = await api.report.dimensions.add(submitData);
|
|
if (code === '200') {
|
|
if (code === '200') {
|
|
- ElMessage.success('添加图表成功');
|
|
|
|
|
|
+ ElMessage.success(`成功添加${selectedCharts.value.length}个图表`);
|
|
emit('modalCallBack', { event: 'confirm' });
|
|
emit('modalCallBack', { event: 'confirm' });
|
|
} else {
|
|
} else {
|
|
ElMessage.error('添加图表失败');
|
|
ElMessage.error('添加图表失败');
|
|
@@ -200,124 +224,88 @@ onMounted(() => {
|
|
</script>
|
|
</script>
|
|
|
|
|
|
<style scoped>
|
|
<style scoped>
|
|
-.chart-edit-container {
|
|
|
|
|
|
+.chart-edit-modal {
|
|
display: flex;
|
|
display: flex;
|
|
flex-direction: column;
|
|
flex-direction: column;
|
|
- height: 600px;
|
|
|
|
- background: #fff;
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
-/* 图表类型选择器 */
|
|
|
|
-.chart-type-selector {
|
|
|
|
- display: flex;
|
|
|
|
- justify-content: center;
|
|
|
|
- gap: 20px;
|
|
|
|
- padding: 20px;
|
|
|
|
- border-bottom: 1px solid #e4e7ed;
|
|
|
|
- background: #f8f9fa;
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
-.chart-type-item {
|
|
|
|
- display: flex;
|
|
|
|
- flex-direction: column;
|
|
|
|
- align-items: center;
|
|
|
|
- gap: 8px;
|
|
|
|
- padding: 16px 24px;
|
|
|
|
- border: 2px solid #e4e7ed;
|
|
|
|
- border-radius: 8px;
|
|
|
|
- cursor: pointer;
|
|
|
|
- transition: all 0.3s ease;
|
|
|
|
|
|
+ height: 700px;
|
|
background: #fff;
|
|
background: #fff;
|
|
- min-width: 80px;
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
-.chart-type-item:hover {
|
|
|
|
- border-color: #409eff;
|
|
|
|
- box-shadow: 0 2px 8px rgba(64, 158, 255, 0.2);
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
-.chart-type-item.active {
|
|
|
|
- border-color: #409eff;
|
|
|
|
- background: #ecf5ff;
|
|
|
|
- color: #409eff;
|
|
|
|
|
|
+ border-radius: 12px;
|
|
|
|
+ overflow: hidden;
|
|
}
|
|
}
|
|
|
|
|
|
-.chart-type-item i {
|
|
|
|
- font-size: 24px;
|
|
|
|
-}
|
|
|
|
|
|
|
|
-.chart-type-item span {
|
|
|
|
- font-size: 14px;
|
|
|
|
- font-weight: 500;
|
|
|
|
-}
|
|
|
|
|
|
|
|
/* 主要内容区域 */
|
|
/* 主要内容区域 */
|
|
-.chart-content {
|
|
|
|
|
|
+.main-content {
|
|
display: flex;
|
|
display: flex;
|
|
flex: 1;
|
|
flex: 1;
|
|
overflow: hidden;
|
|
overflow: hidden;
|
|
}
|
|
}
|
|
|
|
|
|
/* 左侧图表列表 */
|
|
/* 左侧图表列表 */
|
|
-.chart-list-panel {
|
|
|
|
- width: 300px;
|
|
|
|
- border-right: 1px solid #e4e7ed;
|
|
|
|
|
|
+.chart-list-section {
|
|
|
|
+ width: 320px;
|
|
|
|
+ border-right: 1px solid #f0f0f0;
|
|
display: flex;
|
|
display: flex;
|
|
flex-direction: column;
|
|
flex-direction: column;
|
|
|
|
+ background: #fafbfc;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.list-header {
|
|
|
|
+ padding: 20px 20px 16px 20px;
|
|
|
|
+ border-bottom: 1px solid #f0f0f0;
|
|
|
|
+ background: #fff;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.list-header h4 {
|
|
|
|
+ margin: 0 0 16px 0;
|
|
|
|
+ font-size: 16px;
|
|
|
|
+ font-weight: 600;
|
|
|
|
+ color: #1f2937;
|
|
}
|
|
}
|
|
|
|
|
|
.search-box {
|
|
.search-box {
|
|
- padding: 16px;
|
|
|
|
- border-bottom: 1px solid #e4e7ed;
|
|
|
|
|
|
+ position: relative;
|
|
}
|
|
}
|
|
|
|
|
|
.chart-list {
|
|
.chart-list {
|
|
flex: 1;
|
|
flex: 1;
|
|
overflow-y: auto;
|
|
overflow-y: auto;
|
|
- padding: 8px;
|
|
|
|
|
|
+ padding: 16px;
|
|
}
|
|
}
|
|
|
|
|
|
.chart-item {
|
|
.chart-item {
|
|
display: flex;
|
|
display: flex;
|
|
align-items: center;
|
|
align-items: center;
|
|
gap: 12px;
|
|
gap: 12px;
|
|
- padding: 12px;
|
|
|
|
|
|
+ padding: 12px 16px;
|
|
border: 1px solid #e4e7ed;
|
|
border: 1px solid #e4e7ed;
|
|
border-radius: 6px;
|
|
border-radius: 6px;
|
|
- margin-bottom: 8px;
|
|
|
|
- cursor: pointer;
|
|
|
|
- transition: all 0.3s ease;
|
|
|
|
background: #fff;
|
|
background: #fff;
|
|
|
|
+ cursor: pointer;
|
|
|
|
+ transition: all 0.2s ease;
|
|
|
|
+ margin-bottom: 8px;
|
|
}
|
|
}
|
|
|
|
|
|
.chart-item:hover {
|
|
.chart-item:hover {
|
|
- border-color: #409eff;
|
|
|
|
- box-shadow: 0 2px 8px rgba(64, 158, 255, 0.1);
|
|
|
|
|
|
+ border-color: var(--el-color-primary);
|
|
|
|
+ background: #f9f5ff;
|
|
}
|
|
}
|
|
|
|
|
|
.chart-item.active {
|
|
.chart-item.active {
|
|
- border-color: #409eff;
|
|
|
|
- background: #ecf5ff;
|
|
|
|
|
|
+ border-color: var(--el-color-primary);
|
|
|
|
+ /* 淡紫色 */
|
|
|
|
+ background: #f9f5ff;
|
|
|
|
+ box-shadow: 0 0 0 1px var(--el-color-primary);
|
|
}
|
|
}
|
|
|
|
|
|
-.chart-icon {
|
|
|
|
- width: 40px;
|
|
|
|
- height: 40px;
|
|
|
|
- display: flex;
|
|
|
|
- align-items: center;
|
|
|
|
- justify-content: center;
|
|
|
|
- background: #f0f2f5;
|
|
|
|
- border-radius: 6px;
|
|
|
|
- color: #606266;
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
-.chart-item.active .chart-icon {
|
|
|
|
- background: #409eff;
|
|
|
|
- color: #fff;
|
|
|
|
|
|
+.chart-checkbox {
|
|
|
|
+ margin-right: 8px;
|
|
}
|
|
}
|
|
|
|
|
|
-.chart-icon i {
|
|
|
|
- font-size: 18px;
|
|
|
|
|
|
+.chart-checkbox :deep(.el-checkbox__input.is-checked .el-checkbox__inner) {
|
|
|
|
+ background-color: var(--el-color-primary);
|
|
|
|
+ border-color: var(--el-color-primary);
|
|
}
|
|
}
|
|
|
|
|
|
.chart-info {
|
|
.chart-info {
|
|
@@ -328,27 +316,63 @@ onMounted(() => {
|
|
.chart-name {
|
|
.chart-name {
|
|
font-size: 14px;
|
|
font-size: 14px;
|
|
font-weight: 500;
|
|
font-weight: 500;
|
|
- color: #303133;
|
|
|
|
- margin-bottom: 4px;
|
|
|
|
- overflow: hidden;
|
|
|
|
- text-overflow: ellipsis;
|
|
|
|
- white-space: nowrap;
|
|
|
|
|
|
+ color: #1f2937;
|
|
|
|
+ line-height: 1.4;
|
|
}
|
|
}
|
|
|
|
|
|
-.chart-memo {
|
|
|
|
- font-size: 12px;
|
|
|
|
- color: #909399;
|
|
|
|
- overflow: hidden;
|
|
|
|
- text-overflow: ellipsis;
|
|
|
|
- white-space: nowrap;
|
|
|
|
|
|
+.chart-item.active .chart-name {
|
|
|
|
+ color: #1e40af;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.chart-check {
|
|
|
|
+ color: var(--el-color-primary);
|
|
|
|
+ font-size: 20px;
|
|
|
|
+ animation: checkIn 0.3s ease;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+@keyframes checkIn {
|
|
|
|
+ 0% {
|
|
|
|
+ transform: scale(0);
|
|
|
|
+ opacity: 0;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ 50% {
|
|
|
|
+ transform: scale(1.2);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ 100% {
|
|
|
|
+ transform: scale(1);
|
|
|
|
+ opacity: 1;
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.empty-state {
|
|
|
|
+ display: flex;
|
|
|
|
+ flex-direction: column;
|
|
|
|
+ align-items: center;
|
|
|
|
+ justify-content: center;
|
|
|
|
+ padding: 60px 20px;
|
|
|
|
+ color: #9ca3af;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.empty-icon {
|
|
|
|
+ font-size: 48px;
|
|
|
|
+ margin-bottom: 16px;
|
|
|
|
+ opacity: 0.6;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.empty-text {
|
|
|
|
+ font-size: 14px;
|
|
|
|
+ font-weight: 500;
|
|
}
|
|
}
|
|
|
|
|
|
/* 右侧预览区域 */
|
|
/* 右侧预览区域 */
|
|
-.chart-preview-panel {
|
|
|
|
|
|
+.preview-section {
|
|
flex: 1;
|
|
flex: 1;
|
|
display: flex;
|
|
display: flex;
|
|
flex-direction: column;
|
|
flex-direction: column;
|
|
- background: #fafafa;
|
|
|
|
|
|
+ padding: 24px;
|
|
|
|
+ background: #fff;
|
|
}
|
|
}
|
|
|
|
|
|
.empty-preview {
|
|
.empty-preview {
|
|
@@ -357,77 +381,79 @@ onMounted(() => {
|
|
flex-direction: column;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
align-items: center;
|
|
justify-content: center;
|
|
justify-content: center;
|
|
- color: #909399;
|
|
|
|
|
|
+ color: #9ca3af;
|
|
|
|
+ background: #fafbfc;
|
|
|
|
+ border-radius: 12px;
|
|
|
|
+ border: 2px dashed #e5e7eb;
|
|
}
|
|
}
|
|
|
|
|
|
-.empty-preview i {
|
|
|
|
- font-size: 48px;
|
|
|
|
- margin-bottom: 16px;
|
|
|
|
|
|
+.empty-preview .empty-icon {
|
|
|
|
+ font-size: 64px;
|
|
|
|
+ margin-bottom: 20px;
|
|
|
|
+ opacity: 0.4;
|
|
}
|
|
}
|
|
|
|
|
|
-.empty-preview p {
|
|
|
|
|
|
+.empty-preview .empty-text {
|
|
|
|
+ font-size: 16px;
|
|
|
|
+ font-weight: 600;
|
|
|
|
+ margin: 0 0 8px 0;
|
|
|
|
+ color: #6b7280;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.empty-preview .empty-desc {
|
|
font-size: 14px;
|
|
font-size: 14px;
|
|
margin: 0;
|
|
margin: 0;
|
|
|
|
+ color: #9ca3af;
|
|
}
|
|
}
|
|
|
|
|
|
.preview-content {
|
|
.preview-content {
|
|
flex: 1;
|
|
flex: 1;
|
|
display: flex;
|
|
display: flex;
|
|
flex-direction: column;
|
|
flex-direction: column;
|
|
- padding: 20px;
|
|
|
|
}
|
|
}
|
|
|
|
|
|
.preview-header {
|
|
.preview-header {
|
|
- margin-bottom: 20px;
|
|
|
|
|
|
+ margin-bottom: 24px;
|
|
|
|
+ padding-bottom: 16px;
|
|
|
|
+ border-bottom: 1px solid #f0f0f0;
|
|
}
|
|
}
|
|
|
|
|
|
.preview-header h3 {
|
|
.preview-header h3 {
|
|
margin: 0 0 8px 0;
|
|
margin: 0 0 8px 0;
|
|
- font-size: 18px;
|
|
|
|
- color: #303133;
|
|
|
|
|
|
+ font-size: 20px;
|
|
|
|
+ font-weight: 700;
|
|
|
|
+ color: #1f2937;
|
|
}
|
|
}
|
|
|
|
|
|
-.preview-header p {
|
|
|
|
|
|
+.preview-header .chart-desc {
|
|
margin: 0;
|
|
margin: 0;
|
|
font-size: 14px;
|
|
font-size: 14px;
|
|
- color: #606266;
|
|
|
|
|
|
+ color: #6b7280;
|
|
|
|
+ line-height: 1.5;
|
|
}
|
|
}
|
|
|
|
|
|
-.preview-chart {
|
|
|
|
|
|
+.chart-preview {
|
|
flex: 1;
|
|
flex: 1;
|
|
background: #fff;
|
|
background: #fff;
|
|
- border: 1px solid #e4e7ed;
|
|
|
|
- border-radius: 8px;
|
|
|
|
- display: flex;
|
|
|
|
- align-items: center;
|
|
|
|
- justify-content: center;
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
-.chart-placeholder {
|
|
|
|
- display: flex;
|
|
|
|
- flex-direction: column;
|
|
|
|
- align-items: center;
|
|
|
|
- gap: 16px;
|
|
|
|
- color: #909399;
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
-.chart-placeholder i {
|
|
|
|
- font-size: 64px;
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
-.chart-placeholder p {
|
|
|
|
- margin: 0;
|
|
|
|
- font-size: 16px;
|
|
|
|
|
|
+ border: 1px solid #e5e7eb;
|
|
|
|
+ border-radius: 12px;
|
|
|
|
+ padding: 20px;
|
|
|
|
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
|
}
|
|
}
|
|
|
|
|
|
/* 底部操作按钮 */
|
|
/* 底部操作按钮 */
|
|
-.chart-actions {
|
|
|
|
|
|
+.footer-actions {
|
|
display: flex;
|
|
display: flex;
|
|
justify-content: flex-end;
|
|
justify-content: flex-end;
|
|
gap: 12px;
|
|
gap: 12px;
|
|
- padding: 16px 20px;
|
|
|
|
- border-top: 1px solid #e4e7ed;
|
|
|
|
- background: #fff;
|
|
|
|
|
|
+ padding: 24px;
|
|
|
|
+ border-top: 1px solid #f0f0f0;
|
|
|
|
+ background: #fafbfc;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.footer-actions .el-button {
|
|
|
|
+ min-width: 100px;
|
|
|
|
+ font-weight: 600;
|
|
}
|
|
}
|
|
|
|
|
|
/* 滚动条样式 */
|
|
/* 滚动条样式 */
|
|
@@ -436,16 +462,37 @@ onMounted(() => {
|
|
}
|
|
}
|
|
|
|
|
|
.chart-list::-webkit-scrollbar-track {
|
|
.chart-list::-webkit-scrollbar-track {
|
|
- background: #f1f1f1;
|
|
|
|
|
|
+ background: #f1f5f9;
|
|
border-radius: 3px;
|
|
border-radius: 3px;
|
|
}
|
|
}
|
|
|
|
|
|
.chart-list::-webkit-scrollbar-thumb {
|
|
.chart-list::-webkit-scrollbar-thumb {
|
|
- background: #c1c1c1;
|
|
|
|
|
|
+ background: #cbd5e1;
|
|
border-radius: 3px;
|
|
border-radius: 3px;
|
|
}
|
|
}
|
|
|
|
|
|
.chart-list::-webkit-scrollbar-thumb:hover {
|
|
.chart-list::-webkit-scrollbar-thumb:hover {
|
|
- background: #a8a8a8;
|
|
|
|
|
|
+ background: #94a3b8;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+/* 响应式设计 */
|
|
|
|
+@media (max-width: 768px) {
|
|
|
|
+ .chart-edit-modal {
|
|
|
|
+ height: 100vh;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ .main-content {
|
|
|
|
+ flex-direction: column;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ .chart-list-section {
|
|
|
|
+ width: 100%;
|
|
|
|
+ max-height: 300px;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ .type-button {
|
|
|
|
+ min-width: 70px;
|
|
|
|
+ padding: 12px 16px;
|
|
|
|
+ }
|
|
}
|
|
}
|
|
</style>
|
|
</style>
|