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": {}
+}