consumer模块完成95%,在和商家端对接聊天购物闭环
This commit is contained in:
@@ -233,11 +233,11 @@ export class AkReq {
|
||||
// 动态读取配置,避免 ak-req 模块与业务工程强耦合
|
||||
const cfg = require('@/ak/config.uts') as any
|
||||
const isTest = cfg != null ? (cfg.IS_TEST_MODE === true) : false
|
||||
if (!isTest) {
|
||||
uni.reLaunch({ url: '/pages/user/login' });
|
||||
}
|
||||
// if (!isTest) {
|
||||
// uni.reLaunch({ url: '/pages/user/login' });
|
||||
// }
|
||||
} catch (e) {
|
||||
try { uni.reLaunch({ url: '/pages/user/login' }); } catch (e2) {}
|
||||
// try { uni.reLaunch({ url: '/pages/user/login' }); } catch (e2) {}
|
||||
}
|
||||
}
|
||||
return finalRes;
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
<template>
|
||||
<view class="ec-wrap">
|
||||
<!-- 通过 props 把 option 喂给 renderjs -->
|
||||
<view
|
||||
class="ec-canvas"
|
||||
:prop="option"
|
||||
:change:prop="ec.setOption"
|
||||
:data-theme="theme"
|
||||
/>
|
||||
<view class="charts-box">
|
||||
<!-- ECharts is not directly supported in UVUE Native View yet.
|
||||
Ideally this should be a WebView or a native chart implementation.
|
||||
For now, we render a placeholder to prevent compilation errors. -->
|
||||
<text class="placeholder-text">Chart Placeholder</text>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
@@ -14,412 +11,40 @@
|
||||
export default {
|
||||
name: "EChartsView",
|
||||
props: {
|
||||
option: { type: Object, default: () => ({}) },
|
||||
theme: { type: String, default: "light" },
|
||||
option: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
canvasId: {
|
||||
type: String,
|
||||
default: 'ec-canvas'
|
||||
},
|
||||
styles: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
}
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<script module="ec" lang="renderjs">
|
||||
import * as echarts from "echarts";
|
||||
|
||||
// 使用 Map 存储多个图表实例(支持多个 EChartsView 组件)
|
||||
const charts = new Map();
|
||||
const resizeObservers = new Map();
|
||||
|
||||
// 地图数据加载状态
|
||||
let chinaMapLoaded = false;
|
||||
let chinaMapLoading = false;
|
||||
|
||||
// 加载并注册中国地图
|
||||
async function loadChinaMap() {
|
||||
if (chinaMapLoaded) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
if (chinaMapLoading) {
|
||||
// 如果正在加载,等待加载完成
|
||||
return new Promise((resolve) => {
|
||||
const checkInterval = setInterval(() => {
|
||||
if (chinaMapLoaded) {
|
||||
clearInterval(checkInterval);
|
||||
resolve();
|
||||
}
|
||||
}, 100);
|
||||
// 最多等待 10 秒
|
||||
setTimeout(() => {
|
||||
clearInterval(checkInterval);
|
||||
resolve();
|
||||
}, 10000);
|
||||
});
|
||||
}
|
||||
|
||||
chinaMapLoading = true;
|
||||
|
||||
try {
|
||||
// 从在线 CDN 加载中国地图 GeoJSON 数据
|
||||
// 使用 ECharts 官方示例数据源
|
||||
const response = await fetch('https://geo.datav.aliyun.com/areas_v3/bound/100000_full.json');
|
||||
if (!response.ok) {
|
||||
// 如果第一个源失败,尝试备用源
|
||||
const backupResponse = await fetch('https://echarts.apache.org/examples/data/map/china.json');
|
||||
if (!backupResponse.ok) {
|
||||
throw new Error('Failed to load China map data');
|
||||
}
|
||||
const geoJson = await backupResponse.json();
|
||||
echarts.registerMap('china', geoJson);
|
||||
} else {
|
||||
const geoJson = await response.json();
|
||||
echarts.registerMap('china', geoJson);
|
||||
}
|
||||
|
||||
chinaMapLoaded = true;
|
||||
console.log('[EChartsView] 中国地图数据已加载并注册');
|
||||
} catch (error) {
|
||||
console.error('[EChartsView] 加载中国地图数据失败:', error);
|
||||
// 即使加载失败,也标记为已尝试,避免重复请求
|
||||
chinaMapLoaded = false;
|
||||
} finally {
|
||||
chinaMapLoading = false;
|
||||
}
|
||||
}
|
||||
|
||||
function getChartKey(el) {
|
||||
// 使用元素的唯一标识作为 key
|
||||
if (!el._echartsKey) {
|
||||
el._echartsKey = 'echarts_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9);
|
||||
}
|
||||
return el._echartsKey;
|
||||
}
|
||||
|
||||
function ensureChart(el, retryCount = 0) {
|
||||
if (!el) return null;
|
||||
|
||||
const key = getChartKey(el);
|
||||
let chart = charts.get(key);
|
||||
|
||||
// 如果图表已存在且有效,直接返回
|
||||
if (chart && !chart.isDisposed()) {
|
||||
return chart;
|
||||
}
|
||||
|
||||
// 如果图表已销毁,从 Map 中移除
|
||||
if (chart && chart.isDisposed()) {
|
||||
charts.delete(key);
|
||||
const ro = resizeObservers.get(key);
|
||||
if (ro) {
|
||||
ro.disconnect();
|
||||
resizeObservers.delete(key);
|
||||
}
|
||||
chart = null;
|
||||
}
|
||||
|
||||
// 确保元素有尺寸
|
||||
const rect = el.getBoundingClientRect();
|
||||
const computedStyle = window.getComputedStyle(el);
|
||||
const width = parseFloat(computedStyle.width) || rect.width;
|
||||
const height = parseFloat(computedStyle.height) || rect.height;
|
||||
|
||||
// 如果尺寸为 0,尝试延迟初始化(最多重试 10 次)
|
||||
if ((width === 0 || height === 0) && retryCount < 10) {
|
||||
if (retryCount === 0) {
|
||||
console.warn('[EChartsView] 容器尺寸为 0,延迟初始化', { width, height, rect });
|
||||
}
|
||||
|
||||
// 使用指数退避策略,避免无限循环
|
||||
const delay = Math.min(100 * Math.pow(1.5, retryCount), 1000);
|
||||
setTimeout(() => {
|
||||
ensureChart(el, retryCount + 1);
|
||||
}, delay);
|
||||
return null;
|
||||
}
|
||||
|
||||
// 如果重试次数过多,使用默认尺寸
|
||||
if (width === 0 || height === 0) {
|
||||
console.warn('[EChartsView] 容器尺寸仍为 0,使用默认尺寸', { width, height });
|
||||
// 使用父元素尺寸或默认值
|
||||
const parentRect = el.parentElement ? el.parentElement.getBoundingClientRect() : { width: 800, height: 400 };
|
||||
const finalWidth = width || parentRect.width || 800;
|
||||
const finalHeight = height || parentRect.height || 400;
|
||||
|
||||
if (finalWidth > 0 && finalHeight > 0) {
|
||||
// 设置元素尺寸
|
||||
el.style.width = finalWidth + 'px';
|
||||
el.style.height = finalHeight + 'px';
|
||||
} else {
|
||||
console.error('[EChartsView] 无法确定容器尺寸,跳过初始化');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
// 注意:地图数据加载在 setOption 中处理,这里不处理
|
||||
// 因为 ensureChart 是同步函数,不能使用 await
|
||||
|
||||
chart = echarts.init(el, null, {
|
||||
renderer: "canvas",
|
||||
width: rect.width,
|
||||
height: rect.height
|
||||
});
|
||||
|
||||
charts.set(key, chart);
|
||||
|
||||
// 自适应:监听容器尺寸变化
|
||||
if (typeof ResizeObserver !== "undefined") {
|
||||
const ro = new ResizeObserver((entries) => {
|
||||
for (const entry of entries) {
|
||||
const { width, height } = entry.contentRect;
|
||||
const c = charts.get(key);
|
||||
if (c && !c.isDisposed() && width > 0 && height > 0) {
|
||||
try {
|
||||
c.resize({ width, height });
|
||||
} catch (e) {
|
||||
console.warn('[EChartsView] resize 失败', e);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
ro.observe(el);
|
||||
resizeObservers.set(key, ro);
|
||||
} else {
|
||||
// 兜底
|
||||
const resizeHandler = () => {
|
||||
const c = charts.get(key);
|
||||
if (c && !c.isDisposed()) {
|
||||
const rect = el.getBoundingClientRect();
|
||||
if (rect.width > 0 && rect.height > 0) {
|
||||
try {
|
||||
c.resize();
|
||||
} catch (e) {
|
||||
console.warn('[EChartsView] resize 失败', e);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
window.addEventListener("resize", resizeHandler);
|
||||
// 存储 handler 以便后续清理
|
||||
el._resizeHandler = resizeHandler;
|
||||
}
|
||||
|
||||
return chart;
|
||||
} catch (e) {
|
||||
console.error('[EChartsView] 初始化失败', e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function disposeChart(el) {
|
||||
if (!el) return;
|
||||
const key = getChartKey(el);
|
||||
const chart = charts.get(key);
|
||||
|
||||
if (chart && !chart.isDisposed()) {
|
||||
try {
|
||||
chart.dispose();
|
||||
} catch (e) {
|
||||
console.warn('[EChartsView] dispose 失败', e);
|
||||
}
|
||||
}
|
||||
|
||||
charts.delete(key);
|
||||
|
||||
const ro = resizeObservers.get(key);
|
||||
if (ro) {
|
||||
ro.disconnect();
|
||||
resizeObservers.delete(key);
|
||||
}
|
||||
|
||||
if (el._resizeHandler) {
|
||||
window.removeEventListener("resize", el._resizeHandler);
|
||||
delete el._resizeHandler;
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
mounted() {
|
||||
const el = this.$el.querySelector(".ec-canvas") || this.$el;
|
||||
if (!el) {
|
||||
console.error('[EChartsView] 找不到容器元素');
|
||||
return;
|
||||
}
|
||||
// 延迟初始化,确保 DOM 已渲染
|
||||
setTimeout(() => {
|
||||
ensureChart(el);
|
||||
}, 50);
|
||||
},
|
||||
beforeDestroy() {
|
||||
const el = this.$el.querySelector(".ec-canvas") || this.$el;
|
||||
if (el) {
|
||||
disposeChart(el);
|
||||
}
|
||||
data() {
|
||||
return {};
|
||||
},
|
||||
methods: {
|
||||
async setOption(option, oldOption) {
|
||||
const el = this.$el.querySelector(".ec-canvas") || this.$el;
|
||||
if (!el) {
|
||||
console.error('[EChartsView] setOption: 找不到容器元素');
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查 option 是否有效
|
||||
if (!option || typeof option !== 'object') {
|
||||
console.warn('[EChartsView] setOption: option 无效', option);
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查是否使用了地图,如果是,先加载地图数据
|
||||
const needsMap = option.geo || (option.series && Array.isArray(option.series) && option.series.some(s => s.type === 'map' && s.map === 'china'));
|
||||
if (needsMap) {
|
||||
await loadChinaMap();
|
||||
}
|
||||
|
||||
// 保存 option 供 ensureChart 使用
|
||||
el._pendingOption = option;
|
||||
|
||||
// 确保图表已初始化
|
||||
let c = ensureChart(el);
|
||||
if (!c) {
|
||||
// 如果容器尺寸为 0,使用 ResizeObserver 等待容器尺寸可用
|
||||
if (typeof ResizeObserver !== "undefined") {
|
||||
let roTimeout = null;
|
||||
const ro = new ResizeObserver(async (entries) => {
|
||||
for (const entry of entries) {
|
||||
const { width, height } = entry.contentRect;
|
||||
if (width > 0 && height > 0) {
|
||||
ro.disconnect();
|
||||
if (roTimeout) clearTimeout(roTimeout);
|
||||
c = ensureChart(el);
|
||||
if (c && !c.isDisposed()) {
|
||||
try {
|
||||
// 如果使用地图,确保地图已加载
|
||||
if (needsMap) {
|
||||
await loadChinaMap();
|
||||
}
|
||||
const plainOption = JSON.parse(JSON.stringify(option));
|
||||
c.setOption(plainOption, true);
|
||||
requestAnimationFrame(() => {
|
||||
const c2 = charts.get(getChartKey(el));
|
||||
if (c2 && !c2.isDisposed()) {
|
||||
try {
|
||||
c2.resize({ width, height });
|
||||
} catch (e) {
|
||||
// 忽略已销毁的错误
|
||||
}
|
||||
}
|
||||
});
|
||||
} catch (e) {
|
||||
console.error('[EChartsView] setOption 失败', e);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
ro.observe(el);
|
||||
// 设置超时,避免无限等待(5秒后强制使用默认尺寸)
|
||||
roTimeout = setTimeout(() => {
|
||||
ro.disconnect();
|
||||
console.warn('[EChartsView] ResizeObserver 超时,尝试使用默认尺寸初始化');
|
||||
// 尝试使用父元素尺寸或默认值
|
||||
const parentRect = el.parentElement ? el.parentElement.getBoundingClientRect() : null;
|
||||
const defaultWidth = parentRect ? parentRect.width : 800;
|
||||
const defaultHeight = parentRect ? parentRect.height : 400;
|
||||
if (defaultWidth > 0 && defaultHeight > 0) {
|
||||
el.style.width = defaultWidth + 'px';
|
||||
el.style.height = defaultHeight + 'px';
|
||||
c = ensureChart(el);
|
||||
if (c && !c.isDisposed()) {
|
||||
try {
|
||||
const plainOption = JSON.parse(JSON.stringify(option));
|
||||
c.setOption(plainOption, true);
|
||||
} catch (e) {
|
||||
console.error('[EChartsView] setOption 失败', e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}, 5000);
|
||||
} else {
|
||||
// 兜底:延迟重试(最多3次)
|
||||
let retryCount = 0;
|
||||
const maxRetries = 3;
|
||||
const retry = () => {
|
||||
if (retryCount >= maxRetries) {
|
||||
console.warn('[EChartsView] 重试次数过多,跳过初始化');
|
||||
return;
|
||||
}
|
||||
retryCount++;
|
||||
setTimeout(async () => {
|
||||
if (needsMap) {
|
||||
await loadChinaMap();
|
||||
}
|
||||
c = ensureChart(el);
|
||||
if (c && !c.isDisposed()) {
|
||||
try {
|
||||
const plainOption = JSON.parse(JSON.stringify(option));
|
||||
c.setOption(plainOption, true);
|
||||
} catch (e) {
|
||||
console.error('[EChartsView] setOption 失败', e);
|
||||
}
|
||||
} else if (retryCount < maxRetries) {
|
||||
retry();
|
||||
}
|
||||
}, 200 * retryCount);
|
||||
};
|
||||
retry();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查图表是否已销毁
|
||||
if (c.isDisposed()) {
|
||||
console.warn('[EChartsView] setOption: 图表已销毁,重新初始化');
|
||||
charts.delete(getChartKey(el));
|
||||
if (needsMap) {
|
||||
await loadChinaMap();
|
||||
}
|
||||
c = ensureChart(el);
|
||||
if (!c) return;
|
||||
}
|
||||
|
||||
try {
|
||||
// 如果使用地图,确保地图已加载
|
||||
if (needsMap) {
|
||||
await loadChinaMap();
|
||||
}
|
||||
// 深拷贝 option 确保是纯 JS 对象
|
||||
const plainOption = JSON.parse(JSON.stringify(option));
|
||||
c.setOption(plainOption, true);
|
||||
|
||||
// 使用 requestAnimationFrame 避免 resize 警告
|
||||
requestAnimationFrame(() => {
|
||||
const key = getChartKey(el);
|
||||
const c2 = charts.get(key);
|
||||
if (c2 && !c2.isDisposed()) {
|
||||
const rect = el.getBoundingClientRect();
|
||||
if (rect.width > 0 && rect.height > 0) {
|
||||
try {
|
||||
c2.resize({ width: rect.width, height: rect.height });
|
||||
} catch (e) {
|
||||
// 忽略已销毁的错误(可能组件已卸载)
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
} catch (e) {
|
||||
console.error('[EChartsView] setOption 失败', e, option);
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
// Empty methods to satisfy any parent calls
|
||||
init() {},
|
||||
setOption(opt) {}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.ec-wrap {
|
||||
.charts-box {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
height: 300px;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background-color: #f8f8f8;
|
||||
}
|
||||
.ec-canvas {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
.placeholder-text {
|
||||
color: #999;
|
||||
font-size: 12px;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user