提交 4847926a 作者: anergin00

update init

父级 a39267af
import CollectionApi from "./apis/CollectionApi";
import PhotoApi from "./apis/PhotoApi";
import ItineraryApi from "./apis/ItineraryApi";
export { CollectionApi, PhotoApi, ItineraryApi };
import CardApi from "./apis/CardApi";
import PoiApi from "./apis/PoiApi";
export { CollectionApi, PhotoApi, ItineraryApi, CardApi, PoiApi };
export default class CardApi {
constructor(app) {
this.app = app;
}
async getByZone(zoneCode) {
const { qmurl, post } = this.app;
try {
const {
data: { code, data },
} = await post(`${qmurl}/api/v1/applet/getActivityByZone`, {
zoneCode,
});
if (code === 200) {
return data?.activityId;
}
return null;
} catch (error) {
console.error(error);
return null;
}
}
async get(activityId) {
const { qmurl, post, globalData } = this.app;
if (!globalData.user) return null;
try {
const {
data: { code, data },
} = await post(`${qmurl}/api/v1/applet/getActivityInfo`, {
userId: globalData.user.user.userId,
activityId,
});
if (code === 200) {
return data;
}
return null;
} catch (error) {
console.error(error);
return null;
}
}
async check(poi) {
const { qmurl, post, globalData } = this.app;
if (!globalData.user) return false;
if (!poi) return false;
try {
const {
data: { code, data },
} = await post(`${qmurl}/api/v1/applet/checkInActivity`, {
userId: globalData.user.user.userId,
token: globalData.user.access_token,
shopType: poi.facility ? 2 : 1,
shopId: poi.shopId ? poi.shopId : poi.facilityId,
boothNo: poi.shopId ? poi.ywZh?.boothNo : undefined,
});
if (code === 200) return data;
return false;
} catch (error) {
console.error(error);
return false;
}
}
}
......@@ -75,15 +75,16 @@ export default class PhotoApi {
}
}
async search(imageUrl, rect) {
const { marketurl, post, globalData } = this.app;
if (!globalData.user) return false;
if (!imageUrl) return false;
const { marketurl, post, globalData, currentBuilding } = this.app;
if (!globalData.user) throw new Error("用户未登录");
if (!imageUrl) throw new Error("没有图片url");
if (!rect) {
try {
const { width, height } = await wx.getImageInfo({ src: imageUrl });
rect = { width, height, x: 0, y: 0 };
} catch (error) {
console.error(error);
throw new Error("获取图片宽高失败");
}
}
try {
......@@ -92,12 +93,13 @@ export default class PhotoApi {
} = await post(`${marketurl}/detector/baidu/image/search`, {
imageRectDto: rect,
imageUrl,
marketCode: currentBuilding.marketCode,
});
if (code === 20000) return data;
return null;
throw new Error("接口返回code不是20000");
} catch (error) {
console.error(error);
return null;
throw new Error("接口异常");
}
}
}
import { getShopMainGoods, getStarredPhone } from "../util";
export default class PoiApi {
constructor(app) {
this.app = app;
this.MAX_CACHE_SIZE = 1000;
this.poiCache = new Map();
// 内存不足时自动清理
if (wx.onMemoryWarning) {
wx.onMemoryWarning((res) => {
console.warn("Memory warning, clearing POI cache:", res.level);
this.poiCache.clear();
});
}
}
// LRU 缓存设置
_setCache(key, value) {
if (this.poiCache.has(key)) this.poiCache.delete(key);
this.poiCache.set(key, value);
if (this.poiCache.size > this.MAX_CACHE_SIZE) {
this.poiCache.delete(this.poiCache.keys().next().value);
}
}
_getCacheKey({ shopType, shopId, boothNo }) {
return `${shopType}_${shopId}_${boothNo || ""}`;
}
// 数据预处理
_processShopData(item) {
if (!item) return item;
// 简单判断是否为店铺逻辑:type=1 或 有ID且无facilityId
const isShop = item.shopType === "1" || (item.shopId && !item.facilityId);
if (isShop) {
try {
item.maingoods = getShopMainGoods(item, false);
item.maingoodsEn = getShopMainGoods(item, true);
if (item.ywZh?.contactPhone)
item.contactPhone = getStarredPhone(item.ywZh.contactPhone);
if (item.ywEn?.contactPhone)
item.contactPhoneEn = getStarredPhone(item.ywEn.contactPhone);
} catch (e) {
console.error("Format shop data error:", e);
}
}
return item;
}
// --- 核心底层方法:批量获取 (含缓存) ---
async getListByIds(params) {
if (!Array.isArray(params) || params.length === 0) return [];
const uncachedParams = [];
const resultMap = {};
// 1. 缓存分流
params.forEach((param) => {
if (!param) return; // 跳过 populate 产生的无效占位
const key = this._getCacheKey(param);
if (this.poiCache.has(key)) {
const val = this.poiCache.get(key);
this._setCache(key, val); // 刷新 LRU
resultMap[key] = val;
} else {
uncachedParams.push(param);
}
});
// 2. 网络请求 (仅请求未命中的)
if (uncachedParams.length > 0) {
try {
const { qmurl, post } = this.app;
const {
data: { data },
} = await post(
`${qmurl}/api/v1/applet/getShopFacilityPoiListBatch`,
uncachedParams.map(({ shopType, shopId, boothNo }) => ({
shopType,
shopId,
boothNo,
})),
);
if (Array.isArray(data)) {
data.forEach((item, index) => {
const param = uncachedParams[index];
if (param && item) {
this._processShopData(item);
const key = this._getCacheKey(param);
this._setCache(key, item);
resultMap[key] = item;
}
});
}
} catch (e) {
console.error("Batch fetch error:", e);
}
}
// 3. 按原顺序返回
return params.map((param) => {
if (!param) return null;
return resultMap[this._getCacheKey(param)] || null;
});
}
// --- 业务层方法 ---
/**
* 智能填充列表 (Page层主要调用此方法)
*/
async populate(list, options = {}) {
if (!Array.isArray(list) || list.length === 0) return [];
const {
getType = (item) => item.shopType,
getId = (item) => item.shopId,
getBooth = (item) => item.boothNo,
targetField = "poi",
} = options;
// 1. 构造请求参数 (保持索引对应)
const params = list.map((item) => {
const type = getType(item);
const id = getId(item);
// 仅处理合法的店铺(1)或设施(2)
if ((type == 1 || type == 2) && id) {
return {
shopType: String(type),
shopId: id,
boothNo: getBooth(item) || "",
};
}
return null;
});
// 2. 批量获取
const details = await this.getListByIds(params);
// 3. 回填数据
return list
.map((item, index) => {
const detail = details[index];
return detail ? { ...item, [targetField]: detail } : null;
})
.filter(Boolean);
}
/**
* 获取单条详情 (复用 populate)
*/
async getOne({ shopType, shopId, boothNo }) {
const res = await this.populate([{ shopType, shopId, boothNo }], {
targetField: "detail",
});
return res[0]?.detail || null;
}
async getTx() {
try {
const {
data: { data },
} = await this.app.get(
`${this.app.qmurl}/api/v1/applet/getShopFacilityPoiSummary`,
);
return data;
} catch (e) {
console.error(e);
return null;
}
}
}
......@@ -2,35 +2,136 @@ import {
EventEmitter,
get,
post,
getShopMainGoods,
getStarredPhone,
getPoiName,
getDistance,
createGridIndex,
} from "./util";
import md5 from "./md5";
import md5 from "./libs/md5";
import sensors from "./sensorsdata.esm.js";
import { CollectionApi, PhotoApi, ItineraryApi } from "./api";
import { CollectionApi, PhotoApi, ItineraryApi, CardApi, PoiApi } from "./api";
const plugin = requirePlugin("indoormap");
const mapKey = "QUIBZ-OTL6U-T2MVM-GMPIT-EON66-LGBCU";
const buildingId = "3307001549220";
const buildings = [
{
name: "六区",
nameEn: "District 6",
displayName: "全球数贸中心",
displayNameEn: "Global Digital Trade Centre",
marketCode: "1060",
zone: "60",
buildingId: "3307001948721",
latlng: { lat: 29.340547, lng: 120.12591 },
floors: ["B2", "B1", "1F", "2F", "3F", "4F"],
scale: 16.5,
formats: [
{
key: "N_60_1_SSZB",
name: "时尚珠宝",
nameEn: "Fashion Jewelry",
},
{
key: "N_60_1_CYCLWJ",
name: "创意潮流玩具",
nameEn: "Creative & Trendy Toys",
},
{
key: "N_60_1_SSFSML",
name: "时尚服饰面料",
nameEn: "Fashionable Apparel Fabrics",
},
{
key: "N_60_2_HFYMYP",
name: "护肤及医美用品",
nameEn: "Skincare & Medical-Grade Skincare Products",
},
{
key: "N_60_2_YYTLSYP",
name: "婴幼童生活用品",
nameEn: "Baby & Toddler Daily Necessities",
},
{
key: "N_60_3_JTLSYP",
name: "家庭生活用品/健康医疗用品",
nameEn: "Household & Living Supplies / Health & Medical Supplies",
},
{
key: "N_60_3_WRJZRJQ",
name: "无人机及无人化装备/机器人及人工智能装备/AR·VR 装备",
nameEn:
"UAV & Unmanned Equipment / Robotics & AI Equipment / AR/VR Equipment",
},
{
key: "N_60_3_LXHW",
name: "旅行好物",
nameEn: "Travel Products",
},
],
},
{
name: "一区",
nameEn: "District 1",
displayName: "国际商贸城一区",
displayNameEn: "International Trade City District 1",
zone: "01",
buildingId: "3307001301397",
latlng: { lat: 29.327742, lng: 120.103138},
floors: ["1F", "2F", "3F", "4F"],
scale: 15.5,
},
{
name: "二区",
nameEn: "District 2",
displayName: "国际商贸城二区",
displayNameEn: "International Trade City District 2",
zone: "02",
buildingId: "3307001732882",
latlng: { lat: 29.334626, lng: 120.106744},
floors: ["1F", "2F", "3F"],
scale: 15.5,
},
{
name: "二区东",
nameEn: "District 2-East",
displayName: "新能源产品市场",
displayNameEn: "New Energy Products Market",
marketCode: "10031",
zone: "02",
buildingId: "3307001549220",
latlng: { lat: 29.331723, lng: 120.106838 },
floors: ["1F", "2F", "3F"],
scale: 18.6,
formats: [
{ name: "家装五金", key: "N_02_1_JZWJ", nameEn: "Decoration Hardware" },
{ name: "新能源系统", key: "N_02_2_XNY", nameEn: "New Energy Series" },
{
name: "智能五金电器",
key: "N_02_3_WJDQ",
nameEn: "Hardware Appliance",
},
],
},
];
const buildingIdNameMap = buildings.reduce(
(acc, nxt) => ({ ...acc, [nxt.buildingId]: nxt.name }),
{},
);
const buildingNameNameEnMap = buildings.reduce(
(acc, nxt) => ({ ...acc, [nxt.name]: nxt.nameEn }),
{},
);
const isTest = false;
export const qmurl = isTest
? "http://192.168.1.187:9900"
? "https://tencent.chinagoods.com"
: "https://tencent.chinagoods.com";
export const marketurl = isTest
? "https://testapiserver.chinagoods.com"
: "https://apiserver.chinagoods.com";
export const uploadurl = isTest
? "https://kunapi.chinagoods.com"
? "https://testkunapi.chinagoods.com"
: "https://kunapi.chinagoods.com";
export const salt = "chinagoods2024";
const formats = [
{ name: "家装五金", key: "N_02_1_JZWJ", nameEn: "Decoration Hardware" },
{ name: "新能源系统", key: "N_02_2_XNY", nameEn: "New Energy Series" },
{ name: "智能五金电器", key: "N_02_3_WJDQ", nameEn: "Hardware Appliance" },
];
sensors.init({
server_url:
"https://event-tracking.chinagoods.com/sa.gif?project=market_navigation_t&remark=online",
......@@ -55,8 +156,10 @@ sensors.init({
});
App({
buildingId,
formats,
buildings,
buildingIdNameMap,
buildingNameNameEnMap,
currentBuilding: buildings[0],
qmurl,
marketurl,
uploadurl,
......@@ -65,23 +168,60 @@ App({
post,
md5,
mapKey,
shopIdMap: null,
facilityIdMap: null,
txQmMap: null,
locMap: null,
events: new EventEmitter(),
isIOS: wx.getDeviceInfo().platform === "ios",
onLaunch() {
this.collectionApi = new CollectionApi(this);
this.photoApi = new PhotoApi(this);
this.itineraryApi = new ItineraryApi(this);
this.cardApi = new CardApi(this);
this.poiApi = new PoiApi(this);
wx.setNavigationBarTitle({
title: this.globalData.isEn ? "Chinagoods Nav" : "小商AI导航",
});
this.refreshToken();
this.identifySensors();
this.getPoiList();
this.refreshCollection();
this.refreshItinerary();
this.checkUpdate();
const callback = (res) => console.log("userTriggerTranslation", res);
wx.onUserTriggerTranslation(callback);
},
checkUpdate() {
if (wx.canIUse("getUpdateManager")) {
const updateManager = wx.getUpdateManager();
const currentIsEn = this.globalData.isEn;
updateManager.onCheckForUpdate((res) => {
if (res.hasUpdate) {
updateManager.onUpdateReady(() => {
wx.showModal({
title: currentIsEn ? "Update" : "更新提示",
content: currentIsEn
? "New version ready. Update now?"
: "新版本已下载完成,是否立即更新?",
showCancel: false,
confirmText: currentIsEn ? "Update" : "立即更新",
success: () => {
updateManager.applyUpdate();
},
});
});
updateManager.onUpdateFailed(() => {
wx.showModal({
title: currentIsEn ? "Update" : "更新提示",
content: currentIsEn
? "Download failed. Try again later."
: "新版本下载失败,请稍后重试",
showCancel: false,
confirmText: currentIsEn ? "OK" : "确定",
});
});
}
});
}
},
async refreshToken() {
if (this.globalData.user) {
......@@ -96,7 +236,6 @@ App({
this.globalData.user = null;
this.events.emit("user", null);
wx.removeStorageSync("user");
this.identifySensors();
this.refreshCollection();
this.refreshItinerary();
} else {
......@@ -111,15 +250,22 @@ App({
try {
const loginCode = await this.getLoginCode();
if (loginCode && authCode) {
const {
data: { data: user },
} = await post(`${marketurl}/login/wxa/wxa/login`, {
const body = {
loginCode,
authCode,
type: "cgMap",
utm_source: "腾讯地图导航",
});
console.log(user);
};
if (getApp().utm_campaign) {
body.utm_campaign = getApp().utm_campaign;
}
if (getApp().utm_term) {
body.utm_term = getApp().utm_term;
}
console.log("登录入参", body);
const { data } = await post(`${marketurl}/login/wxa/wxa/login`, body);
console.log(data);
const user = data.data;
if (user) {
this.globalData.user = user;
this.events.emit("user", this.globalData.user);
......@@ -127,19 +273,17 @@ App({
this.identifySensors();
this.refreshCollection();
this.refreshItinerary();
return true;
return data;
}
}
this.globalData.user = null;
this.events.emit("user", null);
this.identifySensors();
this.refreshCollection();
this.refreshItinerary();
return false;
return data;
} catch (error) {
this.globalData.user = null;
this.events.emit("user", null);
this.identifySensors();
this.refreshCollection();
this.refreshItinerary();
return false;
......@@ -179,74 +323,40 @@ App({
},
identifySensors() {
try {
if (sensors) {
if (this.globalData.user) {
sensors.identify(this.globalData.user.user.userId);
} else if (this.globalData.unionid) {
sensors.identify(this.globalData.unionid);
} else {
this.getUnionid().then((unionid) => {
if (unionid) {
wx.setStorageSync("unionid", unionid);
this.globalData.unionid = unionid;
sensors.identify(unionid);
}
});
}
if (sensors && this.globalData.user) {
sensors.logout();
sensors.login(this.globalData.user.user.userId);
}
} catch (error) {
console.error(error);
}
},
handlePoiList(params) {
handlePoiList(list) {
try {
const { facilityPoiList, shopPoiList } = params;
const shopIdMap = {};
const facilityIdMap = {};
const txQmMap = {};
facilityPoiList.forEach((fac) => {
try {
facilityIdMap[fac.facilityId] = fac;
if (fac.shineiId && fac.tx) {
txQmMap[fac.shineiId] = fac;
}
} catch (error) {
console.error(error);
}
});
shopPoiList.forEach((shop) => {
try {
shop.maingoods = getShopMainGoods(shop, false);
shop.maingoodsEn = getShopMainGoods(shop, true);
} catch (error) {
console.error(error);
const locMap = {};
list.forEach((item) => {
if (item && item.tx) {
txQmMap[item.shineiId] = item;
if (item.shopId) {
const key = `1_${item.shopId}_${item.boothNo || ""}`;
locMap[key] = item.tx;
}
try {
if (shop.ywZh?.contactPhone) {
shop.contactPhone = getStarredPhone(shop.ywZh?.contactPhone);
}
if (shop.ywEn?.contactPhone) {
shop.contactPhoneEn = getStarredPhone(shop.ywEn?.contactPhone);
}
} catch (error) {
console.error(error);
}
try {
shopIdMap[shop.shopId] = shop;
if (shop.shineiId && shop.tx) {
txQmMap[shop.shineiId] = shop;
if (item.facilityId) {
const key = `2_${item.facilityId}_`;
locMap[key] = item.tx;
}
} catch (error) {
console.error(error);
}
});
this.shopIdMap = shopIdMap;
this.facilityIdMap = facilityIdMap;
this.txQmMap = txQmMap;
this.locMap = locMap;
try {
this.indexData = createGridIndex(
Object.values(txQmMap).map(({ tx }) => tx)
list
.map(({ shineiId, tx }) =>
tx ? { ...tx, shinei_id: shineiId } : null,
)
.filter(Boolean),
);
} catch (error) {}
......@@ -255,38 +365,17 @@ App({
console.error(error);
}
},
async getPoiListFromStorage() {
try {
const { data: shopPoiList } = await wx.getStorage({ key: "shopPoiList" });
const { data: facilityPoiList } = await wx.getStorage({
key: "facilityPoiList",
});
this.handlePoiList({ shopPoiList, facilityPoiList });
console.log("从缓存加载数据成功");
} catch (error) {
console.error("从缓存加载数据失败" + error);
}
},
async getPoiList() {
try {
this.getPoiListFromStorage();
const {
data: {
data: { facilityPoiList, shopPoiList },
},
} = await get(`${qmurl}/api/v1/applet/getShopFacilityPoiList`);
this.handlePoiList({ facilityPoiList, shopPoiList });
const result = await this.poiApi.getTx();
if (result) {
this.handlePoiList(result);
console.log("更新数据成功");
wx.setStorage({ key: "shopPoiList", data: shopPoiList });
wx.setStorage({
key: "facilityPoiList",
data: facilityPoiList,
});
}
} catch (error) {
console.error(error);
}
},
switchLang() {
this.globalData.isEn = !this.globalData.isEn;
this.events.emit("lang", this.globalData.isEn);
......@@ -296,11 +385,27 @@ App({
title: this.globalData.isEn ? "Chinagoods Nav" : "小商AI导航",
});
},
setBuilding(currentBuilding) {
const now = Date.now();
if (this.lastSetBuildingTime && now - this.lastSetBuildingTime < 1000) {
return false;
}
if (!currentBuilding) return false;
if (this.currentBuilding.name === currentBuilding.name) return false;
this.currentBuilding = currentBuilding;
this.lastSetBuildingTime = now;
this.events.emit("building", currentBuilding);
return true;
},
focusPoi(poi) {
if (this.globalData.focusedPoi === poi) return;
this.globalData.focusedPoi = poi;
this.events.emit("focusedPoi", poi);
},
blurPoi() {
if (this.globalData.focusedPoi === null) return;
this.globalData.focusedPoi = null;
this.events.emit("focusedPoi", null);
},
......@@ -316,6 +421,19 @@ App({
isJumpMapApp: true,
navEndPageUrl: "/pages/nav-end/index",
navEndPageDelay: 0,
isEnableMultiBuildingSearch: true,
multiBuildingList: [
{
buildingId: "3307001549220",
buildingName: "义乌国际商贸城(二区东)",
buildingShortName: "二区东",
},
{
buildingId: "3307001948721",
buildingName: "义乌国际商贸城(六区)",
buildingShortName: "六区",
},
],
});
const themeInstance = new plugin.Theme();
......@@ -335,31 +453,52 @@ App({
pageIndex,
} = option;
console.log(option);
const building = getApp().buildings.find(
(el) => el.buildingId === buildingId,
);
const result = {
keyword,
data: [],
count: 0,
};
const { post, qmurl, shopIdMap, facilityIdMap } = getApp();
if (!shopIdMap) return result;
const { post, qmurl, poiApi } = getApp();
const {
data: { data },
} = await post(
`${qmurl}/api/v1/applet/searchShopFacilityList`,
{
keyword,
zone: building.zone,
},
{ lang: "zh" }
{ lang: "zh" },
);
// 提取需要查询的店铺信息
const shopInfoList = data.map(({ shopType, shopId }) => ({
shopType,
shopId,
boothNo: "", // 这里 boothNo 可能需要从其他地方获取,暂时设为空
}));
// 批量获取店铺详情
const shopDetails = await poiApi.getListByIds(shopInfoList);
// 创建临时映射,用于快速查找店铺详情
const shopDetailMap = {};
if (shopDetails) {
shopDetails.forEach((detail, index) => {
const { shopType, shopId } = shopInfoList[index];
shopDetailMap[`${shopType}_${shopId}`] = detail;
});
}
const list = data
.map(({ shopType, shopId }) =>
shopType === 1
? shopIdMap[shopId]
: shopType === 2
? facilityIdMap[shopId]
: null
)
.filter((el) => el.tx)
.map(({ shopType, shopId }) => {
// 从批量获取的详情中查找
const detail = shopDetailMap[`${shopType}_${shopId}`];
return detail;
})
.filter((el) => el && el.tx)
.map((poi, i) => {
const lat = Number(poi.tx.poi_location.latitude);
const lng = Number(poi.tx.poi_location.longitude);
......@@ -375,7 +514,7 @@ App({
});
const sliced = list.slice(
(pageIndex - 1) * pageSize,
pageIndex * pageSize
pageIndex * pageSize,
);
result.data = sliced;
result.count = list.length;
......@@ -383,6 +522,13 @@ App({
});
plugin.watchLocation({
callback: (el) => {
if (el.floorName && !["1F", "2F", "3F"].includes(el.floorName)) {
console.log("定位返回未知楼层名:", el.floorName);
}
if (el.floorName === "FO" || el.floorName === "OUT") {
delete el.buildingId;
delete el.floorName;
}
this.userLocation = el;
this.events.emit("userLocation", this.userLocation);
},
......@@ -435,7 +581,7 @@ App({
this.itineraryList = data;
this.itineraryMap = data.reduce(
(acc, nxt) => ({ ...acc, [nxt.shopId]: nxt.id }),
{}
{},
);
}
} catch (error) {
......
......@@ -11,7 +11,11 @@
"pages/aisearchresult/index",
"pages/itinerary/index",
"pages/nav-end/index",
"pages/poi-map/index"
"pages/poi-map/index",
"pages/verify-openId/index",
"pages/card-detail/index",
"pages/feedback/index",
"pages/explore/index"
],
"permission": {
"scope.userLocation": {
......@@ -34,7 +38,7 @@
"provider": "wx069ba97219f66d99"
},
"indoormap": {
"version": "1.3.27",
"version": "1.3.32",
"provider": "wxfe8f0709a4de371b",
"export": "pluginReport.js"
}
......
module.exports = Behavior({
behaviors: [],
properties: {},
data: {
currentBuilding: getApp().currentBuilding,
},
lifetimes: {
attached() {
this.setData({ currentBuilding: getApp().currentBuilding });
this.setBuilding = (currentBuilding) => {
this.setData({ currentBuilding });
if (this.onCurrentBuildingChange)
this.onCurrentBuildingChange(currentBuilding);
};
getApp().events.on("building", this.setBuilding);
},
detached() {
getApp().events.off("building", this.setBuilding);
},
},
});
......@@ -28,14 +28,13 @@ module.exports = Behavior({
},
methods: {
async toggleCollection(poi) {
const { collectionApi } = getApp();
const { collectionApi, buildingIdNameMap } = getApp();
try {
let success;
const collected = this.isCollected(poi);
try {
getApp().sensors?.track("CollectNavPosition", {
action_type: collected ? "取消收藏" : "收藏",
short_market_name: "二区东",
...(poi.tx
? {
x: Number(poi.tx.poi_location.longitude),
......@@ -46,8 +45,10 @@ module.exports = Behavior({
? {
nav_target_type: poi.facility.name,
booth_floor: poi.tx.poi_fl_seq,
short_market_name: buildingIdNameMap[poi.tx.bld_id],
}
: {
short_market_name: poi.ywZh.marketNameDtl,
store_name: poi.ywZh.name,
booth_addr_street: poi.ywZh.boothAddrStreet,
booth_cover_img: poi.ywZh.cover,
......
......@@ -6,8 +6,15 @@ Component({
/**
* 组件的初始数据
*/
data: { isIOS: getApp().isIOS },
data: { isIOS: getApp().isIOS, isSystem: true },
lifetimes: {
attached() {
const appAuthorizeSetting = wx.getAppAuthorizeSetting();
this.setData({
isSystem: appAuthorizeSetting.bluetoothAuthorized === "authorized",
});
},
},
/**
* 组件的方法列表
*/
......@@ -15,6 +22,13 @@ Component({
close() {
this.triggerEvent("close");
},
openAppAuthorizeSetting() {
wx.openAppAuthorizeSetting({
success(res) {
console.log(res);
},
});
},
doNothing() {},
},
});
......@@ -16,20 +16,47 @@
height: 100vh;
.image {
position: absolute;
top: 32px;
top: 120px;
left: 0;
right: 0;
margin: auto;
width: 304px;
height: 572px;
width: 302px;
height: 396px;
z-index: 1;
}
.close {
position: absolute;
top: 130px;
right: calc(50vw - 151px + 10px);
width: 32px;
height: 32px;
z-index: 2;
}
.btn {
position: absolute;
top: 542px;
top: 452px;
left: 0;
right: 0;
margin: auto;
width: 260px;
height: 38px;
z-index: 2;
}
.btn1 {
position: absolute;
top: 452px;
left: calc(50vw - 126px - 4px);
width: 126px;
height: 38px;
z-index: 2;
}
.btn2 {
position: absolute;
top: 452px;
right: calc(50vw - 126px - 4px);
margin: auto;
width: 126px;
height: 38px;
z-index: 2;
}
}
<textarea disabled class="bluetooth-bg"></textarea>
<view class="bluetooth-modal" bind:tap="close">
<image wx:if="{{isIOS}}" class="image" catch:tap="donothing" src="https://cdnimg.chinagoods.com/png/2025/09/01/dacfdaux2wtvloq0mfvhd48otw8gplir.png" mode="" />
<image wx:else catch:tap="donothing" class="image" src="https://cdnimg.chinagoods.com/png/2025/09/01/mbx4nn2dwya3xovr7lp1yfhzrn6qgxsq.png" catch:tap="doNothing" mode="" />
<block wx:if="{{isSystem}}">
<image wx:if="{{isIOS}}" class="image" catch:tap="doNothing" src="https://cdnimg.chinagoods.com/png/2025/10/30/iowxo8oegleiwnzm1lci81ngipjqxrkm.png" mode="" />
<image wx:else catch:tap="doNothing" class="image" src="https://cdnimg.chinagoods.com/png/2025/10/30/65vtmu6ebrcalmekbb9bimrykmcpnsqi.png" catch:tap="doNothing" mode="" />
<view class="btn" catch:tap="close"></view>
</block>
<block wx:else>
<image wx:if="{{isIOS}}" class="image" catch:tap="doNothing" src="https://cdnimg.chinagoods.com/png/2025/10/30/fu5mur0sheld26mgetp8hjddzh7fh64i.png" mode="" />
<image wx:else catch:tap="doNothing" class="image" src="https://cdnimg.chinagoods.com/png/2025/10/30/br1lik6ag7902e2kjmz3iy0ep5ctvkj6.png" catch:tap="doNothing" mode="" />
<view class="btn1" catch:tap="close"></view>
<view class="btn2" catch:tap="openAppAuthorizeSetting"></view>
</block>
<view class="close" catch:tap="close"></view>
</view>
\ No newline at end of file
......@@ -21,6 +21,13 @@ Component({
activeIcon: "./selectedSurroundings.png",
},
{
url: "/pages/explore/index",
text: "探索",
textEn: "Explore",
icon: "./explore.png",
activeIcon: "./selectedExplore.png",
},
{
url: "/pages/mine/index",
text: "我的",
textEn: "My",
......@@ -36,7 +43,7 @@ Component({
methods: {
handleTap({ currentTarget: { id } }) {
if (
["0", "1", "2"].includes(id) &&
["0", "1", "2", "3"].includes(id) &&
this.data.currentIndex !== Number(id)
) {
const { currentIndex } = this.data;
......
import langBehavior from "../../behaviors/langBehavior";
import buildingBehavior from "../../behaviors/buildingBehavior";
Component({
behaviors: [langBehavior],
behaviors: [langBehavior, buildingBehavior],
properties: {
floors: Array,
floorName: String,
......@@ -16,7 +17,18 @@ Component({
/**
* 组件的初始数据
*/
data: {},
data: {
reversedFloors: [],
scrollTop: 0,
},
observers: {
floors() {
this.setData({ reversedFloors: [...this.data.floors].reverse() });
},
"floorName,reversedFloors"() {
this.scrollToCurrent();
},
},
/**
* 组件的方法列表
......@@ -28,5 +40,28 @@ Component({
handleFloor({ currentTarget: { id: floorName } }) {
this.triggerEvent("floorname", floorName);
},
scrollToCurrent() {
if (this.scollContext) {
const index = this.data.reversedFloors.findIndex(
(item) => item === this.data.floorName
);
if (index > -1) {
const scrollTop = Math.max(0, (index - 2) * 36);
this.setData({ scrollTop });
}
}
},
},
lifetimes: {
attached() {
this.setData({ reversedFloors: [...this.data.floors].reverse() });
this.createSelectorQuery()
.select("#floors")
.node((res) => {
this.scollContext = res.node;
this.scrollToCurrent();
})
.exec();
},
},
});
......@@ -115,11 +115,9 @@
display: flex;
flex-direction: column;
top: -152px;
&.has-reset {
top: -202px;
}
right: 10px;
padding: 7px 4px;
left: 10px;
padding: 7px 0;
width: 40px;
z-index: 2;
gap: 2px;
align-items: center;
......@@ -131,10 +129,17 @@
width: 8px;
height: 3px;
}
.scroller {
width: 40px;
height: 104px;
.list {
display: flex;
width: 40px;
display: inline-flex;
flex-direction: column;
align-items: center;
gap: 4px;
padding: 0 4px;
box-sizing: border-box;
.floor {
display: flex;
width: 32px;
......@@ -154,14 +159,12 @@
}
}
}
}
}
.floors-mask {
position: absolute;
top: -152px;
&.has-reset {
top: -202px;
}
right: 10px;
left: 10px;
width: 40px;
height: 128px;
z-index: 1;
......
......@@ -15,9 +15,10 @@
<textarea disabled class="floors-mask {{showReset?'has-reset':''}}"></textarea>
<view class="floors {{showReset?'has-reset':''}}">
<image class="arrow" src="./floorUp.png" mode="" />
<scroll-view id="floors" scroll-y scroll-top="{{scrollTop}}" class="scroller" enhanced>
<view class="list">
<view wx:for="{{floors}}" class="floor {{floorName===item.floorName?'active':''}}" id="{{item.floorName}}" wx:key="floorName" bind:tap="handleFloor">{{item.displayName}}</view>
<view wx:for="{{reversedFloors}}" class="floor {{floorName===item?'active':''}}" id="{{item}}" wx:key="*this" bind:tap="handleFloor">{{item}}</view>
</view>
</scroll-view>
<image class="arrow" src="./floorDown.png" mode="" />
</view>
\ No newline at end of file
import langBehavior from "../../behaviors/langBehavior";
import buildingBehavior from "../../behaviors/buildingBehavior";
const langs = [
"简体中文",
"English",
"العربية",
// "العربية",
// "日本語",
"한국어",
"Deutsch",
"Français",
"Português",
"Español",
"Italiano",
"Bahasa Melayu",
"русский",
"Bahasa Indonesia",
"ภาษาไทย",
"Türkçe",
"繁體中文 香港/澳門",
"繁體中文 中國臺灣",
"Tiếng Việt",
// "한국어",
// "Deutsch",
// "Français",
// "Português",
// "Español",
// "Italiano",
// "Bahasa Melayu",
// "русский",
// "Bahasa Indonesia",
// "ภาษาไทย",
// "Türkçe",
// "繁體中文 香港/澳門",
// "繁體中文 中國臺灣",
// "Tiếng Việt",
];
Component({
behaviors: [langBehavior],
behaviors: [langBehavior, buildingBehavior],
properties: {
type: String,
},
......@@ -32,6 +33,8 @@ Component({
showOptions: false,
langs,
showTip: false,
buildings: getApp().buildings,
showBuildings: false,
},
/**
......@@ -44,6 +47,12 @@ Component({
closeOptions() {
this.setData({ showOptions: false });
},
openBuildings() {
this.setData({ showBuildings: true });
},
closeBuildings() {
this.setData({ showBuildings: false });
},
chooseLang({ currentTarget: { id: lang } }) {
if (
(lang === "简体中文" && this.data.isEn) ||
......@@ -60,5 +69,17 @@ Component({
this.setData({ showTip: false });
},
donothing() {},
changeBuilding({ currentTarget: { id } }) {
const building = this.data.buildings[id];
getApp().setBuilding(building);
getApp().blurPoi();
this.setData({ showBuildings: false });
},
handleFeedback() {
if (!getApp().checkUser()) return;
wx.navigateTo({
url: `/pages/feedback/index`,
});
},
},
});
.logo {
width: 24px;
height: 24px;
}
.langBtn {
position: absolute;
z-index: 2;
......@@ -6,13 +10,16 @@
display: flex;
flex-direction: column;
width: 40px;
height: 64px;
height: 50px;
gap: 2px;
justify-content: center;
align-items: center;
border-radius: 8px;
background: #fff;
box-shadow: 0px 2px 5px 0px rgba(0, 0, 0, 0.05);
&.index {
height: 150px;
}
&.white {
background: none;
box-shadow: none;
......@@ -25,13 +32,9 @@
color: #f3f3f3;
}
}
.logo {
width: 24px;
height: 24px;
}
.texts {
display: flex;
height: 24px;
flex-direction: column;
color: #333;
text-align: center;
......@@ -42,6 +45,49 @@
line-height: 12px; /* 120% */
}
}
.marketLang {
position: absolute;
z-index: 2;
top: 10px;
right: 10px;
display: flex;
flex-direction: column;
width: 40px;
height: 102px;
gap: 10px;
justify-content: center;
align-items: center;
border-radius: 8px;
background: #fff;
box-shadow: 0px 2px 5px 0px rgba(0, 0, 0, 0.05);
&.index {
height: 150px;
}
.btn {
display: flex;
flex-direction: column;
align-items: center;
gap: 2px;
color: #333;
text-align: center;
font-family: "PingFang SC";
font-size: 10px;
font-style: normal;
font-weight: 400;
line-height: 12px;
}
}
.lang-bg {
position: absolute;
z-index: 1;
top: 10px;
right: 10px;
width: 40px;
height: 64px;
&.index {
height: 102px;
}
}
.options-bg {
position: absolute;
z-index: 3000;
......@@ -52,6 +98,9 @@
border-radius: 0 0 8px 8px;
border-top: #f3f3f3;
background: #fff;
&.tmp {
height: 108px;
}
}
.options {
position: absolute;
......@@ -60,6 +109,9 @@
right: 0;
width: 100vw;
height: 196px;
&.tmp {
height: 108px;
}
.close {
position: absolute;
top: 16px;
......@@ -79,12 +131,49 @@
font-weight: 600;
line-height: 18px;
}
.row {
position: absolute;
top: 56px;
left: 0;
display: flex;
height: 40px;
width: 100vw;
padding: 0 12px;
gap: 4px;
.item {
position: relative;
display: flex;
align-items: center;
padding-left: 10px;
flex: 1;
height: 40px;
border-radius: 4px;
border-bottom: 0.5px solid rgba(255, 255, 255, 0.1);
background: #f7f7f7;
color: #333;
font-family: "PingFang SC";
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: 18px;
.tick {
position: absolute;
width: 12px;
height: 12px;
top: 0;
bottom: 0;
right: 18px;
margin: auto;
}
}
}
.langs {
position: absolute;
top: 56px;
left: 0;
width: 100vw;
height: 128px;
.grid {
width: 1064px;
height: 128px;
......@@ -165,3 +254,52 @@
height: 38px;
}
}
.buildings-bg {
position: absolute;
z-index: 3000;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
}
.buildings {
position: absolute;
z-index: 3001;
top: 10px;
right: 55px;
display: inline-flex;
padding: 2px;
flex-direction: column;
align-items: flex-start;
border-radius: 8px;
background: #4a4a4a;
.tri {
position: absolute;
top: 14px;
right: -3px;
width: 3px;
height: 10px;
}
.building {
display: flex;
padding-left: 10px;
padding-right: 10px;
align-items: center;
min-width: 140px;
height: 40px;
gap: 6px;
color: #fff;
font-family: "PingFang SC";
font-size: 12px;
font-style: normal;
font-weight: 600;
line-height: 16px;
}
.building + .building {
border-top: 1px solid rgba(255, 255, 255, 0.06);
}
}
.size-12 {
width: 12px;
height: 12px;
}
<view class="langBtn {{type}}" catch:tap="handleLang">
<view wx:if="{{type==='index'}}" class="marketLang {{type}}">
<view class="btn" catch:tap="openBuildings">
<image class="logo" src="./market.png" mode="" />
<view>{{isEn?'Market':'市场'}}</view>
</view>
<view class="btn" catch:tap="handleLang">
<image class="logo" src="./switch.png" mode="" />
<view>{{isEn?'Lang':'语言'}}</view>
</view>
<view class="btn" catch:tap="handleFeedback">
<image class="logo" src="./feedback.png" mode="" />
<view>{{isEn?'Report':'反馈'}}</view>
</view>
</view>
<view wx:else class="langBtn {{type}}" catch:tap="handleLang">
<block wx:if="{{type==='white'}}">
<image class="logo" src="./switchWhite.png" mode="" />
</block>
......@@ -9,16 +23,21 @@
<image class="logo" src="./switch.png" mode="" />
</block>
<view class="texts" wx:if="{{type!== 'white'}}">
<view>{{isEn?'Change':'更换'}}</view>
<view>{{isEn?'Lang':'语言'}}</view>
</view>
</view>
<textarea disabled class="options-bg" wx:if="{{showOptions}}"></textarea>
<view wx:if="{{showOptions}}" class="options">
<textarea disabled class="lang-bg {{type}}"></textarea>
<textarea disabled class="options-bg {{langs.length===2?'tmp':''}}" wx:if="{{showOptions}}"></textarea>
<view wx:if="{{showOptions}}" class="options {{langs.length===2?'tmp':''}}">
<view class="t1">{{isEn?'Change language':'更换语言'}}</view>
<image src="./close.png" class="close" mode="" bind:tap="closeOptions" />
<scroll-view scroll-x enhanced show-scrollbar="{{false}}" class="langs">
<view wx:if="{{langs.length===2}}" class="row">
<view class="item" wx:for="{{langs}}" wx:key="*this" bind:tap="chooseLang" id="{{item}}">{{item}}
<image wx:if="{{item==='简体中文'&&!isEn||item==='English'&&isEn}}" src="./tick.png" class="tick" mode="" />
</view>
</view>
<scroll-view wx:else scroll-x enhanced show-scrollbar="{{false}}" class="langs">
<view class="grid">
<view class="item" wx:for="{{langs}}" wx:key="*this" bind:tap="chooseLang" id="{{item}}">{{item}}
<image wx:if="{{item==='简体中文'&&!isEn||item==='English'&&isEn}}" src="./tick.png" class="tick" mode="" />
......@@ -32,3 +51,12 @@
<image catch:tap="donothing" class="image" src="https://cdnimg.chinagoods.com/png/2025/09/03/e3evgoktoetqebfymujhse0kmoxzjkt8.png" mode="" />
<view class="btn" catch:tap="closeTip"></view>
</view>
<textarea disabled class="buildings-bg" wx:if="{{showBuildings}}" bind:tap="closeBuildings"></textarea>
<view class="buildings" wx:if="{{showBuildings}}">
<image src="./tri.png" class="tri" mode="" />
<view wx:for="{{buildings}}" class="building" id="{{index}}" wx:key="name" catch:tap="changeBuilding">
<image wx:if="{{currentBuilding.name===item.name}}" class="size-12" src="./checked.png" mode="" />
<image wx:else class="size-12" src="./unchecked.png" mode="" />
{{isEn ? item.displayNameEn: item.displayName}}
</view>
</view>
\ No newline at end of file
......@@ -6,8 +6,15 @@ Component({
/**
* 组件的初始数据
*/
data: { isIOS: getApp().isIOS },
data: { isIOS: getApp().isIOS, isSystem: true },
lifetimes: {
attached() {
const appAuthorizeSetting = wx.getAppAuthorizeSetting();
this.setData({
isSystem: appAuthorizeSetting.locationAuthorized === "authorized",
});
},
},
/**
* 组件的方法列表
*/
......@@ -15,6 +22,13 @@ Component({
close() {
this.triggerEvent("close");
},
openAppAuthorizeSetting() {
wx.openAppAuthorizeSetting({
success(res) {
console.log(res);
},
});
},
doNothing() {},
},
});
......@@ -16,20 +16,47 @@
height: 100vh;
.image {
position: absolute;
top: 32px;
top: 120px;
left: 0;
right: 0;
margin: auto;
width: 304px;
height: 572px;
width: 302px;
height: 396px;
z-index: 1;
}
.close {
position: absolute;
top: 130px;
right: calc(50vw - 151px + 10px);
width: 32px;
height: 32px;
z-index: 2;
}
.btn {
position: absolute;
top: 542px;
top: 452px;
left: 0;
right: 0;
margin: auto;
width: 260px;
height: 38px;
z-index: 2;
}
.btn1 {
position: absolute;
top: 452px;
left: calc(50vw - 126px - 4px);
width: 126px;
height: 38px;
z-index: 2;
}
.btn2 {
position: absolute;
top: 452px;
right: calc(50vw - 126px - 4px);
margin: auto;
width: 126px;
height: 38px;
z-index: 2;
}
}
<textarea disabled class="bluetooth-bg"></textarea>
<view class="bluetooth-modal" bind:tap="close">
<image wx:if="{{isIOS}}" class="image" catch:tap="donothing" src="https://cdnimg.chinagoods.com/png/2025/09/01/l5z7pr16cdmgrjxqzwtkps1fl5m4yaiy.png" mode="" />
<image wx:else catch:tap="donothing" class="image" src="https://cdnimg.chinagoods.com/png/2025/09/01/6rkar7dc5ot10homibl4ohrnrun8vwtq.png" catch:tap="doNothing" mode="" />
<block wx:if="{{isSystem}}">
<image wx:if="{{isIOS}}" class="image" catch:tap="doNothing" src="https://cdnimg.chinagoods.com/png/2025/10/30/d9bcjoms6szz980q0ngpmaddd59v0jwh.png" mode="" />
<image wx:else catch:tap="doNothing" class="image" src="https://cdnimg.chinagoods.com/png/2025/10/30/srefp3tal0ys1ztdqnkdao8lhrg0pvul.png" catch:tap="doNothing" mode="" />
<view class="btn" catch:tap="close"></view>
</block>
<block wx:else>
<image wx:if="{{isIOS}}" class="image" catch:tap="doNothing" src="https://cdnimg.chinagoods.com/png/2025/10/30/o8oxwdtxr651n3ocmvanea8arvzsz95y.png" mode="" />
<image wx:else catch:tap="doNothing" class="image" src="https://cdnimg.chinagoods.com/png/2025/10/30/gk0taraemutjyhsrbfpzrupz3aqvttj2.png" catch:tap="doNothing" mode="" />
<view class="btn1" catch:tap="close"></view>
<view class="btn2" catch:tap="openAppAuthorizeSetting"></view>
</block>
<view class="close" catch:tap="close"></view>
</view>
\ No newline at end of file
import poiFocusBehavior from "../../behaviors/poiFocusBehavior";
import { findNearbyLocations } from "../../util";
import buildingBehavior from "../../behaviors/buildingBehavior";
import { findNearbyLocations, nearDistrict2East } from "../../util";
const defaultScale = 20;
const defaultRotate = 329;
const defaultLatitude = 29.331723;
const defaultLongitude = 120.106838;
const defaultSkew = 30;
Component({
behaviors: [poiFocusBehavior],
behaviors: [poiFocusBehavior, buildingBehavior],
properties: {
// 公共属性
buildingId: String,
floorName: String,
resetCount: Number,
userLocation: Object,
......@@ -33,15 +31,16 @@ Component({
},
data: {
latitude: defaultLatitude,
longitude: defaultLongitude,
markers: [],
latitude: null,
longitude: null,
setting: {
skew: defaultSkew,
rotate: defaultRotate,
scale: defaultScale,
},
pageShow: true,
markerIds: [],
markers: [],
},
observers: {
......@@ -49,8 +48,10 @@ Component({
this.handleReset();
},
buildingId() {
currentBuilding() {
this.setIndoorFloor();
this.handleModel();
this.handleReset();
},
floorName() {
......@@ -60,22 +61,21 @@ Component({
"floorName,acts"() {
if (!this.data.pageShow) return;
this.setActMarkers();
this.updateMarkers();
},
type() {
this.updateMarkers();
},
facilities() {
if (!this.data.pageShow) return;
this.setFacilityMarkers();
this.handleFacilities();
},
focusedPoi(poi) {
if (!this.data.pageShow) return;
try {
if (this.data.type === "index" && !poi) {
this.mapContext?.removeMarkers({
markerIds: [123],
fail: console.log,
});
this.updateMarkers();
} else {
this.handleFocusedPoi(poi);
}
......@@ -86,53 +86,18 @@ Component({
},
methods: {
handleReset() {
if (!this.data.pageShow) return;
const { userLocation } = this.data;
this.mapContext?.setIndoorFloor({
buildingId: this.data.buildingId,
floorName: this.data.floorName,
});
let moveToCenter = false;
if (userLocation) {
if (
userLocation.latitude < 29.333647 &&
userLocation.latitude > 29.329766 &&
userLocation.longitude < 120.108207 &&
userLocation.longitude > 120.105343
) {
moveToCenter = true;
}
}
this.mapContext?.moveToLocation({
latitude: moveToCenter ? userLocation.latitude : defaultLatitude,
longitude: moveToCenter ? userLocation.longitude : defaultLongitude,
fail: (e) => {
console.log(`复位失败,直接设置经纬度`, e);
this.setData({
latitude: moveToCenter ? userLocation.latitude : defaultLatitude,
longitude: moveToCenter ? userLocation.longitude : defaultLongitude,
});
},
});
this.setData({
setting: {
scale: moveToCenter ? defaultScale : 18.6,
skew: defaultSkew,
rotate: defaultRotate,
},
});
},
// 设置活动标记点(index页面)
setActMarkers() {
this.setData({
markers: this.data.acts
.filter(({ floor_name }) => floor_name === this.data.floorName)
getMarkers() {
const { type, facilities, acts, focusedPoi, floorName } = this.data;
let markers = [];
if (type === "index") {
markers.push(
...acts
.filter(({ floor_name }) => floor_name === floorName)
.map(({ latitude, longitude, short_name, id }) => ({
id,
latitude,
longitude,
iconPath: "./actMarker.png",
iconPath: "/assets/actMarker.png",
width: 1,
height: 1,
zIndex: 1,
......@@ -149,27 +114,126 @@ Component({
display: "ALWAYS",
},
})),
);
if (focusedPoi?.tx?.poi_location) {
markers.push({
id: Date.now(),
latitude: Number(focusedPoi.tx.poi_location.latitude),
longitude: Number(focusedPoi.tx.poi_location.longitude),
iconPath: "/assets/focusMarker.png",
width: 64,
height: 69,
zIndex: 2,
});
},
// 设置设施标记点(surroundings页面)
setFacilityMarkers() {
const { facilities } = this.data;
const markers = facilities.map(({ facilityId, facility, tx }) => ({
}
} else {
markers.push(
...facilities.map(({ facilityId, facility, tx }) => ({
id: facilityId,
latitude: Number(tx.poi_location.latitude),
longitude: Number(tx.poi_location.longitude),
...(focusedPoi?.facilityId === facilityId
? {
id: Date.now(),
zIndex: 2,
iconPath: "/assets/focusMarker.png",
width: 64,
height: 69,
}
: {
zIndex: 1,
iconPath: facility.logo,
width: 32,
height: 32,
}));
}),
})),
);
}
return markers;
},
updateMarkers() {
const { markerIds: lastMarkerIds } = this.data;
const markers = this.getMarkers();
const markerIds = markers.map(({ id }) => id);
const removeMarkerIds = lastMarkerIds.filter(
(markerId) => !markerIds.includes(markerId),
);
if (removeMarkerIds.length)
this.mapContext?.removeMarkers({
markerIds: removeMarkerIds,
fail: console.log,
});
const addMarkers = markers.filter(
({ id }) => !lastMarkerIds.includes(id),
);
if (addMarkers) {
this.mapContext?.addMarkers({
clear: true,
markers,
markers: addMarkers,
fail: console.log,
});
}
this.setData({ markerIds, markers });
},
handleReset() {
if (!this.data.pageShow) return;
const { buildings, currentBuilding } = getApp();
const { userLocation } = this.data;
this.mapContext?.setIndoorFloor({
buildingId: currentBuilding.buildingId,
floorName: this.data.floorName,
});
if (this.data.type === "surroundings" && this.data.facilities?.length) {
return;
}
let moveToUserLocation = false;
if (userLocation) {
// 只有当用户位置在当前查看的建筑中时才移动到用户位置
// 避免用户手动选择建筑后地图跳回实际位置
if (
userLocation.buildingId &&
userLocation.buildingId === currentBuilding.buildingId
) {
moveToUserLocation = true;
} else if (
!userLocation.buildingId &&
nearDistrict2East(userLocation)
) {
moveToUserLocation = true;
}
}
this.mapContext?.moveToLocation({
latitude: moveToUserLocation
? userLocation.latitude
: currentBuilding.latlng.lat,
longitude: moveToUserLocation
? userLocation.longitude
: currentBuilding.latlng.lng,
fail: (e) => {
console.log(`复位失败,直接设置经纬度`, e);
this.setData({
latitude: moveToUserLocation
? userLocation.latitude
: currentBuilding.latlng.lat,
longitude: moveToUserLocation
? userLocation.longitude
: currentBuilding.latlng.lng,
});
},
});
this.setData({
setting: {
scale: moveToUserLocation ? defaultScale : currentBuilding.scale,
skew: defaultSkew,
rotate: defaultRotate,
},
});
},
// 设置设施标记点(surroundings页面)
handleFacilities() {
this.updateMarkers();
const { facilities, markers } = this.data;
if (!facilities.length) return;
const bbox = markers.reduce(
......@@ -184,17 +248,17 @@ Component({
maxLng: -Infinity,
minLat: Infinity,
minLng: Infinity,
}
},
);
const center = {
latitude: (bbox.maxLat + bbox.minLat) / 2,
longitude: (bbox.maxLng + bbox.minLng) / 2,
};
const { currentBuilding } = getApp();
this.setData({
setting: {
scale: 18,
scale: currentBuilding.scale,
skew: defaultSkew,
rotate: defaultRotate,
},
......@@ -214,16 +278,27 @@ Component({
},
setIndoorFloor() {
if (this.mapContext && this.data.buildingId && this.data.floorName) {
if (
this.mapContext &&
getApp().currentBuilding.buildingId &&
this.data.floorName
) {
this.mapContext.setIndoorFloor({
buildingId: this.data.buildingId,
buildingId: getApp().currentBuilding.buildingId,
floorName: this.data.floorName,
});
}
},
handleIndoorChange(e) {
console.log(e);
const {
detail: { buildingId },
} = e;
console.log("handleIndoorChange", buildingId);
const { buildings } = getApp();
const building = buildings.find((el) => el.buildingId === buildingId);
const success = getApp().setBuilding(building);
if (success) this.triggerEvent("buildchange");
},
// 处理地图点击(仅index页面)
......@@ -232,28 +307,41 @@ Component({
this.getPoi({ longitude, latitude });
},
getPoi({ longitude, latitude }) {
const { txQmMap, indexData } = getApp();
async getPoi({ longitude, latitude }) {
const app = getApp();
const { floorName } = this.data;
const list = findNearbyLocations(
// 1. 直接解构获取第一个最近点 ID
const [shineiId] = findNearbyLocations(
latitude,
longitude,
floorName,
indexData
app.indexData,
);
if (list.length) {
const poi = txQmMap[list[0]];
getApp().focusPoi(poi);
} else {
getApp().blurPoi();
}
const baseInfo = app.txQmMap[shineiId];
// 2. 没找到基础信息,直接失焦并返回
if (!baseInfo) return app.blurPoi();
// 3. 动态判断类型和ID,发起请求
const poi = await app.poiApi.getOne({
shopType: baseInfo.facilityId ? "2" : "1",
shopId: baseInfo.shopId || baseInfo.facilityId,
boothNo: baseInfo.boothNo,
});
poi ? app.focusPoi(poi) : app.blurPoi();
},
// 处理标记点点击(surroundings页面)
handleMarkerTap({ detail: { markerId: facilityId } }) {
async handleMarkerTap({ detail: { markerId: facilityId } }) {
if (this.data.type !== "surroundings") return;
const { facilityIdMap } = getApp();
getApp().focusPoi(facilityIdMap[facilityId]);
const app = getApp();
const facility = await app.poiApi.getOne({
shopType: "2",
shopId: facilityId,
});
if (facility) app.focusPoi(facility);
},
// 处理标注点击(index页面)
......@@ -267,50 +355,9 @@ Component({
handleFocusedPoi(poi) {
try {
if (poi?.tx?.poi_location) {
if (this.data.type === "index") {
// index页面的处理
this.mapContext?.addMarkers({
markers: [
{
id: 123,
latitude: Number(poi.tx.poi_location.latitude),
longitude: Number(poi.tx.poi_location.longitude),
iconPath: "/assets/focusMarker.png",
width: 64,
height: 69,
zIndex: 2,
},
],
fail: console.log,
});
} else {
// surroundings页面的处理
const { facilities } = this.data;
const markers = facilities.map(({ facilityId, facility, tx }) => ({
id: facilityId,
latitude: Number(tx.poi_location.latitude),
longitude: Number(tx.poi_location.longitude),
...(poi.facilityId === facilityId
? {
zIndex: 2,
iconPath: "/assets/focusMarker.png",
width: 64,
height: 69,
}
: {
zIndex: 1,
iconPath: facility.logo,
width: 32,
height: 32,
}),
}));
this.mapContext?.addMarkers({
clear: true,
markers,
fail: console.log,
});
}
this.updateMarkers();
// 移动地图到POI位置
this.mapContext?.moveToLocation({
latitude: Number(poi.tx.poi_location.latitude),
longitude: Number(poi.tx.poi_location.longitude),
......@@ -323,21 +370,7 @@ Component({
},
});
} else if (this.data.type !== "index") {
const { facilities } = this.data;
const markers = facilities.map(({ facilityId, facility, tx }) => ({
id: facilityId,
latitude: Number(tx.poi_location.latitude),
longitude: Number(tx.poi_location.longitude),
zIndex: 1,
iconPath: facility.logo,
width: 32,
height: 32,
}));
this.mapContext?.addMarkers({
clear: true,
markers,
fail: console.log,
});
this.updateMarkers();
}
} catch (error) {
console.error(error);
......@@ -346,19 +379,16 @@ Component({
handleModel() {
try {
if (!this.mapContext) return;
if (this.data.floorName === "1F") {
if (
this.data.floorName === "1F" &&
this.data.currentBuilding.name === "二区东"
) {
this.mapContext.addVisualLayer({
layerId: "08adaf0275db",
zIndex: 1000,
success: (e) => {
console.log("addVisualLayer", e);
this.mapContext.executeVisualLayerCommand({
layerId: "08adaf0275db",
command: JSON.stringify({
function: "setRotation",
params: { rotationX: 90, rotationY: 59, rotationZ: 0 },
}),
});
this.executeVisualLayerCommandWithRetry();
},
fail: (e) => console.log("addVisualLayer", e),
});
......@@ -373,19 +403,43 @@ Component({
console.error(error);
}
},
executeVisualLayerCommandWithRetry(retryCount = 0) {
this.mapContext.executeVisualLayerCommand({
layerId: "08adaf0275db",
command: JSON.stringify({
function: "setRotation",
params: { rotationX: 90, rotationY: 59, rotationZ: 0 },
}),
fail: () => {
if (retryCount < 1) {
console.log("executeVisualLayerCommand failed, retrying...");
this.executeVisualLayerCommandWithRetry(retryCount + 1);
} else {
console.log("executeVisualLayerCommand failed after retry");
}
},
});
},
},
lifetimes: {
attached() {
if (getApp().userLocation?.buildingId) {
this.setData({
latitude: getApp().userLocation.latitude,
longitude: getApp().userLocation.longitude,
});
} else {
this.setData({
latitude: getApp().currentBuilding.latlng.lat,
longitude: getApp().currentBuilding.latlng.lng,
});
}
this.mapContext = wx.createMapContext("indexMap", this);
this.mapContext.setCenterOffset({ offset: [0.5, 0.4] });
this.handleModel();
// this.mapContext.setLocMarkerIcon({
// iconPath:
// "https://cdnimg.chinagoods.com/png/2025/08/13/mszq3hfw1m6vgnhb4dt9dhapyxr1yt6b.png",
// });
this.setIndoorFloor();
if (this.data.focusedPoi) this.handleFocusedPoi(this.data.focusedPoi);
},
......
<map class="map" id="indexMap" show-compass show-location="{{userLocation&&!(userLocation&&userLocation.floorName&&userLocation.floorName!==floorName)}}" enable-rotate enable-3D enable-overlooking enable-auto-max-overlooking enable-indoor subkey="QUIBZ-OTL6U-T2MVM-GMPIT-EON66-LGBCU" latitude="{{latitude}}" longitude="{{longitude}}" markers="{{markers}}" max-scale="22" setting="{{setting}}" bind:tap="{{type === 'index' ? 'handleTap' : ''}}" bind:poitap="{{type === 'index' ? 'handleTap' : ''}}" bind:markertap="{{type === 'surroundings' ? 'handleMarkerTap' : ''}}" bind:callouttap="{{type === 'index' ? 'handleCalloutTap' : ''}}" bind:indoorchange="handleIndoorChange"></map>
\ No newline at end of file
<map wx:if="{{latitude&&longitude}}" class="map" id="indexMap" show-compass show-location="{{userLocation&&!(userLocation&&userLocation.floorName&&userLocation.floorName!==floorName)}}" enable-rotate enable-3D enable-overlooking enable-auto-max-overlooking enable-indoor subkey="QUIBZ-OTL6U-T2MVM-GMPIT-EON66-LGBCU" latitude="{{latitude}}" longitude="{{longitude}}" max-scale="22" setting="{{setting}}" bind:tap="{{type === 'index' ? 'handleTap' : ''}}" bind:poitap="{{type === 'index' ? 'handleTap' : ''}}" bind:markertap="{{type === 'surroundings' ? 'handleMarkerTap' : ''}}" bind:callouttap="{{type === 'index' ? 'handleCalloutTap' : ''}}" bind:indoorchange="handleIndoorChange"></map>
\ No newline at end of file
......@@ -36,13 +36,14 @@ Component({
async handleGo() {
const { isEn, focusedPoi: poi, isIndex } = this.data;
try {
const { buildingIdNameMap } = getApp();
getApp().sensors?.track("NavEntryBtnClick", {
page_name: isIndex ? "导航弹出框" : "周边弹出框",
short_market_name: "二区东",
...(poi.tx
? {
x: Number(poi.tx.poi_location.longitude),
y: Number(poi.tx.poi_location.latitude),
short_market_name: buildingIdNameMap[poi.tx.bld_id],
}
: {}),
...(poi.facilityId
......
<view class="popup" wx:if="{{focusedPoi}}">
<slot></slot>
<view class="shop" wx:if="{{focusedPoi.facility&&focusedPoi.facility.name==='yandoocafe'}}">
<view class="shop" wx:if="{{focusedPoi.facility&&focusedPoi.facility.type===2}}">
<image wx:if="{{focusedPoi.facility.logo}}" src="{{focusedPoi.facility.logo}}" class="avatar" mode="" />
<view class="right">
<view class="c-gap-6">
......
import md5 from "./md5";
const app = "indoor";
const secret = "ysGsjd08sUxPsxz2";
/**
* 使用md5加密方式发起请求
* @param {*} url
* @param {*} queryData path为加密参数,必传
* @param {*} options
*/
function MD5Request(url, queryData = {}, options = {}) {
// 是否loading
if (options && options.loading) {
wx.showLoading({
title: "加载中",
mask: true,
});
}
const secretParams = getCommonReqParams(queryData);
url = url + queryData.path || "";
delete queryData.path;
const method = (options && options.method) || "GET";
return new Promise((resolve, reject) => {
wx.request({
url,
data: queryData,
method,
header: {
"Content-Type": "application/json; charset=UTF-8",
...secretParams,
},
success(res) {
resolve(res.data);
},
fail(e) {
console.log(e);
reject(e);
},
complete() {
if (options && options.delay) {
setTimeout(() => {
wx.hideLoading();
}, options.delay);
} else {
wx.hideLoading();
}
},
...options,
});
});
}
// 加密参数
function getCommonReqParams(params) {
const ts = Date.parse(new Date()) / 1000;
const path = params.path;
const sign = md5(path + secret + ts);
return {
app,
ts,
sign,
};
}
export default MD5Request;
......@@ -67,9 +67,9 @@ Page({
}
},
async takePhoto() {
const { state, states } = this.data;
try {
wx.showLoading();
const { state, states } = this.data;
if (state !== states.init) return;
this.setData({ state: states.takingPhoto, isAlbum: false });
const ctx = wx.createCameraContext();
......@@ -235,10 +235,6 @@ Page({
});
const shopId = params.shopId;
if (shopId) {
const { shopIdMap } = getApp();
if (!shopIdMap) return;
const shop = shopIdMap[shopId];
if (!shop) return;
wx.navigateTo({
url: `/pages/poi-map/index?shopId=${shopId}`,
});
......

1.0 KB | W: | H:

637 Bytes | W: | H:

pages/aisearchresult/add.png
pages/aisearchresult/add.png
pages/aisearchresult/add.png
pages/aisearchresult/add.png
  • 2-up
  • Swipe
  • Onion skin

951 Bytes | W: | H:

774 Bytes | W: | H:

pages/aisearchresult/added.png
pages/aisearchresult/added.png
pages/aisearchresult/added.png
pages/aisearchresult/added.png
  • 2-up
  • Swipe
  • Onion skin
import langBehavior from "../../behaviors/langBehavior";
import modalBehavior from "../../behaviors/modalBehavior";
import { toRoutePlan, getDistance } from "../../util";
import { toRoutePlan, getDistance, nearDistrict2East } from "../../util";
import mockData from "./mock.js";
const sortByStates = {
nosort: "nosort",
distanceDesc: "distanceDesc",
distanceAsc: "distanceAsc",
priceDesc: "priceDesc",
priceAsc: "priceAsc",
};
Page({
behaviors: [langBehavior, modalBehavior],
data: {
photoUrl: "",
rectVoList: [],
originalList: [],
voList: [],
currentIndex: 0,
itineraryMap: {},
userLocation: null,
sortByStates,
sortBy: sortByStates.nosort,
},
/**
......@@ -17,12 +29,16 @@ Page({
*/
async onLoad({ photoUrl }) {
this.setUserLocation = () => {
this.setData({ userLocation: getApp().userLocation });
const { userLocation } = getApp();
if (
userLocation &&
(userLocation.buildingId || nearDistrict2East(userLocation))
)
this.setData({ userLocation });
};
getApp().events.on("userLocation", this.setUserLocation);
this.setUserLocation();
photoUrl = decodeURIComponent(photoUrl);
await this.init(photoUrl);
if (!this.data.rectVoList.length) {
......@@ -50,67 +66,137 @@ Page({
app.events.off("itineraryRefresh", this.setItineraryMap);
}
},
handleDistanceSort() {
if (
this.data.sortBy !== sortByStates.distanceDesc &&
this.data.sortBy !== sortByStates.distanceAsc
) {
this.setData({ sortBy: sortByStates.distanceAsc });
} else if (this.data.sortBy === sortByStates.distanceAsc) {
this.setData({ sortBy: sortByStates.distanceDesc });
} else {
this.setData({ sortBy: sortByStates.nosort });
}
this.setSortedList();
},
handlePriceSort() {
if (
this.data.sortBy !== sortByStates.priceDesc &&
this.data.sortBy !== sortByStates.priceAsc
) {
this.setData({ sortBy: sortByStates.priceAsc });
} else if (this.data.sortBy === sortByStates.priceAsc) {
this.setData({ sortBy: sortByStates.priceDesc });
} else {
this.setData({ sortBy: sortByStates.nosort });
}
this.setSortedList();
},
handleNoSort() {
this.setData({ sortBy: sortByStates.nosort });
this.setSortedList();
},
setSortedList() {
if (!this.data.originalList || !Array.isArray(this.data.originalList))
return;
let list = [...this.data.originalList];
if (this.data.sortBy === sortByStates.nosort)
return this.setData({ voList: list });
list.sort((a, b) => {
switch (this.data.sortBy) {
case sortByStates.distanceAsc:
case sortByStates.distanceDesc:
// 无论升序还是降序,distance为null的元素都排在最后
if (a.distance === null && b.distance === null) return 0;
if (a.distance === null) return 1;
if (b.distance === null) return -1;
// 正常的距离比较
return this.data.sortBy === sortByStates.distanceAsc
? a.distance - b.distance
: b.distance - a.distance;
case sortByStates.priceAsc:
return a.price - b.price;
case sortByStates.priceDesc:
return b.price - a.price;
default:
return 0;
}
});
this.setData({ voList: list });
},
async init(photoUrl) {
try {
wx.showLoading();
console.log(photoUrl);
const { photoApi } = getApp();
const { rectVoList = [], voList } = await photoApi.search(photoUrl);
const originalList = await this.processVoList(voList);
this.setData({
photoUrl,
rectVoList: [{ cutImageURL: photoUrl }, ...rectVoList],
voList: this.processVoList(voList),
originalList,
});
this.setSortedList();
} catch (error) {
console.error(error);
const originalList = await this.processVoList(mockData.voList);
this.setData({
photoUrl,
rectVoList: [{ cutImageURL: photoUrl }],
originalList,
});
this.setSortedList();
} finally {
wx.hideLoading();
}
},
processVoList(voList) {
const { shopIdMap } = getApp();
const result = voList
.map((good) => ({
...good,
shop: shopIdMap && shopIdMap[good.shopId],
}))
.filter(({ shop }) => shop);
result.forEach((good) => {
try {
good.yuan = Math.floor(good.price);
good.fen =
"." + String(Math.round(good.price * 100) % 100).padStart(2, "0");
} catch (error) {
good.yuan = good.price;
console.error(error);
}
const poi = good.shop;
if (!poi.tx) good.distance = null;
if (!this.data.userLocation) good.distance = null;
async processVoList(voList) {
if (!Array.isArray(voList)) return [];
// 1. 批量填充
const list = await getApp().poiApi.populate(voList, {
getType: () => "1",
getId: (item) => item.shopId,
targetField: "shop",
});
const { userLocation } = this.data;
// 2. 过滤 + 格式化 (价格 & 距离)
return list
.filter((item) => item?.shop)
.map((item) => {
// 价格格式化
const price = Number(item.price) || 0;
item.yuan = Math.floor(price);
item.fen = `.${String(Math.round(price * 100) % 100).padStart(2, "0")}`;
// 距离计算
const tx = item.shop.tx;
if (tx && userLocation) {
try {
const lat = Number(poi.tx.poi_location.latitude);
const lng = Number(poi.tx.poi_location.longitude);
good.distance = Math.ceil(
getDistance(
lat,
lng,
this.data.userLocation.latitude,
this.data.userLocation.longitude
)
const dist = getDistance(
Number(tx.poi_location.latitude),
Number(tx.poi_location.longitude),
userLocation.latitude,
userLocation.longitude,
);
} catch (error) {
good.distance = null;
}
try {
// 楼层不同增加 60m 距离
const floorDiff = Math.abs(
Number(poi.tx.poi_fl_name[0]) -
Number(this.data.userLocation.floorName[0])
parseInt(tx.poi_fl_name) - parseInt(userLocation.floorName),
);
if (floorDiff && good.distance) good.distance += floorDiff;
} catch (error) {}
if (good.distance > 3000) good.distance = null;
item.distance = Math.ceil(dist) + (floorDiff > 0 ? 60 : 0);
} catch (e) {
console.error(e);
}
}
return item;
});
return result;
},
async handleRectImg(e) {
const {
......@@ -127,10 +213,12 @@ Page({
const res = await photoApi.search(this.data.photoUrl, rectVo.rect);
if (res.voList) {
const originalList = await this.processVoList(res.voList);
this.setData({
voList: this.processVoList(res.voList),
originalList,
currentIndex: Number(index),
});
this.setSortedList();
}
} catch (error) {
console.error(error);
......@@ -159,7 +247,13 @@ Page({
app.refreshItinerary();
}
wx.showToast({
title: success ? (isEn ? "success" : "成功") : isEn ? "fail" : "失败",
title: success
? isEn
? "success"
: "取消收藏"
: isEn
? "fail"
: "失败",
icon: "none",
});
} else {
......@@ -173,7 +267,7 @@ Page({
app.refreshItinerary();
}
wx.showToast({
title: id ? (isEn ? "success" : "成功") : isEn ? "fail" : "失败",
title: id ? (isEn ? "success" : "收藏成功") : isEn ? "fail" : "失败",
icon: "none",
});
}
......@@ -185,13 +279,14 @@ Page({
const { isEn, voList } = this.data;
const { shop: poi } = voList[index];
try {
const { buildingIdNameMap } = getApp();
getApp().sensors?.track("NavEntryBtnClick", {
page_name: "AI识物结果",
short_market_name: "二区东",
...(poi.tx
? {
x: Number(poi.tx.poi_location.longitude),
y: Number(poi.tx.poi_location.latitude),
short_market_name: buildingIdNameMap[poi.tx.bld_id],
}
: {}),
...(poi.facilityId
......
......@@ -83,10 +83,35 @@
bottom: 0;
border-radius: 16px 16px 0;
background: #f7f7f7;
padding: 8px 10px 0 10px;
padding: 4px 10px 0 10px;
display: flex;
flex-direction: column;
gap: 10px;
gap: 4px;
.tabs {
display: flex;
padding: 0 21px;
align-items: center;
justify-content: space-between;
&.dual {
padding: 0 77px;
}
.tab {
display: inline-flex;
align-items: center;
height: 38px;
color: #333;
text-align: center;
font-family: "PingFang SC";
font-size: 16px;
font-style: normal;
font-weight: 400;
gap: 4px;
.icon {
width: 10px;
height: 16px;
}
}
}
.title {
display: flex;
align-items: baseline;
......@@ -109,7 +134,7 @@
.scroll-y {
width: 100%;
flex: 1;
height: calc(100vh - 117px);
height: calc(100vh - 145px);
.goods {
display: inline-flex;
flex-direction: column;
......@@ -149,6 +174,7 @@
font-style: normal;
font-weight: 600;
line-height: 22px;
padding-right: 20px;
}
.t2 {
display: flex;
......@@ -203,20 +229,25 @@
position: absolute;
bottom: 0;
right: 0;
top: 0;
display: flex;
flex-direction: column;
gap: 24px;
.row {
align-items: flex-end;
justify-content: space-between;
.collect {
display: flex;
gap: 6px;
flex-direction: column;
gap: 2px;
align-items: center;
.t5 {
color: #333;
text-align: center;
font-family: "PingFang SC";
font-size: 10px;
font-size: 8px;
font-style: normal;
font-weight: 600;
line-height: 12px;
font-weight: 400;
line-height: 12px; /* 150% */
}
.t51 {
display: flex;
......@@ -239,6 +270,25 @@
}
}
}
.red-btn {
display: flex;
width: 64px;
height: 48px;
flex-direction: column;
align-items: center;
justify-content: center;
border-radius: 8px;
background: linear-gradient(90deg, #fa643c 0%, #e92927 100%);
font-family: "PingFang SC";
font-size: 10px;
font-style: normal;
font-weight: 600;
line-height: 12px;
color: white;
.meta {
color: var(--W-80, rgba(255, 255, 255, 0.8));
}
}
}
}
}
......@@ -308,4 +358,8 @@
width: 20px;
height: 20px;
}
.size-16 {
width: 16px;
height: 16px;
}
}
......@@ -13,6 +13,19 @@
<image class="tri" src="./tri.png" mode="" />
</view>
<view class="bottom">
<view class="tabs {{!userLocation?'dual':''}}">
<view class="tab" bind:tap="handleNoSort">{{isEn?'Comprehensive':'综合'}}</view>
<view class="tab" wx:if="{{!!userLocation}}" bind:tap="handleDistanceSort">{{isEn?'Distance':'距离'}}
<image wx:if="{{sortBy===sortByStates.distanceDesc}}" class="icon" src="./desc.png" mode="" />
<image wx:elif="{{sortBy===sortByStates.distanceAsc}}" class="icon" src="./asc.png" mode="" />
<image wx:else class="icon" src="./sort.png" mode="" />
</view>
<view class="tab" bind:tap="handlePriceSort">{{isEn?'Price':'价格'}}
<image wx:if="{{sortBy===sortByStates.priceDesc}}" class="icon" src="./desc.png" mode="" />
<image wx:elif="{{sortBy===sortByStates.priceAsc}}" class="icon" src="./asc.png" mode="" />
<image wx:else class="icon" src="./sort.png" mode="" />
</view>
</view>
<view class="title" wx:if="{{isEn}}">
Find similar stores for you
<text class="red">{{voList.length}}</text>
......@@ -44,21 +57,20 @@
</view>
</view>
<view class="right-corner">
<view class="row" catch:tap="handleItinerary" id="{{index}}">
<view class="collect" catch:tap="handleItinerary" id="{{index}}">
<image wx:if="{{itineraryMap[item.shopId]}}" class="size-20" src="./added.png" mode="" />
<image wx:else class="size-20" src="./add.png" mode="" />
<view wx:if="{{itineraryMap[item.shopId]}}" class="t5 red">
{{isEn?'Added itinerary':'已加行程'}}
<view class="t5" wx:if="{{itineraryMap[item.shopId]}}">
{{isEn?'Collected':'已收藏'}}
</view>
<view wx:else class="t5">{{isEn?'Add itinerary':'加入行程'}}</view>
<view class="t5" wx:else>
{{isEn?'Collect':'收藏'}}
</view>
<view class="row" wx:if="{{item.shop&&item.shop.tx}}" id="{{index}}" bind:tap="handleGo">
<image class="size-20" src="./nav.png" mode="" />
<view class="t5" wx:if="{{!item.distance}}">{{isEn?'Go Here':'到这去'}}</view>
<view wx:else class="t51">
<view class="t511">{{item.distance}}m</view>
<view class="t512">{{isEn?'Go Here':'到这去'}}</view>
</view>
<view class="red-btn" wx:if="{{item.shop&&item.shop.tx}}" id="{{index}}" bind:tap="handleGo">
<image class="size-16" src="./wnav.png" mode="" />
<view>{{isEn?'Go Here':'立即导航'}}</view>
<view class="meta" wx:if="{{item.distance}}">{{item.distance}}m</view>
</view>
</view>
</view>
......
const mockData = {
voList: [
{
boothNo: "71757",
shopId: "6337134",
shopName: "慕琦银饰",
contacter: "方朝宝",
contactPhone: "13738990708",
mainGoodsArr: ["饰品珠宝"],
level2: "六区/1/3/71757",
shopAddress: "国际商贸城六区一楼3街 4单元71756 71757",
shopCoverUrl:
"https://cdnimg.chinagoods.com//png/2025/09/11/ks4fgtbcvytyfzuayk9noiqzfqhv0n3a.png",
boothAddress: "六区/1/3/71757",
shopDescribe:
"专业生产珠宝饰品的一家实体工厂,自主设计、生产、销售团队,在义乌小商品城有实体店。产品出口世界各地,近几年进军国内市场。以先进的商业操作流程,极具竞争力的价格提供高质产品,期待与您的合作!!!",
marketZone: "60",
marketNameDtl: "六区",
boothAddrStreet: "3",
addrFloor: "1",
boothAddrUnit: "4",
newDetail:
"专业生产珠宝饰品的一家实体工厂,自主设计、生产、销售团队,在义乌小商品城有实体店。产品出口世界各地,近几年进军国内市场。以先进的商业操作流程,极具竞争力的价格提供高质产品,期待与您的合作!!!",
entryYearCnt: "14",
goodsTitle: "慕琦银饰四叶草耳钉晶莹剔透浪漫优雅蝴蝶透亮",
goodsCover: [
"https://cdnimg.chinagoods.com/jpg/2025/09/26/ur901blgtgvek6wjvsuzdwcnxy3uot7h.jpg",
"https://cdnimg.chinagoods.com/jpg/2025/09/26/dtfneykabasiwk7txha80kadjx18sjvv.jpg",
"https://cdnimg.chinagoods.com/jpg/2025/09/26/jkykfxzsletpkecwwghuzsuqdsghtwnh.jpg",
"https://cdnimg.chinagoods.com/jpg/2025/09/26/tffeg3hz866zxeiaydioxfmd156s9rbc.jpg",
"https://cdnimg.chinagoods.com/jpg/2025/09/26/p0xyrnstopvvswtgzs0giga6fmhiovdc.jpg",
],
price: 216,
},
{
boothNo: "71857",
shopId: "6334914",
shopName: "靓黛饰品",
contacter: "周建祥",
contactPhone: "18057953706",
mainGoodsArr: [
"耳饰",
"项链",
"戒指",
"胸饰",
"一体式耳扣",
"韩国东大门饰品",
"无耳洞耳夹",
],
level2: "六区/1/1/71857",
shopAddress: "国际商贸城六区一楼1街 5单元71857",
shopCoverUrl:
"https://cdnimg.chinagoods.com//jpg/2025/09/25/cx4bd8t1jokdfimsjwfwndczqwsyo8cv.jpg",
boothAddress: "六区/1/1/71857",
shopDescribe:
"我们是一家专业做中高端饰品的工厂店,有十二年的生产做货经验和选品经验,产品款式多,更新快,出口韩国品牌连锁店等世界各地,欢迎大家咨询",
marketZone: "60",
marketNameDtl: "六区",
boothAddrStreet: "1",
addrFloor: "1",
boothAddrUnit: "5",
newDetail:
"我们是一家专业做中高端饰品的工厂店,有十二年的生产做货经验和选品经验,产品款式多,更新快,出口韩国品牌连锁店等世界各地,欢迎大家咨询",
entryYearCnt: "9",
goodsTitle:
"独特小众轻奢高级感天然淡水珍珠蝴蝶耳环耳钉女2024年新款耳夹潮",
goodsCover: [
"https://cdnimg.chinagoods.com/img/ylbm/img/ibank/O1CN01pFkjhj1Cti0LZqdD4_!!2208407030139-0-cib.jpg",
"https://cdnimg.chinagoods.com/img/ylbm/img/ibank/O1CN01VV6wuW1Cti0ODpHSR_!!2208407030139-0-cib.jpg",
"https://cdnimg.chinagoods.com/img/ylbm/img/ibank/O1CN01aozoTC1Cti0LZoYJb_!!2208407030139-0-cib.jpg",
"https://cdnimg.chinagoods.com/img/ylbm/img/ibank/O1CN01bbFu0Z1Cti0C37hhq_!!2208407030139-0-cib.jpg",
"https://cdnimg.chinagoods.com/img/ylbm/img/ibank/O1CN019g0ToM1Cti0KKazGf_!!2208407030139-0-cib.jpg",
],
price: 13.75,
},
{
boothNo: "71553",
shopId: "6334923",
shopName: "彩帛饰品",
contacter: "陈柯尹",
contactPhone: "13806790992",
mainGoodsArr: ["饰品"],
level2: "六区/1/4/71553",
shopAddress: "国际商贸城六区一楼4街 3单元71553",
shopCoverUrl:
"https://cdnimg.chinagoods.com//png/2025/07/10/ilewa4des3ku0mn10ps7ljpczv94pawx.png",
boothAddress: "六区/1/4/71553",
shopDescribe:
"我们是一家专业生产珠宝首饰的生产厂家,我们产品款式多,欢迎大家咨询",
marketZone: "60",
marketNameDtl: "六区",
boothAddrStreet: "4",
addrFloor: "1",
boothAddrUnit: "3",
newDetail: "",
entryYearCnt: "4",
goodsTitle: "丽梵蒂1619天然淡水珍珠微镶锆石花朵耳环",
goodsCover: [
"https://ywmall-1301563501.cos.ap-shanghai.myqcloud.com/jpg/2022/05/08/9dfa58f14d12e7a4bef797f6b01697f0.jpg",
"https://ywmall-1301563501.cos.ap-shanghai.myqcloud.com/jpg/2022/05/08/682440d3d635eb79d649d335d15aa7ec.jpg",
"https://ywmall-1301563501.cos.ap-shanghai.myqcloud.com/jpg/2022/05/08/35f4348dd619a09c2e53ef68e9ba667d.jpg",
],
price: 12.9,
},
{
boothNo: "71945",
shopId: "6335062",
shopName: "银蒂饰品",
contacter: "朱素芳",
contactPhone: "13757922558",
mainGoodsArr: ["饰品珠宝"],
level2: "六区/1/横街/71945",
shopAddress: "国际商贸城六区一楼2街 4单元71740 71759 71945",
shopCoverUrl:
"https://cdnimg.chinagoods.com//jpg/2025/09/22/rkllbmegs8tcfoa5fblbn8kah6hajonw.jpg",
boothAddress: "六区/1/横街/71945",
shopDescribe:
"我们是一家专业生产珠宝饰品的生产厂家,我们的产品款式多,更新快,出口到世界各地,店铺内不代表全部,运费&款式,可以客服沟通,欢迎大家咨询!",
marketZone: "60",
marketNameDtl: "六区",
boothAddrStreet: "横街",
addrFloor: "1",
boothAddrUnit: "5",
newDetail:
"我们是一家专业生产珠宝饰品的生产厂家,我们的产品款式多,更新快,出口到世界各地,店铺内不代表全部,运费&款式,可以客服沟通,欢迎大家咨询!",
entryYearCnt: "16",
goodsTitle:
"ins简约高贵S925银耳钉女耳环 礼物感红色耳饰 2021年新款厂家批发",
goodsCover: [
"https://cdnimg.chinagoods.com/jpg/2025/09/22/sqmmpso0eahhvokljpdyot7syfhduii6.jpg",
"https://cdnimg.chinagoods.com/jpg/2025/09/22/beyh1rufsdqhxmzi2lav6eh7bcq1dpap.jpg",
"https://cdnimg.chinagoods.com/jpg/2025/09/22/duphw90uwpvmwvxdk9vvx9tjxropcrds.jpg",
"https://cdnimg.chinagoods.com/jpg/2025/09/22/gyt3w1fyqm4igjh7bi0dv0azklqovzzs.jpg",
"https://cdnimg.chinagoods.com/jpg/2025/09/22/3qdfuqi7n8qfawdkhf3wdr6fppdfiwve.jpg",
],
price: 160,
},
{
boothNo: "71445",
shopId: "6335059",
shopName: "菲跃珠宝",
contacter: "李丽",
contactPhone: "15757992335",
mainGoodsArr: ["珠宝", "K金珠宝"],
level2: "六区/1/2/71445",
shopAddress: "国际商贸城六区一楼2街 3单元71445",
shopCoverUrl:
"https://cdnimg.chinagoods.com//png/2025/07/17/5fse99x1mxmd8d71s5qe4lwovwwm7an2.png",
boothAddress: "六区/1/2/71445",
shopDescribe:
"专注高品质珠宝设计、加工与零售,精选钻石、翡翠、彩宝等材质,提供个性化定制服务,传承工艺,诚信经营。",
marketZone: "60",
marketNameDtl: "六区",
boothAddrStreet: "2",
addrFloor: "1",
boothAddrUnit: "3",
newDetail:
"专注高品质珠宝设计、加工与零售,精选钻石、翡翠、彩宝等材质,提供个性化定制服务,传承工艺,诚信经营。",
entryYearCnt: "7",
goodsTitle:
"文艺风气质立体蝴蝶结珍珠耳钉女超闪s925银针高级感甜美时尚耳环",
goodsCover: [
"https://cdnimg.chinagoods.com/jpg/2025/07/17/67xl0lamwh9qbfzunbdswkp72yyypcfo.jpg",
"https://cdnimg.chinagoods.com/jpg/2025/07/17/ip9hpy7m2ploigfy23phoakt2oybsnkv.jpg",
"https://cdnimg.chinagoods.com/jpg/2025/07/17/wipyrxwfideyzzkzr9zmhj9tryiik54y.jpg",
"https://cdnimg.chinagoods.com/jpg/2025/07/17/qzn8gbxxldhmowjhrv5mnhwcq7jzf1b3.jpg",
"https://cdnimg.chinagoods.com/jpg/2025/07/17/94bsljjschncrwan5uainghs14hjyeso.jpg",
],
price: 59,
},
{
boothNo: "71678",
shopId: "6337379",
shopName: "麦沁",
contacter: "孙小利",
contactPhone: "15057825050",
mainGoodsArr: ["饰品", "耳饰", "项链"],
level2: "六区/1/1/71678",
shopAddress: "国际商贸城六区",
shopCoverUrl:
"https://cdnimg.chinagoods.com//png/2025/09/15/twral1oa8ni9msm07bmc7ddzvzjqizqw.png",
boothAddress: "六区/1/1/71678",
shopDescribe:
"我们是一家专业生产饰品的生产厂家,我们的产品款式多,质量好,出口到世界各地,欢迎大家咨询",
marketZone: "60",
marketNameDtl: "六区",
boothAddrStreet: "1",
addrFloor: "1",
boothAddrUnit: "4",
newDetail: "",
entryYearCnt: "9",
goodsTitle: "轻奢高级感镶钻锆石水晶耳扣简约个性不对称百搭气质耳环女",
goodsCover: [
"https://cdnimg.chinagoods.com/img/ylbm/img/ibank/O1CN01kddV6X1K33QTcGYoQ_!!2215472041107-0-cib.jpg",
"https://cdnimg.chinagoods.com/img/ylbm/img/ibank/O1CN01MflfuI1K33QSuc5yy_!!2215472041107-0-cib.jpg",
"https://cdnimg.chinagoods.com/img/ylbm/img/ibank/O1CN01uZdm4y1K33QQtWBFd_!!2215472041107-0-cib.jpg",
"https://cdnimg.chinagoods.com/img/ylbm/img/ibank/O1CN01mzrD7M1K33QS9Tnmt_!!2215472041107-0-cib.jpg",
"https://cdnimg.chinagoods.com/img/ylbm/img/ibank/O1CN01hWdeV81K33QLHtYBe_!!2215472041107-0-cib.jpg",
],
price: 1.98,
},
{
boothNo: "71719",
shopId: "6334927",
shopName: "亦莎",
contacter: "孙灵娟",
contactPhone: "15959252436",
mainGoodsArr: ["珠宝饰品"],
level2: "六区/1/2/71719",
shopAddress: "国际商贸城六区一楼2街 4单元71719",
shopCoverUrl:
"https://cdnimg.chinagoods.com//png/2025/07/10/nnm0gjw0fbmnw4yz5pgefwnph4sige5h.png",
boothAddress: "六区/1/2/71719",
shopDescribe:
"店铺主营各类原创手工中国风天然淡水珍珠耳环手链项链胸针手作!原创设计,品质保证,欢迎来店咨询!",
marketZone: "60",
marketNameDtl: "六区",
boothAddrStreet: "2",
addrFloor: "1",
boothAddrUnit: "4",
newDetail:
"店铺主营各类原创手工中国风天然淡水珍珠耳环手链项链胸针手作!原创设计,品质保证,欢迎来店咨询!",
entryYearCnt: "5",
goodsTitle: "一对淡水珍珠耳钉s925纯银银针耳钉女ins风百搭新品耳饰小众设计",
goodsCover: [
"https://cdnimg.chinagoods.com/jpg/2025/09/07/civfmpmfiff1wibtzzokkiuml6ll3urj.jpg",
"https://cdnimg.chinagoods.com/jpg/2025/09/07/oxoa8ulckglmcrdedypkj1abniimpp4w.jpg",
"https://cdnimg.chinagoods.com/jpg/2025/09/07/denrssns2szpcg1yktqikgut6k05dyfb.jpg",
"https://cdnimg.chinagoods.com/jpg/2025/09/07/d8ourbkxs5mqfhxpdpumievdsreymwir.jpg",
"https://cdnimg.chinagoods.com/jpg/2025/09/07/tciysvk8nfxhtndkukoizlj8t0jx7by7.jpg",
],
price: 22,
},
{
boothNo: "71453",
shopId: "6334972",
shopName: "HYACINTH珠宝饰品",
contacter: "徐勇",
contactPhone: "13926893639",
mainGoodsArr: ["耳环", "项链", "手链", "戒指"],
level2: "六区/1/2/71453",
shopAddress: "国际商贸城六区一楼2街 3单元71453",
shopCoverUrl:
"https://cdnimg.chinagoods.com//png/2025/07/15/xpu2apw0gco9lot0msxh0hxpivo4kwdo.png",
boothAddress: "六区/1/2/71453",
shopDescribe: "主营:耳环、项链、耳夹",
marketZone: "60",
marketNameDtl: "六区",
boothAddrStreet: "2",
addrFloor: "1",
boothAddrUnit: "3",
newDetail: "主营:耳环、项链、耳夹",
entryYearCnt: "0",
goodsTitle:
"Hyacinth小众设计几何格子耳钉韩国ins小巧气质百搭学生耳饰2025年新款潮黄铜锆石",
goodsCover: [
"https://cdnimg.chinagoods.com/img/ylbm/img/ibank/O1CN01t7sPbq23acwtTb3r3_!!3186407272-0-cib.jpg",
"https://cdnimg.chinagoods.com/img/ylbm/img/ibank/O1CN01ERNPuc23acwriCw4C_!!3186407272-0-cib.jpg",
"https://cdnimg.chinagoods.com/img/ylbm/img/ibank/O1CN01oHdnwB23acwtvVaMl_!!3186407272-0-cib.jpg",
"https://cdnimg.chinagoods.com/img/ylbm/img/ibank/O1CN01afRftl23acwx0KcZ3_!!3186407272-0-cib.jpg",
"https://cdnimg.chinagoods.com/img/ylbm/img/ibank/O1CN01R8QzQr23acwsO8aJM_!!3186407272-0-cib.jpg",
],
price: 12.3,
},
{
boothNo: "71874",
shopId: "6335186",
shopName: "四叶草",
contacter: "吴旭东",
contactPhone: "13067708080",
mainGoodsArr: ["耳环", "手链", "项链"],
level2: "六区/1/2/71874",
shopAddress: "国际商贸城六区一楼2街 5单元71874",
shopCoverUrl:
"https://cdnimg.chinagoods.com//png/2025/07/28/8nrggkrya1ch7cciyyyxtojogrgrv6ru.png",
boothAddress: "六区/1/2/71874",
shopDescribe: "主要经营各种饰品工厂直销来样定做欢迎前来采购",
marketZone: "60",
marketNameDtl: "六区",
boothAddrStreet: "2",
addrFloor: "1",
boothAddrUnit: "5",
newDetail: "主要经营各种饰品工厂直销来样定做欢迎前来采购",
entryYearCnt: "0",
goodsTitle:
"时尚百搭韩系胸针高级感小众设计别针高档装饰服装固定防走光胸花",
goodsCover: [
"https://cdnimg.chinagoods.com/jpg/2025/07/28/beibn9fxmtcclbxypbwn1f7rkuclx1jx.jpg",
"https://cdnimg.chinagoods.com/jpg/2025/07/28/fzdg5h8pvrwhdak1uboqjsqchmqitrjv.jpg",
"https://cdnimg.chinagoods.com/jpg/2025/07/28/c0xvvbba0a9vlufng5maj9srwocbg4e1.jpg",
],
price: 50,
},
],
};
export default mockData;
import langBehavior from "../../behaviors/langBehavior";
import { getPoiName } from "../../util";
Page({
behaviors: [langBehavior],
/**
* 页面的初始数据
*/
data: {
card: null,
collapsed: true,
isOverflow: false,
pois: [],
scrollTop: 0,
showToast: false,
},
/**
* 生命周期函数--监听页面加载
*/
onLoad({ cardId }) {
this.getCard(cardId);
},
back() {
wx.navigateBack();
},
async getCard(cardId) {
const app = getApp();
try {
const card = await app.cardApi.get(cardId);
if (!card) return wx.navigateBack();
this.setData({ card });
// 1. 自动填充 (默认配置即可匹配 shopType/shopId 字段,且会自动过滤无效项)
const list = await app.poiApi.populate(card.activityShopList || []);
// 2. 格式化并更新视图
this.setData({
pois: list.map((item) => ({
...item,
poiName: getPoiName(item.poi, this.data.isEn),
})),
});
wx.nextTick(() => this.checkTextOverflow());
} catch (error) {
console.error(error);
wx.navigateBack();
}
},
async toPoi({ currentTarget: { id } }) {
const poi = this.data.pois.find((el) => el.shopId === id);
if (poi.checkInStatus === 1) return;
if (this.data.card.isFinish) {
this.setData({ showToast: true });
await new Promise((resolve) => {
setTimeout(() => {
this.setData({ showToast: false });
resolve();
}, 1000);
});
}
if (poi.shopType === 1)
wx.navigateTo({
url: `/pages/poi-map/index?shopId=${poi.shopId}&isCardNav=true`,
});
else {
wx.navigateTo({
url: `/pages/poi-map/index?facilityId=${poi.shopId}&isCardNav=true`,
});
}
},
showDetail() {
wx.previewImage({
urls: [this.data.card?.pointMapImage],
});
},
handleScroll({ detail: { scrollTop } }) {
this.setData({ scrollTop });
},
/**
* 生命周期函数--监听页面初次渲染完成
*/
onReady() {},
/**
* 生命周期函数--监听页面显示
*/
onShow() {
const menuButtonInfo = wx.getMenuButtonBoundingClientRect();
const windowInfo = wx.getWindowInfo();
const rightPadding = windowInfo.screenWidth - menuButtonInfo.right;
const contentTop = menuButtonInfo.top;
this.setData({
menuButtonInfo,
rightPadding,
contentTop,
});
},
// 添加toggle点击事件
toggleCollapse() {
this.setData({
collapsed: !this.data.collapsed,
});
},
// 添加判断文本是否超过3行的逻辑
checkTextOverflow() {
// 先临时设置为展开状态,获取完整高度
this.setData({ collapsed: false }, () => {
const query = wx.createSelectorQuery();
query
.select(".text")
.boundingClientRect((rect) => {
const lineHeight = 23; // 与样式中定义的line-height一致
const maxLines = 3;
const maxHeight = lineHeight * maxLines;
const isOverflow = rect.height > maxHeight;
// 根据是否溢出设置初始状态
this.setData({
isOverflow,
collapsed: isOverflow, // 如果溢出,默认折叠;否则,默认展开
});
})
.exec();
});
},
/**
* 生命周期函数--监听页面隐藏
*/
onHide() {},
/**
* 生命周期函数--监听页面卸载
*/
onUnload() {
getApp().blurPoi();
},
/**
* 页面相关事件处理函数--监听用户下拉动作
*/
onPullDownRefresh() {},
/**
* 页面上拉触底事件的处理函数
*/
onReachBottom() {},
/**
* 用户点击右上角分享
*/
onShareAppMessage() {},
});
{
"usingComponents": {},
"navigationStyle": "custom"
}
.card {
position: relative;
width: 100vw;
height: 100vh;
.bg {
position: absolute;
width: 100vw;
height: 100vh;
z-index: 1;
}
.bgTop {
position: absolute;
width: 100vw;
z-index: 2;
}
.header {
position: absolute;
top: 0;
left: 0;
z-index: 5;
display: flex;
width: 100vw;
padding-bottom: 6px;
box-sizing: border-box;
align-items: center;
padding-left: 10px;
.back {
width: 22px;
height: 22px;
}
.placeholder {
width: 22px;
}
.title {
flex: 1;
display: flex;
justify-content: center;
align-items: center;
color: #333;
font-family: "PingFang SC";
font-size: 17px;
font-style: normal;
font-weight: 500;
line-height: normal;
}
}
.toast {
position: absolute;
z-index: 6;
top: 99px;
left: 0;
right: 0;
margin: auto;
width: 312px;
padding: 16px 24px;
display: flex;
flex-direction: column;
gap: 8px;
border-radius: 8px;
background: rgba(0, 0, 0, 0.7);
align-items: center;
.t1 {
color: #fff;
font-family: "PingFang SC";
font-size: 16px;
font-style: normal;
font-weight: 600;
line-height: 22px;
}
.t2 {
color: #fff;
font-family: "PingFang SC";
font-size: 12px;
font-style: normal;
font-weight: 400;
line-height: 16px;
}
}
.scroller {
position: absolute;
box-sizing: border-box;
top: 0;
left: 0;
z-index: 4;
width: 100vw;
height: 100vh;
.scroll-content {
position: relative;
width: 100vw;
display: inline-flex;
flex-direction: column;
align-items: center;
gap: 16px;
padding: 0 12px;
.top {
width: 100%;
height: 216px;
}
.desc {
width: calc(100vw - 24px);
display: flex;
flex-direction: column;
align-items: center;
gap: 20px;
padding: 16px 14px;
border-radius: 16px;
border: 1px solid var(--W-100, #fff);
background: linear-gradient(
180deg,
#fff 0%,
rgba(255, 255, 255, 0.2) 100%
);
backdrop-filter: blur(4px);
.text {
width: 100%;
color: #333;
font-family: "PingFang SC";
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: 23px;
/* 默认展示全部 */
overflow: visible;
text-overflow: unset;
display: block;
}
/* 折叠状态 */
.text:not(.expanded) {
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
}
.toggle {
display: flex;
align-items: center;
gap: 4px;
color: #666;
font-family: "PingFang SC";
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: normal;
.arrow {
width: 14px;
height: 14px;
}
}
}
.pois {
position: relative;
width: calc(100vw - 24px);
border-radius: 16px;
padding: 12px;
overflow: hidden;
display: inline-flex;
flex-direction: column;
gap: 14px;
.title {
position: relative;
z-index: 2;
width: 100%;
color: #fff;
font-family: "PingFang SC";
font-size: 16px;
font-style: normal;
font-weight: 600;
line-height: normal;
vertical-align: baseline;
.meta {
display: inline;
margin-left: 8px;
color: rgba(255, 255, 255, 0.75);
font-family: "PingFang SC";
font-size: 12px;
font-style: normal;
font-weight: 400;
line-height: normal;
}
}
.poisBg {
position: absolute;
z-index: 1;
top: 0;
left: 0;
right: 0;
bottom: 0;
width: 100%;
height: 100%;
}
.grid {
width: 100%;
padding: 12px;
background: #ffecec;
border-radius: 16px;
position: relative;
z-index: 2;
display: grid;
grid-template-columns: 1fr 1fr;
gap: 12px 9px;
.item {
position: relative;
width: calc((100vw - 36px - 9px - 36px) / 2);
display: flex;
flex: 1;
height: 40px;
align-items: center;
justify-content: center;
.item-bg {
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
z-index: 1;
width: 100%;
height: 100%;
}
.text {
position: relative;
z-index: 2;
padding: 0 5px;
color: #fff;
font-family: "PingFang SC";
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: normal;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.ticked {
position: absolute;
z-index: 4;
top: -4px;
right: 0;
width: 56px;
height: 24px;
.img {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 1;
}
.meta {
position: absolute;
z-index: 2;
top: 3px;
left: 12px;
right: 0px;
color: #fff;
font-family: "PingFang SC";
font-size: 10px;
font-style: normal;
font-weight: 400;
line-height: 14px;
text-align: center;
}
}
}
}
}
.poiMap {
padding: 0 12px 12px 12px;
display: flex;
flex-direction: column;
align-items: center;
width: calc(100vw - 24px);
border-radius: 16px;
margin-bottom: 10px;
background: linear-gradient(155deg, #fff 3.79%, #fff6f6 84.49%);
border: 1px solid #fff;
.title {
max-width: calc(100vw - 24px);
margin-top: -1px;
position: relative;
padding: 0 23px;
display: flex;
align-items: center;
justify-content: center;
height: 36px;
gap: 3px;
.titlebg {
position: absolute;
z-index: 1;
top: 0;
left: 0;
right: 0;
bottom: 0;
width: 100%;
height: 100%;
}
.star {
position: relative;
width: 10px;
height: 10px;
z-index: 2;
flex-shrink: 0;
}
.text {
position: relative;
z-index: 2;
color: #222;
font-family: "PingFang SC";
font-size: 16px;
font-style: normal;
font-weight: 600;
line-height: normal;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
}
.map {
margin-top: 12px;
width: calc(100vw - 48px);
border-radius: 16px;
}
}
}
}
}
<view class="card">
<image wx:if="{{card}}" class="bg" src="{{card.backgroundImage}}" mode="" />
<image wx:if="{{card}}" class="bgTop" src="{{card.topBackgroundImage}}" mode="widthFix" />
<view class="header" style="background: rgba(255,255,255,{{scrollTop>144?1:scrollTop/144}}); padding-top: {{contentTop}}px; height: {{menuButtonInfo.height+contentTop+6}}px; padding-right: {{rightPadding}}px;">
<image class="back" src="./back.png" mode="" bind:tap="back" />
<view class="title">{{isEn?'Check-in Rewards':'活动有奖'}}</view>
<view class="placeholder"></view>
</view>
<view class="toast" wx:if="{{showToast}}">
<view class="t1">{{isEn?'Check-in Complete!':'打卡圆满完成'}}</view>
<view class="t2">{{isEn?'Rewards sent to Coupon Center. View now on homepage.':'活动奖励已发放至您的卡券中心,请至首页查看!'}}</view>
</view>
<scroll-view class="scroller" bindscroll="handleScroll" scroll-y wx:if="{{card}}">
<view class="scroll-content">
<view class="top"></view>
<view class="desc">
<view class="text {{collapsed ? '' : 'expanded'}}">{{isEn?card.introEn:card.intro}}</view>
<view class="toggle" wx:if="{{isOverflow}}" bind:tap="toggleCollapse">
{{isEn?(collapsed?'Read More':'Read Less'):(collapsed?'展开全部':'收起')}}
<image class="arrow" src="./{{collapsed?'arrowDown':'arrowUp'}}.png" mode="" />
</view>
</view>
<view class="pois">
<image class="poisBg" src="https://cdnimg.chinagoods.com/png/2025/12/18/zsk9v9knwhz4cf9wvfz6dfjnv1ju7s4n.png" mode="" />
<view class="title">
{{isEn?card.pointTitleEn:card.pointTitle }}
<view class="meta" wx:if="{{isEn?card.tipEn:card.tip}}">{{isEn?card.tipEn:card.tip}}</view>
</view>
<view class="grid">
<view class="item" wx:for="{{pois}}" wx:key="poiName" id="{{item.shopId}}" bind:tap="toPoi">
<view class="ticked" wx:if="{{item.checkInStatus===1}}">
<image class="img" src="./ticked.png" mode="" />
<view class="meta">{{isEn?'Checked':'已打卡'}}</view>
</view>
<image wx:if="{{item.checkInStatus===0}}" class="item-bg" src="https://cdnimg.chinagoods.com/png/2025/12/18/zlvfpfl0rxrrebfjyvyairvz3poq2dbp.png" mode="" />
<image wx:else src="https://cdnimg.chinagoods.com/png/2025/12/18/tqgtxm4vnpwe1neblbtyg8gob2j4my2q.png" class="item-bg" mode="" />
<view class="text">{{item.poiName}}</view>
</view>
</view>
</view>
<view class="poiMap" wx:if="{{card.pointMapImage}}">
<view class="title" wx:if="{{card.pointExplain}}">
<image class="titlebg" src="./titlebg.png" mode="" />
<image class="star" src="./star.png" mode="" />
<view class="text">{{card.pointExplain}}</view>
<image class="star" src="./star.png" mode="" />
</view>
<image class="map" bind:tap="showDetail" src="{{card.pointMapImage}}" mode="widthFix" />
</view>
</view>
</scroll-view>
</view>
\ No newline at end of file
import { getDistance } from "../../util";
// Constants
const API_URL = "https://apis.map.qq.com/ws/place/v1/search";
const API_PARAMS = "get_rich=0";
const PAGE_SIZE = 10;
const SEARCH_RADIUS = 1000;
/**
* 构建API请求URL
*/
export function buildApiUrl(keyword, pageIndex, currentBuilding, mapKey) {
const params = [
API_PARAMS,
`key=${mapKey}`,
`keyword=${encodeURI(keyword)}`,
`boundary=nearby(${currentBuilding.latlng.lat},${currentBuilding.latlng.lng},${SEARCH_RADIUS})`,
`page_size=${PAGE_SIZE}`,
`page_index=${pageIndex}`,
].join("&");
return `${API_URL}?${params}`;
}
/**
* 处理店铺数据,计算距离和分类
*/
export function processShopData(shops, currentBuilding) {
return shops.map((shop) => {
const result = { ...shop };
try {
result.distance = Math.ceil(
getDistance(
currentBuilding.latlng.lat,
currentBuilding.latlng.lng,
shop.location.lat,
shop.location.lng
)
);
result.minite = Math.ceil(result.distance / 1.4 / 60);
} catch (error) {
console.warn("Failed to calculate distance:", error);
result.distance = null;
result.minite = null;
}
try {
result.category = result.category?.split(":").pop() || "";
} catch (error) {
console.warn("Failed to process category:", error);
result.category = "";
}
return result;
});
}
/**
* 获取店铺列表数据
*/
export async function getShopList(
keyword,
pageIndex,
currentBuilding,
mapKey,
appInstance
) {
const apiUrl = buildApiUrl(keyword, pageIndex, currentBuilding, mapKey);
const { data } = await appInstance.get(apiUrl);
const rawData = data.data || [];
const totalCount = data.count || 0;
const processedData = processShopData(rawData, currentBuilding);
return {
rawData,
totalCount,
processedData,
};
}
import langBehavior from "../../behaviors/langBehavior";
import { getShopList } from "./explore-helper";
const DEFAULT_HEIGHT = 420;
const FULLSCREEN_HEIGHT_OFFSET = 20;
const DRAG_THRESHOLD = 100;
const TRANSITION_DURATION = 500;
const MARKER_ICON =
"https://cdnimg.chinagoods.com/png/2026/01/22/va5ytkkh5lxvtmpvft6j8c6h8ll0hik2.png";
const MARKER_WIDTH = 48;
const MARKER_HEIGHT = 48;
const MAX_VISIBLE_MARKERS = 10;
const MAP_PADDING = [50, 50, 50, 50];
Component({
behaviors: [langBehavior],
data: {
currentIndex: 0,
categories: [],
keyword: null,
latitude: null,
longitude: null,
pageIndex: 1,
hasMore: true,
loading: false,
list: [],
markers: [],
viewState: "default",
height: DEFAULT_HEIGHT,
touchStart: null,
doTransition: false,
moveTouches: null,
endTouches: null,
windowHeight: wx.getWindowInfo().windowHeight,
scrollTop: 0,
nextTop: 0,
snapped: false,
detail: null,
},
observers: {
moveTouches(touches) {
const { touchStart, viewState, scrollTop } = this.data;
if (!touchStart) return;
// 查找当前触摸点
const touch =
Array.from(touches).find(
({ identifier }) => identifier === touchStart.identifier
) || touches[0];
if (!touch) return;
const delta = touch.clientY - touchStart.y;
// 全屏状态下的限制
if (viewState === "fullscreen") {
if (delta < 0 || scrollTop > 0) return;
}
// 计算新高度
const baseHeight = this.baseHeight();
const newHeight = Math.max(
Math.min(
baseHeight - delta,
this.data.windowHeight - FULLSCREEN_HEIGHT_OFFSET
),
146
);
this.setData({
snapped: true,
height: newHeight,
});
},
endTouches(touches) {
const { touchStart, viewState, scrollTop } = this.data;
if (!touchStart) return;
// 查找当前触摸点
const touch =
Array.from(touches).find(
({ identifier }) => identifier === touchStart.identifier
) || touches[0];
if (!touch) return;
// 全屏状态下有滚动时不处理
if (viewState === "fullscreen" && scrollTop > 0) return;
const delta = touch.clientY - touchStart.y;
let nextViewState = viewState;
if (this.data.viewState === "default") {
if (delta < -DRAG_THRESHOLD) {
nextViewState = "fullscreen";
} else if (delta > DRAG_THRESHOLD) {
nextViewState = "collapsed";
}
} else if (this.data.viewState === "fullscreen") {
if (delta > DRAG_THRESHOLD) {
nextViewState = "default";
}
} else if (this.data.viewState === "collapsed") {
if (delta < -DRAG_THRESHOLD) {
nextViewState = "default";
}
}
this.setData({
snapped: false,
touchStart: null,
viewState: nextViewState,
doTransition: true,
height: this.baseHeight(),
});
setTimeout(() => {
this.setData({ doTransition: false });
}, TRANSITION_DURATION);
},
viewState(nextState) {
this.setData({
doTransition: true,
height: this.baseHeight(),
});
this.triggerEvent("viewstate", nextState);
setTimeout(() => {
this.setData({ doTransition: false });
}, TRANSITION_DURATION);
},
detail(shop) {
const markerIds = this.data.markers.map((marker) => marker.id);
if (markerIds.length) {
this.mapContext?.removeMarkers({
markerIds,
fail: (err) => console.error("Failed to remove markers:", err),
});
}
if (shop) {
const markers = [this.createMarker(shop, 0)];
this.mapContext?.addMarkers({
markers,
fail: (err) => console.error("Failed to add marker:", err),
});
this.setData({
markers,
latitude: shop.location.lat,
longitude: shop.location.lng,
});
} else {
const visibleShops = this.data.list.slice(0, MAX_VISIBLE_MARKERS);
const markers = this.createMarkers(visibleShops);
this.mapContext?.addMarkers({
markers,
fail: (err) => console.error("Failed to add markers:", err),
});
this.mapContext?.includePoints({
points: markers,
padding: MAP_PADDING,
});
this.setData({
markers,
});
}
},
},
detached() {
// 清理查询选择器
this.scrollerQuery = null;
// 清理地图上下文
this.mapContext = null;
},
methods: {
/**
* 生命周期函数--监听页面加载
*/
async onLoad(options) {
this.scrollerQuery = this.createSelectorQuery()
.select("#scroller")
.scrollOffset();
this.mapContext = wx.createMapContext("explore-map", this);
const app = getApp();
const { currentBuilding } = app;
this.setData({
latitude: currentBuilding.latlng.lat,
longitude: currentBuilding.latlng.lng,
});
await this.getTabs();
this.getList();
},
async getTabs() {
const app = getApp();
try {
const {
data: { data },
} = await app.get(`${app.qmurl}/api/v1/applet/getBusinessFormatList`);
this.setData({ categories: data });
} catch (e) {
console.error(e);
return null;
}
},
handleTab({ detail: tab }) {
wx.reLaunch({
url: tab.url,
});
},
switchCat({ currentTarget: { id } }) {
this.setData({
currentIndex: Number(id),
pageIndex: 1,
hasMore: true,
list: [],
});
this.getList();
},
/**
* 创建地图标记点
*/
createMarker(shop, id) {
return {
id,
latitude: shop.location.lat,
longitude: shop.location.lng,
iconPath: MARKER_ICON,
width: MARKER_WIDTH,
height: MARKER_HEIGHT,
zIndex: 2,
collisionRelation: "alone",
label: {
content: shop.title,
color: "#333",
fontSize: 12,
textAlign: "center",
collision: "poi,marker",
},
};
},
/**
* 创建多个地图标记点
*/
createMarkers(shops) {
return shops.map((shop, index) => this.createMarker(shop, index));
},
async getList() {
const {
loading,
hasMore,
pageIndex,
list,
currentIndex,
categories,
} = this.data;
if (loading || !hasMore) return;
const cat = categories[currentIndex];
this.setData({ loading: true });
const app = getApp();
const { mapKey, currentBuilding } = app;
try {
// 调用API获取数据
const { processedData, totalCount } = await getShopList(
cat.keyword,
pageIndex,
currentBuilding,
mapKey,
app
);
const updatedList = [...list, ...processedData];
// 根据总数据量和已加载数据量判断是否还有更多
const hasMoreData = updatedList.length < totalCount;
// 首次加载时更新标记点
if (pageIndex === 1 && processedData.length > 0) {
const markers = this.createMarkers(processedData);
this.setData({ markers });
this.mapContext?.addMarkers({
markers,
fail: (err) => console.error("Failed to add markers:", err),
});
this.mapContext?.includePoints({
points: markers,
padding: MAP_PADDING,
});
}
// 更新列表数据
this.setData({
list: updatedList,
hasMore: hasMoreData,
pageIndex: pageIndex + 1,
loading: false,
});
} catch (error) {
console.error("Failed to get shop list:", error);
this.setData({ loading: false });
}
},
back() {
this.setData({ detail: null });
},
baseHeight() {
switch (this.data.viewState) {
case "fullscreen":
return this.data.windowHeight - FULLSCREEN_HEIGHT_OFFSET;
case "collapsed":
return 146;
default:
return DEFAULT_HEIGHT;
}
},
handleTouchStart(e) {
if (this.data.touchStart) return;
this.setData({
touchStart: {
identifier: e.changedTouches[0].identifier,
y: e.changedTouches[0].clientY,
},
});
},
handleTouchMove(e) {
// 只在全屏状态下才计算滚动位置
if (this.data.viewState === "fullscreen") {
this.getScrollTop();
}
this.setData({ moveTouches: e.changedTouches });
},
handleTouchEnd(e) {
// 只在全屏状态下才计算滚动位置
if (this.data.viewState === "fullscreen") {
this.getScrollTop();
}
this.setData({ endTouches: e.changedTouches });
},
/**
* 获取滚动位置
*/
getScrollTop() {
// 只有在全屏状态下才需要计算滚动位置
if (this.data.viewState !== "fullscreen") {
// 避免不必要的setData调用
if (this.data.scrollTop !== 0) {
this.setData({ scrollTop: 0 });
}
return;
}
try {
// 重新创建查询以获取最新的滚动位置
const query = this.createSelectorQuery()
.select("#scroller")
.scrollOffset();
query.exec(([{ scrollTop }]) => {
if (scrollTop !== undefined) {
// 确保滚动位置不会小于0
const normalizedScrollTop = Math.max(0, scrollTop);
if (normalizedScrollTop !== this.data.scrollTop) {
this.setData({ scrollTop: normalizedScrollTop });
}
}
});
} catch (error) {
console.error("Failed to get scroll top:", error);
}
},
onScrollToLower() {
this.getList();
},
toDetail({ currentTarget: { id } }) {
this.setData({
detail: this.data.list.find((shop) => String(shop.id) === String(id)),
});
},
handleMarkerTap({ detail: { markerId } }) {
if (this.data.detail) return;
this.setData({
detail: this.data.list[markerId],
});
},
toApp() {
this.mapContext.openMapApp({
longitude: this.data.detail.location.lng,
latitude: this.data.detail.location.lat,
destination: this.data.detail.title,
});
},
makePhoneCall({ currentTarget: { id } }) {
wx.makePhoneCall({
phoneNumber: id,
});
},
/**
* 生命周期函数--监听页面初次渲染完成
*/
onReady() {},
/**
* 生命周期函数--监听页面显示
*/
onShow() {},
/**
* 生命周期函数--监听页面隐藏
*/
onHide() {},
/**
* 生命周期函数--监听页面卸载
*/
onUnload() {},
/**
* 页面相关事件处理函数--监听用户下拉动作
*/
onPullDownRefresh() {},
/**
* 页面上拉触底事件的处理函数
*/
onReachBottom() {
this.getList();
},
/**
* 滚动到底部加载更多
*/
onScrollToLower() {
this.getList();
},
/**
* 更新列高度
*/
updateColumnHeights() {
return new Promise((resolve) => {
const query = wx.createSelectorQuery();
query.selectAll(".column").boundingClientRect();
query.exec((res) => {
if (res && res[0] && res[0].length === 2) {
const columnHeights = res[0].map((item) => item.height);
this.setData({ columnHeights });
resolve();
}
});
});
},
/**
* 用户点击右上角分享
*/
onShareAppMessage() {},
},
});
{
"disableScroll": true,
"usingComponents": {
"custom-tab-bar": "../../components/custom-tab-bar/index"
}
}
/* components/explore/index.wxss */
.explore {
position: relative;
width: 100vw;
height: 100vh;
overflow: hidden;
.map {
width: 100vw;
height: calc(100vh - 140px);
}
.bottom-bg {
position: absolute;
bottom: 0;
z-index: 200;
display: flex;
flex-direction: column;
height: 414px;
width: 100vw;
border-radius: 8px;
border: 1px solid #fff;
background: linear-gradient(180deg, #fbfbfb 0%, #f4f4f4 41.8%);
}
.bottom-hud {
position: absolute;
bottom: 0;
z-index: 200;
height: 420px;
width: 100vw;
.handle {
position: absolute;
top: -4px;
left: 0;
right: 0;
margin: auto;
width: 40px;
height: 4px;
border-radius: 27px;
background: rgba(0, 0, 0, 0.2);
&::before {
content: "";
display: block;
position: absolute;
left: -5px;
right: -5px;
top: -10px;
bottom: -10px;
}
}
.title {
padding-top: 16px;
padding-left: 22px;
display: flex;
gap: 24px;
align-items: center;
.item {
display: inline-flex;
flex-direction: column;
align-items: center;
gap: 2px;
.txt {
color: rgba(0, 0, 0, 0.7);
font-family: "PingFang SC";
font-size: 16px;
font-style: normal;
font-weight: 400;
line-height: 22px;
}
.mark {
width: 16px;
height: 4px;
border-radius: 17px;
}
&.active {
.txt {
color: rgba(0, 0, 0, 0.9);
font-weight: 600;
}
.mark {
background: #e92927;
}
}
}
}
.collapsed {
display: flex;
width: 100vw;
height: 62px;
align-items: center;
justify-content: center;
padding-top: 6px;
color: #333;
font-family: "PingFang SC";
font-size: 14px;
font-style: normal;
font-weight: 600;
line-height: 18px;
}
.scroll-view {
position: absolute;
z-index: 1;
left: 10px;
right: 10px;
top: 58px;
height: calc(100vh - 158px);
width: calc(100vw - 20px);
.list {
display: inline-flex;
flex-direction: column;
width: calc(100vw - 20px);
gap: 8px;
.item {
position: relative;
width: calc(100vw - 20px);
border-radius: 8px;
background: #fff;
padding: 12px 16px;
.content {
display: inline-flex;
flex-direction: column;
flex: 1;
overflow: hidden;
.t1 {
color: #333;
font-family: "PingFang SC";
font-size: 16px;
font-style: normal;
font-weight: 600;
line-height: 22px; /* 137.5% */
}
.t2 {
margin-top: 4px;
color: rgba(0, 0, 0, 0.4);
font-size: 12px;
font-style: normal;
font-weight: 400;
line-height: 16px;
}
.t3 {
display: flex;
width: calc(100vw - 52px);
align-items: center;
gap: 4px;
margin-top: 8px;
color: #666;
font-family: "PingFang SC";
font-size: 12px;
font-style: normal;
font-weight: 400;
line-height: 16px;
.loc {
width: 16px;
height: 16px;
}
.right {
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
}
.tel {
position: absolute;
top: 12px;
right: 16px;
display: flex;
flex-direction: column;
align-items: center;
color: #666;
font-family: "PingFang SC";
font-size: 11px;
font-style: normal;
font-weight: 400;
line-height: normal;
.phone {
width: 24px;
height: 24px;
}
}
}
.loading,
.no-more {
text-align: center;
padding: 10px;
color: #666;
font-size: 14px;
}
}
}
}
.detail {
position: absolute;
bottom: 0;
left: 0;
right: 0;
width: 100vw;
z-index: 2000;
.detail-bg {
position: absolute;
top: 0;
left: 0;
width: 100vw;
bottom: 0;
right: 0;
z-index: 1;
background: #fff;
}
.content {
position: relative;
z-index: 2;
background: #fff;
.close {
position: absolute;
top: 10px;
right: 8px;
width: 32px;
height: 32px;
z-index: 10;
}
.top {
position: relative;
display: flex;
flex-direction: column;
gap: 4px;
padding: 16px;
.t1 {
color: #333;
font-feature-settings: "case" on;
font-family: "PingFang SC";
font-size: 16px;
font-style: normal;
font-weight: 600;
line-height: 22px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
padding-right: 24px;
}
.t2 {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
color: rgba(0, 0, 0, 0.4);
font-size: 12px;
font-style: normal;
font-weight: 400;
line-height: 16px;
padding-right: 24px;
}
.t3 {
display: flex;
width: calc(100vw - 52px);
align-items: center;
gap: 4px;
margin-top: 24px;
color: #666;
font-family: "PingFang SC";
font-size: 12px;
font-style: normal;
font-weight: 400;
line-height: 16px;
.loc {
width: 16px;
height: 16px;
}
.right {
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
.tel {
position: absolute;
bottom: 16px;
right: 16px;
display: flex;
flex-direction: column;
align-items: center;
color: #666;
font-family: "PingFang SC";
font-size: 11px;
font-style: normal;
font-weight: 400;
line-height: normal;
.phone {
width: 24px;
height: 24px;
}
}
}
.bottom {
height: 98px;
border-top: 1px solid rgba(0, 0, 0, 0.06);
padding-top: 12px;
padding-left: 16px;
padding-right: 16px;
.btn {
border-radius: 99px;
background: linear-gradient(90deg, #fa643c 0%, #e92927 100%);
height: 40px;
display: flex;
gap: 8px;
align-items: center;
justify-content: center;
color: #fff;
font-family: "PingFang SC";
font-size: 14px;
font-style: normal;
font-weight: 600;
line-height: 18px;
.img {
width: 24px;
height: 24px;
}
}
}
}
}
}
<view class="explore">
<map wx:if="{{latitude&&longitude}}" id="explore-map" class="map" show-compass latitude="{{latitude}}" longitude="{{longitude}}" max-scale="22" bind:markertap="handleMarkerTap"></map>
<textarea wx:if="{{!detail}}" disabled class="bottom-bg" style="z-index: 100;height: {{height - 6}}px;transition: {{!doTransition ? 'none' : 'height 0.5s ease'}}"></textarea>
<view class="bottom-hud" hidden="{{detail}}" style="height: {{height}}px;transition: {{!doTransition ? 'none' : 'height 0.5s ease'}}" bind:touchstart="handleTouchStart" bind:touchmove="handleTouchMove" bind:touchend="handleTouchEnd" bind:touchcancel="handleTouchEnd">
<view class="handle"></view>
<view class="title" wx:if="{{viewState!=='collapsed'}}">
<view class="item {{currentIndex===index?'active':''}}" wx:for="{{categories}}" wx:key="name" id="{{index}}" bind:tap="switchCat">
<view class="txt">{{isEn?item.nameEn:item.name}}</view>
<view class="mark"></view>
</view>
</view>
<view wx:if="{{viewState==='collapsed'}}" class="collapsed">{{isEn?'Show more':'上滑查看全部结果'}}</view>
<scroll-view hidden="{{viewState==='collapsed'}}" id="scroller" enhanced bounces="{{false}}" scroll-y="{{viewState==='fullscreen'&&!snapped}}" class="scroll-view" scroll-top="{{ nextTop }}" bindscrolltolower="onScrollToLower">
<view class="list">
<view class="item" wx:for="{{list}}" wx:for-item="shop" wx:key="id" id="{{shop.id}}" bind:tap="toDetail">
<view class="content">
<view class="t1">{{shop.title}}</view>
<view class="t2">{{isEn?'Distance':'距此'}}{{shop.distance}}m</view>
<view class="t3">
<image src="./loc.png" class="loc" mode="" />
<view class="right">{{shop.address}}</view>
</view>
</view>
<view wx:if="{{shop.tel}}" id="{{shop.tel}}" class="tel" catch:tap="makePhoneCall">
<image class="phone" src="./tel.png" mode="" />
{{isEn?'Tel':'电话'}}
</view>
</view>
<view wx:if="{{loading}}" class="loading">{{isEn?'Loading...':'加载中...'}}</view>
<view wx:elif="{{!hasMore}}" class="no-more">{{isEn?'No more data':'没有更多数据了'}}</view>
</view>
</scroll-view>
</view>
<view class="detail" wx:if="{{detail}}">
<textarea disabled class="detail-bg"></textarea>
<view class="content">
<image class="close" src="./close.png" mode="" bind:tap="back" />
<view class="top">
<view class="t1">{{detail.title}}</view>
<view class="t2">{{isEn?'Distance':'距此'}}{{detail.distance}}m</view>
<view class="t3">
<image src="./loc.png" class="loc" mode="" />
<view class="right">{{detail.address}}</view>
</view>
<view wx:if="{{detail.tel}}" id="{{detail.tel}}" class="tel" bind:tap="makePhoneCall">
<image class="phone" src="./tel.png" mode="" />
{{isEn?'Tel':'电话'}}
</view>
</view>
<view class="bottom">
<view class="btn" bind:tap="toApp">
<image class="img" src="./dir.png" mode="" />
<view>{{isEn?'Go here':'到这去'}}</view>
</view>
</view>
</view>
</view>
<custom-tab-bar current-index="{{2}}" bind:tab="handleTab"></custom-tab-bar>
</view>
\ No newline at end of file
// pages/feedback/index.js
Page({
/**
* 页面的初始数据
*/
data: { src: "" },
/**
* 生命周期函数--监听页面加载
*/
onLoad() {
const token = getApp().globalData.user.access_token;
this.setData({
src: `https://h5.chinagoods.com/aigc/feedback-list?token=${token}`,
});
},
/**
* 生命周期函数--监听页面初次渲染完成
*/
onReady() {},
/**
* 生命周期函数--监听页面显示
*/
onShow() {},
/**
* 生命周期函数--监听页面隐藏
*/
onHide() {},
/**
* 生命周期函数--监听页面卸载
*/
onUnload() {},
/**
* 页面相关事件处理函数--监听用户下拉动作
*/
onPullDownRefresh() {},
/**
* 页面上拉触底事件的处理函数
*/
onReachBottom() {},
/**
* 用户点击右上角分享
*/
onShareAppMessage() {},
});
{
"usingComponents": {},
"navigationBarTitleText": "贡献反馈"
}
/* pages/feedback/index.wxss */
\ No newline at end of file
<web-view src="{{src}}" />
\ No newline at end of file
......@@ -29,7 +29,7 @@ Component({
moveTouches(touches) {
if (!this.data.touchStart) return;
let touch = Array.from(touches).find(
({ identifier }) => identifier === this.data.touchStart.identifier
({ identifier }) => identifier === this.data.touchStart.identifier,
);
if (!touch) {
if (touches.length > 0) {
......@@ -50,14 +50,14 @@ Component({
this.setData({
height: Math.max(
Math.min(this.baseHeight() - delta, this.data.windowHeight - 24),
146
146,
),
});
},
endTouches(touches) {
if (!this.data.touchStart) return;
let touch = Array.from(touches).find(
({ identifier }) => identifier === this.data.touchStart.identifier
({ identifier }) => identifier === this.data.touchStart.identifier,
);
if (!touch) {
......@@ -188,31 +188,38 @@ Component({
console.log(error);
}
},
handlePoi({ detail: poi }) {
this.triggerEvent("poi", poi);
},
async getNavHistory() {
const { post, qmurl, shopIdMap, facilityIdMap, globalData } = getApp();
if (!globalData.user) return;
if (!shopIdMap) return;
const app = getApp();
const userId = app.globalData.user?.user?.userId;
if (!userId) return;
try {
const {
data: { data },
} = await post(
`${qmurl}/api/v1/applet/getUserFootPrintList?userId=${globalData.user.user.userId}`,
{
userId: globalData.user.user.userId,
}
} = await app.post(
`${app.qmurl}/api/v1/applet/getUserFootPrintList?userId=${userId}`,
{ userId },
);
if (data && data.length !== undefined) {
if (!Array.isArray(data)) return;
// 1. 性能优化:先过滤并截取前 10 条 (避免填充无需显示的后续数据)
const rawList = data.filter((item) => item.shopType === 1).slice(0, 10);
// 2. 自动填充 (利用 populate 默认读取 shopType/shopId 的特性,无需配置参数)
const populatedList = await app.poiApi.populate(rawList, {
targetField: "detail",
});
// 3. 提取结果并更新
this.setData({
navHistory: data
.map(({ shopType, shopId }) =>
shopType === 1 ? shopIdMap[shopId] : null
)
.filter((el) => el)
.slice(0, 10),
navHistory: populatedList.map((item) => item.detail).filter(Boolean),
});
}
} catch (error) {
console.log(error);
console.error(error);
}
},
},
......@@ -222,14 +229,7 @@ Component({
.select("#scroller")
.scrollOffset();
this.getPrimeSpots();
const app = getApp();
if (!app.shopIdMap) {
app.events.once("globalData", () => {
this.getNavHistory();
});
} else {
this.getNavHistory();
}
},
},
});
......@@ -8,7 +8,7 @@
<scroll-view id="scroller" enhanced bounces="{{false}}" scroll-y="{{viewState==='fullscreen'}}" class="scroll-view" scroll-top="{{ nextTop }}" bindscrolltolower="onScrollToLower" lower-threshold="251">
<view style="width: 100%; height: 46px;"></view>
<prime-spot wx:if="{{primeSpots.length>0&&!isEn}}" list="{{primeSpots}}"></prime-spot>
<nav-history wx:if="{{navHistory.length>0}}" list="{{navHistory}}" bind:navhistorycleared="getNavHistory"></nav-history>
<nav-history wx:if="{{navHistory.length>0}}" list="{{navHistory}}" bind:navhistorycleared="getNavHistory" bind:poi="handlePoi"></nav-history>
<shops scroll-end-count="{{scrollEndCount}}"></shops>
</scroll-view>
</view>
......
......@@ -40,7 +40,7 @@ Component({
},
handleShop({ currentTarget: { id: index } }) {
const poi = this.data.list[index];
getApp().focusPoi(poi);
this.triggerEvent("poi", poi);
},
async clearNavHistory() {
const { isEn } = this.data;
......
import langBehavior from "../../../../behaviors/langBehavior";
import buildingBehavior from "../../../../behaviors/buildingBehavior";
Component({
behaviors: [langBehavior],
behaviors: [langBehavior, buildingBehavior],
properties: {},
/**
......@@ -8,22 +9,13 @@ Component({
*/
data: {
placeholderIndex: 0,
placeholders: [
{
en: "District 2-East New Energy Product Market Welcome!",
cn: "欢迎光临二区东新能源产品市场",
placeholders: [],
},
{
en: "Search store number/facilities/block/exit number",
cn: "搜铺号/公共设施/街区号/出入口号",
observers: {
currentBuilding() {
this.setPlaceholder();
},
{
en: "Click or search shoplD for click navigation",
cn: "点击或搜索任意商铺可一键导航前往",
},
],
},
/**
* 组件的方法列表
*/
......@@ -34,6 +26,25 @@ Component({
aiTap() {
this.triggerEvent("ai");
},
setPlaceholder() {
const building = this.data.currentBuilding;
this.setData({
placeholders: [
{
en: `${building.displayNameEn} Welcome!`,
cn: `欢迎光临${building.displayName}`,
},
{
en: "Search store number/facilities/block/exit number",
cn: "搜铺号/公共设施/街区号/出入口号",
},
{
en: "Click or search shoplD for click navigation",
cn: "点击或搜索任意商铺可一键导航前往",
},
],
});
},
},
lifetimes: {
attached() {
......@@ -42,6 +53,7 @@ Component({
placeholderIndex: (this.data.placeholderIndex + 1) % 3,
});
}, 3000);
this.setPlaceholder();
},
detached() {
if (this.interval) {
......
......@@ -51,7 +51,7 @@
color: #000;
border-radius: 99px;
border: 0.2px solid var(--W-100, #fff);
background: rgba(255, 255, 255, 0.08);
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(33.05883026123047px);
}
}
......
......@@ -2,7 +2,7 @@
<image src="./search.png" class="search" mode="" />
<view class="text">{{placeholders[placeholderIndex][isEn?'en':'cn']}}</view>
<view class="ai" catch:tap="aiTap">
<image src="./ai.png" class="icon" mode="" />
<image src="https://cdnimg.chinagoods.com/png/2025/10/10/tabzvwxow9xlggugfr0e0oe3s5pahny2.png" class="icon" mode="" />
<view class="text" style="{{'width: '+(isEn?50:35)+'px'}}">{{isEn?"AI Search":"AI识物"}}</view>
</view>
</view>
\ No newline at end of file
import langBehavior from "../../../../behaviors/langBehavior";
import buildingBehavior from "../../../../behaviors/buildingBehavior";
Component({
behaviors: [langBehavior],
behaviors: [langBehavior, buildingBehavior],
/**
* 组件的属性列表
*/
......@@ -12,7 +13,10 @@ Component({
* 组件的初始数据
*/
data: {
formats: [{ key: "all", name: "全部", nameEn: "All" }, ...getApp().formats],
formats: [
{ key: "all", name: "全部", nameEn: "All" },
...getApp().currentBuilding.formats,
],
key: "all",
page: 1,
page_size: 10,
......@@ -20,6 +24,15 @@ Component({
isLoading: false,
},
observers: {
currentBuilding() {
this.setData({
formats: [
{ key: "all", name: "全部", nameEn: "All" },
...getApp().currentBuilding.formats,
],
key: "all",
});
},
key() {
this.setData({
page: 1,
......@@ -45,61 +58,68 @@ Component({
*/
methods: {
async getList(nxtPage = 1) {
const { post, marketurl, formats, md5, salt, shopIdMap } = getApp();
const { key, isEn, page_size } = this.data;
if (this.data.isLoading) return;
if (!shopIdMap) return;
try {
this.setData({
isLoading: true,
});
const app = getApp();
const { key, isEn, page_size, currentBuilding, list, isLoading } =
this.data;
if (isLoading) return;
this.setData({ isLoading: true });
try {
const {
data: {
data: { total_page, page, first_result, last_result, data },
},
} = await post(`${marketurl}/osc/v1/shops/open/toBaiDu/search`, {
data: { data: res },
} = await app.post(
`${app.marketurl}/osc/v1/shops/open/toBaiDu/search`,
{
page: nxtPage,
page_size,
industryKeyList:
key === "all" ? formats.map((format) => format.key) : [key],
key === "all" ? currentBuilding.formats.map((f) => f.key) : [key],
lang: isEn ? "en" : "zh",
keySecret: md5(md5(salt + nxtPage)),
keySecret: app.md5(app.md5(app.salt + nxtPage)),
zone: currentBuilding.zone,
},
);
// 1. 自动填充
const filledItems = await app.poiApi.populate(res.data, {
getType: () => "1",
getId: (i) => i.shopId,
getBooth: (i) => i.boothNo,
targetField: "detail",
});
const nxtList = data
.map((shop) => {
if (!shopIdMap[shop.shopId])
console.warn("列表接口里有,但是全量没有的店铺", shop);
return shopIdMap[shop.shopId];
})
.filter((el) => el);
// 2. 提取详情
const nxtList = filledItems.map((i) => i.detail).filter(Boolean);
// 3. 批量埋点
nxtList.forEach((poi) => {
const yw = poi.ywZh || {};
try {
getApp().sensors?.track("ExposureBoothActivity", {
app.sensors?.track("ExposureBoothActivity", {
shop_id: poi.shopId,
shop_name: poi.ywZh.name,
booth_no: poi.ywZh.boothNo,
store_name: poi.ywZh.name,
short_market_name: "二区东",
booth_addr_street: poi.ywZh.boothAddrStreet,
industry: poi.ywZh.frontIndustryCategory,
booth_floor: poi.ywZh.addrFloor,
shop_name: yw.name,
booth_no: yw.boothNo,
store_name: yw.name,
short_market_name: yw.marketNameDtl,
booth_addr_street: yw.boothAddrStreet,
industry: yw.frontIndustryCategory,
booth_floor: yw.addrFloor,
});
} catch (error) {
console.error("埋点失败", error);
} catch (e) {
console.error(e);
}
});
this.setData({
list: [...this.data.list, ...nxtList],
total_page,
page,
list: [...list, ...nxtList],
total_page: res.total_page,
page: res.page,
});
} catch (error) {
console.error(error);
} finally {
this.setData({
isLoading: false,
});
this.setData({ isLoading: false });
}
},
handleFormat({ currentTarget: { id: key } }) {
......@@ -110,36 +130,34 @@ Component({
}
},
handleShop({ currentTarget: { id: shopId } }) {
try {
const poi = getApp().shopIdMap[shopId];
getApp().sensors?.track("BoothActivityClick", {
// 1. 立即跳转 (UI 响应优先,不阻塞)
wx.navigateTo({ url: `/pages/shop/index?shopId=${shopId}` });
// 2. 异步埋点 (Fire-and-forget)
const app = getApp();
app.poiApi
.getOne({ shopType: "1", shopId })
.then((poi) => {
if (!poi) return;
const yw = poi.ywZh || {}; // 缓存对象,简化下方取值
app.sensors?.track("BoothActivityClick", {
shop_id: poi.shopId,
shop_name: poi.ywZh.name,
booth_no: poi.ywZh.boothNo,
store_name: poi.ywZh.name,
short_market_name: "二区东",
booth_addr_street: poi.ywZh.boothAddrStreet,
industry: poi.ywZh.frontIndustryCategory,
booth_floor: poi.ywZh.addrFloor,
});
} catch (error) {
console.error("埋点失败", error);
}
wx.navigateTo({
url: `/pages/shop/index?shopId=${shopId}`,
shop_name: yw.name,
booth_no: yw.boothNo,
store_name: yw.name,
short_market_name: yw.marketNameDtl,
booth_addr_street: yw.boothAddrStreet,
industry: yw.frontIndustryCategory,
booth_floor: yw.addrFloor,
});
})
.catch((e) => console.error("埋点失败", e));
},
},
lifetimes: {
attached() {
const app = getApp();
if (app.shopIdMap) {
this.getList();
} else {
app.events.once("globalData", () => {
this.getList();
});
}
},
},
});
import langBehavior from "../../behaviors/langBehavior";
import poiFocusBehavior from "../../behaviors/poiFocusBehavior";
import modalBehavior from "../../behaviors/modalBehavior";
import buildingBehavior from "../../behaviors/buildingBehavior";
import {
getShopShareTitle,
getFacilityShareTitle,
authBluetoothAndLocation,
actToRoutePlan,
} from "../../util";
import MD5Request from "../../libs/MD5Request";
const defaultFloorName = "1F";
const app = getApp();
const parseQueryString = (url) => {
if (!url) return {};
try {
const str = decodeURIComponent(url);
const queryString = str.includes("?") ? str.split("?").pop() : str;
return queryString
.split("&")
.map((kv) => kv.split("="))
.reduce((acc, [k, v]) => {
if (k && v) acc[k] = v;
return acc;
}, {});
} catch (e) {
console.warn("参数解析失败", e);
return {};
}
};
Page({
behaviors: [langBehavior, poiFocusBehavior, modalBehavior],
behaviors: [langBehavior, poiFocusBehavior, modalBehavior, buildingBehavior],
data: {
isIndex: true,
viewstate: "default",
buildingId: "3307001549220",
floorName: defaultFloorName,
floors: [
{ floorName: "3F", displayName: "3F" },
{ floorName: "2F", displayName: "2F" },
{ floorName: "1F", displayName: "1F" },
],
banners: [],
showBanner: false,
bannerIndex: 0,
......@@ -34,9 +48,10 @@ Page({
gettingLocation: false,
getLocationSuccess: false,
hasAuth: false,
cardId: null,
},
handleTab({ detail: tab }) {
if (tab.text === "我的")
if (tab.text === "我的" || tab.text === "探索")
wx.reLaunch({
url: tab.url,
});
......@@ -66,15 +81,32 @@ Page({
this.setData({ reseting: false });
}
},
onCurrentBuildingChange() {
this.getActs();
if (!this.data.focusedPoi) {
this.setFloorName(getApp().currentBuilding.floors[0]);
}
if (!this.data.isIndex) {
if (this.data.activeId) this.setFacilities(this.data.activeId);
}
},
async getActs() {
try {
const { post, mapKey, buildingId } = getApp();
const {
data: { data },
} = await post(`https://indoormall.data.qq.com/mall/shop/activity/list`, {
building_id: buildingId,
const { mapKey, currentBuilding } = getApp();
if (!currentBuilding.buildingId) return;
const queryOptions = {
building_id: currentBuilding.buildingId,
key: mapKey,
});
path: "/mall/shop/activity/list",
};
const options = {
method: "POST",
};
const { data } = await new MD5Request(
"https://indoormall.data.qq.com",
queryOptions,
options,
);
if (data && data.length)
this.setData({
acts: data.map((act) => {
......@@ -122,6 +154,12 @@ Page({
if (this.data.focusedPoi) {
getApp().blurPoi();
}
if (getApp().currentBuilding.name === "六区" && floorName === "4F")
return wx.showToast({
title: "敬请期待",
icon: "none",
});
this.setFloorName(floorName);
if (!this.data.isIndex) {
if (this.data.activeId) this.setFacilities(this.data.activeId);
......@@ -135,7 +173,7 @@ Page({
`${uploadurl}/buyerapi/v1/ads/content/banner?unique_marks=665588`,
{
RequestSource: "Android",
}
},
);
const banners = [];
data.forEach((el) => {
......@@ -177,7 +215,24 @@ Page({
handleBannerIndex({ detail: { current } }) {
this.setData({ bannerIndex: current });
},
async onLoad({ shopId, facilityId, actId, q, fromshare, mode }) {
async onLoad(options) {
const app = getApp();
// ================= 1. 参数预处理 =================
let { shopId, facilityId, actId, q, fromshare, mode, scene } = options;
// 优先处理扫码参数 (scene > q > options)
const scanStr = scene || q;
if (scanStr) {
const scanParams = parseQueryString(scanStr);
if (scanParams.shopId) shopId = scanParams.shopId;
if (scanParams.facilityId) facilityId = scanParams.facilityId;
console.log("扫码参数提取:", scanParams);
}
const hasParam = shopId || facilityId || actId || scanStr;
// ================= 2. 权限与初始化 =================
const { authSetting } = await wx.getSetting();
this.setData({
hasAuth:
......@@ -185,138 +240,185 @@ Page({
neverAuthed:
authSetting["scope.bluetooth"] === undefined &&
authSetting["scope.userLocation"] === undefined,
isIndex: !mode,
});
getApp().events.once("authed", () => {
this.setData({ neverAuthed: false });
});
if (mode) this.setData({ isIndex: false });
this.getFacTypes();
await this.getActs();
const hasParam = shopId || facilityId || actId || q;
if (q) {
console.log(q);
try {
q = decodeURIComponent(q);
const kvMap = q
.split("?")
.pop()
.split("&")
.map((kv) => kv.split("="))
.reduce((acc, [k, v]) => ({ ...acc, [k]: v }), {});
facilityId = kvMap.facilityId;
shopId = kvMap.shopId;
if (kvMap.facilityId) console.log("二维码中提取到设施id", facilityId);
if (kvMap.shopId) console.log("二维码中提取到店铺id", shopId);
} catch (error) {
console.log("处理二维码参数失败", error);
}
}
this.getActs(); // 并行请求,不阻塞后续逻辑
const app = getApp();
if (shopId) {
if (fromshare) app.shareShopId = shopId;
if (!app.shopIdMap) {
app.events.once("globalData", () => {
const shop = app.shopIdMap[shopId];
if (shop) {
if (shop.tx) this.setFloorName(shop.tx.poi_fl_name);
app.focusPoi(shop);
}
});
} else {
const shop = app.shopIdMap[shopId];
if (shop) {
if (shop.tx) this.setFloorName(shop.tx.poi_fl_name);
app.focusPoi(shop);
}
}
}
if (facilityId) {
if (fromshare) app.shareFacilityId = facilityId;
if (!app.facilityIdMap) {
app.events.once("globalData", () => {
const facility = app.facilityIdMap[facilityId];
if (facility) {
if (facility.tx) this.setFloorName(facility.tx.poi_fl_name);
// ================= 3. 事件监听绑定 (核心改造) =================
// 技巧:将绑定了 this 的函数保存在实例属性上,用于 off
this._onAuthed = () => this.setData({ neverAuthed: false });
this._onLocationChange = this.setUserLocation.bind(this);
this._onBuildingChange = this.setCurrentCardId.bind(this);
this._onAppShow = this.handleAppShow.bind(this);
app.focusPoi(facility);
}
});
} else {
const facility = app.facilityIdMap[facilityId];
if (facility) {
if (facility.tx) this.setFloorName(facility.tx.poi_fl_name);
app.focusPoi(facility);
}
}
}
if (actId) {
try {
const act = this.data.acts.find((act) => act.id === Number(actId));
// 注册监听
app.events.once("authed", this._onAuthed);
app.events.on("userLocation", this._onLocationChange);
app.events.on("building", this._onBuildingChange);
wx.onAppShow(this._onAppShow);
// ================= 4. 业务逻辑执行 =================
// 处理 POI 聚焦
this.initPoiFocus(shopId, facilityId, fromshare);
// 处理活动
if (actId && this.data.acts) {
const act = this.data.acts.find((a) => a.id === Number(actId));
if (act) this.setData({ act });
} catch (error) {
console.error(error);
}
// Banner 逻辑
if (!hasParam && !app.bannerShowed) {
this.getBanner();
}
if (!hasParam) {
if (!getApp().bannerShowed) {
await this.getBanner();
// 立即执行一次定位和卡片逻辑
this.setUserLocation();
this.setCurrentCardId();
// 获取楼层
if (this.data.hasAuth && !hasParam) {
this.getFloor();
}
},
onUnload() {
app.events.off("userLocation", this._onLocationChange);
app.events.off("building", this._onBuildingChange);
wx.offAppShow(this._onAppShow);
},
initPoiFocus(shopId, facilityId, fromshare) {
const app = getApp();
const targetType = shopId ? "1" : facilityId ? "2" : null;
const targetId = shopId || facilityId;
if (!targetType || !targetId) return;
// 处理分享标记
if (fromshare) {
if (targetType === "1") app.shareShopId = targetId;
else app.shareFacilityId = targetId;
}
// 获取详情并聚焦
app.poiApi
.getOne({ shopType: targetType, shopId: targetId })
.then((poi) => {
if (poi?.tx) {
this.setBuildingFloor(poi.tx.bld_id, poi.tx.poi_fl_name);
app.focusPoi(poi);
}
})
.catch(console.error);
},
/**
* 定位变化处理逻辑
*/
setUserLocation() {
const { userLocation, currentBuilding, buildings } = getApp();
const oldLoc = this.data.userLocation;
// 调试日志逻辑简化
const isSameBld = userLocation?.buildingId === currentBuilding?.buildingId;
const wasDiffBld = oldLoc?.buildingId !== currentBuilding?.buildingId;
if (oldLoc && wasDiffBld && isSameBld && userLocation?.floorName) {
console.log("外-到-内", oldLoc, "->", userLocation);
} else if (oldLoc?.buildingId && !userLocation?.buildingId) {
console.log("内-到-外", oldLoc, "->", userLocation);
}
this.setUserLocation = () => {
const { userLocation, buildingId } = getApp();
const toIndoor =
this.data.userLocation &&
this.data.userLocation.buildingId !== buildingId &&
userLocation.buildingId === buildingId &&
userLocation.floorName;
this.setData({ userLocation });
// 处理正在定位中的状态
if (this.data.gettingLocation) {
console.log(`定位结果:`, userLocation);
if (this.getFloorTimeout) {
clearTimeout(this.getFloorTimeout);
this.getFloorTimeout = null;
}
this.setData({
gettingLocation: false,
resetCount: this.data.resetCount + 1,
resetCount: (this.data.resetCount || 0) + 1,
getLocationSuccess: true,
});
this.setFloorName(
userLocation &&
userLocation.buildingId === buildingId &&
userLocation.floorName
? userLocation.floorName
: defaultFloorName
const building = buildings.find(
(b) => b.buildingId === userLocation?.buildingId,
);
const isValidFloor = building?.floors.includes(userLocation?.floorName);
this.setBuildingFloor(
userLocation?.buildingId,
isValidFloor ? userLocation.floorName : defaultFloorName,
);
setTimeout(() => {
this.setData({ getLocationSuccess: false });
}, 2000);
} else if (toIndoor && !this.data.focusedPoi) {
} else if (wasDiffBld && isSameBld && !this.data.focusedPoi) {
console.log("定位切到室内");
this.setData({
resetCount: this.data.resetCount + 1,
});
this.setFloorName(userLocation?.floorName);
this.setData({ resetCount: (this.data.resetCount || 0) + 1 });
this.setBuildingFloor(userLocation?.buildingId, userLocation?.floorName);
}
};
getApp().events.on("userLocation", this.setUserLocation);
this.setUserLocation();
if (this.data.hasAuth && !hasParam) this.getFloor();
this.appShowHandler = () => {
},
/**
* App 切前台处理
*/
handleAppShow() {
console.log("appShowHandler", this.data.focusedPoi);
if (!this.data.focusedPoi) this.getFloor();
};
wx.onAppShow(this.appShowHandler);
},
onUnload() {
if (this.setUserLocation)
getApp().events.off("userLocation", this.setUserLocation);
if (this.appShowHandler) wx.offAppShow(this.appShowHandler);
/**
* 获取并设置当前卡片 ID
*/
async setCurrentCardId() {
try {
const { currentBuilding, cardApi } = getApp();
const cardId = await cardApi.getByZone(currentBuilding.zone);
this.setData({ cardId: cardId || null });
} catch (error) {
console.error(error);
this.setData({ cardId: null });
}
},
toCard() {
if (!getApp().checkUser()) return;
wx.navigateTo({
url: `/pages/card-detail/index?cardId=${this.data.cardId}`,
});
},
handlePoi({ detail: poi }) {
if (poi.tx) {
this.setBuildingFloor(poi.tx.bld_id, poi.tx.poi_fl_name);
getApp().focusPoi(poi);
}
},
handleMapBuildChange() {
if (this.data.gettingLocation) return;
this.setFloorName(
this.data.userLocation?.buildingId === getApp().currentBuilding.buildingId
? this.data.userLocation.floorName
: getApp().currentBuilding.floors[0],
);
},
setBuildingFloor(buildingId, floorName) {
const building = getApp().buildings.find(
(building) => building.buildingId && building.buildingId === buildingId,
);
if (building) {
getApp().setBuilding(building);
this.setFloorName(floorName);
}
},
setFloorName(floorName) {
console.log("setFloorName", floorName);
if (floorName !== this.data.floorName) this.setData({ floorName });
},
async getFloor() {
......@@ -329,8 +431,12 @@ Page({
return this.setData({ showAuthModal: true });
}
const { bluetoothEnabled, locationEnabled } = wx.getSystemSetting();
console.log("系统蓝牙状态", bluetoothEnabled);
console.log("系统位置状态", locationEnabled);
console.log(
"系统蓝牙状态",
bluetoothEnabled,
"系统位置状态",
locationEnabled,
);
if (!bluetoothEnabled) {
this.setData({ gettingLocation: false });
return this.openBluetoothModal();
......@@ -340,8 +446,12 @@ Page({
return this.openLocationModal();
}
const appAuthorizeSetting = wx.getAppAuthorizeSetting();
console.log("微信蓝牙权限", appAuthorizeSetting.bluetoothAuthorized);
console.log("微信位置权限", appAuthorizeSetting.locationAuthorized);
console.log(
"微信蓝牙权限",
appAuthorizeSetting.bluetoothAuthorized,
"微信位置权限",
appAuthorizeSetting.locationAuthorized,
);
if (appAuthorizeSetting.bluetoothAuthorized !== "authorized") {
this.setData({ gettingLocation: false });
return this.openBluetoothModal();
......@@ -352,14 +462,18 @@ Page({
}
getApp().ensurePluginInit();
this.getFloorTimeout = setTimeout(() => {
this.getFloorTimeout = setTimeout(
() => {
const lastUserLocation = this.data.userLocation;
this.setData({
gettingLocation: false,
resetCount: this.data.resetCount + 1,
userLocation: null,
userLocation: lastUserLocation || null,
});
this.setFloorName(defaultFloorName);
}, 3000);
if (!lastUserLocation?.buildingId) this.setFloorName(defaultFloorName);
},
!this.data.userLocation ? 10000 : 3000,
);
},
async getFacTypes() {
const { get, qmurl } = getApp();
......@@ -380,19 +494,39 @@ Page({
this.setFacilities(detail);
this.setData({ activeId: detail });
},
setFacilities(id) {
const { facilityIdMap } = getApp();
if (!facilityIdMap) return console.error("无facilityIdMap");
const currentFacType = this.data.facTypes.find((type) => type.id === id);
if (!currentFacType) return console.error("未找到设施大类");
const facilities = currentFacType.facilityIds.map(
(facilityId) => facilityIdMap[facilityId]
);
async setFacilities(id) {
const { poiApi, currentBuilding } = getApp();
const targetType = this.data.facTypes.find((t) => t.id === id);
if (!targetType?.facilityIds?.length) {
return console.error("未找到设施或ID列表为空");
}
try {
// 1. 构造最小化对象列表 (无需在 map 里写死 shopType)
const rawList = targetType.facilityIds.map((id) => ({ id }));
// 2. 自动填充
const filledList = await poiApi.populate(rawList, {
getType: () => "2",
getId: (item) => item.id,
targetField: "fac",
});
// 3. 提取 + 过滤 (链式处理,逻辑更紧凑)
const { floorName } = this.data;
const currentFloorFacilities = facilities.filter(
(fac) => fac.tx?.poi_fl_name === floorName
const currentFloorFacilities = filledList
.map((item) => item.fac)
.filter(
(fac) =>
fac?.tx?.poi_fl_name === floorName &&
fac?.tx?.bld_id === currentBuilding.buildingId,
);
this.setData({ currentFloorFacilities });
} catch (error) {
console.error("获取设施详情失败", error);
}
},
doNothing() {},
......@@ -438,8 +572,8 @@ Page({
if (!this.data.focusedPoi)
return {
title: this.data.isEn
? "Share Chinagoods Nav District 2 East indoor navigation miniprogram"
: "分享义乌商贸城二区东室内导航小程序,助你场内高效找店",
? "Share Chinagoods Nav indoor navigation miniprogram"
: "义乌小商品城市场室内导航,助您精准导航到店",
path: "/pages/index/index",
imageUrl: this.data.isEn
? `https://cdnimg.chinagoods.com/png/2025/08/13/qmzbxkjo3fse9xs5c2uujdc60tsc3wum.png`
......
......@@ -213,4 +213,12 @@
width: 16px;
height: 16px;
}
.card {
position: absolute;
top: 168px;
right: 2px;
width: 56px;
height: 56px;
z-index: 2;
}
}
<view class="index">
<map-wrapper type="{{isIndex?'index':'surroundings'}}" viewstate="{{viewstate}}" user-location="{{userLocation}}" acts="{{acts}}" reset-count="{{resetCount}}" building-id="{{buildingId}}" floor-name="{{floorName}}" facilities="{{currentFloorFacilities}}" bind:poi="handlePoi" bind:act="handleAct"></map-wrapper>
<lang-btn wx:if="{{!(isIndex&&viewstate==='fullscreen')}}"></lang-btn>
<bottom-hud-index wx:if="{{isIndex}}" bind:viewstate="handleBottomHudViewstate" bind:search="handleSearch" bind:ai="handleAi">
<floors never-authed="{{neverAuthed}}" show-success="{{getLocationSuccess}}" refreshing="{{gettingLocation}}" wx:if="{{viewstate!=='fullscreen'&&!focusedPoi}}" floors="{{floors}}" floor-name="{{floorName}}" bind:floorname="handleFloor" bind:resetmap="handleReset"></floors>
<map-wrapper type="{{isIndex?'index':'surroundings'}}" viewstate="{{viewstate}}" user-location="{{userLocation}}" acts="{{acts}}" reset-count="{{resetCount}}" floor-name="{{floorName}}" facilities="{{currentFloorFacilities}}" bind:poi="handlePoi" bind:act="handleAct" bind:buildchange="handleMapBuildChange"></map-wrapper>
<lang-btn wx:if="{{!(isIndex&&viewstate==='fullscreen')}}" type="index"></lang-btn>
<bottom-hud-index wx:if="{{isIndex}}" bind:viewstate="handleBottomHudViewstate" bind:search="handleSearch" bind:ai="handleAi" bind:poi="handlePoi">
<floors never-authed="{{neverAuthed}}" show-success="{{getLocationSuccess}}" refreshing="{{gettingLocation}}" wx:if="{{viewstate!=='fullscreen'&&!focusedPoi}}" floors="{{currentBuilding.floors}}" floor-name="{{floorName}}" bind:floorname="handleFloor" bind:resetmap="handleReset"></floors>
</bottom-hud-index>
<bottom-hud-surroundings wx:else badge-num="{{currentFloorFacilities.length}}" active-id="{{activeId}}" fac-types="{{facTypes}}" bind:factypetap="handleFacTypeTap">
<floors never-authed="{{neverAuthed}}" show-success="{{getLocationSuccess}}" refreshing="{{gettingLocation}}" wx:if="{{!focusedPoi}}" floors="{{floors}}" floor-name="{{floorName}}" bind:floorname="handleFloor" bind:resetmap="handleReset"></floors>
<floors never-authed="{{neverAuthed}}" show-success="{{getLocationSuccess}}" refreshing="{{gettingLocation}}" wx:if="{{!focusedPoi}}" floors="{{currentBuilding.floors}}" floor-name="{{floorName}}" bind:floorname="handleFloor" bind:resetmap="handleReset"></floors>
</bottom-hud-surroundings>
<popup wx:if="{{focusedPoi}}" is-index="{{true}}" bind:showbluetoothmodal="openBluetoothModal" bind:showlocationmodal="openLocationModal" bind:showauthmodal="openAuthModal">
<floors never-authed="{{neverAuthed}}" show-success="{{getLocationSuccess}}" refreshing="{{gettingLocation}}" floors="{{floors}}" floor-name="{{floorName}}" bind:floorname="handleFloor" bind:resetmap="handleReset"></floors>
<floors never-authed="{{neverAuthed}}" show-success="{{getLocationSuccess}}" refreshing="{{gettingLocation}}" floors="{{currentBuilding.floors}}" floor-name="{{floorName}}" bind:floorname="handleFloor" bind:resetmap="handleReset"></floors>
</popup>
<custom-tab-bar current-index="{{isIndex?0:1}}" bind:tab="handleTab"></custom-tab-bar>
<view class="banner" wx:if="{{isIndex&&showBanner}}" bind:tap="closeBanner">
......@@ -46,4 +46,8 @@
</view>
<image src="./close.png" class="close" catch:tap="closeAct" mode="" />
</view>
<block wx:if="{{cardId}}">
<textarea class="card" disabled style="z-index: 1"></textarea>
<image class="card" src="./card.png" mode="" bind:tap="toCard" />
</block>
</view>
\ No newline at end of file
......@@ -20,10 +20,13 @@ Component({
touchStartY: 0,
},
observers: {
itineraryList(list) {
async itineraryList(list) {
this.setData({
notVisitedCount: list.filter(({ status }) => status !== 1).length,
listGroupByYmd: this.groupByCreateTime(list),
});
const listGroupByYmd = await this.groupByCreateTime(list);
this.setData({
listGroupByYmd,
});
},
bulkDeleting() {
......@@ -35,30 +38,28 @@ Component({
async handleGo({ currentTarget: { id } }) {
const { isEn, itineraryList } = this.data;
const iti = itineraryList.find((iti) => Number(id) === iti.id);
const poi = getApp().shopIdMap[iti.shopId];
const app = getApp();
const poi = await app.poiApi.getOne({
shopType: "1",
shopId: iti.shopId,
});
if (!poi) return;
try {
getApp().sensors?.track("NavEntryBtnClick", {
app.sensors?.track("NavEntryBtnClick", {
page_name: "我的行程",
short_market_name: "二区东",
...(poi.tx
? {
x: Number(poi.tx.poi_location.longitude),
y: Number(poi.tx.poi_location.latitude),
}
: {}),
...(poi.facilityId
? {
nav_target_type: poi.facility.name,
booth_floor: poi.tx.poi_fl_seq,
}
: {
store_name: poi.ywZh.name,
booth_addr_street: poi.ywZh.boothAddrStreet,
booth_cover_img: poi.ywZh.cover,
booth_no: poi.ywZh.boothNo,
industry: poi.ywZh.frontIndustryCategory,
booth_floor: poi.ywZh.addrFloor,
}),
short_market_name: poi.ywZh?.marketNameDtl,
store_name: poi.ywZh?.name,
booth_addr_street: poi.ywZh?.boothAddrStreet,
booth_cover_img: poi.ywZh?.cover,
booth_no: poi.ywZh?.boothNo,
industry: poi.ywZh?.frontIndustryCategory,
booth_floor: poi.ywZh?.addrFloor,
});
} catch (error) {
console.error("埋点失败", error);
......@@ -113,7 +114,7 @@ Component({
this.setData({
deleteIdMap: this.data.itineraryList.reduce(
(acc, nxt) => ({ ...acc, [nxt.id]: true }),
{}
{},
),
allSelected: true,
});
......@@ -163,44 +164,45 @@ Component({
wx.hideLoading();
}
},
groupByCreateTime(dataList) {
const { shopIdMap } = getApp();
if (!shopIdMap) return [];
if (!dataList || !Array.isArray(dataList)) return [];
dataList = dataList.filter(({ shopId }) => shopIdMap[shopId]);
if (dataList.length === 0) {
return [];
}
const today = new Date();
const todayStr = `${today.getFullYear()}-${(today.getMonth() + 1)
.toString()
.padStart(2, "0")}-${today.getDate().toString().padStart(2, "0")}`;
async groupByCreateTime(dataList) {
if (!Array.isArray(dataList) || dataList.length === 0) return [];
// 1. 自动填充店铺数据
const populatedList = await getApp().poiApi.populate(dataList, {
getType: () => "1",
getId: (item) => item.shopId,
targetField: "shop",
});
// 2. 获取今天的日期字符串 (YYYY-MM-DD)
const now = new Date();
const todayStr = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}-${String(now.getDate()).padStart(2, "0")}`;
const result = [];
let currentGroup = null;
dataList.forEach((item) => {
const datePart = item.createTime.split(" ")[0];
// 3. 遍历、过滤、格式化、分组 (一次循环完成)
populatedList.forEach((item) => {
// 过滤无效数据
if (!item?.shop) return;
// --- 价格格式化 (无需 try-catch) ---
// Number() 失败会返回 NaN,逻辑依然安全
const price = Number(item.goodsPrice) || 0;
item.yuan = Math.floor(price);
item.fen = `.${String(Math.round(price * 100) % 100).padStart(2, "0")}`;
// --- 分组逻辑 ---
const datePart = (item.createTime || "").split(" ")[0];
const dateKey = datePart === todayStr ? "今天" : datePart;
// 如果是新的一天,创建新组
if (!currentGroup || currentGroup.date !== dateKey) {
currentGroup = {
date: dateKey,
list: [],
};
currentGroup = { date: dateKey, list: [] };
result.push(currentGroup);
}
try {
item.goodsPrice = Number(item.goodsPrice);
item.yuan = Math.floor(item.goodsPrice);
item.fen =
"." +
String(Math.round(item.goodsPrice * 100) % 100).padStart(2, "0");
} catch (error) {
item.yuan = item.goodsPrice;
console.error(error);
}
currentGroup.list.push({ ...item, shop: shopIdMap[item.shopId] });
currentGroup.list.push(item);
});
return result;
......
<view class="itinerary">
<scroll-view class="scroller {{bulkDeleting?'bulk':''}}" scroll-y>
<view class="title">
<view class="left">{{isEn?'My Itinerary':'我的行程'}}
<view class="left">{{isEn?'Saved Items':'商品收藏'}}
<text class="nums"><text class="red">{{notVisitedCount}}</text>/{{itineraryList.length}}</text>
</view>
<view class="btns">
......
......@@ -5,6 +5,7 @@ Page({
checked: false,
phoneLock: false,
showModal: false,
bindingModalData: null,
},
/**
......@@ -29,6 +30,57 @@ Page({
});
}
},
async checkRedPocketOpenid() {
try {
const { marketurl, post, globalData } = getApp();
const {
data: { code, data, message },
} = await post(
`${marketurl}/rpt/user/openId/query`,
{},
{
Authorization: `Bearer ${globalData.user.access_token}`,
}
);
if (code === 20000) {
console.log("检查红包openid", data.hasOpen);
return data.hasOpen;
} else {
console.log("检查红包openid报错", message);
return true;
}
} catch (error) {
console.log("检查红包openid报错", error);
return true;
}
},
async getRedPocketOpenid() {
try {
const { marketurl, post, globalData } = getApp();
const {
data: { code, message },
} = await post(
`${marketurl}/rpt/user/openId`,
{
returnUrl: `https://h5.chinagoods.com/verify-openid/?targetPath=${encodeURIComponent(
"/pages/verify-openId/index"
)}`,
},
{
Authorization: `Bearer ${globalData.user.access_token}`,
}
);
if (code === 20000) {
wx.redirectTo({
url: `/pages/wap/index?url=${encodeURIComponent(message)}`,
});
return false;
} else return true;
} catch (error) {
console.log(error);
}
},
handleUnchecked() {
this.setData({ showModal: true });
},
......@@ -39,8 +91,26 @@ Page({
this.setData({ phoneLock: true });
try {
if (code) {
const success = await getApp().login(code);
if (success) this.back();
const response = await getApp().login(code);
if (!response)
return wx.showToast({
title: "登录失败",
icon: "none",
});
if (response.code === 20000) {
const hasOpenid = await this.checkRedPocketOpenid();
if (hasOpenid) this.back();
else {
await this.getRedPocketOpenid();
}
} else if (response.code === 88051) {
return this.setData({ bindingModalData: response.data });
} else {
return wx.showToast({
title: response.message,
icon: "none",
});
}
}
} catch (error) {
console.log("error", error);
......@@ -48,6 +118,9 @@ Page({
this.setData({ phoneLock: false });
}
},
closeBindingModal() {
this.setData({ bindingModalData: null });
},
/**
* 生命周期函数--监听页面初次渲染完成
*/
......
......@@ -175,4 +175,84 @@
}
}
}
.binding-modal {
position: absolute;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
display: flex;
gap: 40px;
flex-direction: column;
align-items: center;
justify-content: center;
z-index: 3;
background: rgba(0, 0, 0, 0.6);
.modal-content {
width: 304px;
padding: 32px 36px;
border-radius: 8px;
background: #fff;
display: flex;
flex-direction: column;
align-items: center;
gap: 16px;
.r1 {
color: #333;
text-align: center;
font-family: "PingFang SC";
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: 18px;
}
.r2 {
color: #333;
text-align: center;
font-family: "PingFang SC";
font-size: 24px;
font-style: normal;
font-weight: 600;
line-height: normal;
}
.r3 {
display: flex;
width: 100%;
gap: 4px;
color: #666;
text-align: center;
font-family: "PingFang SC";
font-size: 12px;
font-style: normal;
font-weight: 400;
line-height: normal;
.icon {
width: 12px;
height: 12px;
margin-top: 1px;
flex-shrink: 0;
}
}
.r4 {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 40px;
border-radius: 99px;
border: 1px solid #e92927;
color: #e92927;
font-family: "PingFang SC";
font-size: 16px;
font-style: normal;
font-weight: 400;
line-height: 22px;
margin-top: 16px;
}
}
.close {
width: 32px;
height: 32px;
}
}
}
......@@ -25,7 +25,7 @@
</view>
</view>
<lang-btn type="black"></lang-btn>
<view class="modal" wx:if="{{showModal}}" bind:tap="closeModal">
<view class="modal" wx:if="{{showModal&&!bindingModalData}}" bind:tap="closeModal">
<view class="modal-content" catch:tap>
<view class="r1" wx:if="{{isEn}}">
Please read carefully and fully understand the relevant terms, click agree means that you have
......@@ -41,5 +41,21 @@
</view>
</view>
</view>
<view class="binding-modal" wx:if="{{bindingModalData}}" bind:tap="closeBindingModal">
<view class="modal-content" catch:tap>
<view class="r1">
当前微信注册的Chinagoods账号
已绑定其他手机号,占用绑定手机号为
</view>
<view class="r2">
{{bindingModalData}}
</view>
<view class="r3">
<image class="icon" src="./icon.png" mode="" />
<text style="text-align: left;">使用已绑定过微信账号的手机号进行登录,启用导航一键到店</text>
</view>
<view class="r4" catch:tap="closeBindingModal">切换账号登录</view>
</view>
<image src="./close.png" class="close" bind:tap="closeBindingModal" mode="" />
</view>
</view>
\ No newline at end of file
......@@ -34,7 +34,10 @@ Component({
this.setData({
formats: [
{ key: "all", name: "全部收藏", nameEn: "All" },
...getApp().formats.filter(({ key }) => formatKeyMap[key]),
...getApp()
.buildings.map(({ formats }) => formats)
.reduce((acc, nxt) => acc.concat(nxt), [])
.filter(({ key }) => formatKeyMap[key]),
],
});
this.setDisplayList();
......@@ -77,7 +80,7 @@ Component({
this.setData({
deleteIdMap: this.data.displayList.reduce(
(acc, nxt) => ({ ...acc, [nxt.id]: true }),
{}
{},
),
allSelected: true,
});
......@@ -121,16 +124,24 @@ Component({
toggleBulkDeleting() {
this.setData({ bulkDeleting: !this.data.bulkDeleting });
},
setPoiList() {
const { shopIdMap, facilityIdMap } = getApp();
if (!shopIdMap) return this.setData({ poiList: [] });
const poiList = this.data.collectList
.map(({ id, shopId, shopType }) => ({
id,
poi: shopType === 1 ? shopIdMap[shopId] : facilityIdMap[shopId],
}))
.filter(({ poi }) => poi);
this.setData({ poiList });
async setPoiList() {
const { collectList } = this.data;
// 1. 快速判空
if (!collectList?.length) return this.setData({ poiList: [] });
// 2. 自动填充 (省略默认配置,populate 默认读取 item.shopType 和 item.shopId)
const list = await getApp().poiApi.populate(collectList, {
targetField: "poi",
});
// 3. 过滤并重组 (最小化 setData 传输的数据量)
this.setData({
poiList: list
.filter((item) => item.poi)
.map(({ id, poi }) => ({ id, poi })),
});
this.setDisplayList();
},
cleanSwipeDeleteId() {
this.setData({ swipeDeleteId: null });
......@@ -151,14 +162,16 @@ Component({
const { isEn, displayList } = this.data;
const { poi } = displayList[index];
try {
const { buildingIdNameMap } = getApp();
getApp().sensors?.track("NavEntryBtnClick", {
page_name: "我的收藏列表",
short_market_name: "二区东",
rank: Number(index) + 1,
label_name: this.data.formats.find(({ key }) => this.data.key === key)
.name,
...(poi.tx
? {
short_market_name: buildingIdNameMap[poi.tx.bld_id],
x: Number(poi.tx.poi_location.longitude),
y: Number(poi.tx.poi_location.latitude),
}
......@@ -241,7 +254,7 @@ Component({
},
handleShopTap({ currentTarget: { id } }) {
const collect = this.data.displayList.find(
(collect) => Number(id) === collect.id
(collect) => Number(id) === collect.id,
);
if (!collect) return;
if (!this.data.bulkDeleting) {
......
<view class="collection">
<view class="title">
<view class="left">
{{isEn?'My Collection':'我收藏的地点'}}
<view>{{poiList.length}}</view>
{{isEn?'Saved Stores':'店铺收藏'}}
</view>
<block wx:if="{{poiList.length}}">
<view wx:if="{{bulkDeleting}}" class="edit red" bind:tap="toggleBulkDeleting">{{isEn?'Finish':'完成'}}</view>
......

2.9 KB | W: | H:

2.9 KB | W: | H:

pages/mine/history.png
pages/mine/history.png
pages/mine/history.png
pages/mine/history.png
  • 2-up
  • Swipe
  • Onion skin
......@@ -30,7 +30,9 @@ Page({
getApp().login();
},
toggleBulkDeleting({ detail: bulkDeleting }) {
this.setData({ bulkDeleting });
this.setData({
bulkDeleting,
});
},
toCouponCenter() {
getApp().sensors?.track("MyCouponClick", {
......@@ -49,7 +51,11 @@ Page({
url: "/pages/itinerary/index",
});
},
onLoad(options) {
onLoad(params) {
console.log(params);
const { utm_campaign, utm_term } = params;
if (utm_campaign) getApp().utm_campaign = utm_campaign;
if (utm_term) getApp().utm_term = utm_term;
this.setItineraryCount = () =>
this.setData({
itineraryCount: getApp().itineraryList.filter(
......
......@@ -31,7 +31,7 @@
</view>
<view class="btn" bind:tap="toItinerary">
<image class="icon" src="./history.png" mode="" />
<view class="text">{{isEn?"My Itinerary":"我的行程"}}<text style="margin-left: 6px;" wx:if="{{itineraryCount>0}}">{{itineraryCount}}</text></view>
<view class="text">{{isEn?"Saved Items":"商品收藏"}}</view>
<image class="arrow" src="./arrow.png" mode="" />
</view>
</view>
......@@ -39,7 +39,7 @@
</view>
<view style="height: 90px;"></view>
</scroll-view>
<custom-tab-bar wx:if="{{!bulkDeleting}}" current-index="{{2}}" bind:tab="handleTab"></custom-tab-bar>
<custom-tab-bar wx:if="{{!bulkDeleting}}" current-index="{{3}}" bind:tab="handleTab"></custom-tab-bar>
<bluetooth-modal wx:if="{{showBluetoothModal}}" bind:close="closeBluetoothModal"></bluetooth-modal>
<location-modal wx:if="{{showLocationModal}}" bind:close="closeLocationModal"></location-modal>
<auth-modal wx:if="{{showAuthModal}}" bind:close="closeAuthModal"></auth-modal>
......
import langBehavior from "../../behaviors/langBehavior";
import { findNearbyLocations } from "../../util";
import { findNearbyLocations, getPoiName, getDistance } from "../../util";
Page({
behaviors: [langBehavior],
/**
......@@ -9,6 +9,8 @@ Page({
showTip1: false,
showTip2: false,
showTip3: false,
showTip4: false,
pois: null,
couponId: null,
receivingCoupon: false,
poi: null,
......@@ -21,6 +23,7 @@ Page({
*/
async onLoad(options) {
const { lastNav } = getApp().globalData;
if (!lastNav) return this.toIndex();
this.setData({ lastNav });
try {
this.setData({
......@@ -29,12 +32,20 @@ Page({
} catch (error) {
console.error("设置卡路里失败", error);
}
if (!lastNav) return this.toIndex();
const poi = this.getEndPoi();
console.log("导航结束页面poi", poi);
// 自动收藏
// if (poi) this.collectPoi(poi);
this.setData({ poi });
if (getApp().isCardNav) {
getApp().isCardNav = false;
const checkInResult = await this.checkIn(poi);
if (checkInResult === "showTip4") return this.setData({ showTip4: true });
if (checkInResult) return this.setData({ pois: checkInResult });
return;
}
const rewardConfig = await this.getRewardConfig();
console.log(rewardConfig);
if (!rewardConfig) return;
......@@ -44,12 +55,12 @@ Page({
if (isvalid) {
try {
getApp().sensors?.track("ExposureActivityCoupon", {
short_market_name: "二区东",
...(poi.facilityId
? {
booth_floor: poi.tx.poi_fl_seq,
}
: {
short_market_name: poi.ywZh.marketNameDtl,
booth_addr_street: poi.ywZh.boothAddrStreet,
booth_no: poi.ywZh.boothNo,
industry: poi.ywZh.frontIndustryCategory,
......@@ -66,20 +77,117 @@ Page({
},
toIndex() {
getApp().globalData.lastNav = null;
console.log("toIndex");
wx.reLaunch({
url: "/pages/index/index",
});
},
async checkIn(poi) {
try {
const app = getApp();
const res = await app.cardApi.check(poi);
if (!res) return null;
if (res.isFinish && res.receiveIsSuccess === 1) return "showTip4";
if (res.isFinish || !res.activityId) return null;
// 1. 获取活动详情
const card = await app.cardApi.get(res.activityId);
const rawList = card?.activityShopList?.filter(
(el) => el.checkInStatus === 0,
);
if (!rawList?.length) return null;
// 2. 自动填充 POI
const list = await app.poiApi.populate(rawList, {
getType: (item) => item.shopType,
getId: (item) => item.shopId,
getBooth: (item) => item.boothNo,
});
const { userLocation } = app;
// 3. 处理数据 (距离计算 & 格式化)
const pois = list
.filter((item) => item.poi?.tx) // 确保有坐标
.map((item) => {
const { poi } = item;
let distance = null;
if (userLocation) {
const { latitude, longitude } = poi.tx.poi_location;
distance = Math.ceil(
getDistance(
userLocation.latitude,
userLocation.longitude,
Number(latitude),
Number(longitude),
),
);
}
return {
...item,
distance,
poiName: getPoiName(poi, this.data.isEn),
poiAvatar: poi.ywZh?.cover || poi.facility?.logo,
};
});
// 4. 排序与返回
if (userLocation) pois.sort((a, b) => a.distance - b.distance);
return pois.length ? pois : null;
} catch (error) {
console.error(error);
return null;
}
},
handleGo({ currentTarget: { id } }) {
const poi = this.data.pois.find((el) => el.shopId === id);
if (poi.shopType === 1)
wx.navigateTo({
url: `/pages/poi-map/index?shopId=${poi.shopId}&isCardNav=true`,
});
else {
wx.navigateTo({
url: `/pages/poi-map/index?facilityId=${poi.shopId}&isCardNav=true`,
});
}
},
async isCouponOnValid(couponId) {
try {
const { marketurl, get } = getApp();
const { marketurl, get, globalData } = getApp();
const {
data: { code, data },
} = await get(`${marketurl}/coupon/coupon/info?couponId=${couponId}`);
} = await get(`${marketurl}/coupon/coupon/info?couponId=${couponId}`, {
Authorization: `Bearer ${globalData.user.access_token}`,
});
if (code === 200) {
console.log("券详情", data);
if (data.leftQuantity > 0) return true;
// 检查库存
if (data.leftQuantity <= 0) {
console.log("券库存不足", data.leftQuantity);
return false;
}
// 检查有效期
const now = new Date();
const openStart = new Date(data.openStart);
const openEnd = new Date(data.openEnd);
if (now < openStart || now > openEnd) {
console.log("券未到领取时间", openStart, openEnd);
return false;
}
// 检查领取限制
if (data.recordSize >= data.receiveLimit) {
console.log("券到达领取限制", data.recordSize, data.receiveLimit);
return false;
}
return true;
} else {
return false;
}
......@@ -108,24 +216,40 @@ Page({
if (success) getApp().refreshCollection();
console.log(`自动收藏poi${success ? "成功" : "失败"}`);
},
getEndPoi() {
const { txQmMap, globalData, indexData } = getApp();
const { lastNav } = globalData;
const { latitude, longitude, floorName } = lastNav.endPoint;
async getEndPoi() {
const app = getApp();
const { txQmMap, indexData, globalData } = app;
// 防御性编程:防止 lastNav 为空报错
const endPoint = globalData.lastNav?.endPoint;
if (!endPoint) return null;
const { latitude, longitude, floorName } = endPoint;
try {
const list = findNearbyLocations(
// 1. 查找最近点 (利用 indexData 空间索引)
const [shineiId] = findNearbyLocations(
latitude,
longitude,
floorName,
indexData
indexData,
);
if (list.length) {
console.log("txQmMap[list[0]]", txQmMap[list[0]]);
return txQmMap[list[0]];
} else {
return null;
}
const baseInfo = txQmMap?.[shineiId];
// 2. 没找到基础信息,直接返回
if (!baseInfo) return null;
// 3. 动态查询详情
// facilityId 存在与否推断 "2"(设施) 或 "1"(店铺)
const poi = await app.poiApi.getOne({
shopType: baseInfo.facilityId ? "2" : "1",
shopId: baseInfo.shopId || baseInfo.facilityId,
boothNo: baseInfo.boothNo,
});
console.log("getEndPoi result:", poi);
return poi;
} catch (error) {
console.error("getEndPoi error:", error);
return null;
}
},
......@@ -140,7 +264,7 @@ Page({
{
Authorization: `Bearer ${globalData.user.access_token}`,
userId: globalData.user.user.userId,
}
},
);
if (code === 20000) {
console.log("积分领取成功" + message);
......@@ -162,13 +286,13 @@ Page({
const { poi, couponId } = this.data;
getApp().sensors?.track("ActivityCouponClick", {
is_receive: true,
short_market_name: "二区东",
coupon_record_id: couponId,
...(poi.facilityId
? {
booth_floor: poi.tx.poi_fl_seq,
}
: {
short_market_name: poi.ywZh.marketNameDtl,
booth_addr_street: poi.ywZh.boothAddrStreet,
industry: poi.ywZh.frontIndustryCategory,
booth_floor: poi.ywZh.addrFloor,
......@@ -186,7 +310,7 @@ Page({
{},
{
Authorization: `Bearer ${globalData.user.access_token}`,
}
},
);
if (code === 200) {
this.setData({ showTip2: false, showTip3: true });
......@@ -210,13 +334,13 @@ Page({
const { poi, couponId } = this.data;
getApp().sensors?.track("ActivityCouponClick", {
is_receive: false,
short_market_name: "二区东",
coupon_record_id: couponId,
...(poi.facilityId
? {
booth_floor: poi.tx.poi_fl_seq,
}
: {
short_market_name: poi.ywZh.marketNameDtl,
booth_addr_street: poi.ywZh.boothAddrStreet,
industry: poi.ywZh.frontIndustryCategory,
booth_floor: poi.ywZh.addrFloor,
......@@ -227,6 +351,12 @@ Page({
}
this.setData({ showTip2: false });
},
closeTip4() {
this.setData({ showTip4: false });
},
closePois() {
this.setData({ pois: null });
},
doNothing() {},
onReady() {},
......@@ -244,9 +374,7 @@ Page({
* 生命周期函数--监听页面卸载
*/
onUnload() {
wx.reLaunch({
url: "/pages/index/index",
});
getApp().blurPoi();
},
/**
......@@ -265,8 +393,8 @@ Page({
onShareAppMessage() {
return {
title: this.data.isEn
? "Share Chinagoods Nav District 2 East indoor navigation miniprogram"
: "分享义乌商贸城二区东室内导航小程序,助你场内高效找店",
? "Share Chinagoods Nav indoor navigation miniprogram"
: "义乌小商品城市场室内导航,助您精准导航到店",
path: `/pages/index/index?fromshare=true`,
imageUrl: this.data.isEn
? `https://cdnimg.chinagoods.com/png/2025/08/13/qmzbxkjo3fse9xs5c2uujdc60tsc3wum.png`
......
{
"usingComponents": {}
"usingComponents": {
"shop-maingoods": "/components/shop-maingoods/index",
"shop-addr": "/components/shop-addr/index",
"shop-name": "/components/shop-name/index"
}
}
......@@ -251,6 +251,218 @@
z-index: 3;
}
}
.tip4 {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.6);
z-index: 4;
.coupon {
position: absolute;
top: 77px;
left: 0;
right: 0;
margin: auto;
width: 304px;
height: 390px;
z-index: 1;
}
.close {
position: absolute;
display: flex;
top: 515px;
left: 0;
right: 0;
margin: auto;
width: 32px;
height: 32px;
justify-content: center;
align-items: center;
border-radius: 24px;
border: 1px solid rgba(0, 0, 0, 0.06);
background: rgba(0, 0, 0, 0.06);
backdrop-filter: blur(4px);
z-index: 3;
}
}
.pois {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.6);
z-index: 4;
.pois-inner {
position: absolute;
width: 302px;
left: 0;
right: 0;
top: 63px;
margin: auto;
background: #fff;
border-radius: 8px;
overflow: hidden;
.top-bg {
position: absolute;
top: 0;
right: 0;
width: 203px;
height: 212px;
z-index: 1;
}
.close {
position: absolute;
display: flex;
top: 10px;
right: 10px;
width: 32px;
height: 32px;
justify-content: center;
align-items: center;
border-radius: 24px;
background: rgba(0, 0, 0, 0.06);
z-index: 3;
}
.top {
position: relative;
z-index: 2;
padding: 20px 16px;
.avatar {
width: 96px;
height: 96px;
}
.t1 {
color: #333;
font-family: "PingFang SC";
font-size: 18px;
font-style: normal;
font-weight: 600;
line-height: 24px;
margin-top: 10px;
}
.t2 {
color: #666;
font-family: "PingFang SC";
font-size: 12px;
font-style: normal;
font-weight: 400;
line-height: 16px;
margin-top: 6px;
}
.t3 {
color: #666;
font-family: "PingFang SC";
font-size: 12px;
font-style: normal;
font-weight: 400;
line-height: 18px;
margin-top: 6px;
}
.row {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 18px;
.dis {
display: flex;
align-items: baseline;
color: #333;
font-family: "PingFang SC";
font-size: 12px;
font-style: normal;
font-weight: 400;
line-height: 16px;
.v {
font-size: 16px;
font-style: normal;
font-weight: 600;
line-height: 22px;
color: #e92927;
}
}
.go {
display: flex;
width: 174px;
height: 38px;
border-radius: 99px;
background: linear-gradient(90deg, #fa643c 0%, #e92927 100%);
gap: 4px;
align-items: center;
justify-content: center;
color: #fff;
font-family: "PingFang SC";
font-size: 14px;
font-style: normal;
font-weight: 600;
line-height: 18px;
}
}
}
.list {
background: #f3f3f3;
height: 220px;
.inner {
padding: 16px;
.t1 {
color: #333;
font-family: "PingFang SC";
font-size: 14px;
font-style: normal;
font-weight: 600;
line-height: 18px;
margin-bottom: 12px;
}
.item {
display: flex;
align-items: center;
padding: 8px 12px 8px 8px;
height: 56px;
margin-bottom: 8px;
border-radius: 10px;
background: linear-gradient(180deg, #fff 0%, #fff 100%);
.avatar {
width: 40px;
height: 40px;
border-radius: 8px;
}
.name {
flex: 1;
width: 100%;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
color: #333;
font-family: "PingFang SC";
font-size: 12px;
font-style: normal;
font-weight: 600;
line-height: normal;
padding: 0 8px;
}
.go {
display: flex;
align-items: center;
justify-content: center;
width: 64px;
height: 31px;
border-radius: 99px;
background: linear-gradient(90deg, #fa643c 0%, #e92927 100%);
color: #fff3f3;
text-align: center;
font-family: "PingFang SC";
font-size: 12px;
font-style: normal;
font-weight: 600;
line-height: normal;
}
}
}
}
}
}
.size-20 {
width: 20px;
height: 20px;
......
......@@ -53,7 +53,7 @@
<view class="t1">积分领取成功!</view>
<view class="t2">导航到店积分福利已发放至您的会员账户</view>
</view>
<view class="tip2" bing:tap="closeTip2" wx:if="{{showTip2}}">
<view class="tip2" bind:tap="closeTip2" wx:if="{{showTip2}}">
<image class="coupon" src="https://cdnimg.chinagoods.com/png/2025/08/05/tokz4ivypgnaljlau3juad1zq2zgf2o4.png" mode="" catch:tap="doNothing" />
<image catch:tap="receiveCoupon" class="btn" src="./btn.png" mode="" />
<view class="close" catch:tap="closeTip2">
......@@ -64,4 +64,63 @@
<view class="t1">领取成功!</view>
<view class="t2">活动券将发放您的会员账户</view>
</view>
<view class="tip4" bind:tap="closeTip4" wx:if="{{showTip4}}">
<image class="coupon" src="https://cdnimg.chinagoods.com/png/2025/12/19/gnu2fvvbgoe6jvpt984ls43qocmldhad.png" mode="" catch:tap="doNothing" />
<view class="close" catch:tap="closeTip4">
<image class="size-20" src="./close.png" mode="" />
</view>
</view>
<view class="pois" wx:if="{{pois}}" catch:tap="closePois">
<view class="pois-inner" catch:tap="doNothing">
<image class="top-bg" src="https://cdnimg.chinagoods.com/png/2025/12/19/gq8chjlyczzz7rvadgnwgys0daeq2smq.png" mode="" />
<view class="close" catch:tap="closePois">
<image class="size-20" src="./close-black.png" mode="" />
</view>
<view class="top">
<image class="avatar" src="{{pois[0].poiAvatar}}" mode="aspectFill" />
<block wx:if="{{pois[0].poi.facility}}">
<view class="t1">
{{isEn?pois[0].poi.facility.nameEn:pois[0].poi.facility.name}}
</view>
<view class="t2">{{isEn?pois[0].poi.facility.addressEn:pois[0].poi.facility.address}}</view>
</block>
<block wx:else>
<view class="t1">
<shop-name shop="{{pois[0].poi}}"></shop-name>
</view>
<view class="t2">
<shop-addr shop="{{pois[0].poi}}"></shop-addr>
</view>
<view class="t3">
<shop-maingoods shop="{{pois[0].poi}}"></shop-maingoods>
</view>
</block>
<view class="row">
<view class="dis">{{!pois[0].distance?'':isEn?'':'距您:'}}
<view wx:if="{{pois[0].distance}}" class="v">{{pois[0].distance}}m</view>
{{!pois[0].distance?'':isEn?' away':''}}
</view>
<view class="go" bind:tap="handleGo" id="{{pois[0].shopId}}">
<image class="size-20" src="./go.png" mode="" />
<view>{{isEn?'Check in':'去打卡'}}</view>
</view>
</view>
</view>
<scroll-view class="list" wx:if="{{pois.length>1}}" scroll-y>
<view class="inner">
<view class="t1">{{isEn?'Other Check-in Spots':'其它打卡点'}}</view>
<view class="item" wx:for="{{pois}}" wx:key="shopId" wx:if="{{index!==0}}">
<image class="avatar" src="{{item.poiAvatar}}" mode="" />
<view class="name" wx:if="{{item.poi.facility}}">
{{isEn?item.poi.facility.nameEn:item.poi.facility.name}}
</view>
<view class="name" wx:else>
<shop-name shop="{{item.poi}}"></shop-name>
</view>
<view class="go" id="{{item.shopId}}" bind:tap="handleGo">{{isEn?'Check in':'去打卡'}}</view>
</view>
</view>
</scroll-view>
</view>
</view>
</view>
\ No newline at end of file
......@@ -2,56 +2,36 @@ import langBehavior from "../../behaviors/langBehavior";
import poiFocusBehavior from "../../behaviors/poiFocusBehavior";
import modalBehavior from "../../behaviors/modalBehavior";
import { getShopShareTitle, getFacilityShareTitle } from "../../util";
const app = getApp();
Page({
behaviors: [langBehavior, poiFocusBehavior, modalBehavior],
data: {
buildingId: "3307001549220",
floorName: "1F",
floors: [
{ floorName: "3F", displayName: "3F" },
{ floorName: "2F", displayName: "2F" },
{ floorName: "1F", displayName: "1F" },
],
floors: [],
resetCount: 0,
userLocation: null,
reseting: false,
acts: [],
},
async onLoad({ shopId, facilityId }) {
const app = getApp();
if (shopId) {
if (!app.shopIdMap) {
app.events.once("globalData", () => {
const shop = app.shopIdMap[shopId];
async onLoad({ shopId, boothNo, facilityId, isCardNav }) {
app.isCardNav = !!isCardNav;
if (boothNo) {
const shop = await app.poiApi.getOne({ shopType: "1", shopId, boothNo });
if (shop) {
if (shop.tx) this.setData({ floorName: shop.tx.poi_fl_name });
app.focusPoi(shop);
this.setStateByPoi(shop);
}
});
} else {
const shop = app.shopIdMap[shopId];
} else if (shopId) {
const shop = await app.poiApi.getOne({ shopType: "1", shopId });
if (shop) {
if (shop.tx) this.setData({ floorName: shop.tx.poi_fl_name });
app.focusPoi(shop);
}
}
}
if (facilityId) {
if (!app.facilityIdMap) {
app.events.once("globalData", () => {
const facility = app.facilityIdMap[facilityId];
if (facility) {
if (facility.tx)
this.setData({ floorName: facility.tx.poi_fl_name });
app.focusPoi(facility);
this.setStateByPoi(shop);
}
} else if (facilityId) {
const facility = await app.poiApi.getOne({
shopType: "2",
shopId: facilityId,
});
} else {
const facility = app.facilityIdMap[facilityId];
if (facility) {
if (facility.tx) this.setData({ floorName: facility.tx.poi_fl_name });
app.focusPoi(facility);
}
this.setStateByPoi(facility);
}
}
this.setUserLocation = () => {
......@@ -60,6 +40,27 @@ Page({
getApp().events.on("userLocation", this.setUserLocation);
this.setUserLocation();
},
setStateByPoi(poi) {
const app = getApp();
if (poi?.tx) {
const building = app.buildings.find(
({ buildingId }) => buildingId === poi?.tx?.bld_id,
);
this.setData({
floors: building?.floors,
buildingId: poi.tx.bld_id,
floorName: poi.tx.poi_fl_name,
});
} else {
const building = app.currentBuilding;
this.setData({
floors: building.floors,
buildingId: building.buildingId,
floorName: building.floors[0],
});
}
app.focusPoi(poi);
},
onUnload() {
if (this.setUserLocation)
getApp().events.off("userLocation", this.setUserLocation);
......
import poiFocusBehavior from "../../../behaviors/poiFocusBehavior";
const defaultScale = 20;
const defaultRotate = 329;
const defaultLatitude = 29.331723;
const defaultLongitude = 120.106838;
const defaultSkew = 30;
Component({
......@@ -16,12 +14,12 @@ Component({
},
data: {
latitude: defaultLatitude,
longitude: defaultLongitude,
latitude: getApp().currentBuilding.latlng.lat,
longitude: getApp().currentBuilding.latlng.lng,
setting: {
skew: defaultSkew,
rotate: defaultRotate,
scale: defaultScale,
scale: getApp().currentBuilding.scale,
},
},
observers: {
......@@ -32,33 +30,6 @@ Component({
this.setIndoorFloor();
this.handleModel();
},
"floorName,acts"() {
this.setData({
markers: this.data.acts
.filter(({ floor_name }) => floor_name === this.data.floorName)
.map(({ latitude, longitude, short_name, id }) => ({
id,
latitude,
longitude,
iconPath: "./actMarker.png",
width: 1,
height: 1,
zIndex: 1,
callout: {
content: short_name,
color: "#E92927",
fontSize: 12,
bgColor: "#ffffff",
padding: 6,
borderRadius: 100,
borderWidth: 1,
borderColor: "#eeeeee",
textAlign: "center",
display: "ALWAYS",
},
})),
});
},
focusedPoi(poi) {
try {
if (!poi) {
......@@ -118,6 +89,16 @@ Component({
})
);
}, 500);
} else {
this.setData({
latitude: getApp().currentBuilding.latlng.lat,
longitude: getApp().currentBuilding.latlng.lng,
setting: {
skew: defaultSkew,
rotate: defaultRotate,
scale: getApp().currentBuilding.scale,
},
});
}
} catch (error) {
console.error(error);
......
import langBehavior from "../../../behaviors/langBehavior";
import buildingBehavior from "../../../behaviors/buildingBehavior";
Component({
behaviors: [langBehavior],
behaviors: [langBehavior, buildingBehavior],
properties: { q: String },
/**
......@@ -8,22 +9,13 @@ Component({
*/
data: {
placeholderIndex: 0,
placeholders: [
{
en: "District 2-East New Energy Product Market Welcome!",
cn: "欢迎光临二区东新能源产品市场",
placeholders: [],
},
{
en: "Search store number/facilities/block/exit number",
cn: "搜铺号/公共设施/街区号/出入口号",
observers: {
currentBuilding() {
this.setPlaceholder();
},
{
en: "Click or search shoplD for click navigation",
cn: "点击或搜索任意商铺可一键导航前往",
},
],
},
observers: {},
methods: {
handleInput({ detail: { value } }) {
this.triggerEvent("q", value);
......@@ -34,6 +26,25 @@ Component({
aiTap() {
this.triggerEvent("ai");
},
setPlaceholder() {
const building = this.data.currentBuilding;
this.setData({
placeholders: [
{
en: `${building.displayNameEn} Welcome!`,
cn: `欢迎光临${building.displayName}`,
},
{
en: "Search store number/facilities/block/exit number",
cn: "搜铺号/公共设施/街区号/出入口号",
},
{
en: "Click or search shoplD for click navigation",
cn: "点击或搜索任意商铺可一键导航前往",
},
],
});
},
},
lifetimes: {
attached() {
......@@ -42,6 +53,7 @@ Component({
placeholderIndex: (this.data.placeholderIndex + 1) % 3,
});
}, 3000);
this.setPlaceholder();
},
detached() {
if (this.interval) {
......
......@@ -55,7 +55,7 @@
color: #000;
border-radius: 99px;
border: 0.2px solid var(--W-100, #fff);
background: rgba(255, 255, 255, 0.08);
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(33.05883026123047px);
}
}
......
......@@ -4,7 +4,7 @@
<input type="text" class="text" focus="true" value="{{q}}" placeholder-style="color:rgba(0, 0, 0, 0.40);font-size:13px;" placeholder="{{placeholders[placeholderIndex][isEn?'en':'cn']}}" bindinput="handleInput" />
<image wx:if="{{!!q}}" src="./close.png" class="close" bind:tap="handleClose" mode="" />
<view class="ai" bind:tap="aiTap">
<image src="./ai.png" class="icon" mode="" />
<image src="https://cdnimg.chinagoods.com/png/2025/10/10/tabzvwxow9xlggugfr0e0oe3s5pahny2.png" class="icon" mode="" />
<view class="text" style="{{'width: '+(isEn?50:35)+'px'}}">{{isEn?"AI Search":"AI识物"}}</view>
</view>
</view>
\ No newline at end of file
......@@ -13,16 +13,22 @@ Component({
searchHistory: [],
},
observers: {
collectList() {
const { shopIdMap, facilityIdMap } = getApp();
if (!shopIdMap) return this.setData({ collectPoiList: [] });
const collectPoiList = this.data.collectList
.map(({ id, shopId, shopType }) => ({
id,
poi: shopType === 1 ? shopIdMap[shopId] : facilityIdMap[shopId],
}))
.filter(({ poi }) => poi);
this.setData({ collectPoiList });
async collectList() {
const { collectList } = this.data;
if (!collectList?.length) {
return this.setData({ collectPoiList: [] });
}
const list = await getApp().poiApi.populate(collectList, {
targetField: "poi",
});
this.setData({
collectPoiList: list
.filter((item) => item.poi)
.map(({ id, poi }) => ({ id, poi })),
});
},
},
methods: {
......@@ -39,14 +45,16 @@ Component({
const { poi } = this.data.collectPoiList[index];
if (!poi.tx) return;
try {
const { buildingIdNameMap } = getApp();
getApp().sensors?.track("NavEntryBtnClick", {
page_name: "搜索我的收藏",
short_market_name: "二区东",
rank: Number(index) + 1,
...(poi.tx
? {
x: Number(poi.tx.poi_location.longitude),
y: Number(poi.tx.poi_location.latitude),
short_market_name: buildingIdNameMap[poi.tx.bld_id],
}
: {}),
...(poi.facilityId
......@@ -79,25 +87,28 @@ Component({
}
},
async getSearchHistory() {
const { post, qmurl, shopIdMap, facilityIdMap, globalData } = getApp();
if (!globalData.user) return;
if (!shopIdMap) return;
const app = getApp();
const userId = app.globalData.user?.user?.userId;
if (!userId) return;
try {
const {
data: { data },
} = await post(`${qmurl}/api/v1/applet/getUserSearchHistoryList`, {
userId: globalData.user.user.userId,
});
} = await app.post(
`${app.qmurl}/api/v1/applet/getUserSearchHistoryList`,
{ userId },
);
if (!Array.isArray(data)) return;
const rawList = data.slice(0, 20);
const list = await app.poiApi.populate(rawList, { targetField: "poi" });
this.setData({
searchHistory: data
.map(({ shopType, shopId }) =>
shopType === 1
? shopIdMap[shopId]
: shopType === 2
? facilityIdMap[shopId]
: null
)
.filter((el) => el)
searchHistory: list
.map((item) => item.poi)
.filter(Boolean)
.slice(0, 10),
});
} catch (error) {
......@@ -118,7 +129,7 @@ Component({
const {
data: { code },
} = await get(
`${qmurl}/api/v1/applet/clearUserSearchHistory?userId=${globalData.user.user.userId}`
`${qmurl}/api/v1/applet/clearUserSearchHistory?userId=${globalData.user.user.userId}`,
);
if (code === 200) {
wx.showToast({
......@@ -142,96 +153,86 @@ Component({
});
},
async handleShopGo({ currentTarget: { id: shopId } }) {
try {
const index = this.data.searchHistory.findIndex(
(poi) => poi.shopId == shopId
(p) => p.shopId == shopId,
);
const poi = this.data.searchHistory[index];
getApp().sensors?.track("NavEntryBtnClick", {
if (!poi) return;
const app = getApp();
try {
const tx = poi.tx || {};
const yw = poi.ywZh || {};
app.sensors?.track("NavEntryBtnClick", {
page_name: "历史搜索记录",
short_market_name: "二区东",
rank: index + 1,
key_word: this.data.q,
...(poi.tx
...(tx.poi_location
? {
x: Number(poi.tx.poi_location.longitude),
y: Number(poi.tx.poi_location.latitude),
x: Number(tx.poi_location.longitude),
y: Number(tx.poi_location.latitude),
short_market_name: app.buildingIdNameMap[tx.bld_id],
}
: {}),
...(poi.facilityId
? {
nav_target_type: poi.facility.name,
booth_floor: poi.tx.poi_fl_seq,
}
: {
store_name: poi.ywZh.name,
booth_addr_street: poi.ywZh.boothAddrStreet,
booth_cover_img: poi.ywZh.cover,
booth_no: poi.ywZh.boothNo,
industry: poi.ywZh.frontIndustryCategory,
booth_floor: poi.ywZh.addrFloor,
}),
store_name: yw.name,
booth_addr_street: yw.boothAddrStreet,
booth_cover_img: yw.cover,
booth_no: yw.boothNo,
industry: yw.frontIndustryCategory,
booth_floor: yw.addrFloor,
});
} catch (error) {
console.error("埋点失败", error);
} catch (e) {
console.error("埋点失败", e);
}
const shop = getApp().shopIdMap[shopId];
if (!shop) return;
try {
const { isEn } = this.data;
const res = await toRoutePlan(shop, isEn);
if (res === "showBluetoothModal")
return this.triggerEvent("showbluetoothmodal");
if (res === "showLocationModal")
return this.triggerEvent("showlocationmodal");
if (res === "showAuthModal") return this.triggerEvent("showauthmodal");
const res = await toRoutePlan(poi, this.data.isEn);
const eventMap = {
showBluetoothModal: "showbluetoothmodal",
showLocationModal: "showlocationmodal",
showAuthModal: "showauthmodal",
};
if (eventMap[res]) this.triggerEvent(eventMap[res]);
} catch (error) {
console.error(error);
}
},
async handleFacGo({ currentTarget: { id: facilityId } }) {
try {
const index = this.data.searchHistory.findIndex(
(poi) => poi.facilityId == facilityId
(p) => p.facilityId == facilityId,
);
const poi = this.data.searchHistory[index];
getApp().sensors?.track("NavEntryBtnClick", {
if (!poi) return;
const app = getApp();
try {
const tx = poi.tx || {};
app.sensors?.track("NavEntryBtnClick", {
page_name: "历史搜索记录",
short_market_name: "二区东",
rank: index + 1,
...(poi.tx
...(tx.poi_location
? {
x: Number(poi.tx.poi_location.longitude),
y: Number(poi.tx.poi_location.latitude),
x: Number(tx.poi_location.longitude),
y: Number(tx.poi_location.latitude),
short_market_name: app.buildingIdNameMap[tx.bld_id],
}
: {}),
...(poi.facilityId
? {
nav_target_type: poi.facility.name,
booth_floor: poi.tx.poi_fl_seq,
}
: {
store_name: poi.ywZh.name,
booth_addr_street: poi.ywZh.boothAddrStreet,
booth_cover_img: poi.ywZh.cover,
booth_no: poi.ywZh.boothNo,
industry: poi.ywZh.frontIndustryCategory,
booth_floor: poi.ywZh.addrFloor,
}),
nav_target_type: poi.facility?.name,
booth_floor: tx.poi_fl_seq,
});
} catch (error) {
console.error("埋点失败", error);
} catch (e) {
console.error("埋点失败", e);
}
const fac = getApp().facilityIdMap[facilityId];
if (!fac) return;
try {
const { isEn } = this.data;
const res = await toRoutePlan(fac, isEn);
if (res === "showBluetoothModal")
return this.triggerEvent("showbluetoothmodal");
if (res === "showLocationModal")
return this.triggerEvent("showlocationmodal");
if (res === "showAuthModal") return this.triggerEvent("showauthmodal");
const res = await toRoutePlan(poi, this.data.isEn);
const eventMap = {
showBluetoothModal: "showbluetoothmodal",
showLocationModal: "showlocationmodal",
showAuthModal: "showauthmodal",
};
if (eventMap[res]) this.triggerEvent(eventMap[res]);
} catch (error) {
console.error(error);
}
......
......@@ -26,110 +26,154 @@ Component({
*/
methods: {
async getList(loadMore = false) {
const { post, qmurl, shopIdMap, facilityIdMap } = getApp();
const app = getApp();
const { isEn, q, requestCount, lastQ, pageNo, pageSize } = this.data;
// 如果是加载更多,直接从缓存读取数据
// --- 场景 A: 加载更多 ---
if (loadMore) {
const start = pageNo - 1;
const currentPageData = this.data.allList.slice(
start * pageSize,
(start + 1) * pageSize
);
return this.setData({
list: [...this.data.list, ...currentPageData],
pageNo: pageNo + 1,
hasMore: this.data.allList.length > (start + 1) * pageSize,
});
// 检查内存中是否还有未加载的数据
if (
!this.sortedRawList ||
this.sortedRawList.length <= (pageNo - 1) * pageSize
) {
return;
}
await this._processPageData(pageNo + 1);
return;
}
// 新搜索才需要调用接口
if (!shopIdMap) return;
if (q === lastQ) return;
if (!q) return;
// --- 场景 B: 新搜索 ---
if (q === lastQ || !q) return;
const currentRequest = requestCount + 1;
this.setData({
requestCount: currentRequest,
isLoading: true,
lastQ: q,
list: [],
list: [], // 清空展示列表
pageNo: 1,
hasMore: true,
});
try {
// 1. 获取全量 ID 列表 (仅包含 shopId, shopType 等基础字段)
const {
data: { data },
} = await post(
`${qmurl}/api/v1/applet/searchShopFacilityList`,
{ keyword: q },
{ lang: isEn ? "en" : "zh" }
} = await app.post(
`${app.qmurl}/api/v1/applet/searchShopFacilityList`,
{ keyword: q, zone: app.currentBuilding.zone },
{ lang: isEn ? "en" : "zh" },
);
if (currentRequest !== this.data.requestCount) return;
// 2. 【核心步骤】利用 txQmMap 进行全局距离计算和排序
this.sortedRawList = this._calculateDistanceAndSort(data || []);
// 3. 加载第一页详情
await this._processPageData(1);
} catch (error) {
console.error(error);
if (currentRequest === this.data.requestCount) {
const allList = data
.map(({ shopType, shopId }) =>
shopType === 1
? shopIdMap[shopId]
: shopType === 2
? facilityIdMap[shopId]
: null
)
.filter((el) => el)
.map((poi) => {
if (!poi.tx) return { poi, distance: null };
if (!this.data.userLocation) return { poi, distance: null };
if (poi.tx.poi_fl_name !== this.data.userLocation.floorName)
return { poi, distance: null };
this.setData({ isLoading: false, list: [], hasMore: false });
}
}
},
_calculateDistanceAndSort(rawList) {
const app = getApp();
const { userLocation } = this.data;
const { locMap } = app;
if (!locMap) return rawList;
const listWithDistance = rawList.map((item) => {
// 生成与 app.js 中规则一致的 Key
const key = `${item.shopType}_${item.shopId}_${item.boothNo || ""}`;
// O(1) 快速查找坐标
const tx = locMap[key];
let distance = null;
// 计算距离 (同层优先)
if (tx && userLocation && tx.poi_fl_name === userLocation.floorName) {
try {
const lat = Number(poi.tx.poi_location.latitude);
const lng = Number(poi.tx.poi_location.longitude);
return {
poi,
distance: Math.ceil(
distance = Math.ceil(
getDistance(
lat,
lng,
this.data.userLocation.latitude,
this.data.userLocation.longitude
)
Number(tx.poi_location.latitude),
Number(tx.poi_location.longitude),
userLocation.latitude,
userLocation.longitude,
),
};
} catch (error) {
return { poi, distance: null };
);
} catch (e) {}
}
return { ...item, distance };
});
if (this.data.userLocation)
allList.sort((a, b) => {
// 如果 a 或 b 的 distance 为 null,则将其排在后面
if (a.distance === null && b.distance === null) return 0;
if (a.distance === null) return 1; // a 为 null 时排后面
if (b.distance === null) return -1; // b 为 null 时排后面
return a.distance - b.distance; // 正常按距离升序排序
// 2. 全局排序
if (userLocation) {
listWithDistance.sort((a, b) => {
if (a.distance !== null && b.distance !== null) {
return a.distance - b.distance;
}
if (a.distance !== null) return -1;
if (b.distance !== null) return 1;
return 0;
});
}
// 缓存所有数据
const currentPageData = allList.slice(0, pageSize);
return listWithDistance;
},
this.setData({
allList,
list: currentPageData,
isLoading: false,
hasMore: allList.length > pageSize,
pageNo: 2,
});
/**
* 内部方法:按需获取详情并渲染
*/
async _processPageData(targetPage) {
const app = getApp();
const { pageSize } = this.data;
this.setData({ isLoading: true });
try {
// 1. 截取当前页需要的 ID (这些 ID 已经是排好序的了)
const start = (targetPage - 1) * pageSize;
const end = start + pageSize;
const rawSlice = this.sortedRawList.slice(start, end);
if (rawSlice.length === 0) {
return this.setData({ isLoading: false, hasMore: false });
}
} catch (error) {
console.error(error);
if (currentRequest === this.data.requestCount) {
// 2. 批量请求详情
const populatedSlice = await app.poiApi.populate(rawSlice, {
targetField: "poi",
// 这里的配置可以省略,如果字段名标准的话
getType: (item) => item.shopType,
getId: (item) => item.shopId,
getBooth: (item) => item.boothNo,
});
// 3. 合并详情 + 之前计算好的距离
const displaySlice = populatedSlice
.filter((item) => item.poi)
.map((item) => ({
poi: item.poi,
distance: item.distance, // 直接使用第一步计算好的距离,无需再次计算
}));
// 4. 更新视图
this.setData({
list:
targetPage === 1
? displaySlice
: [...this.data.list, ...displaySlice],
pageNo: targetPage,
hasMore: this.sortedRawList.length > end,
isLoading: false,
allList: [],
hasMore: false,
});
}
} catch (error) {
console.error("分页加载失败", error);
this.setData({ isLoading: false });
}
},
async addSearchHistory(shopId, isShop) {
......@@ -152,21 +196,22 @@ Component({
console.error(error);
}
},
handleShop({ currentTarget: { id: shopId } }) {
try {
handleShop({ currentTarget: { id: boothNo } }) {
const index = this.data.list.findIndex(
({ poi }) => poi.shopId == shopId
({ poi }) => poi.boothNo == boothNo,
);
const poi = this.data.list[index].poi;
try {
const { buildingIdNameMap } = getApp();
getApp().sensors?.track("NavItemClick", {
page_name: "搜索结果列表",
short_market_name: "二区东",
rank: index + 1,
key_word: this.data.q,
...(poi.tx
? {
x: Number(poi.tx.poi_location.longitude),
y: Number(poi.tx.poi_location.latitude),
short_market_name: buildingIdNameMap[poi.tx.bld_id],
}
: {}),
...(poi.facilityId
......@@ -185,26 +230,27 @@ Component({
} catch (error) {
console.error("埋点失败", error);
}
this.addSearchHistory(shopId, true);
this.addSearchHistory(poi.shopId, true);
wx.navigateTo({
url: `/pages/poi-map/index?shopId=${shopId}`,
url: `/pages/poi-map/index?shopId=${poi.shopId}&boothNo=${boothNo}`,
});
},
handleFacility({ currentTarget: { id: facilityId } }) {
try {
const index = this.data.list.findIndex(
({ poi }) => poi.facilityId == facilityId
({ poi }) => poi.facilityId == facilityId,
);
const poi = this.data.list[index].poi;
const { buildingIdNameMap } = getApp();
getApp().sensors?.track("NavItemClick", {
page_name: "搜索结果列表",
short_market_name: "二区东",
rank: index + 1,
key_word: this.data.q,
...(poi.tx
? {
x: Number(poi.tx.poi_location.longitude),
y: Number(poi.tx.poi_location.latitude),
short_market_name: buildingIdNameMap[poi.tx.bld_id],
}
: {}),
...(poi.facilityId
......@@ -229,21 +275,22 @@ Component({
url: `/pages/poi-map/index?facilityId=${facilityId}`,
});
},
async handleShopGo({ currentTarget: { id: shopId } }) {
try {
async handleShopGo({ currentTarget: { id: boothNo } }) {
const index = this.data.list.findIndex(
({ poi }) => poi.shopId == shopId
({ poi }) => poi.boothNo == boothNo,
);
const poi = this.data.list[index].poi;
try {
const { buildingIdNameMap } = getApp();
getApp().sensors?.track("NavEntryBtnClick", {
page_name: "搜索结果列表",
short_market_name: "二区东",
rank: index + 1,
key_word: this.data.q,
...(poi.tx
? {
x: Number(poi.tx.poi_location.longitude),
y: Number(poi.tx.poi_location.latitude),
short_market_name: buildingIdNameMap[poi.tx.bld_id],
}
: {}),
...(poi.facilityId
......@@ -252,24 +299,22 @@ Component({
booth_floor: poi.tx.poi_fl_seq,
}
: {
store_name: poi.ywZh.name,
booth_addr_street: poi.ywZh.boothAddrStreet,
booth_cover_img: poi.ywZh.cover,
booth_no: poi.ywZh.boothNo,
industry: poi.ywZh.frontIndustryCategory,
booth_floor: poi.ywZh.addrFloor,
store_name: poi.ywZh?.name,
booth_addr_street: poi.ywZh?.boothAddrStreet,
booth_cover_img: poi.ywZh?.cover,
booth_no: poi.ywZh?.boothNo,
industry: poi.ywZh?.frontIndustryCategory,
booth_floor: poi.ywZh?.addrFloor,
}),
});
} catch (error) {
console.error("埋点失败", error);
}
if (!getApp().checkUser()) return;
this.addSearchHistory(shopId, true);
const shop = getApp().shopIdMap[shopId];
if (!shop) return;
this.addSearchHistory(poi.shopId, true);
try {
const { isEn } = this.data;
const res = await toRoutePlan(shop, isEn);
const res = await toRoutePlan(poi, isEn);
if (res === "showBluetoothModal")
return this.triggerEvent("showbluetoothmodal");
if (res === "showLocationModal")
......@@ -282,18 +327,19 @@ Component({
async handleFacGo({ currentTarget: { id: facilityId } }) {
try {
const index = this.data.list.findIndex(
({ poi }) => poi.facilityId == facilityId
({ poi }) => poi.facilityId == facilityId,
);
const poi = this.data.list[index].poi;
const { buildingIdNameMap } = getApp();
getApp().sensors?.track("NavEntryBtnClick", {
page_name: "搜索结果列表",
short_market_name: "二区东",
rank: index + 1,
key_word: this.data.q,
...(poi.tx
? {
x: Number(poi.tx.poi_location.longitude),
y: Number(poi.tx.poi_location.latitude),
short_market_name: buildingIdNameMap[poi.tx.bld_id],
}
: {}),
...(poi.facilityId
......@@ -315,11 +361,9 @@ Component({
}
if (!getApp().checkUser()) return;
this.addSearchHistory(facilityId, false);
const fac = getApp().facilityIdMap[facilityId];
if (!fac) return;
try {
const { isEn } = this.data;
const res = await toRoutePlan(fac, isEn);
const res = await toRoutePlan(poi, isEn);
if (res === "showBluetoothModal")
return this.triggerEvent("showbluetoothmodal");
if (res === "showLocationModal")
......@@ -337,15 +381,8 @@ Component({
},
lifetimes: {
attached() {
const app = getApp();
if (app.shopIdMap) {
async attached() {
this.getList();
} else {
app.events.once("globalData", () => {
this.getList();
});
}
},
},
observers: {
......
......@@ -22,7 +22,7 @@
</view>
</view>
</view>
<view class="shop" wx:else bind:tap="handleShop" id="{{item.poi.shopId}}">
<view class="shop" wx:else bind:tap="handleShop" id="{{item.poi.boothNo}}">
<image class="size-16" style="margin-top: 3px;" src="./loc.png" mode="" />
<view class="right">
<view class="content">
......@@ -42,7 +42,7 @@
</view>
</view>
</view>
<view class="go" id="{{item.poi.shopId}}" wx:if="{{item.poi.tx}}" catch:tap="handleShopGo">
<view class="go" id="{{item.poi.boothNo}}" wx:if="{{item.poi.tx}}" catch:tap="handleShopGo">
<image class="size-32" src="./nav.png" mode="" />
{{item.distance!==null? item.distance + 'm' : isEn?'Go here':'到这去'}}
</view>
......
......@@ -2,6 +2,7 @@ import langBehavior from "../../behaviors/langBehavior";
import collectBehavior from "../../behaviors/collectBehavior";
import modalBehavior from "../../behaviors/modalBehavior";
import { getShopShareTitle, toRoutePlan } from "../../util";
const app = getApp();
Page({
behaviors: [langBehavior, collectBehavior, modalBehavior],
data: {
......@@ -14,14 +15,7 @@ Page({
onLoad({ shopId }) {
if (!shopId) return this.back();
this.setData({ shopId });
const app = getApp();
if (app.shopIdMap) {
this.getDetail(shopId);
} else {
getApp().events.once("globalData", () => {
this.getDetail(shopId);
});
}
},
onUnload() {},
setCollected() {
......@@ -42,12 +36,11 @@ Page({
}
},
async getDetail(shopId) {
const { post, get, marketurl, md5, salt, shopIdMap } = getApp();
const { post, get, marketurl, md5, salt } = app;
const { isEn } = this.data;
if (!shopIdMap) return;
const shop = shopIdMap[shopId];
const shop = await app.poiApi.getOne({ shopType: "1", shopId });
if (!shop) return this.back();
const { boothNo } = shop.ywZh;
const boothNo = shop.ywZh?.boothNo;
try {
let {
data: { data },
......@@ -68,7 +61,7 @@ Page({
data: { data: products },
},
} = await get(
`${marketurl}/fpms/v1/product/hot/shop?shopId=${shop.shopId}&currentPage=1&pageSize=10`
`${marketurl}/fpms/v1/product/hot/shop?shopId=${shop.shopId}&currentPage=1&pageSize=10`,
);
try {
products.forEach((product) => {
......@@ -88,7 +81,7 @@ Page({
shop_name: poi.ywZh.name,
store_name: poi.ywZh.name,
booth_no: poi.ywZh.boothNo,
short_market_name: "二区东",
short_market_name: poi.ywZh.marketNameDtl,
booth_addr_street: poi.ywZh.boothAddrStreet,
industry: poi.ywZh.frontIndustryCategory,
booth_floor: poi.ywZh.addrFloor,
......@@ -121,7 +114,7 @@ Page({
shop_name: poi.ywZh.name,
store_name: poi.ywZh.name,
booth_no: poi.ywZh.boothNo,
short_market_name: "二区东",
short_market_name: poi.ywZh.marketNameDtl,
booth_addr_street: poi.ywZh.boothAddrStreet,
industry: poi.ywZh.frontIndustryCategory,
booth_floor: poi.ywZh.addrFloor,
......@@ -131,14 +124,14 @@ Page({
}
wx.navigateTo({
url: `/pages/wap/index?url=${encodeURIComponent(
`https://m.chinagoods.com/product/${id}`
`https://m.chinagoods.com/product/${id}`,
)}`,
});
},
toMore() {
wx.navigateTo({
url: `/pages/wap/index?url=${encodeURIComponent(
`https://m.chinagoods.com/shop/${this.data.shopId}`
`https://m.chinagoods.com/shop/${this.data.shopId}`,
)}`,
});
},
......@@ -147,7 +140,6 @@ Page({
const poi = this.data.shop;
getApp().sensors?.track("NavEntryBtnClick", {
page_name: "商铺详情",
short_market_name: "二区东",
...(poi.tx
? {
x: Number(poi.tx.poi_location.longitude),
......@@ -160,6 +152,7 @@ Page({
booth_floor: poi.tx.poi_fl_seq,
}
: {
short_market_name: poi.ywZh.marketNameDtl,
store_name: poi.ywZh.name,
booth_addr_street: poi.ywZh.boothAddrStreet,
booth_cover_img: poi.ywZh.cover,
......
// pages/verify-openId/index.js
Page({
/**
* 页面的初始数据
*/
data: {},
/**
* 生命周期函数--监听页面加载
*/
onLoad(params) {
if (
params.appkey &&
params.code &&
params.openid &&
params.reqid &&
params.sign &&
params.timestamp
) {
this.verifyRedPocketOpenid(params);
} else {
this.back();
}
},
/**
* 生命周期函数--监听页面初次渲染完成
*/
onReady() {},
async verifyRedPocketOpenid(params) {
try {
const { marketurl, post, globalData } = getApp();
const {
data: { code, message },
} = await post(`${marketurl}/rpt/user/openId/verify`, params, {
Authorization: `Bearer ${globalData.user.access_token}`,
});
if (code === 20000) {
console.log("绑定红包openid成功");
} else {
console.log("绑定红包openid报错", message);
}
} catch (error) {
console.log("绑定红包openid报错", error);
} finally {
this.back();
}
},
back() {
const pages = getCurrentPages();
if (pages.length > 1) {
wx.navigateBack();
} else {
wx.redirectTo({
url: "/pages/index/index",
});
}
},
/**
* 生命周期函数--监听页面显示
*/
onShow() {},
/**
* 生命周期函数--监听页面隐藏
*/
onHide() {},
/**
* 生命周期函数--监听页面卸载
*/
onUnload() {},
/**
* 页面相关事件处理函数--监听用户下拉动作
*/
onPullDownRefresh() {},
/**
* 页面上拉触底事件的处理函数
*/
onReachBottom() {},
/**
* 用户点击右上角分享
*/
onShareAppMessage() {},
});
{
"usingComponents": {}
}
\ No newline at end of file
/* pages/verify-openId/index.wxss */
\ No newline at end of file
<view></view>
\ No newline at end of file
......@@ -23,15 +23,39 @@ const addNavHistory = async (shopId, isShop) => {
}
};
const getPoiByPoint = ({ latitude, longitude, floorName }) => {
const getPoiByPoint = async ({ latitude, longitude, floorName }) => {
const { txQmMap, indexData } = getApp();
const app = getApp();
// latitude = 29.3310262816717;
// longitude = 120.106450377363;
// floorName = "F2";
try {
const list = findNearbyLocations(latitude, longitude, floorName, indexData);
if (list.length) {
return txQmMap[list[0]];
const shineiId = list[0];
// 先从 txQmMap 获取基础信息
const baseInfo = txQmMap[shineiId];
console.log("baseInfo from txQmMap:", baseInfo);
// 根据基础信息调用接口获取完整的 poi 信息
let poi = null;
if (baseInfo) {
if (baseInfo.shopId) {
// 如果有 shopId,作为店铺查询
poi = await app.poiApi.getOne({
shopType: "1",
shopId: baseInfo.shopId,
boothNo: baseInfo.boothNo,
});
} else if (baseInfo.facilityId) {
// 如果有 facilityId,作为设施查询
poi = await app.poiApi.getOne({
shopType: "2",
shopId: baseInfo.facilityId,
});
}
}
return poi;
} else return null;
} catch (error) {
console.error(error);
......@@ -53,14 +77,14 @@ const pushMsgToShop = async (poi, options) => {
{
title,
content: `【${phone}】看中了你店铺的商品,正向你店铺赶来,${Math.ceil(
options.time
options.time,
)}分钟后到达您的店铺看品!`,
userId: poi.ywZh.merchantUserId,
sign: md5(title),
},
{
Authorization: `Bearer ${globalData.user.access_token}`,
}
},
);
} catch (error) {
console.error(error);
......@@ -68,31 +92,33 @@ const pushMsgToShop = async (poi, options) => {
};
const handleNavStart = async (options) => {
const poi = getPoiByPoint(options.endPoint);
const poi = await getPoiByPoint(options.endPoint);
console.log("导航开始poi", poi);
if (!poi) return;
const navHistoryId = await addNavHistory(
poi.facilityId ? poi.facilityId : poi.shopId,
!poi.facilityId
!poi.facilityId,
);
console.log("新增导航记录id", navHistoryId);
if (!navHistoryId) return console.error("未获取到导航历史id");
if (poi.shopId) pushMsgToShop(poi, options);
try {
getApp().lastNavHistoryId = navHistoryId;
const { shareShopId, shareFacilityId } = getApp();
getApp().blurPoi();
const { shareShopId, shareFacilityId, buildingIdNameMap } = getApp();
const fromShare =
(poi.facilityId && poi.facilityId == shareFacilityId) ||
(poi.shopId && poi.shopId == shareShopId)
? true
: false;
getApp().sensors?.track("NavStart", {
short_market_name: "二区东",
...(fromShare ? { nav_position_source: "分享位置导航" } : {}),
...(poi.tx
? {
x: Number(poi.tx.poi_location.longitude),
y: Number(poi.tx.poi_location.latitude),
short_market_name: buildingIdNameMap[poi.tx.bld_id],
}
: {}),
nav_route: JSON.stringify(options.line),
......@@ -124,20 +150,20 @@ const handleNavStart = async (options) => {
};
const handleNavEnd = async (options) => {
const poi = getPoiByPoint(options.endPoint);
const poi = await getPoiByPoint(options.endPoint);
console.log("导航结束poi", poi);
if (!poi) return;
const { lastNavHistoryId } = getApp();
const { lastNavHistoryId, buildingIdNameMap } = getApp();
if (!lastNavHistoryId) return console.error("未获取到开始导航的历史id");
try {
getApp().lastNavHistoryId = null;
getApp().sensors?.track("NavEnd", {
short_market_name: "二区东",
nav_track_no: lastNavHistoryId,
...(poi.tx
? {
x: Number(poi.tx.poi_location.longitude),
y: Number(poi.tx.poi_location.latitude),
short_market_name: buildingIdNameMap[poi.tx.bld_id],
}
: {}),
nav_duration: options.time * 60 * 1000,
......
{
"compileType": "miniprogram",
"libVersion": "trial",
"libVersion": "3.8.12",
"packOptions": {
"ignore": [],
"include": []
......@@ -20,12 +20,24 @@
"ignore": [],
"disablePlugins": [],
"outputPath": ""
}
},
"compileWorklet": false,
"uglifyFileName": false,
"uploadWithSourceMap": true,
"packNpmManually": false,
"minifyWXSS": true,
"minifyWXML": true,
"localPlugins": false,
"disableUseStrict": false,
"condition": false,
"swc": false,
"disableSWC": true
},
"condition": {},
"editorSetting": {
"tabIndent": "auto",
"tabSize": 2
},
"appid": "wxfd18331fed2fcd31"
"appid": "wxfd18331fed2fcd31",
"simulatorPluginLibVersion": {}
}
\ No newline at end of file
{
"description": "项目私有配置文件。此文件中的内容将覆盖 project.config.json 中的相同字段。项目的改动优先同步到此文件中。详见文档:https://developers.weixin.qq.com/miniprogram/dev/devtools/projectconfig.html",
"projectname": "%E5%B0%8F%E5%95%86AI%E6%85%A7%E8%BE%BE",
"projectname": "xiaoshangaihuida",
"setting": {
"compileHotReLoad": true,
"urlCheck": true
"urlCheck": false,
"coverView": true,
"lazyloadPlaceholderEnable": false,
"skylineRenderEnable": false,
"preloadBackgroundData": false,
"autoAudits": false,
"useApiHook": true,
"showShadowRootInWxmlPanel": true,
"useStaticServer": false,
"useLanDebug": false,
"showES6CompileOption": false,
"checkInvalidKey": true,
"ignoreDevUnusedFiles": true,
"bigPackageSizeSupport": false
},
"libVersion": "3.8.12",
"condition": {
"miniprogram": {
"list": [
{
"name": "二维码带参",
"pathName": "pages/index/index",
"query": "q=https%3A%2F%2Ftencent.chinagoods.com%3FshopId%3D6202578",
"scene": null,
"launchMode": "default"
},
{
"name": "隐私协议",
"pathName": "pages/privacy/index",
"query": "",
"launchMode": "default",
"scene": null
},
{
"name": "actId=743&fromshare=true",
"pathName": "pages/index/index",
"query": "actId=743&fromshare=true",
"launchMode": "default",
"scene": null
},
{
"name": "扫码",
"pathName": "pages/index/index",
"query": "q=https%3A%2F%2Ftencent.chinagoods.com%3FshopId%3D6316857",
"launchMode": "default",
"scene": null
},
{
"name": "shopId=6316857&fromshare=true",
"pathName": "pages/index/index",
"query": "shopId=6316857&fromshare=true",
"launchMode": "default",
"scene": null
},
{
"name": "pages/nav-end/index",
"pathName": "pages/nav-end/index",
"query": "",
"launchMode": "default",
"scene": null
},
{
"name": "搜索",
"pathName": "pages/search/index",
"query": "",
"launchMode": "default",
"scene": null
},
{
"name": "AI搜图结果",
"pathName": "pages/aisearchresult/index",
"query": "photoUrl=https://cdnimg.chinagoods.com/jpg/2025/07/23/dasglnnscv9iummdzwhg7vxjz0fjevd4.jpg",
"launchMode": "default",
"scene": null
},
{
"name": "AI识图",
"pathName": "pages/aisearch/index",
"query": "",
"launchMode": "default",
"scene": null
},
{
"name": "facilityId=1",
"pathName": "pages/index/index",
"query": "facilityId=1",
"launchMode": "default",
"scene": null
},
{
"name": "login",
"pathName": "pages/login/index",
"query": "",
"launchMode": "default",
"scene": null
},
{
"name": "我的",
"pathName": "pages/mine/index",
"query": "",
"launchMode": "default",
"scene": null
}
]
}
}
"condition": {}
}
\ No newline at end of file
......@@ -126,12 +126,13 @@ export const getAddr = (shop, isEn) => {
}
if (isEn) {
try {
const { buildingNameNameEnMap } = getApp();
enAddr = `${shop.ywZh.boothNo},${
isCorridor
? `Corridor.${isCorridor}`
: `St.${shop.ywZh.boothAddrStreet}`
},${shop.ywZh.addrFloor}F,${
shop.ywZh.marketNameDtl === "二区东" ? "District 2-East" : ""
buildingNameNameEnMap[shop.ywZh.marketNameDtl]
}`;
} catch (error) {
console.error(error);
......@@ -139,10 +140,7 @@ export const getAddr = (shop, isEn) => {
return { enAddr, buildingFloor, street };
}
try {
buildingFloor =
shop.ywZh.marketNameDtl === "二区东"
? `二区东${shop.ywZh.addrFloor}F`
: "";
buildingFloor = `${shop.ywZh.marketNameDtl}${shop.ywZh.addrFloor}F`;
} catch (error) {
console.error(error);
}
......@@ -203,7 +201,7 @@ export const confirm = ({ title, content, confirmText, cancelText }) =>
export const getShopMainGoods = (shop, isEn) => {
return isEn && shop.ywEn && shop.ywEn.mainGoods?.length
? `Business: ${shop.ywEn.mainGoods.join(",")}`
: !isEn && shop.ywZh.mainGoods?.length
: !isEn && shop.ywZh?.mainGoods?.length
? `主营: ${shop.ywZh.mainGoods.join("、")}`
: "";
};
......@@ -242,33 +240,21 @@ export const toRoutePlan = async (poi, isEn) => {
const {
poi_location: { latitude, longitude },
poi_fl_name: floorName,
bld_id,
} = poi.tx;
const userLocation = await wx.getLocation({ type: "gcj02" });
const distance = getDistance(
userLocation.latitude,
userLocation.longitude,
Number(latitude),
Number(longitude)
);
console.log("直线距离", distance);
if (distance > 3000)
return wx.openLocation({
latitude: Number(latitude),
longitude: Number(longitude),
name,
});
const destPoint = encodeURIComponent(
JSON.stringify({
name,
latitude: Number(latitude),
longitude: Number(longitude),
floorName,
buildingId: "3307001549220",
buildingId: bld_id,
buildingName: "义乌国际商贸城",
})
}),
);
wx.navigateTo({
url: `plugin://indoormap/routeplan?banNav=false&transitDisable=true&buildingId=3307001549220&buildingName=义乌国际商贸城&destPoint=${destPoint}`,
url: `plugin://indoormap/routeplan?banNav=false&transitDisable=true&buildingId=${bld_id}&buildingName=义乌国际商贸城&destPoint=${destPoint}`,
});
} catch (error) {
console.error(error);
......@@ -304,33 +290,20 @@ export const actToRoutePlan = async (act) => {
return "showLocationModal";
if (!getApp().checkUser()) return "login needed";
getApp().ensurePluginInit();
const { name, latitude, longitude, floor_name } = act;
const userLocation = await wx.getLocation({ type: "gcj02" });
const distance = getDistance(
userLocation.latitude,
userLocation.longitude,
Number(latitude),
Number(longitude)
);
console.log("直线距离", distance);
if (distance > 3000)
return wx.openLocation({
latitude: Number(latitude),
longitude: Number(longitude),
name,
});
const { name, latitude, longitude, floor_name, building_id } = act;
const destPoint = encodeURIComponent(
JSON.stringify({
name,
latitude: Number(latitude),
longitude: Number(longitude),
floorName: floor_name,
buildingId: "3307001549220",
buildingId: building_id,
buildingName: "义乌国际商贸城",
})
}),
);
wx.navigateTo({
url: `plugin://indoormap/routeplan?banNav=false&transitDisable=true&buildingId=3307001549220&buildingName=义乌国际商贸城&destPoint=${destPoint}`,
url: `plugin://indoormap/routeplan?banNav=false&transitDisable=true&buildingId=${building_id}&buildingName=义乌国际商贸城&destPoint=${destPoint}`,
});
} catch (error) {
console.error(error);
......@@ -379,7 +352,7 @@ export const authBluetoothAndLocation = async () => {
: authSetting["scope.bluetooth"] === undefined
? "没授权过"
: "拒绝"
}]`
}]`,
);
if (authSetting["scope.bluetooth"] === undefined) {
getApp().sensors?.track("OpenSystemLocationSvcDialog", {
......@@ -409,7 +382,7 @@ export const authBluetoothAndLocation = async () => {
: authSetting["scope.userLocation"] === undefined
? "没授权过"
: "拒绝"
}]`
}]`,
);
if (authSetting["scope.userLocation"] === undefined) {
getApp().sensors?.track("OpenSystemLocationSvcDialog", {
......@@ -510,7 +483,7 @@ export function createGridIndex(locations, gridSize = 20) {
maxLat: -Infinity,
minLon: Infinity,
maxLon: -Infinity,
}
},
);
// 计算网格转换系数
......@@ -558,7 +531,7 @@ export function findNearbyLocations(
targetLon,
floorName,
indexData,
radius = 10
radius = 10,
) {
try {
// 参数验证
......@@ -656,7 +629,7 @@ export function findNearbyLocations(
targetLat,
targetLon,
Number(loc.poi_location.latitude),
Number(loc.poi_location.longitude)
Number(loc.poi_location.longitude),
);
return { ...loc, distance };
} catch (err) {
......@@ -680,3 +653,11 @@ export const checkAuth = async () => {
}
return true;
};
export const nearDistrict2East = ({ latitude, longitude }) => {
return (
latitude < 29.333647 &&
latitude > 29.329766 &&
longitude < 120.108207 &&
longitude > 120.105343
);
};
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论