diff --git a/miniapp/README.md b/miniapp/README.md index 99f3dc3..4c3ec1d 100644 --- a/miniapp/README.md +++ b/miniapp/README.md @@ -3,6 +3,7 @@ 当前目录已从骨架升级为可用版,支持以下能力: - 顶部 Logo / 主题 / 上传 / 导出区域 +- 启动默认加载:`data/sankey.xlsx`(位于小程序包内 `miniapp/data/sankey.xlsx`) - 文件上传与解析:`csv / xls / xlsx` - 默认列映射: - 源数据列优先匹配 `data/value/数据/值` @@ -18,4 +19,5 @@ 注意事项: - `xlsx` 解析依赖 npm 包,若在微信开发者工具中提示模块缺失,请先执行“工具 -> 构建 npm”。 +- 当默认文件或上传文件未加载成功时,“数据选择”仅显示空状态提示,不展示占位列名。 - SVG 文件导出后是否可直接预览,取决于当前系统与微信版本对 SVG 文档的支持。 diff --git a/miniapp/data/sankey.xlsx b/miniapp/data/sankey.xlsx new file mode 100644 index 0000000..f76dbaa Binary files /dev/null and b/miniapp/data/sankey.xlsx differ diff --git a/miniapp/pages/index/index.js b/miniapp/pages/index/index.js index 492d8f1..b48dac4 100644 --- a/miniapp/pages/index/index.js +++ b/miniapp/pages/index/index.js @@ -129,6 +129,8 @@ const TARGET_ALIGN_OPTIONS = [ { value: 'top', label: '顶部' }, { value: 'bottom', label: '底部' } ]; +const DEFAULT_SANKEY_FILE_NAME = 'data/sankey.xlsx'; +const DEFAULT_SANKEY_FILE_PATHS = ['/data/sankey.xlsx', 'data/sankey.xlsx']; /** * 数值限制,避免 UI 参数导致布局异常。 @@ -141,6 +143,32 @@ function clampNumber(value, min, max, fallback) { return Math.min(max, Math.max(min, normalized)); } +/** + * 统一错误文案: + * - xlsx 解析能力缺失时,固定提示用户去“构建 npm” + * - 其他错误返回原始 message,便于定位 + */ +function toFriendlyParseError(error, fallbackMessage) { + const message = error && error.message ? String(error.message) : ''; + if (message.indexOf('xlsx 解析') >= 0) { + return '当前环境未启用 xlsx 解析,请先在开发者工具执行“构建 npm”'; + } + return message || fallbackMessage; +} + +/** + * 兼容 onWindowResize 不同回调结构,提取 windowHeight。 + */ +function getWindowHeightFromResizePayload(payload) { + if (payload && payload.size && Number.isFinite(payload.size.windowHeight)) { + return payload.size.windowHeight; + } + if (payload && Number.isFinite(payload.windowHeight)) { + return payload.windowHeight; + } + return null; +} + /** * 方向切换:target->source 时对连线做镜像翻转。 */ @@ -384,10 +412,10 @@ Page({ data: { selectedThemeIndex: 1, showThemeSheet: false, - uploadMessage: '点击上传或将csv/xls文件拖到这里上传', + uploadMessage: '默认加载 data/sankey.xlsx 中...', parseError: '', buildError: '', - columnHeaders: ['列1', '列2', '列3'], + columnHeaders: [], tableRows: [], sourceDataColumn: null, sourceDescriptionColumns: [], @@ -396,7 +424,7 @@ Page({ linksCount: 0, droppedRows: 0, buildWarnings: [], - infoLogs: ['解析信息: 尚未加载数据文件'], + infoLogs: ['解析信息: 正在加载默认数据文件 data/sankey.xlsx'], sankeyLinks: [], sankeyNodes: [], gapOptions: GAP_OPTIONS, @@ -413,7 +441,68 @@ Page({ targetAlignOptionLabels: TARGET_ALIGN_OPTIONS.map((item) => item.label), targetAlignValues: TARGET_ALIGN_OPTIONS.map((item) => item.value), targetAlignIndex: 0, - targetAlignMode: TARGET_ALIGN_OPTIONS[0].value + targetAlignMode: TARGET_ALIGN_OPTIONS[0].value, + bottomPanelsHeightPx: 300 + }, + + /** + * 页面加载时: + * 1) 根据窗口高度计算底部双窗口的目标高度(默认 300,低高度窗口自动压缩) + * 2) 监听窗口变化,保持整体布局稳定 + */ + onLoad() { + this.updateBottomPanelsHeight(); + if (typeof wx.onWindowResize === 'function') { + this._handleWindowResize = (payload) => { + const nextWindowHeight = getWindowHeightFromResizePayload(payload); + this.updateBottomPanelsHeight(nextWindowHeight); + }; + wx.onWindowResize(this._handleWindowResize); + } + }, + + /** + * 页面卸载时移除窗口变化监听,避免重复绑定。 + */ + onUnload() { + if (this._handleWindowResize && typeof wx.offWindowResize === 'function') { + wx.offWindowResize(this._handleWindowResize); + } + }, + + /** + * 计算底部双窗口高度: + * - 优先保持 300px + * - 在小高度窗口中自动收缩到 180~300 区间,避免挤压主预览区 + */ + updateBottomPanelsHeight(windowHeight) { + let resolvedWindowHeight = Number(windowHeight); + if (!Number.isFinite(resolvedWindowHeight)) { + try { + if (typeof wx.getWindowInfo === 'function') { + resolvedWindowHeight = wx.getWindowInfo().windowHeight; + } else { + resolvedWindowHeight = wx.getSystemInfoSync().windowHeight; + } + } catch (error) { + resolvedWindowHeight = 760; + } + } + + const remainForBottomPanels = resolvedWindowHeight - 360; + const nextHeight = clampNumber(remainForBottomPanels, 180, 300, 300); + if (nextHeight === this.data.bottomPanelsHeightPx) { + return; + } + + this.setData( + { + bottomPanelsHeightPx: nextHeight + }, + () => { + this.drawSankey(); + } + ); }, /** @@ -522,6 +611,86 @@ Page({ */ onReady() { this.drawSankey(); + this.loadDefaultSankeyFile(); + }, + + /** + * 统一读取并解析文件。 + * - CSV 按 utf8 文本读取 + * - XLS/XLSX 按二进制读取 + */ + readAndApplyFile(filePath, fileName, onReadFailPrefix) { + const that = this; + const extension = getFileExtension(fileName); + const isCsvFile = extension === 'csv'; + const readOptions = { + filePath, + success(readRes) { + try { + const filePayload = isCsvFile ? String(readRes.data || '') : readRes.data; + const table = parseTableByFileName(fileName, filePayload); + that.applyParsedTable(table, fileName); + } catch (error) { + that.setData({ + parseError: toFriendlyParseError(error, '文件解析失败') + }); + that.refreshInfoLogs(); + } + }, + fail(err) { + that.setData({ + parseError: `${onReadFailPrefix}: ${err && err.errMsg ? err.errMsg : '未知错误'}` + }); + that.refreshInfoLogs(); + } + }; + if (isCsvFile) { + readOptions.encoding = 'utf8'; + } + wx.getFileSystemManager().readFile(readOptions); + }, + + /** + * 默认加载项目内置数据:data/sankey.xlsx + * 说明: + * - 多路径兜底,兼容不同开发者工具路径解析差异 + * - 加载失败不展示占位列,保持“未加载文件”状态 + */ + loadDefaultSankeyFile() { + const that = this; + const tryReadByIndex = (index, lastErrorMessage) => { + if (index >= DEFAULT_SANKEY_FILE_PATHS.length) { + that.setData({ + uploadMessage: '点击上传或将csv/xls文件拖到这里上传', + parseError: `默认文件加载失败: ${lastErrorMessage || `未找到 ${DEFAULT_SANKEY_FILE_NAME}`}` + }); + that.refreshInfoLogs(); + return; + } + + const candidatePath = DEFAULT_SANKEY_FILE_PATHS[index]; + wx.getFileSystemManager().readFile({ + filePath: candidatePath, + success(readRes) { + try { + const table = parseTableByFileName(DEFAULT_SANKEY_FILE_NAME, readRes.data); + that.applyParsedTable(table, DEFAULT_SANKEY_FILE_NAME); + } catch (error) { + that.setData({ + uploadMessage: '点击上传或将csv/xls文件拖到这里上传', + parseError: toFriendlyParseError(error, '默认文件解析失败') + }); + that.refreshInfoLogs(); + } + }, + fail(err) { + const errorMessage = err && err.errMsg ? err.errMsg : '未知错误'; + tryReadByIndex(index + 1, errorMessage); + } + }); + }; + + tryReadByIndex(0, ''); }, /** @@ -540,33 +709,7 @@ Page({ } const filePath = picked.path; const fileName = picked.name || 'unknown.csv'; - const extension = getFileExtension(fileName); - const isCsvFile = extension === 'csv'; - const readOptions = { - filePath, - success(readRes) { - try { - const filePayload = isCsvFile ? String(readRes.data || '') : readRes.data; - const table = parseTableByFileName(fileName, filePayload); - that.applyParsedTable(table, fileName); - } catch (error) { - that.setData({ - parseError: error && error.message ? error.message : '文件解析失败' - }); - that.refreshInfoLogs(); - } - }, - fail(err) { - that.setData({ - parseError: `读取文件失败: ${err && err.errMsg ? err.errMsg : '未知错误'}` - }); - that.refreshInfoLogs(); - } - }; - if (isCsvFile) { - readOptions.encoding = 'utf8'; - } - wx.getFileSystemManager().readFile(readOptions); + that.readAndApplyFile(filePath, fileName, '读取文件失败'); }, fail(err) { if (err && String(err.errMsg || '').indexOf('cancel') >= 0) { @@ -601,7 +744,7 @@ Page({ uploadMessage: `已加载: ${fileName}(${rows.length} 行)`, parseError: '', buildError: '', - columnHeaders: headers.length > 0 ? headers : ['列1', '列2', '列3'], + columnHeaders: headers.length > 0 ? headers : [], tableRows: rows, sourceDataColumn, sourceDescriptionColumns, diff --git a/miniapp/pages/index/index.wxml b/miniapp/pages/index/index.wxml index 6e90231..6956633 100644 --- a/miniapp/pages/index/index.wxml +++ b/miniapp/pages/index/index.wxml @@ -90,61 +90,64 @@ - + + + 未加载文件,暂无列信息 - - - - 源数据(link value) + + + + 源数据(link value) + + + + {{item}} + + - - - {{item}} - - - - - - - 源标签(Source label) + + + + 源标签(Source label) + + + + {{item}} + + - - - {{item}} - - - - - - - 目标标签(target label) + + + + 目标标签(target label) + + + + {{item}} + + - - - {{item}} - - - + - + {{item}} - + diff --git a/miniapp/pages/index/index.wxss b/miniapp/pages/index/index.wxss index c850bcc..c2cf72d 100644 --- a/miniapp/pages/index/index.wxss +++ b/miniapp/pages/index/index.wxss @@ -1,8 +1,11 @@ .page { - min-height: 100vh; + height: 100vh; padding: 8px; background: #f3f4f6; box-sizing: border-box; + display: flex; + flex-direction: column; + overflow: hidden; } .header { @@ -10,6 +13,7 @@ display: flex; align-items: center; gap: 8px; + flex-shrink: 0; } .logo { @@ -31,6 +35,7 @@ align-items: center; gap: 6px; padding-left: 6px; + flex-shrink: 0; } .tool-icon { @@ -101,6 +106,11 @@ background: #fff; padding: 3.2px; box-sizing: border-box; + flex: 1; + min-height: 220px; + display: flex; + flex-direction: column; + overflow: hidden; } .preview-head { @@ -227,7 +237,9 @@ .preview-canvas { margin-top: 3px; width: 100%; - height: 371px; + height: auto; + flex: 1; + min-height: 120px; border-radius: 4px; background: #f7f8fa; } @@ -237,6 +249,9 @@ display: grid; grid-template-columns: 1fr 1fr; gap: 8px; + height: 300px; + min-height: 180px; + flex-shrink: 0; } .panel { @@ -245,18 +260,37 @@ background: #fff; padding: 8px; box-sizing: border-box; + height: 100%; + overflow: hidden; } .data-panel { + display: flex; + flex-direction: column; + min-height: 0; +} + +.data-scroll { + margin-top: 12px; + flex: 1; + height: 0; + min-height: 0; display: flex; flex-direction: column; gap: 12px; } +.empty-tip { + color: #86909c; + font-size: 12px; + line-height: 1.4; +} + .log-panel { display: flex; flex-direction: column; gap: 8px; + min-height: 0; } .panel-title { @@ -316,11 +350,11 @@ .log-list { flex: 1; + height: 0; min-height: 0; display: flex; flex-direction: column; gap: 6px; - overflow: auto; } .log-item { @@ -334,6 +368,7 @@ color: #86909c; font-size: 14px; line-height: 1.3; + flex-shrink: 0; } .theme-sheet-mask { diff --git a/miniapp/project.config.json b/miniapp/project.config.json new file mode 100644 index 0000000..967484a --- /dev/null +++ b/miniapp/project.config.json @@ -0,0 +1,19 @@ +{ + "description": "sankey miniapp standalone config", + "packOptions": { + "ignore": [] + }, + "setting": { + "es6": true, + "enhance": true, + "postcss": true, + "minified": true + }, + "compileType": "miniprogram", + "libVersion": "trial", + "appid": "wxcf0f89b6eb65759e", + "projectname": "sankey-miniapp", + "miniprogramRoot": "./", + "srcMiniprogramRoot": "./", + "condition": {} +}