mirror of
https://github.com/fofolee/uTools-quickcommand.git
synced 2025-06-18 12:36:58 +08:00
285 lines
8.2 KiB
Vue
285 lines
8.2 KiB
Vue
<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>
|