消消方块阵换皮表情
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

896 lines
31 KiB

// 由于Cocos Creator插件环境特殊,使用动态require方式
import * as fs from 'fs';
import * as path from 'path';
import * as os from 'os';
import * as XLSX from 'xlsx';
import * as chokidar from 'chokidar'; // 补充导入chokidar模块
// 定义类型以提高代码可读性
interface PluginConfig {
excelPath: string;
jsonExportPath: string;
tsExportPath: string;
autoExport: boolean;
watcher: any;
}
class ExcelToJsonPlugin {
private config: PluginConfig = {
excelPath: './配置表/excelList',
jsonExportPath: './assets/bundles/first/config',
tsExportPath: './assets/script/interface',
autoExport: true,
watcher: null
};
private fileWatcher: fs.FSWatcher | null = null;
async load() {
// 先执行一次导出
await this.exportAllExcel();
// 设置文件监听
this.setupFileWatcher();
// // 注册消息处理
// Editor.Message.addBroadcastListener('excel-to-json-ts:profile-updated', () => {
// this.loadConfig();
// // 如果配置了自动导出,重新设置监听
// if (this.config.autoExport) {
// this.stopFileWatcher();
// this.setupFileWatcher();
// this.exportAllExcel(); // 配置保存后立即导出一次
// } else {
// this.stopFileWatcher();
// }
// });
}
unload() {
this.log('[Excel插件] 插件已卸载');
this.stopFileWatcher();
}
// 加载配置
async loadConfig() {
try {
let conf = await Editor.Profile.getProject('excel-to-json-ts');
if (conf)
this.config = conf;
else {
Editor.Profile.setProject('excel-to-json-ts', "excelPath", this.config.excelPath);
Editor.Profile.setProject('excel-to-json-ts', "jsonExportPath", this.config.jsonExportPath);
Editor.Profile.setProject('excel-to-json-ts', "tsExportPath", this.config.tsExportPath);
this.warn(`[Excel插件] 路径配置不正确`);
}
this.log('[Excel插件] 配置已加载');
this.log('[Excel插件] excelPath' + this.config.excelPath);
this.log('[Excel插件] jsonPath' + this.config.jsonExportPath);
this.log('[Excel插件] tsPath' + this.config.tsExportPath);
} catch (err) {
this.error(`[Excel插件] 加载配置失败: ${err}`);
}
}
// 手动导出
exportManual() {
this.exportAllExcel();
}
// 导出所有Excel(增强健壮性)
async exportAllExcel() {
await this.loadConfig();
if (!this.config.excelPath) {
this.error('[Excel插件] Excel目录路径未配置');
return;
}
if (!fs.existsSync(this.config.excelPath)) {
this.error(`[Excel插件] Excel目录不存在: ${this.config.excelPath}`);
return;
}
try {
// 确保导出目录存在
if (this.config.jsonExportPath && !fs.existsSync(this.config.jsonExportPath)) {
fs.mkdirSync(this.config.jsonExportPath, { recursive: true });
}
if (this.config.tsExportPath && !fs.existsSync(this.config.tsExportPath)) {
fs.mkdirSync(this.config.tsExportPath, { recursive: true });
}
// 获取目录中所有Excel文件
const excelFiles = this.getExcelFilesInDirectory(this.config.excelPath);
if (excelFiles.length === 0) {
this.warn(`[Excel插件] 目录中没有找到Excel文件: ${this.config.excelPath}`);
return;
}
this.log(`[Excel插件] 找到 ${excelFiles.length} 个Excel文件`);
let successCount = 0;
let errorCount = 0;
// 处理每个Excel文件
for (const excelFile of excelFiles) {
try {
this.processExcelFile(excelFile);
successCount++;
} catch (err) {
this.error(`[Excel插件] 处理文件失败 ${path.basename(excelFile)}: ${err}`);
errorCount++;
}
}
if (successCount > 0) {
this.refreshAssetDatabase();
this.success(`[Excel插件] 导出完成,成功处理 ${successCount} 个文件,失败 ${errorCount}`);
} else {
this.error(`[Excel插件] 导出失败,没有成功处理任何文件`);
}
} catch (err) {
this.error(`[Excel插件] 导出失败: ${err}`);
}
}
// 获取目录中的所有Excel文件
getExcelFilesInDirectory(dirPath: string): string[] {
return fs.readdirSync(dirPath)
.filter(file => {
// 过滤隐藏文件(以点开头的文件)
if (file.startsWith('.') || file.startsWith('~')) {
return false;
}
const ext = path.extname(file).toLowerCase();
return (ext === '.xlsx' || ext === '.xls') &&
fs.statSync(path.join(dirPath, file)).isFile();
})
.map(file => path.join(dirPath, file));
}
// 处理单个Excel文件
processExcelFile(filePath: string) {
try {
// 安全读取打开状态的Excel文件
const workbook = this.readExcelSafely(filePath);
const sheetNames = workbook.SheetNames;
// 导出每个工作表
for (const sheetName of sheetNames) {
if (sheetName.match("Sheet") || !sheetName.match("|")) {
continue;
}
this.exportSheet(workbook, sheetName, filePath);
}
} catch (err) {
throw new Error(`处理文件 ${path.basename(filePath)} 失败: ${err}`);
}
}
// 安全读取打开状态的Excel文件(终极版)
readExcelSafely(filePath: string): any {
const tempPath = path.join(os.tmpdir(), `excel-temp-${Date.now()}-${path.basename(filePath)}`);
try {
// 详细记录文件状态
const stats = fs.statSync(filePath);
// this.log(`[Excel插件] 尝试读取文件 - 大小: ${stats.size} bytes, 最后修改: ${new Date(stats.mtimeMs).toLocaleTimeString()}`);
// 尝试复制文件,带详细错误处理
try {
fs.copyFileSync(filePath, tempPath);
} catch (copyErr) {
this.warn(`[Excel插件] 第一次复制失败: ${copyErr}`);
// 再次尝试,有时第一次会失败
try {
fs.copyFileSync(filePath, tempPath);
} catch (finalErr) {
this.error(`[Excel插件] 两次复制尝试均失败: ${finalErr}`);
throw finalErr;
}
}
// 验证临时文件
const tempStats = fs.statSync(tempPath);
if (tempStats.size === 0) {
throw new Error("临时文件大小为0,复制可能未完成");
}
return XLSX.readFile(tempPath);
} catch (err: any) {
throw new Error(`读取Excel失败: ${err.message || err}`);
} finally {
// 确保清理临时文件
try {
if (fs.existsSync(tempPath)) {
fs.unlinkSync(tempPath);
}
} catch (e) {
this.warn(`[Excel插件] 无法删除临时文件: ${tempPath} - ${e}`);
}
}
}
// 导出单个工作表
exportSheet(workbook: any, sheetName: string, filePath: string) {
try {
const worksheet = workbook.Sheets[sheetName];
const jsonData = XLSX.utils.sheet_to_json(worksheet, { header: 1 });
// 检查是否有足够的表头行(至少3行:字段名、类型、说明)
if (jsonData.length < 3) {
this.warn(`[Excel插件] 工作表 "${sheetName}" 数据不足,跳过导出 (文件: ${path.basename(filePath)})的${sheetName}`);
return;
}
if (!sheetName.match("|") || sheetName.split("|").length < 2) {
this.warn(`[Excel插件] 工作表 "${sheetName}" 名称格式错误,跳过导出 (文件: ${path.basename(filePath)})的${sheetName}`);
return;
}
const sName = sheetName.split("|")[1];
// 提取表头信息
const headers = {
fields: jsonData[0],
types: jsonData[1],
descriptions: jsonData[2]
};
// 从第4行开始是数据
const dataRows = jsonData.slice(3) as any[][];
// 生成JSON数据
const jsonResult = this.generateJsonData(dataRows, headers);
// 生成TS接口
const tsInterface = this.generateTSInterface(sName, headers);
// 生成文件名
const safeSheetName = this.makeSafeFileName(sName);
const jsonFileName = `${safeSheetName}.json`;
//const tsFileName = `ITable${safeSheetName}.ts`;
const tsFileName = `AutoTable.ts`;
// 写入JSON文件
if (this.config.jsonExportPath) {
const jsonPath = path.join(this.config.jsonExportPath, jsonFileName);
fs.writeFileSync(jsonPath, JSON.stringify(jsonResult, null, 4), 'utf-8');
}
// 写入TS文件
if (this.config.tsExportPath) {
const tsPath = path.join(this.config.tsExportPath, tsFileName);
let tsContent = "";
try {
if (fs.existsSync(tsPath)) {
tsContent = fs.readFileSync(tsPath, 'utf-8');
}
} catch (err) {
this.warn(`[Excel插件] 读取TS文件失败: ${err}`);
}
if (tsContent == "") {
tsContent = "declare global {\n\n}\nexport { };";
}
if (this.extractFullInterfaceBlock(tsContent, "ITable" + sName)) {
//console.log("替换接口:" + sName + ",内容:" + tsInterface);
tsContent = this.replaceInterfaceBlock(tsContent, "ITable" + sName, tsInterface);
} else {
//console.log("加到末尾:" + tsInterface);
tsContent = tsContent.replace(/}\nexport { };$/, tsInterface + "}\nexport { };");
}
fs.writeFileSync(tsPath, tsContent, 'utf-8');
}
} catch (err) {
throw new Error(`工作表 "${sheetName}" 导出失败 (文件: ${path.basename(filePath)}): ${err}`);
}
}
/**
* 转义正则特殊字符
*/
escapeRegExp(str: string): string {
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}
/**
* 精准查找接口声明行(全字匹配)
*/
findInterfaceDeclarationLine(fileContent: string, interfaceName: string): {
lineStart: number;
lineEnd: number;
lineContent: string;
} | null {
// 使用单词边界 \b 确保全字匹配
const regex = new RegExp(
`\\binterface\\s+${this.escapeRegExp(interfaceName)}\\b(?:\\s*<[^>]*>)?\\s*[\\{\\n]`,
'g'
);
let match;
while ((match = regex.exec(fileContent)) !== null) {
// 找到匹配位置,确定完整行
const matchStart = match.index;
// 向前找到行开始
const lineStart = fileContent.lastIndexOf('\n', matchStart - 1) + 1;
// 向后找到行结束
const lineEnd = fileContent.indexOf('\n', matchStart);
const actualLineEnd = lineEnd === -1 ? fileContent.length : lineEnd;
const lineContent = fileContent.substring(lineStart, actualLineEnd);
// 验证:确保这一行确实是我们的目标接口
if (lineContent.trim().startsWith(`interface ${interfaceName}`)) {
return {
lineStart,
lineEnd: actualLineEnd,
lineContent
};
}
}
return null;
}
/**
* 提取完整的接口块(包括上方注释)
*/
extractFullInterfaceBlock(fileContent: string, interfaceName: string): {
start: number;
end: number;
content: string;
} | null {
const interfaceLine = this.findInterfaceDeclarationLine(fileContent, interfaceName);
if (!interfaceLine) {
return null;
}
// === 向前查找连续的注释块 ===
let blockStart = interfaceLine.lineStart;
let currentPos = interfaceLine.lineStart;
while (currentPos > 0) {
const prevLineEnd = currentPos - 1;
const prevLineStart = fileContent.lastIndexOf('\n', prevLineEnd - 1) + 1;
const prevLine = fileContent.substring(prevLineStart, prevLineEnd).trim();
// 检查是否为注释行或空行
if (prevLine === '' ||
prevLine.startsWith('//') ||
prevLine.startsWith('/*') ||
prevLine.startsWith('*')) {
currentPos = prevLineStart;
} else {
blockStart = currentPos;
break;
}
}
if (currentPos === 0) {
blockStart = 0;
}
// === 向后查找接口体结束 ===
// 找到 interface 行中的开括号位置
const openBraceIndex = interfaceLine.lineContent.indexOf('{');
let startPos = interfaceLine.lineStart;
let braceCount = 0;
if (openBraceIndex !== -1) {
// 开括号在同一行
startPos = interfaceLine.lineStart + openBraceIndex;
braceCount = 1;
} else {
// 开括号在下一行,找到第一个 {
const nextOpenBrace = fileContent.indexOf('{', interfaceLine.lineEnd);
if (nextOpenBrace === -1) return null;
startPos = nextOpenBrace;
braceCount = 1;
}
let pos = startPos + 1; // 跳过 {
while (pos < fileContent.length && braceCount > 0) {
if (fileContent[pos] === '{') {
braceCount++;
} else if (fileContent[pos] === '}') {
braceCount--;
}
pos++;
}
if (braceCount !== 0) {
return null; // 括号不匹配
}
const blockEnd = pos;
const fullContent = fileContent.substring(blockStart, blockEnd);
return {
start: blockStart,
end: blockEnd,
content: fullContent
};
}
/**
* 完全替换接口块(全字匹配,精准替换)
*/
replaceInterfaceBlock(
fileContent: string,
interfaceName: string,
newInterfaceBlock: string
): string {
const blockInfo = this.extractFullInterfaceBlock(fileContent, interfaceName);
if (!blockInfo) {
throw new Error(`❌ 接口 "${interfaceName}" 未找到!请检查名称是否正确。`);
}
// 验证:确保提取的块确实只包含目标接口
const interfaceLines = blockInfo.content.split('\n');
const interfaceDeclLines = interfaceLines.filter(line =>
line.trim().startsWith('interface ')
);
if (interfaceDeclLines.length !== 1) {
throw new Error(`❌ 找到多个 interface 声明,可能匹配错误!`);
}
if (!interfaceDeclLines[0].includes(`interface ${interfaceName}`)) {
throw new Error(`❌ 匹配到错误的接口名称!`);
}
//console.log(`✅ 精准匹配到接口 "${interfaceName}"`);
return (
fileContent.substring(0, blockInfo.start) +
newInterfaceBlock +
fileContent.substring(blockInfo.end)
);
}
// 设置文件监听(终极版)
setupFileWatcher() {
this.stopFileWatcher();
if (!this.config.excelPath) return;
if (!fs.existsSync(this.config.excelPath)) {
this.error(`[Excel插件] 监听目录不存在: ${this.config.excelPath}`);
return;
}
try {
// 使用更可靠的监听方式
this.fileWatcher = fs.watch(this.config.excelPath, {
recursive: false,
encoding: 'utf8'
}, async (eventType, filename) => {
if (!filename) return;
this.loadConfig();
if (this.config.autoExport == false) return;
// 确保是字符串
const fileNameStr = typeof filename === 'string' ? filename : filename + "";
const ext = path.extname(fileNameStr).toLowerCase();
if (ext === '.xlsx' || ext === '.xls') {
const filePath = path.join(this.config.excelPath, fileNameStr);
// 额外验证:确保是文件且存在
try {
if (fs.existsSync(filePath) && fs.statSync(filePath).isFile()) {
this.log(`[Excel插件] 检测到文件变更: ${fileNameStr} (${eventType})`);
// 使用终极重试机制
// 重试次数增加到8,基础延迟1秒
this.processExcelFileWithRetry(filePath, 8, 1000);
}
} catch (err) {
this.warn(`[Excel插件] 验证文件状态时出错: ${err}`);
}
}
});
// 处理监听错误
if (this.fileWatcher && typeof this.fileWatcher.on === 'function') {
this.fileWatcher.on('error', (error: Error) => {
this.error(`[Excel插件] 文件监听错误: ${error}`);
// 尝试重新设置监听
setTimeout(() => {
this.stopFileWatcher();
this.setupFileWatcher();
}, 15000);
});
}
this.log('[Excel插件] 已启动Excel目录监听(终极版)');
} catch (err) {
this.error(`[Excel插件] 目录监听失败: ${err}`);
// 尝试重新设置监听
setTimeout(() => {
this.stopFileWatcher();
this.setupFileWatcher();
}, 20000);
}
}
// 带终极重试机制处理Excel文件
processExcelFileWithRetry(filePath: string, maxRetries: number = 8, baseDelay: number = 1000) {
let attempt = 0;
const startTime = Date.now();
const tryProcess = () => {
attempt++;
const elapsed = Date.now() - startTime;
const delay = baseDelay * Math.min(1.5 ** (attempt - 1), 4); // 指数退避,但上限为4倍基础延迟
this.log(`[Excel插件] 处理文件 "${path.basename(filePath)}" - 尝试 ${attempt}/${maxRetries} (延迟 ${Math.round(delay)}ms, 已耗时 ${Math.round(elapsed / 1000)}s)`);
setTimeout(() => {
try {
// 额外检查文件是否可读
const stats = fs.statSync(filePath);
if (stats.size === 0) {
throw new Error("文件大小为0,可能仍在写入");
}
// 检查文件最后修改时间,确保不是刚被修改
const now = Date.now();
const fileAge = now - stats.mtimeMs;
if (fileAge < 2000) { // 文件修改时间少于2秒,可能还在写入
throw new Error(`文件刚被修改 (${Math.round(fileAge)}ms前),可能仍在写入`);
}
this.processExcelFile(filePath);
const totalTime = Date.now() - startTime;
this.refreshAssetDatabase();
this.success(`[Excel插件] 文件 "${path.basename(filePath)}" 自动导出成功 (总耗时 ${Math.round(totalTime / 1000)}s, 尝试 ${attempt}/${maxRetries})`);
} catch (err: any) {
// 详细记录错误
this.warn(`[Excel插件] 尝试 ${attempt} 失败: ${err.message || err}`);
// 检查是否是文件锁定或权限错误
const isFileLockError = err.message && (
err.message.includes('EPERM') ||
err.message.includes('EBUSY') ||
err.message.includes('permission') ||
err.message.includes('locked') ||
err.message.includes('EACCES') ||
err.message.includes('file size 0') ||
err.message.includes('刚被修改')
);
if (isFileLockError && attempt < maxRetries) {
// 计算下一次尝试的延迟(指数退避)
const nextDelay = baseDelay * Math.min(1.5 ** attempt, 4);
this.warn(`[Excel插件] 文件 "${path.basename(filePath)}" 可能仍在保存中,将在 ${Math.round(nextDelay)}ms 后重试 (尝试 ${attempt + 1}/${maxRetries})`);
setTimeout(tryProcess, nextDelay);
} else {
const totalTime = Date.now() - startTime;
this.error(`[Excel插件] 文件 "${path.basename(filePath)}" 自动导出失败 (总耗时 ${Math.round(totalTime / 1000)}s): ${err}`);
// 如果是文件锁定错误但已达到最大重试次数,提供具体建议
if (isFileLockError) {
this.error(`[Excel插件] 建议: 确保Excel完全保存后再进行修改,或增加重试次数和延迟`);
}
}
}
}, delay);
};
// 开始第一次尝试
tryProcess();
}
// 停止文件监听(更新)
stopFileWatcher() {
if (this.fileWatcher) {
try {
// 使用类型断言确保调用close方法
(this.fileWatcher as any).close();
this.fileWatcher = null;
} catch (e) {
this.warn(`[Excel插件] 停止监听时出错: ${e}`);
}
if (this.config.watcher) {
clearTimeout(this.config.watcher);
this.config.watcher = null;
}
this.log('[Excel插件] 已停止Excel文件监听');
}
}
// 生成JSON数据
generateJsonData(dataRows: any[][], headers: any): any[] {
const result = [];
for (const row of dataRows) {
const obj: any = {};
for (let i = 0; i < headers.fields.length; i++) {
const field = headers.fields[i];
const type = headers.types[i];
if (field) {
// 处理空单元格,使用对应类型的默认值
const value = row[i] !== undefined ? row[i] : this.getDefaultValue(type);
obj[field] = this.convertValue(value, type);
}
}
result.push(obj);
}
return result;
}
// 根据类型获取默认值
getDefaultValue(type: string): any {
if (!type) return null;
const cleanType = type.toLowerCase().trim();
switch (cleanType) {
case 'int':
case 'float':
return 0;
case 'boolean':
return false;
case 'int[]':
case 'float[]':
case 'string[]':
case 'any[]':
return [];
case 'string':
return '';
default:
return '';
}
}
// 类型转换
convertValue(value: any, type: string): any {
if (value === null || value === undefined) return null;
const cleanType = type.toLowerCase().trim();
try {
switch (cleanType) {
case 'int':
return parseInt(value);
case 'float':
return parseFloat(value);
case 'json':
return JSON.parse(value);
case 'boolean':
return value.toString().toLowerCase() === 'true' || value === 1;
case 'int[]':
return this.parseArray(value, 'int');
case 'float[]':
return this.parseArray(value, 'float');
case 'string[]':
return this.parseArray(value, 'string');
case 'any[]':
return this.parseArray(value, 'any');
default:
return value;
}
} catch (e) {
this.warn(`[Excel插件] 类型转换失败 [${type}]: ${value} - ${e}`);
return value;
}
}
// 解析数组
parseArray(value: any, targetType: string): any[] {
if (Array.isArray(value)) return value;
// 尝试分割字符串
let items = [];
if (typeof value === 'string') {
items = value.split(/[,;#\s]\s*/).filter(v => v);
} else {
items = [value];
}
return items.map(item => {
if (targetType === 'any') {
// 自动识别类型
if (!isNaN(item)) {
return !isNaN(parseFloat(item)) ? parseFloat(item) : parseInt(item);
}
if (item.toLowerCase() === 'true' || item.toLowerCase() === 'false') {
return item.toLowerCase() === 'true';
}
return item;
}
// 指定类型转换
switch (targetType) {
case 'int': return parseInt(item);
case 'float': return parseFloat(item);
case 'string': return String(item);
default: return item;
}
});
}
generateTSInterfaceStart(sheetName: string): string {
const interfaceName = `ITable${sheetName}`;
const safeSheetName = `${this.makeSafeFileName(sheetName)}`;
let tsContent = ` /**\n * ${safeSheetName} 配置数据\n */\n`;
tsContent += ` interface ${interfaceName} {\n`;
return tsContent;
}
// 生成TS接口
generateTSInterface(sheetName: string, headers: any): string {
const interfaceName = `ITable${sheetName}`;
const safeSheetName = `${this.makeSafeFileName(sheetName)}`;
let tsContent = ` /**\n * ${safeSheetName} 配置数据\n */\n`;
tsContent += ` interface ${interfaceName} {\n`;
for (let i = 0; i < headers.fields.length; i++) {
const field = headers.fields[i];
const type = headers.types[i];
const description = headers.descriptions[i] || '字段说明';
if (field) {
tsContent += ` /** ${description} */\n`;
tsContent += ` readonly ${field}: ${this.convertToTSType(type)};\n`;
}
}
tsContent += ` }\n`;
return tsContent;
}
// 转换为TS类型
convertToTSType(excelType: string): string {
if (!excelType) return 'any';
const type = excelType.toLowerCase().trim();
switch (type) {
case 'string':
return 'string';
case 'int':
case 'float':
return 'number';
case 'boolean':
return 'boolean';
case 'int[]':
return 'number[]';
case 'float[]':
return 'number[]';
case 'string[]':
return 'string[]';
case 'any[]':
return '(number | string | boolean)[]';
default:
return 'any';
}
}
// 转换为PascalCase
toPascalCase(str: string): string {
return str.replace(/(\w)(\w*)/g, (_, g1, g2) =>
g1.toUpperCase() + g2.toLowerCase()
).replace(/[^a-zA-Z0-9]/g, '');
}
// 创建安全的文件名
makeSafeFileName(name: string): string {
return name.replace(/[^a-zA-Z0-9_]/g, '_');
}
// 日志输出
log(message: string) {
console.log(message);
}
error(message: string) {
console.error(message);
}
warn(message: string) {
console.warn(message);
}
success(message: string) {
console.log(message);
}
// 消息处理
messages = {
'excel-to-json-ts:export-manual': () => {
this.exportManual();
}
}
// 刷新资源管理器
async refreshAssetDatabase() {
try {
// 确保路径是项目内的路径(db://assets/开头)
const jsonDbPath = this.convertToDbPath(this.config.jsonExportPath);
const tsDbPath = this.convertToDbPath(this.config.tsExportPath);
// 刷新JSON导出目录
if (jsonDbPath && jsonDbPath.startsWith('db://')) {
await Editor.Message.request('asset-db', 'refresh-asset', jsonDbPath);
}
// 刷新TS导出目录
if (tsDbPath && tsDbPath.startsWith('db://')) {
await Editor.Message.request('asset-db', 'refresh-asset', tsDbPath);
}
this.success('[Excel插件] 资源刷新请求已发送');
} catch (err) {
this.error(`[Excel插件] 资源刷新失败: ${err}`);
}
}
// 将系统路径转换为Cocos的db路径
convertToDbPath(systemPath: string): string {
if (!systemPath) return '';
try {
// 获取项目路径
const projectPath = Editor.Project.path;
// 规范化路径(统一使用正斜杠)
systemPath = systemPath.replace(/\\/g, '/');
const normalizedProjectPath = projectPath.replace(/\\/g, '/');
// 确保路径以/结尾
const projectPathWithSlash = normalizedProjectPath.endsWith('/')
? normalizedProjectPath
: normalizedProjectPath + '/';
// 检查是否在项目目录内
if (systemPath.startsWith(projectPathWithSlash)) {
// 计算相对路径
const relativePath = systemPath.substring(projectPathWithSlash.length);
return `db://${relativePath}`;
}
// 如果路径包含assets目录,尝试直接转换
if (systemPath.includes('/assets/') || systemPath.includes('\\assets\\')) {
const assetsIndex = systemPath.toLowerCase().indexOf('/assets/');
if (assetsIndex !== -1) {
const relativeToAssets = systemPath.substring(assetsIndex + 1);
return `db://${relativeToAssets}`;
}
}
this.warn(`[Excel插件] 路径不在项目内,可能无法正确刷新: ${systemPath}`);
return systemPath;
} catch (err) {
this.error(`[Excel插件] 路径转换失败: ${err}`);
return systemPath;
}
}
}
export const excelToJsonPlugin = new ExcelToJsonPlugin();