Migrate all Vue components from Options API to <script setup>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -39,7 +39,7 @@
|
||||
<p class="h2 mb-2">Trace #{{ showTraceId }}</p>
|
||||
<div class="h-36 w-full px-2 mb-2 border border-neutral-300 rounded">
|
||||
<div class="h-full w-full">
|
||||
<div id="cyTrace" ref="cyTrace" class="h-full min-w-full relative"></div>
|
||||
<div id="cyTrace" ref="cyTraceRef" class="h-full min-w-full relative"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="overflow-y-auto overflow-x-auto scrollbar w-full h-[calc(100%_-_200px)] infiniteTable " @scroll="handleScroll">
|
||||
@@ -59,221 +59,224 @@
|
||||
</div>
|
||||
</Sidebar>
|
||||
</template>
|
||||
<script>
|
||||
<script setup>
|
||||
import { ref, computed, watch } from 'vue';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useLoadingStore } from '@/stores/loading';
|
||||
import { useAllMapDataStore } from '@/stores/allMapData';
|
||||
import cytoscapeMapTrace from '@/module/cytoscapeMapTrace.js';
|
||||
|
||||
export default {
|
||||
props: ['sidebarTraces', 'cases'],
|
||||
setup() {
|
||||
const loadingStore = useLoadingStore();
|
||||
const allMapDataStore = useAllMapDataStore();
|
||||
const { isLoading } = storeToRefs(loadingStore);
|
||||
const { infinit404, infiniteStart, traceId, traces, traceTaskSeq, infiniteFirstCases } = storeToRefs(allMapDataStore);
|
||||
const props = defineProps(['sidebarTraces', 'cases']);
|
||||
const emit = defineEmits(['switch-Trace-Id']);
|
||||
|
||||
return {allMapDataStore, infinit404, infiniteStart, traceId, traces, traceTaskSeq, infiniteFirstCases, isLoading }
|
||||
},
|
||||
data() {
|
||||
const loadingStore = useLoadingStore();
|
||||
const allMapDataStore = useAllMapDataStore();
|
||||
const { isLoading } = storeToRefs(loadingStore);
|
||||
const { infinit404, infiniteStart, traceId, traces, traceTaskSeq, infiniteFirstCases } = storeToRefs(allMapDataStore);
|
||||
|
||||
const processMap = ref({
|
||||
nodes:[],
|
||||
edges:[],
|
||||
});
|
||||
const showTraceId = ref(null);
|
||||
const infinitMaxItems = ref(false);
|
||||
const infiniteData = ref([]);
|
||||
const infiniteFinish = ref(true); // 無限滾動是否載入完成
|
||||
const cyTraceRef = ref(null);
|
||||
|
||||
const traceTotal = computed(() => {
|
||||
return traces.value.length;
|
||||
});
|
||||
|
||||
const traceList = computed(() => {
|
||||
const sum = traces.value.map(trace => trace.count).reduce((acc, cur) => acc + cur, 0);
|
||||
const result = traces.value.map(trace => {
|
||||
return {
|
||||
processMap:{
|
||||
nodes:[],
|
||||
edges:[],
|
||||
},
|
||||
showTraceId: null,
|
||||
infinitMaxItems: false,
|
||||
infiniteData: [],
|
||||
infiniteFinish: true, // 無限滾動是否載入完成
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
traceTotal: function() {
|
||||
return this.traces.length;
|
||||
},
|
||||
traceList: function() {
|
||||
const sum = this.traces.map(trace => trace.count).reduce((acc, cur) => acc + cur, 0);
|
||||
const result = this.traces.map(trace => {
|
||||
return {
|
||||
id: trace.id,
|
||||
value: this.progressWidth(Number(((trace.count / sum) * 100).toFixed(1))),
|
||||
count: trace.count.toLocaleString(),
|
||||
base_count: trace.count,
|
||||
ratio: this.getPercentLabel(trace.count / sum),
|
||||
};
|
||||
})
|
||||
return result;
|
||||
},
|
||||
caseData: function() {
|
||||
const data = JSON.parse(JSON.stringify(this.infiniteData)); // 深拷貝原始 cases 的內容
|
||||
data.forEach(item => {
|
||||
item.attributes.forEach((attribute, index) => {
|
||||
item[`att_${index}`] = attribute.value; // 建立新的 key-value pair
|
||||
});
|
||||
delete item.attributes; // 刪除原本的 attributes 屬性
|
||||
})
|
||||
return data;
|
||||
},
|
||||
columnData: function() {
|
||||
const data = JSON.parse(JSON.stringify(this.cases)); // 深拷貝原始 cases 的內容
|
||||
let result = [
|
||||
{ field: 'id', header: 'Case Id' },
|
||||
{ field: 'started_at', header: 'Start time' },
|
||||
{ field: 'completed_at', header: 'End time' },
|
||||
];
|
||||
if(data.length !== 0){
|
||||
result = [
|
||||
{ field: 'id', header: 'Case Id' },
|
||||
{ field: 'started_at', header: 'Start time' },
|
||||
{ field: 'completed_at', header: 'End time' },
|
||||
...(data[0]?.attributes ?? []).map((att, index) => ({ field: `att_${index}`, header: att.key })),
|
||||
];
|
||||
id: trace.id,
|
||||
value: progressWidth(Number(((trace.count / sum) * 100).toFixed(1))),
|
||||
count: trace.count.toLocaleString(),
|
||||
base_count: trace.count,
|
||||
ratio: getPercentLabel(trace.count / sum),
|
||||
};
|
||||
})
|
||||
return result;
|
||||
});
|
||||
|
||||
const caseData = computed(() => {
|
||||
const data = JSON.parse(JSON.stringify(infiniteData.value)); // 深拷貝原始 cases 的內容
|
||||
data.forEach(item => {
|
||||
item.attributes.forEach((attribute, index) => {
|
||||
item[`att_${index}`] = attribute.value; // 建立新的 key-value pair
|
||||
});
|
||||
delete item.attributes; // 刪除原本的 attributes 屬性
|
||||
})
|
||||
return data;
|
||||
});
|
||||
|
||||
const columnData = computed(() => {
|
||||
const data = JSON.parse(JSON.stringify(props.cases)); // 深拷貝原始 cases 的內容
|
||||
let result = [
|
||||
{ field: 'id', header: 'Case Id' },
|
||||
{ field: 'started_at', header: 'Start time' },
|
||||
{ field: 'completed_at', header: 'End time' },
|
||||
];
|
||||
if(data.length !== 0){
|
||||
result = [
|
||||
{ field: 'id', header: 'Case Id' },
|
||||
{ field: 'started_at', header: 'Start time' },
|
||||
{ field: 'completed_at', header: 'End time' },
|
||||
...(data[0]?.attributes ?? []).map((att, index) => ({ field: `att_${index}`, header: att.key })),
|
||||
];
|
||||
}
|
||||
return result
|
||||
});
|
||||
|
||||
watch(infinit404, (newValue) => {
|
||||
if(newValue === 404) infinitMaxItems.value = true;
|
||||
});
|
||||
|
||||
watch(traceId, (newValue) => {
|
||||
showTraceId.value = newValue;
|
||||
}, { immediate: true });
|
||||
|
||||
watch(showTraceId, (newValue, oldValue) => {
|
||||
const isScrollTop = document.querySelector('.infiniteTable');
|
||||
if(isScrollTop && typeof isScrollTop.scrollTop !== 'undefined') if(newValue !== oldValue) isScrollTop.scrollTop = 0;
|
||||
});
|
||||
|
||||
watch(infiniteFirstCases, (newValue) => {
|
||||
if(infiniteFirstCases.value) infiniteData.value = JSON.parse(JSON.stringify(newValue));
|
||||
});
|
||||
|
||||
/**
|
||||
* Number to percentage
|
||||
* @param {number} val 原始數字
|
||||
* @returns {string} 轉換完成的百分比字串
|
||||
*/
|
||||
function getPercentLabel(val){
|
||||
if((val * 100).toFixed(1) >= 100) return `100%`;
|
||||
else return `${(val * 100).toFixed(1)}%`;
|
||||
}
|
||||
|
||||
/**
|
||||
* set progress bar width
|
||||
* @param {number} value 百分比數字
|
||||
* @returns {string} 樣式的寬度設定
|
||||
*/
|
||||
function progressWidth(value){
|
||||
return `width:${value}%;`
|
||||
}
|
||||
|
||||
/**
|
||||
* switch case data
|
||||
* @param {number} id case id
|
||||
* @param {number} count 總 case 數量
|
||||
*/
|
||||
async function switchCaseData(id, count) {
|
||||
// 點同一筆 id 不要有動作
|
||||
if(id == showTraceId.value) return;
|
||||
isLoading.value = true; // 都要 loading 畫面
|
||||
infinit404.value = null;
|
||||
infinitMaxItems.value = false;
|
||||
showTraceId.value = id;
|
||||
infiniteStart.value = 0;
|
||||
emit('switch-Trace-Id', {id: showTraceId.value, count: count}); // 傳遞到 Map index 再關掉 loading
|
||||
}
|
||||
|
||||
/**
|
||||
* 將 trace element nodes 資料彙整
|
||||
*/
|
||||
function setNodesData(){
|
||||
// 避免每次渲染都重複累加
|
||||
processMap.value.nodes = [];
|
||||
// 將 api call 回來的資料帶進 node
|
||||
traceTaskSeq.value.forEach((node, index) => {
|
||||
processMap.value.nodes.push({
|
||||
data: {
|
||||
id: index,
|
||||
label: node,
|
||||
backgroundColor: '#CCE5FF',
|
||||
bordercolor: '#003366',
|
||||
shape: 'round-rectangle',
|
||||
height: 80,
|
||||
width: 100
|
||||
}
|
||||
return result
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
infinite404: function(newValue) {
|
||||
if(newValue === 404) this.infinitMaxItems = true;
|
||||
},
|
||||
traceId: {
|
||||
handler(newValue) {
|
||||
this.showTraceId = newValue;
|
||||
},
|
||||
immediate: true
|
||||
},
|
||||
showTraceId: function(newValue, oldValue) {
|
||||
const isScrollTop = document.querySelector('.infiniteTable');
|
||||
if(isScrollTop && typeof isScrollTop.scrollTop !== 'undefined') if(newValue !== oldValue) isScrollTop.scrollTop = 0;
|
||||
},
|
||||
infiniteFirstCases: function(newValue){
|
||||
if(this.infiniteFirstCases) this.infiniteData = JSON.parse(JSON.stringify(newValue));
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* Number to percentage
|
||||
* @param {number} val 原始數字
|
||||
* @returns {string} 轉換完成的百分比字串
|
||||
*/
|
||||
getPercentLabel(val){
|
||||
if((val * 100).toFixed(1) >= 100) return `100%`;
|
||||
else return `${(val * 100).toFixed(1)}%`;
|
||||
},
|
||||
/**
|
||||
* set progress bar width
|
||||
* @param {number} value 百分比數字
|
||||
* @returns {string} 樣式的寬度設定
|
||||
*/
|
||||
progressWidth(value){
|
||||
return `width:${value}%;`
|
||||
},
|
||||
/**
|
||||
* switch case data
|
||||
* @param {number} id case id
|
||||
* @param {number} count 總 case 數量
|
||||
*/
|
||||
async switchCaseData(id, count) {
|
||||
// 點同一筆 id 不要有動作
|
||||
if(id == this.showTraceId) return;
|
||||
this.isLoading = true; // 都要 loading 畫面
|
||||
this.infinit404 = null;
|
||||
this.infinitMaxItems = false;
|
||||
this.showTraceId = id;
|
||||
this.infiniteStart = 0;
|
||||
this.$emit('switch-Trace-Id', {id: this.showTraceId, count: count}); // 傳遞到 Map index 再關掉 loading
|
||||
},
|
||||
/**
|
||||
* 將 trace element nodes 資料彙整
|
||||
*/
|
||||
setNodesData(){
|
||||
// 避免每次渲染都重複累加
|
||||
this.processMap.nodes = [];
|
||||
// 將 api call 回來的資料帶進 node
|
||||
this.traceTaskSeq.forEach((node, index) => {
|
||||
this.processMap.nodes.push({
|
||||
data: {
|
||||
id: index,
|
||||
label: node,
|
||||
backgroundColor: '#CCE5FF',
|
||||
bordercolor: '#003366',
|
||||
shape: 'round-rectangle',
|
||||
height: 80,
|
||||
width: 100
|
||||
}
|
||||
});
|
||||
})
|
||||
},
|
||||
/**
|
||||
* 將 trace edge line 資料彙整
|
||||
*/
|
||||
setEdgesData(){
|
||||
this.processMap.edges = [];
|
||||
this.traceTaskSeq.forEach((edge, index) => {
|
||||
this.processMap.edges.push({
|
||||
data: {
|
||||
source: `${index}`,
|
||||
target: `${index + 1}`,
|
||||
lineWidth: 1,
|
||||
style: 'solid'
|
||||
}
|
||||
});
|
||||
});
|
||||
// 關係線數量筆節點少一個
|
||||
this.processMap.edges.pop();
|
||||
},
|
||||
/**
|
||||
* create trace cytoscape's map
|
||||
*/
|
||||
createCy(){
|
||||
const graphId = this.$refs.cyTrace;
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
this.setNodesData();
|
||||
this.setEdgesData();
|
||||
cytoscapeMapTrace(this.processMap.nodes, this.processMap.edges, graphId);
|
||||
},
|
||||
/**
|
||||
* create map
|
||||
*/
|
||||
async show() {
|
||||
this.isLoading = await true; // createCy 執行完關閉
|
||||
// 因 trace api 連動,所以關閉側邊欄時讓數值歸 traces 第一筆 id
|
||||
this.showTraceId = await this.traces[0]?.id;
|
||||
this.infiniteStart = await 0;
|
||||
this.setNodesData();
|
||||
this.setEdgesData();
|
||||
this.createCy();
|
||||
this.isLoading = false;
|
||||
},
|
||||
/**
|
||||
* 無限滾動: 監聽 scroll 有沒有滾到底部
|
||||
* @param {element} event 滾動傳入的事件
|
||||
*/
|
||||
handleScroll(event) {
|
||||
if(this.infinitMaxItems || this.cases.length < 20 || this.infiniteFinish === false) return;
|
||||
|
||||
const container = event.target;
|
||||
const overScrollHeight = container.scrollTop + container.clientHeight >= container.scrollHeight;
|
||||
|
||||
if(overScrollHeight) this.fetchData();
|
||||
},
|
||||
/**
|
||||
* 無限滾動: 滾到底後,要載入數據
|
||||
*/
|
||||
async fetchData() {
|
||||
try {
|
||||
this.isLoading = true;
|
||||
this.infiniteFinish = false;
|
||||
this.infiniteStart += 20;
|
||||
await this.allMapDataStore.getTraceDetail();
|
||||
this.infiniteData = await [...this.infiniteData, ...this.cases];
|
||||
this.infiniteFinish = await true;
|
||||
this.isLoading = await false;
|
||||
} catch(error) {
|
||||
console.error('Failed to load data:', error);
|
||||
/**
|
||||
* 將 trace edge line 資料彙整
|
||||
*/
|
||||
function setEdgesData(){
|
||||
processMap.value.edges = [];
|
||||
traceTaskSeq.value.forEach((edge, index) => {
|
||||
processMap.value.edges.push({
|
||||
data: {
|
||||
source: `${index}`,
|
||||
target: `${index + 1}`,
|
||||
lineWidth: 1,
|
||||
style: 'solid'
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
});
|
||||
// 關係線數量筆節點少一個
|
||||
processMap.value.edges.pop();
|
||||
}
|
||||
|
||||
/**
|
||||
* create trace cytoscape's map
|
||||
*/
|
||||
function createCy(){
|
||||
const graphId = cyTraceRef.value;
|
||||
|
||||
setNodesData();
|
||||
setEdgesData();
|
||||
cytoscapeMapTrace(processMap.value.nodes, processMap.value.edges, graphId);
|
||||
}
|
||||
|
||||
/**
|
||||
* create map
|
||||
*/
|
||||
async function show() {
|
||||
isLoading.value = await true; // createCy 執行完關閉
|
||||
// 因 trace api 連動,所以關閉側邊欄時讓數值歸 traces 第一筆 id
|
||||
showTraceId.value = await traces.value[0]?.id;
|
||||
infiniteStart.value = await 0;
|
||||
setNodesData();
|
||||
setEdgesData();
|
||||
createCy();
|
||||
isLoading.value = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 無限滾動: 監聽 scroll 有沒有滾到底部
|
||||
* @param {element} event 滾動傳入的事件
|
||||
*/
|
||||
function handleScroll(event) {
|
||||
if(infinitMaxItems.value || props.cases.length < 20 || infiniteFinish.value === false) return;
|
||||
|
||||
const container = event.target;
|
||||
const overScrollHeight = container.scrollTop + container.clientHeight >= container.scrollHeight;
|
||||
|
||||
if(overScrollHeight) fetchData();
|
||||
}
|
||||
|
||||
/**
|
||||
* 無限滾動: 滾到底後,要載入數據
|
||||
*/
|
||||
async function fetchData() {
|
||||
try {
|
||||
isLoading.value = true;
|
||||
infiniteFinish.value = false;
|
||||
infiniteStart.value += 20;
|
||||
await allMapDataStore.getTraceDetail();
|
||||
infiniteData.value = await [...infiniteData.value, ...props.cases];
|
||||
infiniteFinish.value = await true;
|
||||
isLoading.value = await false;
|
||||
} catch(error) {
|
||||
console.error('Failed to load data:', error);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user