285 lines
8.2 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<q-btn-group :stretch="stretch" class="text-primary result-menu">
<q-btn
icon="image"
label="转图片"
@click="dataUrlToImg"
v-show="imagebtn"
dense
size="sm"
></q-btn>
<q-btn
icon="data_object"
@click="updateStringifyResult"
v-show="textbtn && hasObject"
:label="!dense ? '格式化' : ''"
dense
size="sm"
>
<q-tooltip v-if="!dense">格式化JSON</q-tooltip>
</q-btn>
<q-btn
icon="pivot_table_chart"
@click="updateTableResult"
v-show="textbtn && hasTable"
:label="!dense ? '转表格' : ''"
dense
size="sm"
>
</q-btn>
<q-btn
icon="content_paste"
@click="copyResult"
v-show="textbtn"
dense
size="sm"
><q-tooltip v-if="!dense">复制到剪贴板</q-tooltip></q-btn
>
<q-btn icon="send" size="sm" @click="sendResult" v-show="textbtn" dense
><q-tooltip v-if="!dense">发送到活动窗口</q-tooltip></q-btn
>
<q-btn icon="save" v-if="!dense" size="sm" @click="saveResult" dense
><q-tooltip>保存到文件</q-tooltip></q-btn
>
<q-btn icon="close" v-close-popup v-show="closebtn" dense size="sm" />
</q-btn-group>
</template>
<script>
export default {
props: {
dense: Boolean,
stretch: Boolean,
textbtn: Boolean,
imagebtn: Boolean,
closebtn: Boolean,
runResult: Array,
selectText: String,
},
emits: ["updateResult", "showImg"],
computed: {
/**
* 处理运行结果数据,同时完成以下任务:
* 1. 查找符合表格显示条件的数组数据(数组元素都是对象)
* 2. 格式化所有数据项对象转JSON处理null等特殊情况
*
* @returns {Object} 处理后的数据对象
* - items: 格式化后的所有数据项数组,每项包含:
* - raw: 原始数据
* - formatted: 格式化后的字符串
* - tableData: 找到的第一个可转换为表格的数组如果没有则为null
*/
processedData() {
// 空数据处理
if (!this.runResult) return { items: [], tableData: null };
let tableData = null;
const items = this.runResult.map((item, index) => {
// 1. 表格数据检测
// 如果还没找到表格数据,且当前项是非空数组,则检查是否可以作为表格数据
if (!tableData && Array.isArray(item) && item.length > 0) {
// 检查数组的每个元素是否都是非null的普通对象非数组
const isValidTable = item.every(
(elem) =>
typeof elem === "object" && elem !== null && !Array.isArray(elem)
);
// 如果是有效的表格数据,保存起来
if (isValidTable) {
tableData = item;
}
}
// 2. 数据格式化
// 2.1 处理非对象类型:直接返回原值
if (typeof item !== "object") {
return { raw: item, formatted: item };
}
// 2.2 处理 null转换为字符串 "null"
if (item === null) {
return { raw: null, formatted: "null" };
}
// 2.3 处理对象类型
try {
// 尝试将对象转换为格式化的JSON字符串
return {
raw: item,
formatted: JSON.stringify(item, null, 2),
};
} catch (error) {
// 如果JSON转换失败则使用toString()方法
return {
raw: item,
formatted: item.toString(),
};
}
});
// 返回处理后的数据
return { items, tableData };
},
formattedItems() {
return this.processedData.items;
},
validTableData() {
return this.processedData.tableData;
},
hasObject() {
return this.formattedItems.some(
(item) => typeof item.raw === "object" && item.raw !== null
);
},
hasTable() {
return this.validTableData !== null;
},
},
methods: {
copyResult() {
window.utools.copyText(this.selectText || this.getFormattedContent());
quickcommand.showMessageBox("已复制到剪贴板");
},
sendResult() {
window.utools.hideMainWindowTypeString(
this.selectText || this.getFormattedContent()
);
},
dataUrlToImg() {
const imagePattern = /data:image\/.*?;base64,.*/g;
const imageUrls = this.getFormattedContent()
.match(imagePattern)
?.map(
(dataUrl) => `<img src="${dataUrl}" style="max-width: 100%;"><br>`
);
if (!imageUrls) {
return quickcommand.showMessageBox("dataUrl 格式不正确!");
}
this.$emit("showImg", imageUrls);
},
saveResult() {
const saveOptions = {
defaultPath: "quickcommand-result.txt",
filters: [{ name: "txt", extensions: ["txt"] }],
};
window.saveFile(this.getFormattedContent(), saveOptions);
},
getFormattedContent() {
return this.formattedItems.map((item) => item.formatted).join("\n");
},
updateStringifyResult() {
this.updateResult([this.getFormattedContent()]);
},
updateTableResult() {
const tableData = this.validTableData;
if (!tableData) return;
const headers = [
...new Set(tableData.flatMap((obj) => Object.keys(obj))),
];
if (!headers.length) return;
// 计算字符串显示宽度的辅助函数
const getDisplayWidth = (str) => {
return String(str)
.split("")
.reduce((width, char) => {
// 判断是否为全角字符
// 包括:中文字符、全角标点符号、全角空格、日韩字符等
return (
width +
(/[\u2E80-\u2FFF\u3000-\u303F\u3040-\u309F\u30A0-\u30FF\u3100-\u312F\u3200-\u32FF\u3400-\u4DBF\u4E00-\u9FFF\uF900-\uFAFF\uFE30-\uFE4F\uFF00-\uFFEF]/.test(
char
)
? 2
: 1)
);
}, 0);
};
const formatValue = (value) => {
const liteView = (obj) => {
if (Array.isArray(obj)) {
return `[${obj.slice(0, 3).join(",")} ${
obj.length > 3 ? "..." : ""
}]`;
}
const keys = Object.keys(obj);
return `{${keys.slice(0, 3).join(",")} ${
keys.length > 3 ? "..." : ""
}}`;
};
if (value == null) return "";
switch (typeof value) {
case "string":
return value.replace(/\n/g, "\\n");
case "object":
return liteView(value);
default:
return String(value);
}
};
// 计算每列的最大宽度
const columnWidths = headers.map((header) => {
const maxDataWidth = Math.max(
getDisplayWidth(header),
...tableData.map((obj) => {
const value = obj[header];
return value === undefined || value === null
? 0
: getDisplayWidth(formatValue(value));
})
);
return maxDataWidth;
});
// 生成对齐的行
const createAlignedRow = (cells) =>
"| " +
cells
.map((cell, index) => {
const cellStr = String(cell);
const displayWidth = getDisplayWidth(cellStr);
const padding = " ".repeat(
Math.max(0, columnWidths[index] - displayWidth)
);
return cellStr + padding;
})
.join(" | ") +
" |";
const rows = [
// 表头行
createAlignedRow(headers),
// 分隔行
"| " +
headers.map((_, i) => "-".repeat(columnWidths[i])).join(" | ") +
" |",
// 数据行
...tableData.map((obj) =>
createAlignedRow(
headers.map((header) => {
const value = obj[header];
return value === undefined || value === null
? ""
: formatValue(value);
})
)
),
];
this.updateResult([rows.join("\n")]);
},
updateResult(result) {
this.$emit("updateResult", result);
},
},
};
</script>
<style scoped>
.result-menu {
gap: 4px;
}
</style>