✨ 右侧上部完成
This commit is contained in:
parent
e381bb62a4
commit
065a05d9f4
|
@ -94,20 +94,54 @@ export const logOutputSize = (): string => {
|
||||||
return `${size.toFixed(2)} ${units[index]}`;
|
return `${size.toFixed(2)} ${units[index]}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 计算文件夹字节大小
|
/**
|
||||||
function getFolderSize(folderPath: string) {
|
* 计算文件夹大小(排除图片文件)
|
||||||
|
* @param folderPath 文件夹路径
|
||||||
|
* @param currentDepth 当前递归深度(内部使用)
|
||||||
|
* @returns 文件夹大小(字节)
|
||||||
|
*/
|
||||||
|
function getFolderSize(folderPath: string, currentDepth: number = 0): number {
|
||||||
|
// 公共常量定义
|
||||||
|
const EXCLUDED_FILE_EXTENSIONS = ['.jpg', '.jpeg', '.png', '.gif', '.bmp', '.webp', '.svg'];
|
||||||
|
const MAX_DEPTH = 10; // 最大递归深度限制
|
||||||
|
|
||||||
|
// 安全检查
|
||||||
|
if (!fs.existsSync(folderPath)) {
|
||||||
|
throw new Error(`文件夹不存在: ${folderPath}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentDepth > MAX_DEPTH) {
|
||||||
|
console.warn(`达到最大递归深度 ${MAX_DEPTH},停止遍历: ${folderPath}`);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
let size = 0;
|
let size = 0;
|
||||||
|
|
||||||
fs.readdirSync(folderPath).forEach((fileName: string) => {
|
try {
|
||||||
const filePath = path.join(folderPath, fileName);
|
const files = fs.readdirSync(folderPath);
|
||||||
const stats = fs.statSync(filePath);
|
|
||||||
|
|
||||||
if (stats.isFile()) {
|
for (const fileName of files) {
|
||||||
size += stats.size;
|
const filePath = path.join(folderPath, fileName);
|
||||||
} else if (stats.isDirectory()) {
|
|
||||||
size += getFolderSize(filePath);
|
try {
|
||||||
|
const stats = fs.statSync(filePath);
|
||||||
|
|
||||||
|
if (stats.isFile()) {
|
||||||
|
// 检查文件扩展名是否在排除列表中
|
||||||
|
const ext = path.extname(fileName).toLowerCase();
|
||||||
|
if (!EXCLUDED_FILE_EXTENSIONS.includes(ext)) {
|
||||||
|
size += stats.size;
|
||||||
|
}
|
||||||
|
} else if (stats.isDirectory()) {
|
||||||
|
size += getFolderSize(filePath, currentDepth + 1);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`无法访问文件: ${filePath}`, error);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
} catch (error) {
|
||||||
|
console.error(`无法读取目录: ${folderPath}`, error);
|
||||||
|
}
|
||||||
|
|
||||||
return size;
|
return size;
|
||||||
}
|
}
|
||||||
|
|
|
@ -71,6 +71,7 @@ export default defineFakeRoute([
|
||||||
message: '操作成功',
|
message: '操作成功',
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
|
// 销售设备数量区域占比
|
||||||
{
|
{
|
||||||
url: `${BASE_URL}/region-sales-ratio`,
|
url: `${BASE_URL}/region-sales-ratio`,
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
|
@ -101,4 +102,18 @@ export default defineFakeRoute([
|
||||||
message: '操作成功',
|
message: '操作成功',
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
url: `${BASE_URL}/data-ratio`,
|
||||||
|
method: 'GET',
|
||||||
|
response: () => ({
|
||||||
|
code: 200,
|
||||||
|
data: Array(6)
|
||||||
|
.fill(0)
|
||||||
|
.map((_, index) => ({
|
||||||
|
name: `数据-${index + 1}`,
|
||||||
|
value: randomNumber(),
|
||||||
|
})),
|
||||||
|
message: '操作成功',
|
||||||
|
}),
|
||||||
|
},
|
||||||
]);
|
]);
|
||||||
|
|
|
@ -24,3 +24,8 @@ export const getDataShow = () => {
|
||||||
export const getRegionSalesRatio = () => {
|
export const getRegionSalesRatio = () => {
|
||||||
return request.get('data-analyse/region-sales-ratio');
|
return request.get('data-analyse/region-sales-ratio');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/* 数据占有率 */
|
||||||
|
export const getDataRatio = () => {
|
||||||
|
return request.get('data-analyse/data-ratio');
|
||||||
|
};
|
||||||
|
|
|
@ -3,6 +3,7 @@ import { defineStore } from 'pinia';
|
||||||
import {
|
import {
|
||||||
getBrandsDistribution,
|
getBrandsDistribution,
|
||||||
getCompanySalesDistribution,
|
getCompanySalesDistribution,
|
||||||
|
getDataRatio,
|
||||||
getDataShow,
|
getDataShow,
|
||||||
getDeviceSalesStats,
|
getDeviceSalesStats,
|
||||||
getRegionSalesRatio,
|
getRegionSalesRatio,
|
||||||
|
@ -25,6 +26,8 @@ export const useDataAnalyseStore = defineStore('dataAnalyseStore', {
|
||||||
dataShow: [],
|
dataShow: [],
|
||||||
// 销售设备数量区域占比
|
// 销售设备数量区域占比
|
||||||
regionSalesRatio: [],
|
regionSalesRatio: [],
|
||||||
|
// 数据占有率
|
||||||
|
dataRatio: [],
|
||||||
}),
|
}),
|
||||||
actions: {
|
actions: {
|
||||||
/* 销售设备总量 */
|
/* 销售设备总量 */
|
||||||
|
@ -51,6 +54,11 @@ export const useDataAnalyseStore = defineStore('dataAnalyseStore', {
|
||||||
async fetchRegionSalesRatio() {
|
async fetchRegionSalesRatio() {
|
||||||
this.regionSalesRatio = await getRegionSalesRatio();
|
this.regionSalesRatio = await getRegionSalesRatio();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/* 数据占有率 */
|
||||||
|
async fetchDataRatio() {
|
||||||
|
this.dataRatio = await getDataRatio();
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import { useDebounceFn, useEventListener } from '@vueuse/core';
|
import { useDebounceFn, useEventListener } from '@vueuse/core';
|
||||||
|
import type { EChartsType } from 'echarts';
|
||||||
|
|
||||||
import echarts from '@/plugins/echarts';
|
import echarts from '@/plugins/echarts';
|
||||||
|
|
||||||
|
@ -38,3 +39,18 @@ export const graphicLinearGradient = (
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const resetSelect = (myChart: EChartsType) => {
|
||||||
|
myChart.dispatchAction({
|
||||||
|
type: 'downplay',
|
||||||
|
seriesIndex: 0,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const selectSector = (myChart: EChartsType, dataIndex: number) => {
|
||||||
|
myChart.dispatchAction({
|
||||||
|
type: 'highlight',
|
||||||
|
seriesIndex: 0,
|
||||||
|
dataIndex,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import type { EChartsOption } from 'echarts';
|
||||||
import type { Ref } from 'vue';
|
import type { Ref } from 'vue';
|
||||||
|
|
||||||
import echarts from '@/plugins/echarts';
|
import echarts from '@/plugins/echarts';
|
||||||
|
@ -5,7 +6,7 @@ import { debounceChart } from '@/utils/chart';
|
||||||
|
|
||||||
let myChart = null;
|
let myChart = null;
|
||||||
|
|
||||||
const option = {
|
const option: EChartsOption = {
|
||||||
grid: {
|
grid: {
|
||||||
left: '4%',
|
left: '4%',
|
||||||
right: '4%',
|
right: '4%',
|
||||||
|
@ -21,9 +22,9 @@ const option = {
|
||||||
},
|
},
|
||||||
tooltip: { trigger: 'axis' },
|
tooltip: { trigger: 'axis' },
|
||||||
legend: {
|
legend: {
|
||||||
|
show: false,
|
||||||
data: ['增加值'],
|
data: ['增加值'],
|
||||||
icon: 'rich',
|
icon: 'rich',
|
||||||
show: true,
|
|
||||||
itemWidth: 18,
|
itemWidth: 18,
|
||||||
itemHeight: 2,
|
itemHeight: 2,
|
||||||
textStyle: { color: '#AFBDD1', fontSize: '12px' },
|
textStyle: { color: '#AFBDD1', fontSize: '12px' },
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import type { EChartsOption } from 'echarts';
|
||||||
import type { Ref } from 'vue';
|
import type { Ref } from 'vue';
|
||||||
|
|
||||||
import echarts from '@/plugins/echarts';
|
import echarts from '@/plugins/echarts';
|
||||||
|
@ -5,7 +6,101 @@ import { debounceChart } from '@/utils/chart';
|
||||||
|
|
||||||
let myChart = null;
|
let myChart = null;
|
||||||
|
|
||||||
const option = {};
|
const option: EChartsOption = {
|
||||||
|
grid: {
|
||||||
|
left: '4%',
|
||||||
|
right: '4%',
|
||||||
|
bottom: '19%',
|
||||||
|
top: '20%',
|
||||||
|
containLabel: true,
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
text: '单位:(万元)',
|
||||||
|
textStyle: { color: '#83A2C0FF', fontSize: 12 },
|
||||||
|
top: '4%',
|
||||||
|
left: '2%',
|
||||||
|
},
|
||||||
|
tooltip: { trigger: 'axis' },
|
||||||
|
legend: {
|
||||||
|
show: false,
|
||||||
|
data: ['增加值'],
|
||||||
|
icon: 'rich',
|
||||||
|
itemWidth: 18,
|
||||||
|
itemHeight: 2,
|
||||||
|
textStyle: { color: '#AFBDD1', fontSize: '12px' },
|
||||||
|
top: 8,
|
||||||
|
right: 10,
|
||||||
|
itemGap: 34,
|
||||||
|
},
|
||||||
|
|
||||||
|
xAxis: {
|
||||||
|
data: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
|
||||||
|
type: 'category',
|
||||||
|
boundaryGap: false,
|
||||||
|
axisLine: {
|
||||||
|
symbol: 'none',
|
||||||
|
lineStyle: {
|
||||||
|
color: '#50637A',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
axisTick: { show: false },
|
||||||
|
axisLabel: {
|
||||||
|
interval: 0,
|
||||||
|
color: '#6071A9',
|
||||||
|
fontSize: 12,
|
||||||
|
padding: [10, 0, 0, 0],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
yAxis: {
|
||||||
|
type: 'value',
|
||||||
|
axisLabel: {
|
||||||
|
color: '#6071A9',
|
||||||
|
fontSize: 12,
|
||||||
|
padding: [0, 10, 0, 0],
|
||||||
|
},
|
||||||
|
splitLine: {
|
||||||
|
lineStyle: {
|
||||||
|
color: '#50637A',
|
||||||
|
type: 'dashed',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
name: '增加值',
|
||||||
|
data: [],
|
||||||
|
type: 'line',
|
||||||
|
// smooth: true,
|
||||||
|
color: '#00F7FF',
|
||||||
|
lineStyle: {
|
||||||
|
width: 2,
|
||||||
|
},
|
||||||
|
areaStyle: {
|
||||||
|
color: new echarts.graphic.LinearGradient(
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
[
|
||||||
|
{
|
||||||
|
offset: 0,
|
||||||
|
color: 'rgba(0, 247, 255, .6)',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
offset: 0.8,
|
||||||
|
color: 'rgba(0, 247, 255, .2)',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
false
|
||||||
|
),
|
||||||
|
shadowColor: 'rgba(0, 0, 0, 0.1)',
|
||||||
|
shadowBlur: 10,
|
||||||
|
},
|
||||||
|
symbol: 'circle',
|
||||||
|
symbolSize: 6,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
/** 渲染图表 */
|
/** 渲染图表 */
|
||||||
export const renderBodyChart = (element: Ref<HTMLDivElement>) => {
|
export const renderBodyChart = (element: Ref<HTMLDivElement>) => {
|
||||||
|
|
|
@ -13,7 +13,29 @@ const data = [
|
||||||
{ name: '朱八', value: 10086 },
|
{ name: '朱八', value: 10086 },
|
||||||
];
|
];
|
||||||
|
|
||||||
const option = {};
|
const option = {
|
||||||
|
title: {
|
||||||
|
text: '34467',
|
||||||
|
subtext: '历史预警数',
|
||||||
|
left: 'center',
|
||||||
|
top: 'center',
|
||||||
|
textStyle: { color: '#fff', fontSize: 34 },
|
||||||
|
subtextStyle: { color: '#fff', fontSize: 16 },
|
||||||
|
},
|
||||||
|
grid: { containLabel: false },
|
||||||
|
polar: { radius: [60, '100%'] },
|
||||||
|
angleAxis: { show: false, startAngle: 90 },
|
||||||
|
radiusAxis: { show: false, type: 'category' },
|
||||||
|
tooltip: {},
|
||||||
|
series: {
|
||||||
|
type: 'bar',
|
||||||
|
data: [],
|
||||||
|
barWidth: 20,
|
||||||
|
barGap: 0,
|
||||||
|
coordinateSystem: 'polar',
|
||||||
|
label: { show: true, position: 'middle' },
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
/** 渲染图表 */
|
/** 渲染图表 */
|
||||||
export const renderFooterChart = (element: Ref<HTMLDivElement>) => {
|
export const renderFooterChart = (element: Ref<HTMLDivElement>) => {
|
||||||
|
|
|
@ -0,0 +1,120 @@
|
||||||
|
import { useIntervalFn } from '@vueuse/core';
|
||||||
|
import type { EChartsOption } from 'echarts';
|
||||||
|
import { type Ref, ref } from 'vue';
|
||||||
|
|
||||||
|
import echarts from '@/plugins/echarts';
|
||||||
|
import { debounceChart, resetSelect, selectSector } from '@/utils/chart';
|
||||||
|
|
||||||
|
const totalIndex = ref<number>(0);
|
||||||
|
const currentIndex = ref<number>(0);
|
||||||
|
let myChart = null;
|
||||||
|
|
||||||
|
const option: EChartsOption = {
|
||||||
|
tooltip: { position: 'right', trigger: 'item' },
|
||||||
|
legend: {
|
||||||
|
orient: 'horizontal', // 垂直排列
|
||||||
|
right: 0, // 距离右侧10px
|
||||||
|
left: 240,
|
||||||
|
top: 'center', // 垂直居中
|
||||||
|
align: 'left', // 文本左对齐
|
||||||
|
icon: 'circle', // 圆形图标
|
||||||
|
itemGap: 50, // 图例项间隔
|
||||||
|
itemWidth: 10, // 图例标记宽度
|
||||||
|
itemHeight: 10, // 图例标记高度
|
||||||
|
textStyle: { color: '#fff', fontSize: 14 },
|
||||||
|
formatter(name) {
|
||||||
|
const series = myChart.getOption().series;
|
||||||
|
// 获取对应系列的数据
|
||||||
|
const seriesData = series[0].data;
|
||||||
|
const dataIndex = series[0].data.findIndex((item) => item.name === name);
|
||||||
|
const value = seriesData[dataIndex]?.value;
|
||||||
|
|
||||||
|
return `${name}: ${value}%`;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
left: '0%', // 图表左侧不留白
|
||||||
|
right: '0%', // 为图例留出25%空间
|
||||||
|
top: '0%',
|
||||||
|
bottom: '0%',
|
||||||
|
containLabel: false,
|
||||||
|
},
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
name: '数据占有率',
|
||||||
|
type: 'pie',
|
||||||
|
radius: ['54%', '70%'],
|
||||||
|
center: ['24%', '50%'],
|
||||||
|
avoidLabelOverlap: false,
|
||||||
|
padAngle: 4,
|
||||||
|
itemStyle: { borderRadius: 4 },
|
||||||
|
label: { show: false, position: 'center' },
|
||||||
|
emphasis: {
|
||||||
|
label: {
|
||||||
|
show: true,
|
||||||
|
color: '#fff',
|
||||||
|
fontSize: 29,
|
||||||
|
fontWeight: 'lighter',
|
||||||
|
formatter: ({ value }) => {
|
||||||
|
return `${value}%\n总占比`;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
labelLine: { show: false },
|
||||||
|
data: [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
visualMap: {
|
||||||
|
show: false,
|
||||||
|
showLabel: false,
|
||||||
|
type: 'piecewise',
|
||||||
|
pieces: [
|
||||||
|
{ min: 0, max: 20, label: '低', color: '#FF6363' },
|
||||||
|
{ min: 20, max: 30, label: '低中', color: '#FFCC00' },
|
||||||
|
{ min: 30, max: 50, label: '中', color: '#00FFFF' },
|
||||||
|
{ min: 50, max: 70, label: '中高', color: '#00CCFF' },
|
||||||
|
{ min: 70, max: 100, label: '高', color: '#0096FF' },
|
||||||
|
],
|
||||||
|
seriesIndex: 0,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
/** 渲染图表 */
|
||||||
|
export const renderTopChart = (element: Ref<HTMLDivElement>) => {
|
||||||
|
myChart = echarts.init(element.value, null, {
|
||||||
|
renderer: 'canvas',
|
||||||
|
devicePixelRatio: window.devicePixelRatio,
|
||||||
|
});
|
||||||
|
|
||||||
|
debounceChart(myChart);
|
||||||
|
|
||||||
|
myChart.setOption(option);
|
||||||
|
|
||||||
|
// 轮播数据
|
||||||
|
const { pause, resume } = useIntervalFn(() => {
|
||||||
|
currentIndex.value = currentIndex.value >= totalIndex.value ? 0 : currentIndex.value;
|
||||||
|
|
||||||
|
resetSelect(myChart);
|
||||||
|
selectSector(myChart, currentIndex.value);
|
||||||
|
|
||||||
|
currentIndex.value++;
|
||||||
|
}, 1000);
|
||||||
|
|
||||||
|
// 鼠标移入移出暂停动画
|
||||||
|
element.value.addEventListener('mouseenter', () => {
|
||||||
|
resetSelect(myChart);
|
||||||
|
pause();
|
||||||
|
});
|
||||||
|
element.value.addEventListener('mouseleave', () => {
|
||||||
|
resume();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/** 更新图表数据 */
|
||||||
|
export const updateTopChart = (data: any) => {
|
||||||
|
totalIndex.value = data.length;
|
||||||
|
|
||||||
|
const series = myChart.getOption().series;
|
||||||
|
series[0].data = data;
|
||||||
|
myChart.setOption({ series });
|
||||||
|
};
|
|
@ -1,23 +1,40 @@
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
import { useIntervalFn } from '@vueuse/core';
|
||||||
|
import { storeToRefs } from 'pinia';
|
||||||
import { onMounted, ref } from 'vue';
|
import { onMounted, ref } from 'vue';
|
||||||
|
|
||||||
|
import { useDataAnalyseHook } from '@/store/modules/dataAnalyse';
|
||||||
import { renderBodyChart } from '@/views/data-analyse/charts/right-body';
|
import { renderBodyChart } from '@/views/data-analyse/charts/right-body';
|
||||||
import { renderFooterChart } from '@/views/data-analyse/charts/right-footer';
|
import { renderFooterChart } from '@/views/data-analyse/charts/right-footer';
|
||||||
|
import { renderTopChart, updateTopChart } from '@/views/data-analyse/charts/right-top';
|
||||||
import PanelTitle from '@/views/data-analyse/components/PanelTitle.vue';
|
import PanelTitle from '@/views/data-analyse/components/PanelTitle.vue';
|
||||||
|
|
||||||
|
const topChartRef = ref();
|
||||||
|
const bodyChartRef = ref();
|
||||||
const footerChartRef = ref();
|
const footerChartRef = ref();
|
||||||
const footerChart = () => {
|
|
||||||
|
const dataAnalyseHook = useDataAnalyseHook();
|
||||||
|
const { dataRatio } = storeToRefs(dataAnalyseHook);
|
||||||
|
|
||||||
|
/* 渲染图表 */
|
||||||
|
const renderChart = () => {
|
||||||
|
renderTopChart(topChartRef);
|
||||||
|
renderBodyChart(bodyChartRef);
|
||||||
renderFooterChart(footerChartRef);
|
renderFooterChart(footerChartRef);
|
||||||
};
|
};
|
||||||
|
|
||||||
const bodyChartRef = ref();
|
const initAppData = async () => {
|
||||||
const bodyChart = () => {
|
await dataAnalyseHook.fetchDataRatio();
|
||||||
renderBodyChart(bodyChartRef);
|
updateTopChart(dataRatio.value);
|
||||||
};
|
};
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
bodyChart();
|
renderChart();
|
||||||
footerChart();
|
initAppData();
|
||||||
|
|
||||||
|
useIntervalFn(() => {
|
||||||
|
initAppData();
|
||||||
|
}, 10000);
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -25,7 +42,7 @@ onMounted(() => {
|
||||||
<div class="data-analyse-right">
|
<div class="data-analyse-right">
|
||||||
<header class="data-analyse-right__header">
|
<header class="data-analyse-right__header">
|
||||||
<PanelTitle title="数据占有率" />
|
<PanelTitle title="数据占有率" />
|
||||||
<div ref="bodyChartRef" class="data-analyse-right-chart" />
|
<div ref="topChartRef" class="data-analyse-right__header-chart" />
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<main class="data-analyse-right__body">
|
<main class="data-analyse-right__body">
|
||||||
|
@ -50,6 +67,11 @@ onMounted(() => {
|
||||||
margin: 10px 0 0 0;
|
margin: 10px 0 0 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 305px;
|
height: 305px;
|
||||||
|
|
||||||
|
&-chart {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&__body {
|
&__body {
|
||||||
|
|
Loading…
Reference in New Issue