提交 e295ab8a 作者: 姚志忠

Initial commit

父级
import CollectionApi from "./apis/CollectionApi";
import PhotoApi from "./apis/PhotoApi";
import ItineraryApi from "./apis/ItineraryApi";
export { CollectionApi, PhotoApi, ItineraryApi };
export default class CollectionApi {
constructor(app) {
this.app = app;
}
async get() {
const { qmurl, post, globalData } = this.app;
if (!globalData.user) return null;
try {
const {
data: { code, data },
} = await post(`${qmurl}/api/v1/applet/getUserCollectionList`, {
userId: globalData.user.user.userId,
});
if (code === 200) {
return data;
}
return null;
} catch (error) {
console.error(error);
return null;
}
}
async post(poi) {
const { qmurl, post, globalData } = this.app;
if (!globalData.user) return false;
if (!poi) return false;
try {
const {
data: { code },
} = await post(`${qmurl}/api/v1/applet/addUserCollection`, {
userId: globalData.user.user.userId,
shopType: poi.facility ? 2 : 1,
shopId: poi.shopId ? poi.shopId : poi.facilityId,
});
if (code === 200) return true;
return false;
} catch (error) {
console.error(error);
return false;
}
}
async delete(poi) {
const { qmurl, post, globalData } = this.app;
if (!globalData.user) return false;
if (!poi) return false;
try {
const {
data: { code },
} = await post(`${qmurl}/api/v1/applet/delUserCollection`, {
userId: globalData.user.user.userId,
shopType: poi.facility ? 2 : 1,
shopId: poi.shopId ? poi.shopId : poi.facilityId,
});
if (code === 200) return true;
return false;
} catch (error) {
console.error(error);
return false;
}
}
async bulkDelete(ids) {
const { qmurl, post, globalData } = this.app;
if (!globalData.user) return false;
if (!ids) return;
try {
const {
data: { code },
} = await post(`${qmurl}/api/v1/applet/delUserCollectionBatch`, {
ids,
});
if (code === 200) return true;
return false;
} catch (error) {
console.error(error);
return false;
}
}
}
export default class ItineraryApi {
constructor(app) {
this.app = app;
}
async get() {
const { qmurl, post, globalData } = this.app;
if (!globalData.user) return null;
try {
const {
data: { code, data },
} = await post(`${qmurl}/api/v1/applet/getUserTripList`, {
userId: globalData.user.user.userId,
});
if (code === 200) {
return data;
}
return null;
} catch (error) {
console.error(error);
return null;
}
}
async post({ shopId, goodsTitle, goodsPrice, goodsCover }) {
const { qmurl, post, globalData } = this.app;
if (!globalData.user) return false;
if (!goodsTitle) return false;
try {
const {
data: { code, data },
} = await post(`${qmurl}/api/v1/applet/addUserTrip`, {
userId: globalData.user.user.userId,
shopId,
goodsTitle,
goodsPrice,
goodsCover,
});
if (code === 200) return data;
return null;
} catch (error) {
console.error(error);
return null;
}
}
async updateStatus(id) {
const { qmurl, post, globalData } = this.app;
if (!globalData.user) return false;
if (!id) return false;
try {
const {
data: { code },
} = await post(`${qmurl}/api/v1/applet/updUserTripStatus`, {
id,
status: 1,
});
if (code === 200) return true;
return false;
} catch (error) {
console.error(error);
return false;
}
}
async bulkDelete(ids) {
const { qmurl, post, globalData } = this.app;
if (!globalData.user) return false;
if (!ids) return;
try {
const {
data: { code },
} = await post(`${qmurl}/api/v1/applet/delUserTrip`, {
ids,
});
if (code === 200) return true;
return false;
} catch (error) {
console.error(error);
return false;
}
}
}
export default class PhotoApi {
constructor(app) {
this.app = app;
}
upload(filePath) {
return new Promise((resolve) => {
const { uploadurl } = this.app;
wx.uploadFile({
url: `${uploadurl}/buyerapi/v1/common/upload`, //仅为示例,非真实的接口地址
filePath,
name: "file",
success(res) {
try {
resolve(JSON.parse(res.data).real_file_path);
} catch (error) {
resolve(null);
}
},
fail() {
resolve(null);
},
});
});
}
async get() {
const { qmurl, post, globalData } = this.app;
if (!globalData.user) return null;
try {
const {
data: { code, data },
} = await post(`${qmurl}/api/v1/applet/getUserPhotoRecordList`, {
userId: globalData.user.user.userId,
});
if (code === 200) return data;
return null;
} catch (error) {
console.error(error);
return null;
}
}
async post(photoUrl, source = 1) {
const { qmurl, post, globalData } = this.app;
if (!globalData.user) return false;
if (!photoUrl) return false;
try {
const {
data: { code },
} = await post(`${qmurl}/api/v1/applet/addUserPhotoRecord`, {
userId: globalData.user.user.userId,
photoUrl,
source,
});
if (code === 200) return true;
return false;
} catch (error) {
console.error(error);
return false;
}
}
async clear(source) {
const { qmurl, get, globalData } = this.app;
if (!globalData.user) return false;
if (!source) return false;
try {
const {
data: { code },
} = await get(
`${qmurl}/api/v1/applet/clearUserPhotoRecord?userId=${globalData.user.user.userId}&source=${source}`
);
if (code === 200) return true;
return false;
} catch (error) {
console.error(error);
return false;
}
}
async search(imageUrl, rect) {
const { marketurl, post, globalData } = this.app;
if (!globalData.user) return false;
if (!imageUrl) return false;
if (!rect) {
try {
const { width, height } = await wx.getImageInfo({ src: imageUrl });
rect = { width, height, x: 0, y: 0 };
} catch (error) {
console.error(error);
}
}
try {
const {
data: { data, code },
} = await post(`${marketurl}/detector/baidu/image/search`, {
imageRectDto: rect,
imageUrl,
});
if (code === 20000) return data;
return null;
} catch (error) {
console.error(error);
return null;
}
}
}
import {
EventEmitter,
get,
post,
getShopMainGoods,
getStarredPhone,
getPoiName,
getDistance,
createGridIndex,
} from "./util";
import md5 from "./md5";
import sensors from "./sensorsdata.esm.js";
import { CollectionApi, PhotoApi, ItineraryApi } from "./api";
const plugin = requirePlugin("indoormap");
const mapKey = "QUIBZ-OTL6U-T2MVM-GMPIT-EON66-LGBCU";
const buildingId = "3307001549220";
const isTest = false;
export const qmurl = isTest
? "http://192.168.1.187:9900"
: "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://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",
show_log: false, //是否开启日志
name: "sensors",
allow_amend_share_path: true,
global_properties: {
platform_type: "mini_programs",
platform_lang: "zh",
platform_site: "china",
},
autoTrack: {
appLaunch: true,
appShow: true,
appHide: true,
pageShow: true,
pageShare: true,
mpClick: true,
mpFavorite: true,
pageLeave: true,
},
});
App({
buildingId,
formats,
qmurl,
marketurl,
uploadurl,
salt,
get,
post,
md5,
mapKey,
shopIdMap: null,
facilityIdMap: null,
txQmMap: 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);
wx.setNavigationBarTitle({
title: this.globalData.isEn ? "Chinagoods Nav" : "小商AI导航",
});
this.refreshToken();
this.identifySensors();
this.getPoiList();
this.refreshCollection();
this.refreshItinerary();
},
async refreshToken() {
if (this.globalData.user) {
const {
data: { code, data: user },
} = await post(`${marketurl}/login/wxa/auth/refresh_token`, {
accessToken: this.globalData.user.access_token,
refreshToken: this.globalData.user.refresh_token,
});
if (code !== 20000) {
console.log("token刷新失败清除用户信息");
this.globalData.user = null;
this.events.emit("user", null);
wx.removeStorageSync("user");
this.identifySensors();
this.refreshCollection();
this.refreshItinerary();
} else {
console.log("token刷新成功");
this.globalData.user = user;
this.events.emit("user", this.globalData.user);
wx.setStorageSync("user", user);
}
}
},
async login(authCode) {
try {
const loginCode = await this.getLoginCode();
if (loginCode && authCode) {
const {
data: { data: user },
} = await post(`${marketurl}/login/wxa/wxa/login`, {
loginCode,
authCode,
type: "cgMap",
utm_source: "腾讯地图导航",
});
console.log(user);
if (user) {
this.globalData.user = user;
this.events.emit("user", this.globalData.user);
wx.setStorageSync("user", user);
this.identifySensors();
this.refreshCollection();
this.refreshItinerary();
return true;
}
}
this.globalData.user = null;
this.events.emit("user", null);
this.identifySensors();
this.refreshCollection();
this.refreshItinerary();
return false;
} catch (error) {
this.globalData.user = null;
this.events.emit("user", null);
this.identifySensors();
this.refreshCollection();
this.refreshItinerary();
return false;
}
},
async getLoginCode() {
try {
const loginCode = await new Promise((resolve, reject) => {
wx.login({
success(res) {
if (res.code) {
resolve(res.code);
} else {
reject(res.errMsg);
}
},
fail: reject,
});
});
return loginCode;
} catch (error) {
console.error(error);
return null;
}
},
async getUnionid() {
const loginCode = await this.getLoginCode();
try {
const {
data: { data: unionid },
} = await get(`${qmurl}/api/v1/applet/appletLogin?code=${loginCode}`);
return unionid;
} catch (error) {
console.error(error);
return null;
}
},
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);
}
});
}
}
} catch (error) {
console.error(error);
}
},
handlePoiList(params) {
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);
}
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;
}
} catch (error) {
console.error(error);
}
});
this.shopIdMap = shopIdMap;
this.facilityIdMap = facilityIdMap;
this.txQmMap = txQmMap;
try {
this.indexData = createGridIndex(
Object.values(txQmMap).map(({ tx }) => tx)
);
} catch (error) {}
this.events.emit("globalData");
} catch (error) {
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 });
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);
if (this.globalData.isEn) wx.setStorageSync("isEn", "isEn");
else wx.removeStorageSync("isEn");
wx.setNavigationBarTitle({
title: this.globalData.isEn ? "Chinagoods Nav" : "小商AI导航",
});
},
focusPoi(poi) {
this.globalData.focusedPoi = poi;
this.events.emit("focusedPoi", poi);
},
blurPoi() {
this.globalData.focusedPoi = null;
this.events.emit("focusedPoi", null);
},
ensurePluginInit() {
if (!this.globalData.isPluginInited) {
this.globalData.isPluginInited = true;
plugin.init({
key: mapKey,
appid: "wxfd18331fed2fcd31",
referer: "小商品城地图导航",
isEnableCustomSearchService: true,
isShowIndoorName: true,
isJumpMapApp: true,
navEndPageUrl: "/pages/nav-end/index",
navEndPageDelay: 0,
});
const themeInstance = new plugin.Theme();
themeInstance.setGlobalThemeColor({
colorPrimary: "#E32424",
lightColorPrimary: "#fef0eb",
});
plugin.setCustomSearchService(async (option) => {
const {
type,
buildingId,
floorName,
keyword,
latitude,
longitude,
pageSize,
pageIndex,
} = option;
console.log(option);
const result = {
keyword,
data: [],
count: 0,
};
const { post, qmurl, shopIdMap, facilityIdMap } = getApp();
if (!shopIdMap) return result;
const {
data: { data },
} = await post(
`${qmurl}/api/v1/applet/searchShopFacilityList`,
{
keyword,
},
{ lang: "zh" }
);
const list = data
.map(({ shopType, shopId }) =>
shopType === 1
? shopIdMap[shopId]
: shopType === 2
? facilityIdMap[shopId]
: null
)
.filter((el) => el.tx)
.map((poi, i) => {
const lat = Number(poi.tx.poi_location.latitude);
const lng = Number(poi.tx.poi_location.longitude);
return {
id: i + 1,
name: getPoiName(poi, false),
floorName: poi.tx.poi_fl_name,
latitude: lat,
longitude: lng,
category: poi.facility ? "周边" : poi.ywZh.frontIndustryCategory,
distance: getDistance(latitude, longitude, lat, lng),
};
});
const sliced = list.slice(
(pageIndex - 1) * pageSize,
pageIndex * pageSize
);
result.data = sliced;
result.count = list.length;
return result;
});
plugin.watchLocation({
callback: (el) => {
this.userLocation = el;
this.events.emit("userLocation", this.userLocation);
},
});
}
},
checkUser() {
if (this.globalData.user) {
return true;
} else {
wx.navigateTo({
url: "/pages/login/index",
});
return false;
}
},
collectList: [],
shopCollectMap: {},
facilityCollectMap: {},
async refreshCollection() {
if (this.globalData.user) {
try {
const data = await this.collectionApi.get();
const shopCollectMap = data
.filter(({ shopType }) => shopType === 1)
.reduce((acc, nxt) => ({ ...acc, [nxt.shopId]: nxt }), {});
const facilityCollectMap = data
.filter(({ shopType }) => shopType === 2)
.reduce((acc, nxt) => ({ ...acc, [nxt.shopId]: nxt }), {});
this.collectList = data;
this.shopCollectMap = shopCollectMap;
this.facilityCollectMap = facilityCollectMap;
} catch (error) {
console.error(error);
}
} else {
this.collectList = [];
this.shopCollectMap = {};
this.facilityCollectMap = {};
}
this.events.emit("collectionRefresh");
},
itineraryList: [],
itineraryMap: {},
async refreshItinerary() {
if (this.globalData.user) {
try {
const data = await this.itineraryApi.get();
if (data) {
this.itineraryList = data;
this.itineraryMap = data.reduce(
(acc, nxt) => ({ ...acc, [nxt.shopId]: nxt.id }),
{}
);
}
} catch (error) {
console.error("获取行程列表失败" + error);
}
} else {
this.itineraryList = [];
this.itineraryMap = {};
}
this.events.emit("itineraryRefresh", this.itineraryList);
},
globalData: {
focusedPoi: null,
unionid: wx.getStorageSync("unionid") ? wx.getStorageSync("unionid") : null,
isEn:
wx.getAppBaseInfo().language === "en" || wx.getStorageSync("isEn")
? true
: false,
user:
wx.getStorageSync("user") && typeof wx.getStorageSync("user") === "object"
? wx.getStorageSync("user")
: null,
isPluginInited: false,
},
});
{
"pages": [
"pages/index/index",
"pages/mine/index",
"pages/search/index",
"pages/shop/index",
"pages/wap/index",
"pages/login/index",
"pages/privacy/index",
"pages/aisearch/index",
"pages/aisearchresult/index",
"pages/itinerary/index",
"pages/nav-end/index",
"pages/poi-map/index"
],
"permission": {
"scope.userLocation": {
"desc": "获取您的位置信息将用于在首页地图展示您所在的楼层及精确位置"
}
},
"requiredPrivateInfos": ["getLocation"],
"window": {
"navigationBarTextStyle": "black",
"navigationBarTitleText": "小商AI导航",
"navigationBarBackgroundColor": "#ffffff"
},
"style": "v2",
"componentFramework": "glass-easel",
"sitemapLocation": "sitemap.json",
"lazyCodeLoading": "requiredComponents",
"plugins": {
"WechatSI": {
"version": "0.3.6",
"provider": "wx069ba97219f66d99"
},
"indoormap": {
"version": "1.3.27",
"provider": "wxfe8f0709a4de371b",
"export": "pluginReport.js"
}
}
}
view,
image {
box-sizing: border-box;
}
module.exports = Behavior({
properties: {},
data: {
collectList: getApp().collectList,
shopCollectMap: getApp().shopCollectMap,
facilityCollectMap: getApp().facilityCollectMap,
},
lifetimes: {
attached() {
this.setCollectionData = () => {
this.setData({
collectList: getApp().collectList,
shopCollectMap: getApp().shopCollectMap,
facilityCollectMap: getApp().facilityCollectMap,
});
if (this.setCollected) {
this.setCollected();
}
};
this.setCollectionData();
getApp().events.on("collectionRefresh", this.setCollectionData);
},
detached() {
getApp().events.off("collectionRefresh", this.setCollectionData);
},
},
methods: {
async toggleCollection(poi) {
const { collectionApi } = 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),
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,
main_product_arr: poi.ywZh.mainGoods,
}),
});
} catch (error) {
console.error("埋点失败", error);
}
if (collected) {
success = await collectionApi.delete(poi);
if (success) {
wx.showToast({
title: this.data.isEn ? "uncollected" : "取消收藏",
icon: "none",
});
getApp().refreshCollection();
return;
}
} else {
success = await collectionApi.post(poi);
if (success) {
wx.showToast({
title: this.data.isEn ? "collected" : "已收藏",
icon: "none",
});
getApp().refreshCollection();
return;
}
}
} catch (error) {
console.error(error);
wx.showToast({
title: this.data.isEn ? "collect fail" : "收藏失败",
icon: "none",
});
return;
}
},
isCollected(poi) {
const app = getApp();
if (poi.shopId) {
return !!app.shopCollectMap[poi.shopId];
}
if (poi.facilityId) {
return !!app.facilityCollectMap[poi.facilityId];
}
return false;
},
},
});
module.exports = Behavior({
behaviors: [],
properties: {},
data: {
isEn: getApp().globalData.isEn,
},
lifetimes: {
attached() {
this.setData({ isEn: getApp().globalData.isEn });
this.setIsEn = (isEn) => {
this.setData({ isEn });
if (this.onIsEnChange && typeof this.onIsEnChange === "function") {
this.onIsEnChange(isEn);
}
};
getApp().events.on("lang", this.setIsEn);
},
detached() {
getApp().events.off("lang", this.setIsEn);
},
},
});
module.exports = Behavior({
behaviors: [],
properties: {},
data: {
showBluetoothModal: false,
showLocationModal: false,
showAuthModal: false,
},
methods: {
openBluetoothModal() {
this.setData({ showBluetoothModal: true });
},
closeBluetoothModal() {
this.setData({ showBluetoothModal: false });
},
openLocationModal() {
this.setData({ showLocationModal: true });
},
closeLocationModal() {
this.setData({ showLocationModal: false });
},
openAuthModal() {
this.setData({ showAuthModal: true });
},
closeAuthModal() {
this.setData({ showAuthModal: false });
},
},
});
module.exports = Behavior({
behaviors: [],
properties: {},
data: {
focusedPoi: getApp().globalData.focusedPoi,
},
lifetimes: {
attached() {
this.setData({ focusedPoi: getApp().globalData.focusedPoi });
this.setFocusedPoi = (focusedPoi) => {
this.setData({ focusedPoi }, () => {
if (
this.onFocusedPoiChange &&
typeof this.onFocusedPoiChange === "function"
) {
this.onFocusedPoiChange(focusedPoi);
}
});
};
getApp().events.on("focusedPoi", this.setFocusedPoi);
},
detached() {
getApp().events.off("focusedPoi", this.setFocusedPoi);
},
},
});
module.exports = Behavior({
behaviors: [],
properties: {},
data: {
user: getApp().globalData.user,
},
lifetimes: {
attached() {
this.setData({ user: getApp().globalData.user });
this.setUser = (user) => {
this.setData({ user });
};
getApp().events.on("user", this.setUser);
},
detached() {
getApp().events.off("user", this.setUser);
},
},
});
import langBehavior from "../../behaviors/langBehavior";
Component({
behaviors: [langBehavior],
properties: {},
/**
* 组件的初始数据
*/
data: {},
/**
* 组件的方法列表
*/
methods: {
closeTip() {
this.triggerEvent("close");
},
openSetting() {
console.log("openSetting");
wx.openSetting({
success: ({ authSetting }) => {
if (
authSetting["scope.bluetooth"] &&
authSetting["scope.userLocation"]
)
this.triggerEvent("close");
},
});
},
donothing() {},
},
});
{
"component": true,
"usingComponents": {}
}
\ No newline at end of file
.tip-bg {
position: absolute;
z-index: 3000;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
background: rgba(0, 0, 0, 0.6);
}
.tip {
position: absolute;
z-index: 3001;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
.arrow {
position: absolute;
top: 7px;
right: 64px;
width: 246px;
height: 32.5px;
}
.image {
position: absolute;
top: 53px;
left: 0;
right: 0;
margin: auto;
width: 304px;
height: 572px;
}
.btns {
position: absolute;
top: 564px;
left: 0;
right: 0;
margin: auto;
width: 260px;
display: flex;
gap: 8px;
.btn {
flex: 1;
height: 38px;
}
}
}
<textarea disabled class="tip-bg"></textarea>
<view class="tip" bind:tap="closeTip">
<image src="./arrow.png" class="arrow" mode="" />
<image catch:tap="donothing" class="image" src="https://cdnimg.chinagoods.com/png/2025/09/03/idwejhk7ytqknlmzs8crrxz8kexgx8tu.png" mode="" />
<view class="btns">
<view class="btn" catch:tap="closeTip"></view>
<view class="btn" catch:tap="openSetting"></view>
</view>
</view>
\ No newline at end of file
import langBehavior from "../../behaviors/langBehavior";
Component({
behaviors: [langBehavior],
properties: {},
/**
* 组件的初始数据
*/
data: { isIOS: getApp().isIOS },
/**
* 组件的方法列表
*/
methods: {
close() {
this.triggerEvent("close");
},
doNothing() {},
},
});
{
"component": true,
"usingComponents": {}
}
\ No newline at end of file
.bluetooth-bg {
position: absolute;
z-index: 3000;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
background: rgba(0, 0, 0, 0.6);
}
.bluetooth-modal {
position: fixed;
top: 0;
left: 0;
z-index: 3001;
width: 100vw;
height: 100vh;
.image {
position: absolute;
top: 32px;
left: 0;
right: 0;
margin: auto;
width: 304px;
height: 572px;
}
.btn {
position: absolute;
top: 542px;
left: 0;
right: 0;
margin: auto;
width: 260px;
height: 38px;
}
}
<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="" />
<view class="btn" catch:tap="close"></view>
</view>
\ No newline at end of file
Component({
behaviors: [collectBehavior],
properties: {
poi: {
type: Object,
value: null
}
},
data: {
collected: false
},
observers: {
poi() {
this.setData({
collected: this.isCollected(this.data.poi)
});
}
},
methods: {
async handleCollect() {
if (!getApp().checkUser()) return;
const result = await this.toggleCollection(this.data.poi);
if (result.success) {
getApp().refreshCollection();
}
}
}
});
\ No newline at end of file
import langBehavior from "../../behaviors/langBehavior";
import poiFocusBehavior from "../../behaviors/poiFocusBehavior";
Component({
behaviors: [langBehavior, poiFocusBehavior],
properties: { currentIndex: Number },
data: {
tabs: [
{
url: "/pages/index/index",
text: "导航",
textEn: "Nav",
icon: "./index.png",
activeIcon: "./selectedIndex.png",
},
{
url: "/pages/index/index?mode=surroundings",
text: "周边",
textEn: "Nearby",
icon: "./surroundings.png",
activeIcon: "./selectedSurroundings.png",
},
{
url: "/pages/mine/index",
text: "我的",
textEn: "My",
icon: "./mine.png",
activeIcon: "./selectedMine.png",
},
],
},
/**
* 组件的方法列表
*/
methods: {
handleTap({ currentTarget: { id } }) {
if (
["0", "1", "2"].includes(id) &&
this.data.currentIndex !== Number(id)
) {
const { currentIndex } = this.data;
const tab = this.data.tabs[id];
const lastTab = this.data.tabs[currentIndex];
getApp().sensors?.track("BottomMenuClick", {
menu_name: tab.text,
btn_name_referrer: lastTab.text,
});
this.triggerEvent("tab", tab);
}
},
},
});
{
"component": true,
"usingComponents": {}
}
\ No newline at end of file
.tab-bar {
position: fixed;
bottom: 0;
left: 0;
width: 100vw;
height: 80px;
display: flex;
justify-content: space-between;
background: #fff;
padding: 0 16px 0 16px;
z-index: 1000;
border-top: 1px solid rgba(0, 0, 0, 0.03);
&.hide {
display: none;
}
.tab {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 2px;
width: 80px;
height: 56px;
color: rgba(0, 0, 0, 0.4);
font-size: 10px;
line-height: 14px;
font-weight: 500;
&.active {
color: #ed383e;
font-weight: 600;
}
.icon {
width: 24px;
height: 24px;
}
.text {
opacity: 0.9;
}
}
}
<view class="tab-bar {{!!focusedPoi?'hide':''}}">
<view class="tab {{currentIndex===index?'active':''}}" wx:key="url" wx:for="{{tabs}}" id="{{index}}" bind:tap="handleTap">
<image src="{{item.activeIcon}}" hidden="{{currentIndex!==index}}" class="icon" mode="" />
<image src="{{item.icon}}" hidden="{{currentIndex===index}}" class="icon" mode="" />
<view class="text">{{isEn ? item.textEn: item.text}}</view>
</view>
</view>
\ No newline at end of file
import langBehavior from "../../behaviors/langBehavior";
Component({
behaviors: [langBehavior],
/**
* 组件的属性列表
*/
properties: {},
/**
* 组件的初始数据
*/
data: {},
/**
* 组件的方法列表
*/
methods: {},
});
{
"component": true,
"usingComponents": {}
}
\ No newline at end of file
.empty {
display: flex;
flex-direction: column;
align-items: center;
gap: 12px;
color: #999;
font-family: "PingFang SC";
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: normal;
.icon {
width: 114px;
height: 56px;
}
}
<view class="empty">
<image src="./empty.png" class="icon" mode="" />
{{isEn?'No information available yet':'还没有任何信息'}}
</view>
\ No newline at end of file
import langBehavior from "../../behaviors/langBehavior";
Component({
behaviors: [langBehavior],
/**
* 组件的属性列表
*/
properties: {},
/**
* 组件的初始数据
*/
data: {},
/**
* 组件的方法列表
*/
methods: {},
});
{
"component": true,
"usingComponents": {}
}
\ No newline at end of file
.end {
width: 100%;
display: flex;
height: 40px;
gap: 6px;
align-items: center;
justify-content: center;
.icon {
width: 20px;
height: 20px;
}
color: #999;
font-family: "PingFang SC";
font-size: 12px;
font-style: normal;
font-weight: 400;
line-height: normal;
}
<view class="end">
<image class="icon" src="./end.png" mode="" />
{{isEn?'no more':'暂无更多'}}
</view>
\ No newline at end of file
import langBehavior from "../../behaviors/langBehavior";
Component({
behaviors: [langBehavior],
properties: {
floors: Array,
floorName: String,
showReset: {
type: Boolean,
value: true,
},
refreshing: Boolean,
showSuccess: Boolean,
neverAuthed: Boolean,
},
/**
* 组件的初始数据
*/
data: {},
/**
* 组件的方法列表
*/
methods: {
handleReset() {
this.triggerEvent("resetmap");
},
handleFloor({ currentTarget: { id: floorName } }) {
this.triggerEvent("floorname", floorName);
},
},
});
{
"component": true,
"usingComponents": {}
}
\ No newline at end of file
.reset-wrapper {
position: absolute;
top: -64px;
right: 10px;
.tip1 {
position: absolute;
z-index: 1;
top: 0;
right: 0;
height: 40px;
border-radius: 8px;
background: rgba(0, 0, 0, 0.6);
backdrop-filter: blur(1px);
display: inline-flex;
padding-left: 14px;
padding-right: 50px;
color: #fff;
font-family: "PingFang SC";
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: 18px;
align-items: center;
white-space: nowrap;
}
.tip2 {
position: absolute;
z-index: 1;
top: 0;
right: 0;
height: 40px;
border-radius: 8px;
background: linear-gradient(90deg, #fa643c 0%, #e92927 100%);
display: inline-flex;
padding-left: 14px;
padding-right: 50px;
color: #fff;
font-family: "PingFang SC";
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: 18px;
align-items: center;
white-space: nowrap;
}
.tip3 {
position: absolute;
right: 44px;
top: 0;
height: 40px;
display: flex;
align-items: center;
.left {
display: inline-flex;
align-items: center;
height: 24px;
border-radius: 4px;
background: linear-gradient(90deg, #fa643c 0%, #e92927 100%);
padding: 0 6px;
color: #fff;
text-align: center;
font-family: "PingFang SC";
font-size: 12px;
font-style: normal;
font-weight: 400;
line-height: 16px;
white-space: nowrap;
}
.right {
width: 3px;
height: 8px;
}
}
}
.reset {
position: absolute;
z-index: 2;
top: 0;
right: 0;
display: flex;
width: 40px;
height: 40px;
justify-content: center;
align-items: center;
border-radius: 8px;
background: #fff;
box-shadow: 0 2px 5px 0 rgba(0, 0, 0, 0.05);
z-index: 2;
.size-24 {
width: 24px;
height: 24px;
}
.refresh {
animation: spin 0.5s linear infinite;
}
}
@keyframes spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
.reset-mask {
position: absolute;
top: -74px;
right: 0px;
width: 60px;
height: 60px;
z-index: 1;
}
.floors {
position: absolute;
display: flex;
flex-direction: column;
top: -152px;
&.has-reset {
top: -202px;
}
right: 10px;
padding: 7px 4px;
z-index: 2;
gap: 2px;
align-items: center;
border-radius: 8px;
background: var(--VI-0, #fff);
box-shadow: 0px 2px 5px 0px rgba(0, 0, 0, 0.05);
z-index: 2;
.arrow {
width: 8px;
height: 3px;
}
.list {
display: flex;
flex-direction: column;
gap: 4px;
.floor {
display: flex;
width: 32px;
height: 32px;
align-items: center;
justify-content: center;
color: #323337;
text-align: center;
font-family: DINPro;
font-size: 14px;
font-style: normal;
font-weight: 500;
border-radius: 8px;
&.active {
background: #fff3f3;
color: #e92927;
}
}
}
}
.floors-mask {
position: absolute;
top: -152px;
&.has-reset {
top: -202px;
}
right: 10px;
width: 40px;
height: 128px;
z-index: 1;
}
<textarea disabled class="reset-mask" wx:if="{{showReset}}"></textarea>
<view class="reset-wrapper">
<view class="reset" bind:tap="handleReset" wx:if="{{showReset}}">
<image wx:if="{{refreshing}}" class="size-24 refresh" src="./refresh.png" mode="" />
<image wx:else class="size-24" src="./reset.png" mode="" />
</view>
<view class="tip1" wx:if="{{refreshing}}">{{isEn?'Locating...':'定位中…'}}</view>
<view class="tip2" wx:elif="{{showSuccess}}">{{isEn?'Successfully located':'定位成功'}}</view>
<view class="tip3" wx:elif="{{neverAuthed}}">
<view class="left">{{isEn?'Tap to locate':'点击获取定位'}}</view>
<image src="./tri.png" class="right" mode="" />
</view>
</view>
<textarea disabled class="floors-mask {{showReset?'has-reset':''}}"></textarea>
<view class="floors {{showReset?'has-reset':''}}">
<image class="arrow" src="./floorUp.png" mode="" />
<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>
<image class="arrow" src="./floorDown.png" mode="" />
</view>
\ No newline at end of file
// components/highlight-text/index.js
Component({
properties: {
text: {
type: String,
value: "",
},
keyword: {
type: String,
value: "",
},
},
observers: {
"text, keyword": function (text, keyword) {
this.updateTextArray(text, keyword);
},
},
data: {
textArray: [],
},
methods: {
updateTextArray(text, keyword) {
if (!text || !keyword) {
this.setData({
textArray: [{ text: text || "", isKeyword: false }],
});
return;
}
const reg = new RegExp(`(${keyword})`, "gi");
const parts = text.split(reg);
const textArray = parts.map((part) => ({
text: part,
isKeyword: part.toLowerCase() === keyword.toLowerCase(),
}));
this.setData({ textArray });
},
},
});
{
"component": true,
"usingComponents": {}
}
\ No newline at end of file
/* components/highlight-text/index.wxss */
.highlight {
color: #e92927;
}
<block wx:for="{{textArray}}" wx:key="index">
<text class="{{item.isKeyword ? 'highlight' : ''}}">{{item.text}}</text>
</block>
\ No newline at end of file
import langBehavior from "../../behaviors/langBehavior";
const langs = [
"简体中文",
"English",
"العربية",
// "日本語",
"한국어",
"Deutsch",
"Français",
"Português",
"Español",
"Italiano",
"Bahasa Melayu",
"русский",
"Bahasa Indonesia",
"ภาษาไทย",
"Türkçe",
"繁體中文 香港/澳門",
"繁體中文 中國臺灣",
"Tiếng Việt",
];
Component({
behaviors: [langBehavior],
properties: {
type: String,
},
/**
* 组件的初始数据
*/
data: {
showOptions: false,
langs,
showTip: false,
},
/**
* 组件的方法列表
*/
methods: {
handleLang() {
this.setData({ showOptions: true });
},
closeOptions() {
this.setData({ showOptions: false });
},
chooseLang({ currentTarget: { id: lang } }) {
if (
(lang === "简体中文" && this.data.isEn) ||
(lang === "English" && !this.data.isEn)
) {
getApp().switchLang();
}
this.setData({ showOptions: false });
if (!(lang === "简体中文" || lang === "English")) {
this.setData({ showTip: true });
}
},
closeTip() {
this.setData({ showTip: false });
},
donothing() {},
},
});
{
"component": true,
"usingComponents": {}
}
\ No newline at end of file
.langBtn {
position: absolute;
z-index: 2;
top: 10px;
right: 10px;
display: flex;
flex-direction: column;
width: 40px;
height: 64px;
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);
&.white {
background: none;
box-shadow: none;
height: 40px;
}
&.black {
background: rgba(0, 0, 0, 0.5);
box-shadow: none;
.texts {
color: #f3f3f3;
}
}
.logo {
width: 24px;
height: 24px;
}
.texts {
display: flex;
height: 24px;
flex-direction: column;
color: #333;
text-align: center;
font-family: "PingFang SC";
font-size: 10px;
font-style: normal;
font-weight: 400;
line-height: 12px; /* 120% */
}
}
.options-bg {
position: absolute;
z-index: 3000;
top: 0;
right: 0;
width: 100vw;
height: 196px;
border-radius: 0 0 8px 8px;
border-top: #f3f3f3;
background: #fff;
}
.options {
position: absolute;
z-index: 3001;
top: 0;
right: 0;
width: 100vw;
height: 196px;
.close {
position: absolute;
top: 16px;
right: 8px;
width: 32px;
height: 32px;
}
.t1 {
position: absolute;
top: 23px;
left: 12px;
color: #333;
text-align: center;
font-family: "PingFang SC";
font-size: 14px;
font-style: normal;
font-weight: 600;
line-height: 18px;
}
.langs {
position: absolute;
top: 56px;
left: 0;
width: 100vw;
height: 128px;
.grid {
width: 1064px;
height: 128px;
padding: 0 12px;
display: grid;
grid-auto-flow: column;
grid-template-rows: 1fr 1fr 1fr;
gap: 4px;
.item {
position: relative;
display: flex;
align-items: center;
padding-left: 10px;
width: 170px;
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;
}
}
}
}
}
.tip-bg {
position: absolute;
z-index: 3000;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
background: rgba(0, 0, 0, 0.6);
}
.tip {
position: absolute;
z-index: 3001;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
.arrow {
position: absolute;
top: 7px;
right: 64px;
width: 246px;
height: 32.5px;
}
.image {
position: absolute;
top: 53px;
left: 0;
right: 0;
margin: auto;
width: 304px;
height: 530px;
}
.btn {
position: absolute;
top: 521px;
left: 0;
right: 0;
margin: auto;
z-index: 2;
width: 260px;
height: 38px;
}
}
<view class="langBtn {{type}}" catch:tap="handleLang">
<block wx:if="{{type==='white'}}">
<image class="logo" src="./switchWhite.png" mode="" />
</block>
<block wx:elif="{{type==='black'}}">
<image class="logo" src="./switchWhite.png" mode="" />
</block>
<block wx:else>
<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">
<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 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="" />
</view>
</view>
</scroll-view>
</view>
<textarea disabled class="tip-bg" wx:if="{{showTip}}"></textarea>
<view wx:if="{{showTip}}" class="tip" bind:tap="closeTip">
<image src="./arrow.png" class="arrow" mode="" />
<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>
\ No newline at end of file
import langBehavior from "../../behaviors/langBehavior";
Component({
behaviors: [langBehavior],
/**
* 组件的属性列表
*/
properties: {},
/**
* 组件的初始数据
*/
data: {},
/**
* 组件的方法列表
*/
methods: {},
});
{
"component": true,
"usingComponents": {}
}
\ No newline at end of file
.loading {
width: 100%;
display: flex;
height: 90px;
padding-top: 20px;
padding-bottom: 50px;
gap: 6px;
align-items: center;
justify-content: center;
.icon {
width: 20px;
height: 20px;
animation: spin 0.5s linear infinite;
}
@keyframes spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
color: #999;
font-family: "PingFang SC";
font-size: 12px;
font-style: normal;
font-weight: 400;
line-height: normal;
}
<view class="loading">
<image class="icon" src="./loading.png" mode="" />
{{isEn?'loading':'加载中'}}
</view>
\ No newline at end of file
import langBehavior from "../../behaviors/langBehavior";
Component({
behaviors: [langBehavior],
properties: {},
/**
* 组件的初始数据
*/
data: { isIOS: getApp().isIOS },
/**
* 组件的方法列表
*/
methods: {
close() {
this.triggerEvent("close");
},
doNothing() {},
},
});
{
"component": true,
"usingComponents": {}
}
\ No newline at end of file
.bluetooth-bg {
position: absolute;
z-index: 3000;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
background: rgba(0, 0, 0, 0.6);
}
.bluetooth-modal {
position: fixed;
top: 0;
left: 0;
z-index: 3001;
width: 100vw;
height: 100vh;
.image {
position: absolute;
top: 32px;
left: 0;
right: 0;
margin: auto;
width: 304px;
height: 572px;
}
.btn {
position: absolute;
top: 542px;
left: 0;
right: 0;
margin: auto;
width: 260px;
height: 38px;
}
}
<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="" />
<view class="btn" catch:tap="close"></view>
</view>
\ No newline at end of file
import poiFocusBehavior from "../../behaviors/poiFocusBehavior";
import { findNearbyLocations } from "../../util";
const defaultScale = 20;
const defaultRotate = 329;
const defaultLatitude = 29.331723;
const defaultLongitude = 120.106838;
const defaultSkew = 30;
Component({
behaviors: [poiFocusBehavior],
properties: {
// 公共属性
buildingId: String,
floorName: String,
resetCount: Number,
userLocation: Object,
// index页面特有属性
acts: {
type: Array,
value: [],
},
// surroundings页面特有属性
facilities: {
type: Array,
value: [],
},
// 用于区分当前是哪个页面
type: {
type: String,
value: "index", // 'index' 或 'surroundings'
},
},
data: {
latitude: defaultLatitude,
longitude: defaultLongitude,
markers: [],
setting: {
skew: defaultSkew,
rotate: defaultRotate,
scale: defaultScale,
},
pageShow: true,
},
observers: {
resetCount() {
this.handleReset();
},
buildingId() {
this.setIndoorFloor();
},
floorName() {
this.setIndoorFloor();
this.handleModel();
},
"floorName,acts"() {
if (!this.data.pageShow) return;
this.setActMarkers();
},
facilities() {
if (!this.data.pageShow) return;
this.setFacilityMarkers();
},
focusedPoi(poi) {
if (!this.data.pageShow) return;
try {
if (this.data.type === "index" && !poi) {
this.mapContext?.removeMarkers({
markerIds: [123],
fail: console.log,
});
} else {
this.handleFocusedPoi(poi);
}
} catch (error) {
console.error(error);
}
},
},
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)
.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",
},
})),
});
},
// 设置设施标记点(surroundings页面)
setFacilityMarkers() {
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),
iconPath: facility.logo,
width: 32,
height: 32,
}));
this.mapContext?.addMarkers({
clear: true,
markers,
fail: console.log,
});
if (!facilities.length) return;
const bbox = markers.reduce(
({ maxLat, maxLng, minLat, minLng }, { latitude, longitude }) => ({
maxLat: Math.max(maxLat, latitude),
maxLng: Math.max(maxLng, longitude),
minLat: Math.min(minLat, latitude),
minLng: Math.min(minLng, longitude),
}),
{
maxLat: -Infinity,
maxLng: -Infinity,
minLat: Infinity,
minLng: Infinity,
}
);
const center = {
latitude: (bbox.maxLat + bbox.minLat) / 2,
longitude: (bbox.maxLng + bbox.minLng) / 2,
};
this.setData({
setting: {
scale: 18,
skew: defaultSkew,
rotate: defaultRotate,
},
});
this.mapContext?.moveToLocation({
longitude: center.longitude,
latitude: center.latitude,
fail: (e) => {
console.log(`显示markers中心失败,直接设置经纬度`, e);
this.setData({
longitude: center.longitude,
latitude: center.latitude,
});
},
});
},
setIndoorFloor() {
if (this.mapContext && this.data.buildingId && this.data.floorName) {
this.mapContext.setIndoorFloor({
buildingId: this.data.buildingId,
floorName: this.data.floorName,
});
}
},
handleIndoorChange(e) {
console.log(e);
},
// 处理地图点击(仅index页面)
handleTap({ detail: { longitude, latitude } }) {
if (this.data.type !== "index") return;
this.getPoi({ longitude, latitude });
},
getPoi({ longitude, latitude }) {
const { txQmMap, indexData } = getApp();
const { floorName } = this.data;
const list = findNearbyLocations(
latitude,
longitude,
floorName,
indexData
);
if (list.length) {
const poi = txQmMap[list[0]];
getApp().focusPoi(poi);
} else {
getApp().blurPoi();
}
},
// 处理标记点点击(surroundings页面)
handleMarkerTap({ detail: { markerId: facilityId } }) {
if (this.data.type !== "surroundings") return;
const { facilityIdMap } = getApp();
getApp().focusPoi(facilityIdMap[facilityId]);
},
// 处理标注点击(index页面)
handleCalloutTap({ detail: { markerId } }) {
if (this.data.type !== "index") return;
const act = this.data.acts.find(({ id }) => id === markerId);
getApp().blurPoi();
this.triggerEvent("act", act);
},
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.mapContext?.moveToLocation({
latitude: Number(poi.tx.poi_location.latitude),
longitude: Number(poi.tx.poi_location.longitude),
fail: (e) => {
console.log(`移位至poi失败,直接设置经纬度`, e);
this.setData({
latitude: Number(poi.tx.poi_location.latitude),
longitude: Number(poi.tx.poi_location.longitude),
});
},
});
} 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,
});
}
} catch (error) {
console.error(error);
}
},
handleModel() {
try {
if (!this.mapContext) return;
if (this.data.floorName === "1F") {
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 },
}),
});
},
fail: (e) => console.log("addVisualLayer", e),
});
} else {
this.mapContext.removeVisualLayer({
layerId: "08adaf0275db",
success: (e) => console.log("removeVisualLayer", e),
fail: (e) => console.log("removeVisualLayer", e),
});
}
} catch (error) {
console.error(error);
}
},
},
lifetimes: {
attached() {
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);
},
detached() {
this.mapContext = null;
},
},
pageLifetimes: {
show() {
this.setData({ pageShow: true });
},
hide() {
this.setData({ pageShow: false });
},
},
});
{
"component": true,
"usingComponents": {}
}
\ No newline at end of file
/* pages/index/bottom-hud/map-wrapper/index.wxss */
.map {
position: absolute;
top: 0;
left: 0;
width: 100vw;
height: calc(100vh - 90px);
z-index: 1;
}
<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
import poiFocusBehavior from "../../behaviors/poiFocusBehavior";
import langBehavior from "../../behaviors/langBehavior";
import collectBehavior from "../../behaviors/collectBehavior";
import { toRoutePlan } from "../../util";
Component({
behaviors: [poiFocusBehavior, langBehavior, collectBehavior],
properties: {
isIndex: Boolean,
},
data: {
collected: false,
},
observers: {
focusedPoi() {
this.setCollected();
},
},
methods: {
setCollected() {
if (this.data.focusedPoi)
this.setData({ collected: this.isCollected(this.data.focusedPoi) });
},
handleClose() {
getApp().blurPoi();
const pages = getCurrentPages();
if (pages.length > 1) {
wx.navigateBack();
}
},
handleDetail() {
wx.navigateTo({
url: `/pages/shop/index?shopId=${this.data.focusedPoi.shopId}`,
});
},
async handleGo() {
const { isEn, focusedPoi: poi, isIndex } = this.data;
try {
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),
}
: {}),
...(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,
}),
});
} catch (error) {
console.error("埋点失败", error);
}
try {
const res = await toRoutePlan(poi, isEn);
if (res === "showBluetoothModal")
return this.triggerEvent("showbluetoothmodal");
if (res === "showLocationModal")
return this.triggerEvent("showlocationmodal");
if (res === "showAuthModal") return this.triggerEvent("showauthmodal");
} catch (error) {
console.error(error);
}
},
async handleCollect() {
if (!getApp().checkUser()) return;
this.toggleCollection(this.data.focusedPoi);
},
},
lifetimes: {
attached() {
this.setCollected();
},
},
});
{
"component": true,
"usingComponents": {
"shop-bottom": "../shop-bottom/index",
"shop-maingoods": "../shop-maingoods/index",
"shop-addr": "../shop-addr/index",
"shop-name": "../shop-name/index"
}
}
.popup {
position: absolute;
bottom: 0;
left: 0;
z-index: 300;
width: 100vw;
border-radius: 16px 16px 0px 0px;
background: #fff;
backdrop-filter: blur(4px);
padding: 18px 17px 110px 16px;
min-height: 188px;
.close {
position: absolute;
width: 32px;
height: 32px;
top: 8px;
right: 8px;
}
.t1 {
color: #333;
font-family: "PingFang SC";
font-size: 16px;
font-style: normal;
font-weight: 600;
line-height: 22px;
padding-right: 23px;
}
.t2 {
display: inline-flex;
align-items: center;
gap: 4px;
color: #666;
font-family: "PingFang SC";
font-size: 12px;
font-style: normal;
font-weight: 400;
line-height: 16px;
}
.t3 {
color: #666;
font-family: "PingFang SC";
font-size: 12px;
font-style: normal;
font-weight: 400;
line-height: 18px;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
text-overflow: ellipsis;
word-wrap: break-word;
}
.stick {
width: 1px;
height: 16px;
background: #666666;
}
.fac {
display: flex;
gap: 10px;
.avatar {
width: 40px;
height: 40px;
border-radius: 50%;
}
.right {
display: flex;
flex-direction: column;
gap: 6px;
}
}
.shop {
display: flex;
gap: 10px;
.avatar {
width: 96px;
height: 96px;
border-radius: 8px;
}
.right {
display: flex;
flex-direction: column;
gap: 16px;
flex: 1;
.c-gap-6 {
display: flex;
flex-direction: column;
gap: 6px;
}
}
}
}
<view class="popup" wx:if="{{focusedPoi}}">
<slot></slot>
<view class="shop" wx:if="{{focusedPoi.facility&&focusedPoi.facility.name==='yandoocafe'}}">
<image wx:if="{{focusedPoi.facility.logo}}" src="{{focusedPoi.facility.logo}}" class="avatar" mode="" />
<view class="right">
<view class="c-gap-6">
<view class="t1">{{isEn?focusedPoi.facility.nameEn:focusedPoi.facility.name}}</view>
<view class="t2">{{isEn?focusedPoi.facility.addressEn:focusedPoi.facility.address}}</view>
</view>
</view>
</view>
<view class="fac" wx:elif="{{focusedPoi.facility}}">
<image wx:if="{{focusedPoi.facility.logo}}" src="{{focusedPoi.facility.logo}}" class="avatar" mode="" />
<view class="right">
<view class="t1">{{isEn?focusedPoi.facility.nameEn:focusedPoi.facility.name}}</view>
<view class="t2">{{isEn?focusedPoi.facility.addressEn:focusedPoi.facility.address}}</view>
</view>
</view>
<view class="fac" wx:elif="{{isEn&&!focusedPoi.ywEn}}">
<image wx:if="{{focusedPoi.ywZh.cover}}" src="{{focusedPoi.ywZh.cover}}" class="avatar" mode="" />
<view class="right">
<view class="t1">
<shop-name shop="{{focusedPoi}}"></shop-name>
</view>
<view class="t2">
<shop-addr shop="{{focusedPoi}}"></shop-addr>
</view>
</view>
</view>
<view class="shop" wx:else>
<image wx:if="{{focusedPoi.ywZh.cover}}" src="{{focusedPoi.ywZh.cover}}" class="avatar" mode="" />
<view class="right">
<view class="c-gap-6">
<view class="t1">
<shop-name shop="{{focusedPoi}}"></shop-name>
</view>
<view class="t2">
<shop-addr shop="{{focusedPoi}}"></shop-addr>
</view>
</view>
<view class="t3">
<shop-maingoods shop="{{focusedPoi}}"></shop-maingoods>
</view>
</view>
</view>
<image class="close" src="./close.png" mode="" bind:tap="handleClose" />
<shop-bottom show-detail="{{focusedPoi.ywZh}}" collected="{{collected}}" show-go="{{focusedPoi.tx}}" bind:detail="handleDetail" bind:go="handleGo" bind:collect="handleCollect"></shop-bottom>
</view>
\ No newline at end of file
import langBehavior from "../../behaviors/langBehavior";
import { getAddr } from "../../util";
Component({
behaviors: [langBehavior],
properties: {
shop: Object,
keyword: {
type: String,
value: "",
},
bigGap: Boolean,
},
data: {
enAddr: "",
buildingFloor: "",
street: "",
},
observers: {
isEn() {
this.setText();
},
shop() {
this.setText();
},
},
methods: {
setText() {
const { isEn, shop } = this.data;
const { enAddr, buildingFloor, street } = getAddr(shop, isEn);
this.setData({ enAddr, buildingFloor, street });
},
},
lifetimes: {
attached() {
this.setText();
},
},
});
{
"component": true,
"usingComponents": {
"highlight-text": "../highlight-text/index"
}
}
.shop-addr {
display: flex;
gap: 4px;
align-items: center;
&.big-gap {
gap: 8px;
}
.stick {
width: 1px;
height: 8px;
flex-shrink: 0;
background: currentColor;
}
}
<block wx:if="{{keyword}}">
<view class="shop-addr" wx:if="{{isEn}}">
<highlight-text text="{{enAddr}}" keyword="{{keyword}}"></highlight-text>
</view>
<view wx:else class="shop-addr">
<highlight-text text="{{buildingFloor}}" keyword="{{keyword}}"></highlight-text>
<view class="stick"></view>
<highlight-text text="{{street}}" keyword="{{keyword}}"></highlight-text>
<view class="stick"></view>
<highlight-text text="{{shop.ywZh.boothNo}}" keyword="{{keyword}}"></highlight-text>
</view>
</block>
<block wx:else>
<view class="shop-addr" wx:if="{{isEn}}">
{{enAddr}}
</view>
<view wx:else class="shop-addr {{bigGap?'big-gap':''}}">
{{buildingFloor}}
<view class="stick"></view>
{{street}}
<view class="stick"></view>
{{shop.ywZh.boothNo}}
</view>
</block>
\ No newline at end of file
import langBehavior from "../../behaviors/langBehavior";
Component({
behaviors: [langBehavior],
properties: {
showDetail: Boolean,
collected: Boolean,
showGo: Boolean,
},
/**
* 组件的初始数据
*/
data: {},
/**
* 组件的方法列表
*/
methods: {
handleDetail() {
this.triggerEvent("detail");
},
handleCollect() {
this.triggerEvent("collect");
},
handleGo() {
this.triggerEvent("go");
},
},
});
{
"component": true,
"usingComponents": {}
}
\ No newline at end of file
.shop-bottom {
position: absolute;
bottom: 0;
left: 0;
width: 100vw;
height: 90px;
background: #fff;
border-top: 1px solid var(--B-3, rgba(0, 0, 0, 0.03));
padding: 12px;
padding-top: 8px;
display: flex;
gap: 27px;
.btns {
display: flex;
gap: 16px;
height: 40px;
.btn {
display: flex;
flex-direction: column;
align-items: center;
gap: 4px;
width: auto;
padding: 0;
padding-left: 0;
padding-right: 0;
padding-top: 0;
padding-bottom: 0;
background-color: none;
background: none;
color: #999;
font-family: "PingFang SC";
font-size: 10px;
font-style: normal;
font-weight: 400;
line-height: 12px;
.size-24 {
width: 24px;
height: 24px;
}
}
}
.go {
display: flex;
flex: 1;
height: 38px;
border-radius: 99px;
background: linear-gradient(90deg, #fa643c 0%, #e92927 100%);
gap: 4px;
align-items: center;
justify-content: center;
.size-20 {
width: 20px;
height: 20px;
}
color: #fff;
font-family: "PingFang SC";
font-size: 14px;
font-style: normal;
font-weight: 600;
line-height: 18px;
}
}
<view class="shop-bottom">
<view class="btns">
<view class="btn" wx:if="{{showDetail}}" bind:tap="handleDetail">
<image class="size-24" src="./detail.png" mode="" />
<view>{{isEn?'Detail':'详情'}}</view>
</view>
<button class="btn" open-type="share">
<image class="size-24" src="./share.png" mode="" />
<view>{{isEn?'Share':'分享'}}</view>
</button>
<view class="btn" bind:tap="handleCollect">
<image wx:if="{{collected}}" class="size-24" src="./starred.png" mode="" />
<image wx:else class="size-24" src="./star.png" mode="" />
<view>{{isEn?'Collect':'收藏'}}</view>
</view>
</view>
<view class="go" wx:if="{{showGo}}" bind:tap="handleGo">
<image class="size-20" src="./go.png" mode="" />
<view>{{isEn?'Go here':'到这去'}}</view>
</view>
</view>
\ No newline at end of file
import langBehavior from "../../behaviors/langBehavior";
import { getShopMainGoods } from "../../util";
Component({
behaviors: [langBehavior],
properties: {
shop: Object,
},
/**
* 组件的初始数据
*/
data: {
text: "",
},
observers: {
isEn() {
this.setText();
},
shop() {
this.setText();
},
},
methods: {
setText() {
const { isEn, shop } = this.data;
const txt = getShopMainGoods(shop, isEn);
const text = txt ? txt : ` `;
this.setData({ text });
},
},
lifetimes: {
attached() {
this.setText();
},
},
});
{
"component": true,
"usingComponents": {}
}
\ No newline at end of file
/* components/shop-maingoods/index.wxss */
\ No newline at end of file
<text>{{text}}</text>
\ No newline at end of file
import langBehavior from "../../behaviors/langBehavior";
import { getPoiName } from "../../util";
Component({
behaviors: [langBehavior],
properties: {
shop: Object,
keyword: {
type: String,
value: "",
},
},
/**
* 组件的初始数据
*/
data: {
text: "",
},
observers: {
isEn() {
this.setText();
},
shop() {
this.setText();
},
},
methods: {
setText() {
const { isEn, shop } = this.data;
const text = shop ? getPoiName(shop, isEn) : "";
this.setData({ text });
},
},
lifetimes: {
attached() {
this.setText();
},
},
});
{
"component": true,
"usingComponents": { "highlight-text": "../highlight-text/index" }
}
/* components/shop-name/index.wxss */
\ No newline at end of file
<block wx:if="{{keyword}}">
<highlight-text text="{{text}}" keyword="{{keyword}}"></highlight-text>
</block>
<text wx:else>{{text}}</text>
\ No newline at end of file
/*
* JavaScript MD5
* https://github.com/blueimp/JavaScript-MD5
*
* Copyright 2011, Sebastian Tschan
* https://blueimp.net
*
* Licensed under the MIT license:
* https://opensource.org/licenses/MIT
*
* Based on
* A JavaScript implementation of the RSA Data Security, Inc. MD5 Message
* Digest Algorithm, as defined in RFC 1321.
* Version 2.2 Copyright (C) Paul Johnston 1999 - 2009
* Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet
* Distributed under the BSD License
* See http://pajhome.org.uk/crypt/md5 for more info.
*/
/* global define */
/* eslint-disable strict */
(function ($) {
"use strict";
/**
* Add integers, wrapping at 2^32.
* This uses 16-bit operations internally to work around bugs in interpreters.
*
* @param {number} x First integer
* @param {number} y Second integer
* @returns {number} Sum
*/
function safeAdd(x, y) {
var lsw = (x & 0xffff) + (y & 0xffff);
var msw = (x >> 16) + (y >> 16) + (lsw >> 16);
return (msw << 16) | (lsw & 0xffff);
}
/**
* Bitwise rotate a 32-bit number to the left.
*
* @param {number} num 32-bit number
* @param {number} cnt Rotation count
* @returns {number} Rotated number
*/
function bitRotateLeft(num, cnt) {
return (num << cnt) | (num >>> (32 - cnt));
}
/**
* Basic operation the algorithm uses.
*
* @param {number} q q
* @param {number} a a
* @param {number} b b
* @param {number} x x
* @param {number} s s
* @param {number} t t
* @returns {number} Result
*/
function md5cmn(q, a, b, x, s, t) {
return safeAdd(bitRotateLeft(safeAdd(safeAdd(a, q), safeAdd(x, t)), s), b);
}
/**
* Basic operation the algorithm uses.
*
* @param {number} a a
* @param {number} b b
* @param {number} c c
* @param {number} d d
* @param {number} x x
* @param {number} s s
* @param {number} t t
* @returns {number} Result
*/
function md5ff(a, b, c, d, x, s, t) {
return md5cmn((b & c) | (~b & d), a, b, x, s, t);
}
/**
* Basic operation the algorithm uses.
*
* @param {number} a a
* @param {number} b b
* @param {number} c c
* @param {number} d d
* @param {number} x x
* @param {number} s s
* @param {number} t t
* @returns {number} Result
*/
function md5gg(a, b, c, d, x, s, t) {
return md5cmn((b & d) | (c & ~d), a, b, x, s, t);
}
/**
* Basic operation the algorithm uses.
*
* @param {number} a a
* @param {number} b b
* @param {number} c c
* @param {number} d d
* @param {number} x x
* @param {number} s s
* @param {number} t t
* @returns {number} Result
*/
function md5hh(a, b, c, d, x, s, t) {
return md5cmn(b ^ c ^ d, a, b, x, s, t);
}
/**
* Basic operation the algorithm uses.
*
* @param {number} a a
* @param {number} b b
* @param {number} c c
* @param {number} d d
* @param {number} x x
* @param {number} s s
* @param {number} t t
* @returns {number} Result
*/
function md5ii(a, b, c, d, x, s, t) {
return md5cmn(c ^ (b | ~d), a, b, x, s, t);
}
/**
* Calculate the MD5 of an array of little-endian words, and a bit length.
*
* @param {Array} x Array of little-endian words
* @param {number} len Bit length
* @returns {Array<number>} MD5 Array
*/
function binlMD5(x, len) {
/* append padding */
x[len >> 5] |= 0x80 << len % 32;
x[(((len + 64) >>> 9) << 4) + 14] = len;
var i;
var olda;
var oldb;
var oldc;
var oldd;
var a = 1732584193;
var b = -271733879;
var c = -1732584194;
var d = 271733878;
for (i = 0; i < x.length; i += 16) {
olda = a;
oldb = b;
oldc = c;
oldd = d;
a = md5ff(a, b, c, d, x[i], 7, -680876936);
d = md5ff(d, a, b, c, x[i + 1], 12, -389564586);
c = md5ff(c, d, a, b, x[i + 2], 17, 606105819);
b = md5ff(b, c, d, a, x[i + 3], 22, -1044525330);
a = md5ff(a, b, c, d, x[i + 4], 7, -176418897);
d = md5ff(d, a, b, c, x[i + 5], 12, 1200080426);
c = md5ff(c, d, a, b, x[i + 6], 17, -1473231341);
b = md5ff(b, c, d, a, x[i + 7], 22, -45705983);
a = md5ff(a, b, c, d, x[i + 8], 7, 1770035416);
d = md5ff(d, a, b, c, x[i + 9], 12, -1958414417);
c = md5ff(c, d, a, b, x[i + 10], 17, -42063);
b = md5ff(b, c, d, a, x[i + 11], 22, -1990404162);
a = md5ff(a, b, c, d, x[i + 12], 7, 1804603682);
d = md5ff(d, a, b, c, x[i + 13], 12, -40341101);
c = md5ff(c, d, a, b, x[i + 14], 17, -1502002290);
b = md5ff(b, c, d, a, x[i + 15], 22, 1236535329);
a = md5gg(a, b, c, d, x[i + 1], 5, -165796510);
d = md5gg(d, a, b, c, x[i + 6], 9, -1069501632);
c = md5gg(c, d, a, b, x[i + 11], 14, 643717713);
b = md5gg(b, c, d, a, x[i], 20, -373897302);
a = md5gg(a, b, c, d, x[i + 5], 5, -701558691);
d = md5gg(d, a, b, c, x[i + 10], 9, 38016083);
c = md5gg(c, d, a, b, x[i + 15], 14, -660478335);
b = md5gg(b, c, d, a, x[i + 4], 20, -405537848);
a = md5gg(a, b, c, d, x[i + 9], 5, 568446438);
d = md5gg(d, a, b, c, x[i + 14], 9, -1019803690);
c = md5gg(c, d, a, b, x[i + 3], 14, -187363961);
b = md5gg(b, c, d, a, x[i + 8], 20, 1163531501);
a = md5gg(a, b, c, d, x[i + 13], 5, -1444681467);
d = md5gg(d, a, b, c, x[i + 2], 9, -51403784);
c = md5gg(c, d, a, b, x[i + 7], 14, 1735328473);
b = md5gg(b, c, d, a, x[i + 12], 20, -1926607734);
a = md5hh(a, b, c, d, x[i + 5], 4, -378558);
d = md5hh(d, a, b, c, x[i + 8], 11, -2022574463);
c = md5hh(c, d, a, b, x[i + 11], 16, 1839030562);
b = md5hh(b, c, d, a, x[i + 14], 23, -35309556);
a = md5hh(a, b, c, d, x[i + 1], 4, -1530992060);
d = md5hh(d, a, b, c, x[i + 4], 11, 1272893353);
c = md5hh(c, d, a, b, x[i + 7], 16, -155497632);
b = md5hh(b, c, d, a, x[i + 10], 23, -1094730640);
a = md5hh(a, b, c, d, x[i + 13], 4, 681279174);
d = md5hh(d, a, b, c, x[i], 11, -358537222);
c = md5hh(c, d, a, b, x[i + 3], 16, -722521979);
b = md5hh(b, c, d, a, x[i + 6], 23, 76029189);
a = md5hh(a, b, c, d, x[i + 9], 4, -640364487);
d = md5hh(d, a, b, c, x[i + 12], 11, -421815835);
c = md5hh(c, d, a, b, x[i + 15], 16, 530742520);
b = md5hh(b, c, d, a, x[i + 2], 23, -995338651);
a = md5ii(a, b, c, d, x[i], 6, -198630844);
d = md5ii(d, a, b, c, x[i + 7], 10, 1126891415);
c = md5ii(c, d, a, b, x[i + 14], 15, -1416354905);
b = md5ii(b, c, d, a, x[i + 5], 21, -57434055);
a = md5ii(a, b, c, d, x[i + 12], 6, 1700485571);
d = md5ii(d, a, b, c, x[i + 3], 10, -1894986606);
c = md5ii(c, d, a, b, x[i + 10], 15, -1051523);
b = md5ii(b, c, d, a, x[i + 1], 21, -2054922799);
a = md5ii(a, b, c, d, x[i + 8], 6, 1873313359);
d = md5ii(d, a, b, c, x[i + 15], 10, -30611744);
c = md5ii(c, d, a, b, x[i + 6], 15, -1560198380);
b = md5ii(b, c, d, a, x[i + 13], 21, 1309151649);
a = md5ii(a, b, c, d, x[i + 4], 6, -145523070);
d = md5ii(d, a, b, c, x[i + 11], 10, -1120210379);
c = md5ii(c, d, a, b, x[i + 2], 15, 718787259);
b = md5ii(b, c, d, a, x[i + 9], 21, -343485551);
a = safeAdd(a, olda);
b = safeAdd(b, oldb);
c = safeAdd(c, oldc);
d = safeAdd(d, oldd);
}
return [a, b, c, d];
}
/**
* Convert an array of little-endian words to a string
*
* @param {Array<number>} input MD5 Array
* @returns {string} MD5 string
*/
function binl2rstr(input) {
var i;
var output = "";
var length32 = input.length * 32;
for (i = 0; i < length32; i += 8) {
output += String.fromCharCode((input[i >> 5] >>> i % 32) & 0xff);
}
return output;
}
/**
* Convert a raw string to an array of little-endian words
* Characters >255 have their high-byte silently ignored.
*
* @param {string} input Raw input string
* @returns {Array<number>} Array of little-endian words
*/
function rstr2binl(input) {
var i;
var output = [];
output[(input.length >> 2) - 1] = undefined;
for (i = 0; i < output.length; i += 1) {
output[i] = 0;
}
var length8 = input.length * 8;
for (i = 0; i < length8; i += 8) {
output[i >> 5] |= (input.charCodeAt(i / 8) & 0xff) << i % 32;
}
return output;
}
/**
* Calculate the MD5 of a raw string
*
* @param {string} s Input string
* @returns {string} Raw MD5 string
*/
function rstrMD5(s) {
return binl2rstr(binlMD5(rstr2binl(s), s.length * 8));
}
/**
* Calculates the HMAC-MD5 of a key and some data (raw strings)
*
* @param {string} key HMAC key
* @param {string} data Raw input string
* @returns {string} Raw MD5 string
*/
function rstrHMACMD5(key, data) {
var i;
var bkey = rstr2binl(key);
var ipad = [];
var opad = [];
var hash;
ipad[15] = opad[15] = undefined;
if (bkey.length > 16) {
bkey = binlMD5(bkey, key.length * 8);
}
for (i = 0; i < 16; i += 1) {
ipad[i] = bkey[i] ^ 0x36363636;
opad[i] = bkey[i] ^ 0x5c5c5c5c;
}
hash = binlMD5(ipad.concat(rstr2binl(data)), 512 + data.length * 8);
return binl2rstr(binlMD5(opad.concat(hash), 512 + 128));
}
/**
* Convert a raw string to a hex string
*
* @param {string} input Raw input string
* @returns {string} Hex encoded string
*/
function rstr2hex(input) {
var hexTab = "0123456789abcdef";
var output = "";
var x;
var i;
for (i = 0; i < input.length; i += 1) {
x = input.charCodeAt(i);
output += hexTab.charAt((x >>> 4) & 0x0f) + hexTab.charAt(x & 0x0f);
}
return output;
}
/**
* Encode a string as UTF-8
*
* @param {string} input Input string
* @returns {string} UTF8 string
*/
function str2rstrUTF8(input) {
return unescape(encodeURIComponent(input));
}
/**
* Encodes input string as raw MD5 string
*
* @param {string} s Input string
* @returns {string} Raw MD5 string
*/
function rawMD5(s) {
return rstrMD5(str2rstrUTF8(s));
}
/**
* Encodes input string as Hex encoded string
*
* @param {string} s Input string
* @returns {string} Hex encoded string
*/
function hexMD5(s) {
return rstr2hex(rawMD5(s));
}
/**
* Calculates the raw HMAC-MD5 for the given key and data
*
* @param {string} k HMAC key
* @param {string} d Input string
* @returns {string} Raw MD5 string
*/
function rawHMACMD5(k, d) {
return rstrHMACMD5(str2rstrUTF8(k), str2rstrUTF8(d));
}
/**
* Calculates the Hex encoded HMAC-MD5 for the given key and data
*
* @param {string} k HMAC key
* @param {string} d Input string
* @returns {string} Raw MD5 string
*/
function hexHMACMD5(k, d) {
return rstr2hex(rawHMACMD5(k, d));
}
/**
* Calculates MD5 value for a given string.
* If a key is provided, calculates the HMAC-MD5 value.
* Returns a Hex encoded string unless the raw argument is given.
*
* @param {string} string Input string
* @param {string} [key] HMAC key
* @param {boolean} [raw] Raw output switch
* @returns {string} MD5 output
*/
function md5(string, key, raw) {
if (!key) {
if (!raw) {
return hexMD5(string);
}
return rawMD5(string);
}
if (!raw) {
return hexHMACMD5(key, string);
}
return rawHMACMD5(key, string);
}
if (typeof define === "function" && define.amd) {
define(function () {
return md5;
});
} else if (typeof module === "object" && module.exports) {
module.exports = md5;
} else {
$.md5 = md5;
}
})(this);
import langBehavior from "../../behaviors/langBehavior";
import { confirm } from "../../util";
Page({
behaviors: [langBehavior],
data: {
states: {
init: "init",
takingPhoto: "takingPhoto",
},
state: "init",
currentPhotoUrl: "",
isPhotoMode: true,
isAlbum: true,
isFullscreen: false,
albumList: [],
photoList: [],
lastSwitchTime: 0, // 记录上次切换时间
},
/**
* 生命周期函数--监听页面加载
*/
async onLoad() {
this.getList();
// 页面打开时记录初始调用时间
this.setData({
lastSwitchTime: Date.now(),
});
},
async getList() {
const { photoApi } = getApp();
const list = await photoApi.get();
if (list) {
// 处理photoList,添加标志位
const processedPhotoList = [];
let lastDate = null;
list
.filter(({ source }) => source === 2)
.forEach((item, index) => {
// 从createTime中直接截取月日
const dateParts = item.createTime.split(" ")[0].split("-");
const month = dateParts[1];
const day = dateParts[2];
const currentDate = new Date();
// 判断是否是今天
const isToday =
parseInt(month) === currentDate.getMonth() + 1 &&
parseInt(day) === currentDate.getDate();
// 如果是第一个元素或者与上一个元素日期不同
if (index === 0 || lastDate !== `${month}-${day}`) {
item.dateFlag = isToday ? "今日" : `${month}-${day}`;
} else {
item.dateFlag = "";
}
// 更新lastDate
lastDate = `${month}-${day}`;
processedPhotoList.push(item);
});
this.setData({
albumList: list.filter(({ source }) => source === 1),
photoList: processedPhotoList,
});
}
},
async takePhoto() {
try {
wx.showLoading();
const { state, states } = this.data;
if (state !== states.init) return;
this.setData({ state: states.takingPhoto, isAlbum: false });
const ctx = wx.createCameraContext();
const filePath = await new Promise((resolve) =>
ctx.takePhoto({
quality: "high",
success: (res) => {
resolve(res.tempImagePath);
},
fail: () => resolve(null),
})
);
if (!filePath) return console.error("没获取到照片本地地址");
const { photoApi } = getApp();
const photoUrl = await photoApi.upload(filePath);
if (!photoUrl) return console.error("照片上传失败");
const success = await photoApi.post(photoUrl, 2);
if (!success) console.error("关联照片失败");
this.setData({ state: states.init });
wx.navigateTo({
url: `/pages/aisearchresult/index?photoUrl=${encodeURIComponent(
photoUrl
)}`,
});
} catch (error) {
console.error(error);
} finally {
this.setData({ state: states.init });
wx.hideLoading();
}
},
switchCameraMode(event) {
// 防抖处理:1秒内不允许重复切换
const currentTime = Date.now();
if (currentTime - this.data.lastSwitchTime < 1000) {
return; // 忽略1秒内的重复调用
}
// 更新上次调用时间为当前时间
this.setData({
lastSwitchTime: currentTime,
});
// 从事件对象中获取传递的参数
const toPhotoMode = event.currentTarget.dataset.mode === "true";
this.setData({
isPhotoMode: toPhotoMode,
});
},
handleAlbum() {
this.setData({ isAlbum: true });
},
handlePhoto() {
this.setData({ isAlbum: false });
},
async handleAddFromAlbum() {
const filePath = await new Promise((resolve) => {
wx.chooseMedia({
count: 1,
mediaType: ["image"],
sourceType: ["album"],
success(res) {
resolve(res.tempFiles[0].tempFilePath);
},
});
});
if (!filePath) return;
try {
wx.showLoading();
const { photoApi } = getApp();
const photoUrl = await photoApi.upload(filePath);
if (!photoUrl) return console.error("照片上传失败");
const success = await photoApi.post(photoUrl, 1);
if (!success) console.error("关联照片失败");
wx.navigateTo({
url: `/pages/aisearchresult/index?photoUrl=${encodeURIComponent(
photoUrl
)}`,
});
} catch (error) {
console.error(error);
} finally {
wx.hideLoading();
}
},
toggleFullscreen() {
this.setData({ isFullscreen: !this.data.isFullscreen });
},
handleFromImage({ currentTarget: { id: index } }) {
const list = this.data.isAlbum ? this.data.albumList : this.data.photoList;
const photoUrl = list[index].photoUrl;
wx.navigateTo({
url: `/pages/aisearchresult/index?photoUrl=${encodeURIComponent(
photoUrl
)}`,
});
},
async handleClearAlbum() {
const { isEn, albumList } = this.data;
if (!albumList.length) return;
const confirmed = await confirm({
title: "",
content: isEn
? `Are you sure you want to clear the album?`
: `确定清空相册吗?`,
confirmText: isEn ? `Confirm clear` : `确认清空`,
cancelText: isEn ? `Cancel` : `取消`,
});
if (!confirmed) return;
const app = getApp();
const { photoApi } = app;
try {
const success = await photoApi.clear(1);
if (success) {
wx.showToast({
title: isEn ? "success" : "清空成功",
icon: "none",
});
this.getList();
}
} catch (error) {
console.error(error);
}
},
async handleClearPhoto() {
const { isEn, photoList } = this.data;
if (!photoList.length) return;
const confirmed = await confirm({
title: "",
content: isEn
? `Are you sure you want to clear the photos you took?`
: `确定清空拍过的照片吗?`,
confirmText: isEn ? `Confirm clear` : `确认清空`,
cancelText: isEn ? `Cancel` : `取消`,
});
if (!confirmed) return;
const app = getApp();
const { photoApi } = app;
try {
const success = await photoApi.clear(2);
if (success) {
wx.showToast({
title: isEn ? "success" : "清空成功",
icon: "none",
});
this.getList();
}
} catch (error) {
console.error(error);
}
},
handleScanCode({ detail: { result: url } }) {
try {
const queryString = url.split("?")[1] || "";
const params = {};
queryString.split("&").forEach((param) => {
const [key, value] = param.split("=");
if (key) {
params[key] = decodeURIComponent(value.replace(/\+/g, " "));
}
});
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}`,
});
}
} catch (error) {
console.error(error);
}
},
onReady() {},
/**
* 生命周期函数--监听页面显示
*/
onShow() {
this.getList();
// 页面显示时也更新初始调用时间
this.setData({
lastSwitchTime: Date.now(),
});
},
/**
* 生命周期函数--监听页面隐藏
*/
onHide() {},
/**
* 生命周期函数--监听页面卸载
*/
onUnload() {},
/**
* 页面相关事件处理函数--监听用户下拉动作
*/
onPullDownRefresh() {},
/**
* 页面上拉触底事件的处理函数
*/
onReachBottom() {},
/**
* 用户点击右上角分享
*/
onShareAppMessage() {},
});
{
"usingComponents": {
"lang-btn": "../../components/lang-btn/index"
}
}
/* pages/aisearch/index.wxss */
.aisearch {
width: 100vw;
height: 100vh;
background: #0e111a;
.camera {
position: absolute;
z-index: 1;
width: 100vw;
top: 0;
left: 0;
bottom: 25vw;
object-fit: cover;
&.scancode {
bottom: 0;
}
}
.hud {
position: absolute;
z-index: 2;
width: 100vw;
height: 100vh;
top: 0;
left: 0;
.framework {
position: absolute;
left: 0;
bottom: 300px;
width: 100vw;
height: 100vw;
}
.btns {
position: absolute;
bottom: 150px;
height: 150px;
left: 0;
right: 0;
display: flex;
flex-direction: column;
gap: 16px;
align-items: center;
.shot {
position: relative;
display: flex;
flex-direction: column;
align-items: center;
gap: 3px;
.tip {
display: flex;
flex-direction: column;
align-items: center;
.content {
border-radius: 99px;
background: rgba(0, 0, 0, 0.7);
padding: 7px 16px;
color: var(--W-100, #fff);
/* 脚注-Footnote/R */
font-family: "PingFang SC";
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: 18px;
}
.tail {
width: 12px;
height: 6px;
}
}
.btn {
position: relative;
.loading {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
margin: auto;
animation: spin 0.5s linear infinite;
}
@keyframes spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
}
}
.tabs {
display: flex;
gap: 56px;
color: rgba(255, 255, 255, 0.8);
font-family: "PingFang SC";
font-size: 18px;
font-style: normal;
font-weight: 400;
line-height: 24px;
.tab {
display: flex;
flex-direction: column;
gap: 2px;
align-items: center;
&.active {
color: #fff;
font-weight: 600;
&::after {
display: block;
width: 20px;
height: 4px;
content: "";
border-radius: 99px;
background: #fff;
}
}
}
}
}
.pics {
position: absolute;
bottom: 0;
left: 0;
height: calc(25vw + 40px);
z-index: 100;
transition: height 0.3s ease-in-out;
display: flex;
flex-direction: column;
&.fullscreen {
height: calc(100vh - 10px);
}
.r1 {
display: flex;
align-items: center;
justify-content: space-between;
background: rgba(0, 0, 0, 0.8);
padding: 0 16px;
height: 40px;
width: 100vw;
.r11 {
display: flex;
align-items: center;
gap: 32px;
.tab {
color: #999;
font-family: "PingFang SC";
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: 30px;
&.active {
color: #fff;
font-weight: 600;
}
}
}
}
.r2 {
flex: 1;
overflow: hidden;
background: #0e111a;
.grid {
display: grid;
grid-template-columns: 1fr 1fr 1fr 1fr;
gap: 1px;
align-content: start;
}
.item {
width: calc((100vw - 3px) / 4);
height: calc((100vw - 3px) / 4);
&.add {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
background: #333;
gap: 3px;
.text {
padding: 0 12px;
color: rgba(255, 255, 255, 0.5);
text-align: center;
font-family: "PingFang SC";
font-size: 12px;
font-style: normal;
font-weight: 400;
line-height: 16px;
}
}
}
.first-row {
padding: 0 16px;
grid-column: 1 / 5;
display: flex;
height: 50px;
align-items: center;
justify-content: space-between;
color: #fff;
font-family: "PingFang SC";
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: 18px;
.delete {
display: flex;
gap: 4px;
color: #999;
text-align: center;
font-family: "PingFang SC";
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: 18px;
}
}
.date-row {
align-items: center;
justify-content: space-between;
padding: 0 16px;
grid-column: 1 / 5;
display: flex;
height: 42px;
color: #fff;
font-family: "PingFang SC";
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: 18px;
}
}
}
}
.size-64 {
width: 64px;
height: 64px;
}
.size-48 {
width: 48px;
height: 48px;
}
.size-24 {
width: 24px;
height: 24px;
}
.size-20 {
width: 20px;
height: 20px;
}
.size-18 {
width: 18px;
height: 18px;
}
}
<view class="aisearch">
<camera wx:if="{{isPhotoMode === true}}" class="camera" mode="normal" device-position="back" />
<camera wx:elif="{{isPhotoMode === false}}" class="camera scancode" mode="scanCode" device-position="back" bindscancode="handleScanCode" />
<view class="hud">
<lang-btn type="black"></lang-btn>
<image class="framework" src="./framework.png" mode="" />
<view class="btns">
<view class="shot">
<view class="tip">
<view class="content" wx:if="{{!isPhotoMode}}">
{{isEn?'Align the business location code and automatically identify it':'对准商位码,自动识别'}}
</view>
<view class="content" wx:elif="{{state===states.takingPhoto}}">
{{isEn?'Recognizing, please wait...':'识别中,请稍候...'}}
</view>
<view class="content" wx:else>
{{isEn?'Point at the product and take photos for identification':'对准商品,拍照识别'}}
</view>
<image wx:if="{{isPhotoMode}}" src="./tiptail.png" class="tail" mode="" />
<view wx:else style="height: 3px;"></view>
</view>
<view wx:if="{{isPhotoMode}}" class="size-64 btn" bind:tap="takePhoto">
<image class="size-64" src="./shot.png" mode="" />
<image wx:if="{{state===states.takingPhoto}}" src="./loading.png" class="size-48 loading" mode="" />
</view>
<view wx:else class="size-64"></view>
</view>
<view class="tabs">
<view class="tab {{isPhotoMode?'active':''}}" bind:tap="switchCameraMode" data-mode="true">{{isEn?'Al Search':'AI识物'}}</view>
<view class="tab {{!isPhotoMode?'active':''}}" bind:tap="switchCameraMode" data-mode="false">{{isEn?'Scan Code':'扫码'}}</view>
</view>
</view>
<view wx:if="{{isPhotoMode}}" class="pics {{isFullscreen?'fullscreen':''}}">
<view class="r1">
<view class="r11">
<view class="tab {{isAlbum?'active':''}}" bind:tap="handleAlbum">{{isEn?'Photos':'相册'}}</view>
<view class="tab {{!isAlbum?'active':''}}" bind:tap="handlePhoto">{{isEn?'I took pictures':'我拍过的'}}</view>
</view>
<image wx:if="{{isFullscreen}}" class="size-20" src="./arrowdown.png" mode="" bind:tap="toggleFullscreen" />
<image wx:else class="size-20" src="./arrowup.png" mode="" bind:tap="toggleFullscreen" />
</view>
<scroll-view class="r2" scroll-y="{{isFullscreen}}">
<view class="grid">
<view wx:if="{{isAlbum&&isFullscreen}}" class="first-row">
<view></view>
<view class="delete" bind:tap="handleClearAlbum">
<image class="size-18" src="./delete.png" mode="" />
{{isEn?'Clear':'清空'}}
</view>
</view>
<view class="item add" wx:if="{{isAlbum}}" bind:tap="handleAddFromAlbum">
<image class="size-24" src="./plus.png" mode="" />
<view class="text">{{isEn?'Add more accessible photos':'添加更多可访问照片'}}</view>
</view>
<block wx:for="{{isAlbum?albumList:photoList}}" wx:key="photoUrl">
<view wx:if="{{index===0&&!isAlbum&&isFullscreen}}" class="first-row">
{{item.dateFlag!=='今日'?item.dateFlag:isEn?'Today':'今天'}}
<view class="delete" bind:tap="handleClearPhoto">
<image class="size-18" src="./delete.png" mode="" />
{{isEn?'Clear':'清空'}}
</view>
</view>
<view wx:if="{{index!==0&&!isAlbum&&isFullscreen&&item.dateFlag}}" class="date-row">
{{item.dateFlag}}
</view>
<image class="item" src="{{item.photoUrl}}" mode="aspectFill" id="{{index}}" bind:tap="handleFromImage" />
</block>
</view>
</scroll-view>
</view>
</view>
</view>
\ No newline at end of file
import langBehavior from "../../behaviors/langBehavior";
import modalBehavior from "../../behaviors/modalBehavior";
import { toRoutePlan, getDistance } from "../../util";
Page({
behaviors: [langBehavior, modalBehavior],
data: {
photoUrl: "",
rectVoList: [],
voList: [],
currentIndex: 0,
itineraryMap: {},
userLocation: null,
},
/**
* 生命周期函数--监听页面加载
*/
async onLoad({ photoUrl }) {
this.setUserLocation = () => {
this.setData({ userLocation: getApp().userLocation });
};
getApp().events.on("userLocation", this.setUserLocation);
this.setUserLocation();
photoUrl = decodeURIComponent(photoUrl);
await this.init(photoUrl);
if (!this.data.rectVoList.length) {
const { isEn } = this.data;
wx.showToast({
title: isEn ? "fail" : "识别失败",
icon: "none",
});
wx.navigateBack();
}
this.setItineraryMap = () => {
const app = getApp();
this.setData({ itineraryMap: app.itineraryMap });
};
const app = getApp();
app.events.on("itineraryRefresh", this.setItineraryMap);
this.setItineraryMap();
},
onUnload() {
const app = getApp();
if (this.setUserLocation)
app.events.off("userLocation", this.setUserLocation);
if (this.setItineraryMap) {
app.events.off("itineraryRefresh", this.setItineraryMap);
}
},
async init(photoUrl) {
try {
wx.showLoading();
console.log(photoUrl);
const { photoApi } = getApp();
const { rectVoList = [], voList } = await photoApi.search(photoUrl);
this.setData({
photoUrl,
rectVoList: [{ cutImageURL: photoUrl }, ...rectVoList],
voList: this.processVoList(voList),
});
} catch (error) {
console.error(error);
} 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;
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
)
);
} catch (error) {
good.distance = null;
}
try {
const floorDiff = Math.abs(
Number(poi.tx.poi_fl_name[0]) -
Number(this.data.userLocation.floorName[0])
);
if (floorDiff && good.distance) good.distance += floorDiff;
} catch (error) {}
if (good.distance > 3000) good.distance = null;
});
return result;
},
async handleRectImg(e) {
const {
currentTarget: { id: index },
} = e;
this.switchTo(index);
},
async switchTo(index) {
wx.showLoading();
try {
if (Number(index) === this.data.currentIndex) return;
const rectVo = this.data.rectVoList[index];
const { photoApi } = getApp();
const res = await photoApi.search(this.data.photoUrl, rectVo.rect);
if (res.voList) {
this.setData({
voList: this.processVoList(res.voList),
currentIndex: Number(index),
});
}
} catch (error) {
console.error(error);
} finally {
wx.hideLoading();
}
},
handleNext() {
this.switchTo((this.data.currentIndex + 1) % this.data.rectVoList.length);
},
async handleItinerary(e) {
try {
const {
currentTarget: { id: index },
} = e;
const app = getApp();
const { itineraryApi } = app;
const { voList, itineraryMap, isEn } = this.data;
const good = voList[index];
if (!good) return;
if (itineraryMap[good.shopId]) {
const success = await itineraryApi.bulkDelete([
itineraryMap[good.shopId],
]);
if (success) {
app.refreshItinerary();
}
wx.showToast({
title: success ? (isEn ? "success" : "成功") : isEn ? "fail" : "失败",
icon: "none",
});
} else {
const id = await itineraryApi.post({
shopId: good.shopId,
goodsTitle: good.goodsTitle,
goodsPrice: good.price,
goodsCover: good.goodsCover[0],
});
if (id) {
app.refreshItinerary();
}
wx.showToast({
title: id ? (isEn ? "success" : "成功") : isEn ? "fail" : "失败",
icon: "none",
});
}
} catch (error) {
console.error(error);
}
},
async handleGo({ currentTarget: { id: index } }) {
const { isEn, voList } = this.data;
const { shop: poi } = voList[index];
try {
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),
}
: {}),
...(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,
}),
});
} catch (error) {
console.error("埋点失败", error);
}
try {
const res = await toRoutePlan(poi, isEn);
if (res === "showBluetoothModal")
return this.setData({ showBluetoothModal: true });
if (res === "showLocationModal") return this.openLocationModal();
if (res === "showAuthModal") return this.openAuthModal();
} catch (error) {
console.error(error);
}
},
toLocalUrl(url) {
return new Promise((resolve) => {
wx.downloadFile({
url,
success({ tempFilePath }) {
resolve(tempFilePath);
},
fail(e) {
console.error(e);
resolve(null);
},
});
});
},
toEditLocalUrl(src) {
return new Promise((resolve) => {
wx.editImage({
src,
success({ tempFilePath }) {
resolve(tempFilePath);
},
fail(e) {
console.log(e);
resolve(null);
},
});
});
},
async bindCutout() {
const localUrl = await this.toLocalUrl(this.data.photoUrl);
if (!localUrl) return;
const editLocalUrl = await this.toEditLocalUrl(localUrl);
if (!editLocalUrl) return;
const { photoApi } = getApp();
const url = await photoApi.upload(editLocalUrl);
await this.init(url);
},
/**
* 生命周期函数--监听页面初次渲染完成
*/
onReady() {},
/**
* 生命周期函数--监听页面显示
*/
onShow() {},
/**
* 生命周期函数--监听页面隐藏
*/
onHide() {},
/**
* 生命周期函数--监听页面卸载
*/
/**
* 页面相关事件处理函数--监听用户下拉动作
*/
onPullDownRefresh() {},
/**
* 页面上拉触底事件的处理函数
*/
onReachBottom() {},
/**
* 用户点击右上角分享
*/
onShareAppMessage() {},
});
{
"usingComponents": {
"shop-name": "../../components/shop-name/index",
"shop-addr": "../../components/shop-addr/index",
"end": "../../components/end/index",
"bluetooth-modal": "../../components/bluetooth-modal/index",
"location-modal": "../../components/location-modal/index",
"auth-modal": "../../components/auth-modal/index"
}
}
/* pages/aisearchresult/index.wxss */
.aisearchresult {
position: relative;
width: 100vw;
height: 100vh;
background: rgba(0, 0, 0, 0.9);
overflow: hidden;
.scroll-x {
margin-top: 10px;
width: calc(100vw - 92px);
.list {
display: inline-flex;
display: flex;
gap: 5px;
padding-left: 10px;
.rect {
position: relative;
width: 48px;
height: 48px;
border-radius: 4px;
overflow: hidden;
&.active {
border: 1px solid #fa643c;
}
.meta {
position: absolute;
display: flex;
align-items: center;
justify-content: center;
top: 0;
right: 0;
width: 16px;
height: 12px;
background: #ed383e;
border-radius: 0px 0px 0px 4px;
color: #fff;
font-family: "PingFang SC";
font-size: 10px;
font-style: normal;
font-weight: 600;
line-height: 12px;
}
.img {
width: 48px;
height: 48px;
object-fit: cover;
}
}
}
}
.cutout {
position: absolute;
display: flex;
align-items: center;
height: 30px;
padding: 0 8px;
top: 22px;
right: 10px;
border-radius: 99px;
background: #fff;
color: #333;
text-align: center;
font-family: "PingFang SC";
font-size: 13px;
font-style: normal;
font-weight: 400;
line-height: normal;
.icon {
width: 16px;
height: 16px;
}
.tri {
width: 4.9px;
height: 3px;
margin-left: 6px;
}
}
.bottom {
position: absolute;
top: 74px;
width: 100vw;
left: 0;
bottom: 0;
border-radius: 16px 16px 0;
background: #f7f7f7;
padding: 8px 10px 0 10px;
display: flex;
flex-direction: column;
gap: 10px;
.title {
display: flex;
align-items: baseline;
gap: 4px;
color: #666;
font-family: "PingFang SC";
font-size: 12px;
font-style: normal;
font-weight: 400;
line-height: 16px;
.red {
color: #e92927;
font-family: "PingFang SC";
font-size: 20px;
font-style: normal;
font-weight: 600;
line-height: 25px;
}
}
.scroll-y {
width: 100%;
flex: 1;
height: calc(100vh - 117px);
.goods {
display: inline-flex;
flex-direction: column;
gap: 8px;
.good {
width: calc(100vw - 20px);
padding: 12px;
height: 120px;
display: flex;
gap: 10px;
border-radius: 8px;
background: #fff;
.avatar {
width: 96px;
height: 96px;
border-radius: 6px;
}
.right {
flex: 1;
position: relative;
display: flex;
flex-direction: column;
height: 100%;
justify-content: space-between;
overflow: hidden;
.right-top {
display: flex;
flex-direction: column;
gap: 2px;
.t1 {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
color: rgba(0, 0, 0, 0.9);
font-family: "PingFang SC";
font-size: 15px;
font-style: normal;
font-weight: 600;
line-height: 22px;
}
.t2 {
display: flex;
gap: 1px;
align-items: baseline;
color: #ed383e;
.left {
font-size: 12px;
font-style: normal;
font-weight: 400;
line-height: 16px;
}
.mid {
font-size: 16px;
font-style: normal;
font-weight: 600;
line-height: 22px;
}
.right {
font-size: 12px;
font-style: normal;
font-weight: 600;
line-height: 16px;
}
}
}
.right-bottom {
display: flex;
flex-direction: column;
gap: 4px;
.t3 {
color: #333;
font-family: "PingFang SC";
font-size: 13px;
font-style: normal;
font-weight: 400;
line-height: 16px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.t4 {
color: #666;
font-family: "PingFang SC";
font-size: 10px;
font-style: normal;
font-weight: 400;
line-height: 12px;
}
}
.right-corner {
position: absolute;
bottom: 0;
right: 0;
display: flex;
flex-direction: column;
gap: 24px;
.row {
display: flex;
gap: 6px;
align-items: center;
.t5 {
color: #333;
font-family: "PingFang SC";
font-size: 10px;
font-style: normal;
font-weight: 600;
line-height: 12px;
}
.t51 {
display: flex;
flex-direction: column;
.t511 {
color: #333;
font-family: "PingFang SC";
font-size: 12px;
font-style: normal;
font-weight: 600;
line-height: 16px;
}
.t512 {
color: #666;
font-family: "PingFang SC";
font-size: 10px;
font-style: normal;
font-weight: 400;
line-height: 12px;
}
}
}
}
}
}
}
.empty {
display: flex;
flex-direction: column;
width: 100%;
padding: 117px 16px 0 16px;
gap: 46px;
align-items: center;
.meta {
display: flex;
flex-direction: column;
gap: 12px;
color: #999;
font-family: "PingFang SC";
font-size: 13px;
font-style: normal;
font-weight: 400;
.img {
width: 110.5px;
height: 53px;
}
}
.btns {
width: 100%;
display: flex;
gap: 8px;
.btn1 {
display: flex;
align-items: center;
justify-content: center;
flex: 1;
color: #666;
text-align: center;
font-family: "PingFang SC";
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: 18px;
border-radius: 99px;
border: 1px solid #e5e5e5;
height: 38px;
}
.btn2 {
display: flex;
align-items: center;
justify-content: center;
flex: 1;
border-radius: 99px;
background: linear-gradient(90deg, #fa643c 0%, #e92927 100%);
color: #fff;
text-align: center;
font-family: "PingFang SC";
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: 18px;
height: 38px;
}
}
}
}
}
.size-20 {
width: 20px;
height: 20px;
}
}
<view class="aisearchresult">
<scroll-view scroll-x class="scroll-x">
<view class="list">
<view wx:for="{{rectVoList}}" wx:key="cutImageURL" class="rect {{currentIndex === index?'active':''}}" id="{{index}}" bind:tap="handleRectImg">
<view class="meta" wx:if="{{currentIndex === index}}">{{voList.length}}</view>
<image class="img" src="{{item.cutImageURL}}" mode="aspectFill" />
</view>
</view>
</scroll-view>
<view class="cutout" bind:tap="bindCutout">
<image class="icon" src="./cut.png" mode="" />
{{isEn?'Cutout':'自选'}}
<image class="tri" src="./tri.png" mode="" />
</view>
<view class="bottom">
<view class="title" wx:if="{{isEn}}">
Find similar stores for you
<text class="red">{{voList.length}}</text>
</view>
<view class="title" wx:else>
为您找到相似
<text class="red">{{voList.length}}</text>
个商品
</view>
<scroll-view scroll-y class="scroll-y">
<view wx:if="{{voList.length}}" class="goods">
<view class="good" wx:for="{{voList}}" wx:key="goodsTitle">
<image class="avatar" wx:if="{{item.goodsCover&&item.goodsCover[0]}}" src="{{item.goodsCover[0]}}" mode="aspectFill" />
<view class="right">
<view class="right-top">
<view class="t1">{{item.goodsTitle}}</view>
<view class="t2">
<view class="left">¥</view>
<view class="mid">{{item.yuan}}</view>
<view class="right">{{item.fen}}</view>
</view>
</view>
<view class="right-bottom">
<view class="t3">
<shop-name shop="{{item.shop}}"></shop-name>
</view>
<view class="t4">
<shop-addr shop="{{item.shop}}"></shop-addr>
</view>
</view>
<view class="right-corner">
<view class="row" 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>
<view wx:else class="t5">{{isEn?'Add itinerary':'加入行程'}}</view>
</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>
</view>
</view>
</view>
<end></end>
</view>
<view wx:else class="empty">
<view class="meta">
<image class="img" src="./empty.png" mode="" />
<view>{{isEn?'No relevant results found':'未找到此商品相关商铺'}}</view>
</view>
<view class="btns">
<view class="btn1" bind:tap="bindCutout">{{isEn?'Cutout':'自选截图区域'}}</view>
<view class="btn2" wx:if="{{rectVoList.length>1}}" bind:tap="handleNext">
{{isEn?'Next':'切换下一张'}}
</view>
</view>
</view>
</scroll-view>
</view>
<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>
</view>
\ No newline at end of file
import langBehavior from "../../../behaviors/langBehavior";
import poiFocusBehavior from "../../../behaviors/poiFocusBehavior";
Component({
behaviors: [langBehavior, poiFocusBehavior],
properties: {},
/**
* 组件的初始数据
*/
data: {
viewState: "default",
height: 405,
touchStart: null,
doTransition: false,
moveTouches: null,
endTouches: null,
windowHeight: wx.getWindowInfo().windowHeight,
scrollTop: 0,
nextTop: 0,
scrollEndCount: 0,
primeSpots: [],
navHistory: [],
},
observers: {
height(v) {
this.triggerEvent("height", v);
},
moveTouches(touches) {
if (!this.data.touchStart) return;
let touch = Array.from(touches).find(
({ identifier }) => identifier === this.data.touchStart.identifier
);
if (!touch) {
if (touches.length > 0) {
const fallbackTouch = touches[0];
touch = {
clientY: fallbackTouch.clientY,
identifier: fallbackTouch.identifier,
};
} else {
return;
}
}
const delta = touch.clientY - this.data.touchStart.y;
if (this.data.viewState === "fullscreen") {
if (delta < 0) return;
if (this.data.scrollTop > 0) return;
}
this.setData({
height: Math.max(
Math.min(this.baseHeight() - delta, this.data.windowHeight - 24),
146
),
});
},
endTouches(touches) {
if (!this.data.touchStart) return;
let touch = Array.from(touches).find(
({ identifier }) => identifier === this.data.touchStart.identifier
);
if (!touch) {
if (touches.length > 0) {
const fallbackTouch = touches[0];
touch = {
clientY: fallbackTouch.clientY,
identifier: fallbackTouch.identifier,
};
} else {
return;
}
}
if (this.data.viewState === "fullscreen" && this.data.scrollTop > 0)
return;
const delta = touch.clientY - this.data.touchStart.y;
this.data.touchStart = null;
let nextViewState = this.data.viewState;
// 根据拖拽距离决定下一个状态
if (this.data.viewState === "default") {
if (delta < -100) {
nextViewState = "fullscreen";
} else if (delta > 100) {
nextViewState = "collapsed";
}
} else if (this.data.viewState === "fullscreen") {
if (delta > 100) {
nextViewState = "default";
}
} else if (this.data.viewState === "collapsed") {
if (delta < -100) {
nextViewState = "default";
}
}
this.setData({
viewState: nextViewState,
doTransition: true,
height: this.baseHeight(),
});
setTimeout(() => {
this.setData({ doTransition: false });
}, 500);
},
viewState(nextState) {
this.setData({
doTransition: true,
height: this.baseHeight(),
});
this.triggerEvent("viewstate", nextState);
setTimeout(() => {
this.setData({ doTransition: false });
}, 500);
},
focusedPoi(v) {
if (v) {
this.setData({ viewState: "collapsed" });
} else {
this.setData({ viewState: "default" });
}
},
},
methods: {
baseHeight() {
switch (this.data.viewState) {
case "fullscreen":
return this.data.windowHeight - 24;
case "collapsed":
return 136;
default:
return 405;
}
},
handleTouchStart(e) {
this.getScrollTop();
if (this.data.touchStart) return;
this.setData({
touchStart: {
identifier: e.changedTouches[0].identifier,
y: e.changedTouches[0].clientY,
},
});
},
handleTouchMove(e) {
this.getScrollTop();
this.setData({ moveTouches: e.changedTouches });
},
handleTouchEnd(e) {
this.getScrollTop();
this.setData({ endTouches: e.changedTouches });
},
handleSearch() {
this.triggerEvent("search");
},
handleAi() {
this.triggerEvent("ai");
},
onScrollToLower() {
this.setData({ scrollEndCount: this.data.scrollEndCount + 1 });
},
getScrollTop() {
if (this.data.viewState !== "fullscreen")
return this.setData({ nextTop: 0, scrollTop: 0 });
try {
this.scrollerQuery?.exec(([{ scrollTop }]) => {
this.setData({ scrollTop });
if (scrollTop < 0) {
this.setData({ nextTop: 0, scrollTop: 0 });
}
});
} catch (error) {
console.error(error);
}
},
async getPrimeSpots() {
const { get, qmurl } = getApp();
try {
const {
data: { data },
} = await get(`${qmurl}/api/v1/applet/getDiamondList`);
if (data && data.length !== undefined) {
this.setData({ primeSpots: data });
}
} catch (error) {
console.log(error);
}
},
async getNavHistory() {
const { post, qmurl, shopIdMap, facilityIdMap, globalData } = getApp();
if (!globalData.user) return;
if (!shopIdMap) return;
try {
const {
data: { data },
} = await post(
`${qmurl}/api/v1/applet/getUserFootPrintList?userId=${globalData.user.user.userId}`,
{
userId: globalData.user.user.userId,
}
);
if (data && data.length !== undefined) {
this.setData({
navHistory: data
.map(({ shopType, shopId }) =>
shopType === 1 ? shopIdMap[shopId] : null
)
.filter((el) => el)
.slice(0, 10),
});
}
} catch (error) {
console.log(error);
}
},
},
lifetimes: {
attached() {
this.scrollerQuery = this.createSelectorQuery()
.select("#scroller")
.scrollOffset();
this.getPrimeSpots();
const app = getApp();
if (!app.shopIdMap) {
app.events.once("globalData", () => {
this.getNavHistory();
});
} else {
this.getNavHistory();
}
},
},
});
{
"component": true,
"usingComponents": {
"search-bar": "./search-bar/index",
"shops": "./shops/index",
"prime-spot": "./prime-spot/index",
"nav-history": "./nav-history/index"
}
}
.bottom-bg {
position: absolute;
bottom: 0;
z-index: 200;
display: flex;
flex-direction: column;
height: 379px;
width: 100vw;
background: linear-gradient(
180deg,
rgba(243, 243, 243, 0) 0%,
#f8f8f8 2.1%,
#f3f3f3 100%
);
}
.bottom-hud {
position: absolute;
bottom: 0;
z-index: 200;
height: 405px;
width: 100vw;
.handle {
position: absolute;
top: -10px;
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;
}
}
.scroll-view {
position: absolute;
z-index: 1;
left: 10px;
right: 10px;
top: 10px;
border-radius: 8px;
height: calc(100vh - 24px - 90px);
width: calc(100vw - 20px);
}
}
<view>
<textarea disabled class="bottom-bg" style="z-index: 100;height: {{height+14}}px;transition: {{!doTransition ? 'none' : 'height 0.5s ease'}}"></textarea>
<view class="bottom-hud" style="height: {{height}}px;transition: {{!doTransition ? 'none' : 'height 0.5s ease'}}" bind:touchstart="handleTouchStart" bind:touchmove="handleTouchMove" bind:touchend="handleTouchEnd" bind:touchcancel="handleTouchEnd">
<slot></slot>
<view class="handle"></view>
<search-bar bind:search="handleSearch" bind:ai="handleAi"></search-bar>
<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>
<shops scroll-end-count="{{scrollEndCount}}"></shops>
</scroll-view>
</view>
</view>
\ No newline at end of file
import langBehavior from "../../../../behaviors/langBehavior";
import { confirm, getPoiName } from "../../../../util";
const getSlicedPoiName = (poi, isEn) => {
const text = getPoiName(poi, isEn);
return text.substring(0, 5);
};
Component({
behaviors: [langBehavior],
properties: { list: Array },
/**
* 组件的初始数据
*/
data: {
collapsed: true,
name1: null,
name2: null,
},
observers: {
"list,isEn"() {
const poi1 = this.data.list[0];
const poi2 = this.data.list[1];
this.setData({
name1: poi1 ? getSlicedPoiName(poi1, this.data.isEn) : null,
name2: poi2 ? getSlicedPoiName(poi2, this.data.isEn) : null,
});
},
},
/**
* 组件的方法列表
*/
methods: {
getSlicedPoiName(poi, isEn) {
const text = getPoiName(poi, isEn);
return text.substring(0, 5);
},
toggleCollapsed() {
this.setData({ collapsed: !this.data.collapsed });
},
handleShop({ currentTarget: { id: index } }) {
const poi = this.data.list[index];
getApp().focusPoi(poi);
},
async clearNavHistory() {
const { isEn } = this.data;
const confirmed = await confirm({
title: "",
content: isEn ? `Whether to clear the history?` : `是否清空历史记录?`,
confirmText: isEn ? `Clear` : `确认`,
cancelText: isEn ? `Cancel` : `取消`,
});
if (!confirmed) return;
const { get, qmurl, globalData } = getApp();
try {
const {
data: { code },
} = await get(
`${qmurl}/api/v1/applet/clearUserFootPrint?userId=${globalData.user.user.userId}`
);
if (code === 200) {
wx.showToast({
title: this.data.isEn ? "success" : "清空成功",
icon: "none",
});
this.triggerEvent("navhistorycleared");
}
} catch (error) {
console.error(error);
}
},
},
});
{
"component": true,
"usingComponents": {
"shop-name": "../../../../components/shop-name/index",
"end": "../../../../components/end/index"
}
}
.nav-history {
position: relative;
display: flex;
flex-direction: column;
align-items: center;
width: calc(100vw - 20px);
border-radius: 8px;
background: #fff;
padding: 12px;
margin-bottom: 8px;
min-height: 40px;
box-sizing: border-box;
.title {
position: absolute;
top: 0;
left: 12px;
right: 40px;
height: 40px;
overflow: hidden;
display: flex;
align-items: center;
.t1 {
color: #333;
font-family: "PingFang SC";
font-size: 12px;
font-style: normal;
font-weight: 400;
line-height: normal;
flex-shrink: 0;
}
.stick {
width: 1px;
height: 12px;
background: #e5e5e5;
margin-left: 8px;
margin-right: 11px;
flex-shrink: 0;
}
.shop {
display: inline-flex;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
gap: 6px;
align-items: center;
flex-shrink: 0;
.t2 {
color: #333;
font-family: "PingFang SC";
font-size: 12px;
font-style: normal;
font-weight: 600;
line-height: 16px;
}
}
.shop + .shop {
margin-left: 24px;
}
}
.shops {
display: flex;
width: calc(100vw - 44px);
margin-top: 28px;
padding: 10px 0;
border-top: 1px solid rgba(0, 0, 0, 0.06);
border-bottom: 1px solid rgba(0, 0, 0, 0.06);
gap: 8px;
flex-wrap: wrap;
.shop {
display: inline-flex;
height: 32px;
max-width: calc(100vw - 44px);
padding: 0 14px;
border-radius: 99px;
background: #f3f3f3;
color: #333;
font-family: "PingFang SC";
font-size: 12px;
font-style: normal;
font-weight: 400;
line-height: 16px;
gap: 6px;
align-items: center;
.txt {
flex: 1;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
}
}
.clear {
margin-top: 11px;
color: #666;
font-family: "PingFang SC";
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: 20px;
}
.toggle {
position: absolute;
top: 11px;
right: 11px;
width: 18px;
height: 18px;
}
.size-16 {
width: 16px;
height: 16px;
flex-shrink: 0;
}
}
<view class="nav-history">
<view class="title">
<view class="t1">{{isEn?'Recently Visited':'最近去过'}}</view>
<view class="stick"></view>
<view class="shop" id="0" bind:tap="handleShop">
<image src="./shop.png" class="size-16" mode="" />
<view class="t2">
{{name1}}
</view>
</view>
<view class="shop" wx:if="{{list[1]}}" id="1" bind:tap="handleShop">
<image src="./shop.png" class="size-16" mode="" />
<view class="t2">
{{name2}}
</view>
</view>
</view>
<image bind:tap="toggleCollapsed" wx:if="{{collapsed}}" class="toggle" src="./arrowdown.png" mode="" />
<image bind:tap="toggleCollapsed" wx:else class="toggle" src="./arrowup.png" mode="" />
<view wx:if="{{!collapsed&&list.length>=3}}" class="shops">
<block wx:for="{{list}}" wx:key="index">
<view class="shop" id="{{index}}" wx:if="{{index>1}}" bind:tap="handleShop">
<image src="./shop.png" class="size-16" mode="" />
<view class="txt">
<shop-name shop="{{item}}"></shop-name>
</view>
</view>
</block>
</view>
<view wx:if="{{!collapsed&&list.length<3}}" style="width: 100%;height: 30px;border-bottom: 1px solid rgba(0, 0, 0, 0.06);"></view>
<view wx:if="{{!collapsed}}" class="clear" bind:tap="clearNavHistory">
{{isEn?'Clear':'清空记录'}}
</view>
</view>
\ No newline at end of file
import langBehavior from "../../../../behaviors/langBehavior";
Component({
behaviors: [langBehavior],
properties: {
list: Array,
},
/**
* 组件的初始数据
*/
data: {},
/**
* 组件的方法列表
*/
methods: {
handleTap({ currentTarget: { id: index } }) {
const item = this.data.list[index];
if (!item) return;
getApp().sensors?.track("MLVajraAreaClick", {
menu_name: item.name,
});
if (!item.needLogin)
return wx.navigateTo({
url: `/pages/wap/index?url=${encodeURIComponent(item.directUrl)}`,
});
if (!getApp().checkUser()) return;
const token = getApp().globalData.user.access_token;
wx.navigateTo({
url: `/pages/wap/index?url=${encodeURIComponent(
item.directUrl.includes("?")
? item.directUrl + `&token=${token}`
: item.directUrl + `?token=${token}`
)}`,
});
},
},
});
{
"component": true,
"usingComponents": {}
}
\ No newline at end of file
.prime-spot {
display: flex;
width: calc(100vw - 20px);
height: 88px;
padding: 10px 18px 10px 18px;
border-radius: 8px;
background: var(--W-100, #fff);
align-items: center;
justify-content: space-between;
margin-bottom: 8px;
.entry {
display: flex;
flex-direction: column;
align-items: center;
gap: 6px;
.size-48 {
width: 48px;
height: 48px;
}
color: #333;
text-align: center;
font-family: "PingFang SC";
font-size: 11px;
font-style: normal;
font-weight: 400;
line-height: normal;
}
}
<view class="prime-spot">
<view class="entry" wx:for="{{list}}" wx:key="name" id="{{index}}" bind:tap="handleTap">
<image class="size-48" src="{{item.logo}}" mode="" />
{{isEn?item.nameEn:item.name}}
</view>
</view>
\ No newline at end of file
import langBehavior from "../../../../behaviors/langBehavior";
Component({
behaviors: [langBehavior],
properties: {},
/**
* 组件的初始数据
*/
data: {
placeholderIndex: 0,
placeholders: [
{
en: "District 2-East New Energy Product Market Welcome!",
cn: "欢迎光临二区东新能源产品市场",
},
{
en: "Search store number/facilities/block/exit number",
cn: "搜铺号/公共设施/街区号/出入口号",
},
{
en: "Click or search shoplD for click navigation",
cn: "点击或搜索任意商铺可一键导航前往",
},
],
},
/**
* 组件的方法列表
*/
methods: {
searchTap() {
this.triggerEvent("search");
},
aiTap() {
this.triggerEvent("ai");
},
},
lifetimes: {
attached() {
this.interval = setInterval(() => {
this.setData({
placeholderIndex: (this.data.placeholderIndex + 1) % 3,
});
}, 3000);
},
detached() {
if (this.interval) {
clearInterval(this.interval);
this.interval = null;
}
},
},
});
{
"component": true,
"usingComponents": {}
}
\ No newline at end of file
.search-bar {
position: absolute;
top: 0;
left: 10px;
right: 10px;
height: 48px;
border-radius: 8px;
background: var(--W-100, #fff);
box-shadow: 0px 8px 10px 0px rgba(121, 70, 62, 0.1);
display: flex;
gap: 8px;
padding: 0 12px;
align-items: center;
z-index: 2;
.search {
width: 24px;
height: 24px;
}
.text {
flex: 1;
overflow: hidden;
color: #666;
text-overflow: ellipsis;
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: 18px;
white-space: nowrap;
overflow: hidden;
}
.ai {
position: relative;
width: 40px;
height: 40px;
.icon {
width: 40px;
height: 40px;
}
.text {
position: absolute;
bottom: 0;
left: -7px;
right: -7px;
margin: auto;
font-size: 9px;
font-style: normal;
font-weight: 600;
line-height: 12px;
height: 12px;
text-align: center;
color: #000;
border-radius: 99px;
border: 0.2px solid var(--W-100, #fff);
background: rgba(255, 255, 255, 0.08);
backdrop-filter: blur(33.05883026123047px);
}
}
}
<view class="search-bar" catch:tap="searchTap">
<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="" />
<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";
Component({
behaviors: [langBehavior],
/**
* 组件的属性列表
*/
properties: {
scrollEndCount: Number,
},
/**
* 组件的初始数据
*/
data: {
formats: [{ key: "all", name: "全部", nameEn: "All" }, ...getApp().formats],
key: "all",
page: 1,
page_size: 10,
list: [],
isLoading: false,
},
observers: {
key() {
this.setData({
page: 1,
list: [],
});
this.getList();
},
isEn() {
this.setData({
page: 1,
list: [],
});
this.getList();
},
scrollEndCount() {
if (!this.data.isLoading && this.data.page < this.data.total_page) {
this.getList(this.data.page + 1);
}
},
},
/**
* 组件的方法列表
*/
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 {
data: {
data: { total_page, page, first_result, last_result, data },
},
} = await post(`${marketurl}/osc/v1/shops/open/toBaiDu/search`, {
page: nxtPage,
page_size,
industryKeyList:
key === "all" ? formats.map((format) => format.key) : [key],
lang: isEn ? "en" : "zh",
keySecret: md5(md5(salt + nxtPage)),
});
const nxtList = data
.map((shop) => {
if (!shopIdMap[shop.shopId])
console.warn("列表接口里有,但是全量没有的店铺", shop);
return shopIdMap[shop.shopId];
})
.filter((el) => el);
nxtList.forEach((poi) => {
try {
getApp().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,
});
} catch (error) {
console.error("埋点失败", error);
}
});
this.setData({
list: [...this.data.list, ...nxtList],
total_page,
page,
});
} catch (error) {
console.error(error);
} finally {
this.setData({
isLoading: false,
});
}
},
handleFormat({ currentTarget: { id: key } }) {
try {
this.setData({ key });
} catch (error) {
console.error(error);
}
},
handleShop({ currentTarget: { id: shopId } }) {
try {
const poi = getApp().shopIdMap[shopId];
getApp().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}`,
});
},
},
lifetimes: {
attached() {
const app = getApp();
if (app.shopIdMap) {
this.getList();
} else {
app.events.once("globalData", () => {
this.getList();
});
}
},
},
});
{
"component": true,
"usingComponents": {
"loading": "../../../../components/loading/index",
"end": "../../../../components/end/index",
"shop-name": "../../../../components/shop-name/index",
"shop-addr": "../../../../components/shop-addr/index",
"shop-maingoods": "../../../../components/shop-maingoods/index"
}
}
/* pages/index/bottom-hud/shops/index.wxss */
.shops {
position: relative;
width: 100%;
min-height: 100vh;
display: flex;
flex-direction: column;
border-radius: 8px 8px 0 0;
border: 1px solid #fff;
background: linear-gradient(180deg, #fff 0%, #f5f5f5 41.8%);
overflow: hidden;
.bg-top {
position: absolute;
width: 100vw;
left: -10px;
height: 43px;
top: -36px;
background: rgba(255, 188, 157, 0.4);
filter: blur(20px);
}
.title {
margin-top: 12px;
margin-left: 12px;
display: inline-flex;
gap: 4px;
align-items: center;
.medal {
width: 24px;
height: 24px;
}
.text {
color: var(--B-80, rgba(0, 0, 0, 0.8));
font-feature-settings: "case" on;
font-family: "PingFang SC";
font-size: 16px;
font-style: normal;
font-weight: 600;
line-height: 24px;
}
}
.formats {
margin-top: 8px;
margin-bottom: 8px;
color: #333;
font-family: "PingFang SC";
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: 18px;
width: calc(100vw - 20px);
overflow-y: hidden;
overflow-x: auto;
height: 22px;
.list {
display: inline-flex;
gap: 16px;
margin: 0 12px;
.item {
position: relative;
height: 22px;
white-space: nowrap;
&.active {
color: #e92927;
font-weight: 600;
&::after {
content: "";
position: absolute;
left: 0;
right: 0;
bottom: 0;
margin: auto;
width: 16px;
height: 2px;
border-radius: 17px;
background: #ed383e;
}
}
}
}
}
.grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 8px;
.shop {
display: flex;
height: calc(50vw - 14px + 78px);
flex-direction: column;
border-radius: 8px;
background: #fff;
overflow: hidden;
width: calc(50vw - 14px);
.avatar {
width: calc(50vw - 14px);
height: calc(50vw - 14px);
object-fit: cover;
}
.texts {
flex: 1;
display: flex;
flex-direction: column;
gap: 4px;
justify-content: center;
padding-right: 10px;
overflow: hidden;
.t1 {
padding-left: 10px;
color: #333;
font-family: "PingFang SC";
font-size: 14px;
font-style: normal;
font-weight: 600;
line-height: 18px;
height: 18px;
overflow: hidden;
-webkit-line-clamp: 1;
word-break: break-all;
}
.t2 {
padding-left: 10px;
color: #666;
font-family: "PingFang SC";
font-size: 12px;
font-style: normal;
font-weight: 400;
line-height: 16px;
height: 16px;
overflow: hidden;
white-space: nowrap;
}
.t3 {
padding-left: 10px;
color: #666;
font-family: "PingFang SC";
font-size: 12px;
font-style: normal;
font-weight: 400;
line-height: 16px;
height: 16px;
overflow: hidden;
-webkit-line-clamp: 1;
word-break: break-all;
}
}
}
}
.end-wrapper {
padding-top: 8px;
padding-bottom: 44px;
}
}
<view class="shops">
<view class="bg-top"></view>
<view class="title">
<image class="medal" src="./medal.png" mode="" />
<view class="text">{{isEn?"Powerful Merchants":"实力商家"}}</view>
</view>
<scroll-view scroll-x enable-flex enhanced show-scrollbar="{{false}}" class="formats">
<view class="list">
<view class="item {{key===item.key?'active':''}}" wx:for="{{formats}}" id="{{item.key}}" wx:key="key" bind:tap="handleFormat">
{{isEn?item.nameEn:item.name}}
</view>
</view>
</scroll-view>
<view class="grid">
<view class="shop" wx:for="{{list}}" wx:key="shopId" id="{{item.shopId}}" bind:tap="handleShop">
<image class="avatar" wx:if="{{item.ywZh}}" src="{{item.ywZh.cover}}" mode="aspectFill" />
<image class="avatar" wx:elif="{{item.ywEn}}" src="{{item.ywEn.cover}}" mode="aspectFill" />
<view class="texts">
<view class="t1">
<shop-name shop="{{item}}"></shop-name>
</view>
<view class="t2">
<shop-addr shop="{{item}}"></shop-addr>
</view>
<view class="t3">
<shop-maingoods shop="{{item}}"></shop-maingoods>
</view>
</view>
</view>
</view>
<loading wx:if="{{isLoading}}"></loading>
<view wx:else class="end-wrapper">
<end></end>
</view>
</view>
\ No newline at end of file
import langBehavior from "../../../behaviors/langBehavior";
import poiFocusBehavior from "../../../behaviors/poiFocusBehavior";
Component({
behaviors: [langBehavior, poiFocusBehavior],
properties: {
facTypes: Array,
activeId: Number,
badgeNum: Number,
},
/**
* 组件的初始数据
*/
data: {
viewState: "default",
height: 188,
touchStart: null,
doTransition: false,
moveTouches: null,
endTouches: null,
windowHeight: wx.getWindowInfo().windowHeight,
scrollTop: 0,
nextTop: 0,
scrollEndCount: 0,
},
observers: {
focusedPoi(v) {
if (v) {
this.setData({ viewState: "collapsed" });
} else {
this.setData({ viewState: "default" });
}
},
},
methods: {
async getFacTypes() {
const { get, qmurl } = getApp();
try {
const {
data: { data },
} = await get(`${qmurl}/api/v1/applet/getFacilityClassList`);
if (data && data.length !== undefined) {
this.setData({ facTypes: data });
}
} catch (error) {
console.log(error);
}
},
focusFacType({ currentTarget: { id } }) {
this.triggerEvent("factypetap", Number(id));
},
},
lifetimes: {
attached() {},
},
});
{
"component": true,
"usingComponents": {}
}
@keyframes slideInTop {
from {
bottom: -90px;
opacity: 0;
}
to {
bottom: 0;
opacity: 1;
}
}
.bottom-bg {
position: absolute;
bottom: 0;
z-index: 200;
display: flex;
flex-direction: column;
height: 178px;
width: 100vw;
animation: slideInTop 0.5s ease-out;
}
.bottom-hud {
position: absolute;
bottom: 0;
z-index: 200;
height: 178px;
width: 100vw;
background: #fff;
border-radius: 16px 16px 0 0;
animation: slideInTop 0.5s ease-out;
box-shadow: 0 -2px 8px 0 rgba(0, 0, 0, 0.05);
.scroll-view {
position: absolute;
z-index: 1;
left: 0;
right: 0;
top: 12px;
height: 74px;
width: 100vw;
.facs {
padding: 0 16px;
display: inline-flex;
gap: 0;
.fac {
position: relative;
display: flex;
width: 64px;
height: 74px;
padding: 8px 4px 5px 4px;
flex-direction: column;
justify-content: center;
align-items: center;
gap: 4px;
.icon {
width: 40px;
height: 40px;
flex-shrink: 0;
}
.badge {
position: absolute;
padding: 0 4px;
height: 16px;
top: 6px;
left: 42px;
color: #fff;
display: flex;
align-items: center;
justify-content: center;
font-family: "PingFang SC";
font-size: 12px;
font-style: normal;
font-weight: 600;
border-radius: 99px;
border: 1px solid #f8f8f8;
background: linear-gradient(90deg, #fa643c 0%, #e92927 100%);
}
.text {
color: #333;
font-family: "PingFang SC";
font-size: 12px;
font-style: normal;
font-weight: 400;
line-height: normal;
overflow: hidden;
-webkit-line-clamp: 1;
word-break: break-all;
}
&.active {
border-radius: 8px;
background: rgba(0, 0, 0, 0.03);
}
}
}
}
}
<view wx:if="{{facTypes&&facTypes.length}}">
<textarea disabled class="bottom-bg"></textarea>
<view class="bottom-hud">
<slot></slot>
<scroll-view id="scroller" scroll-x enhanced show-scrollbar="{{false}}" bounces="{{false}}" class="scroll-view">
<view class="facs">
<view class="fac {{activeId===item.id?'active':''}}" wx:for="{{facTypes}}" wx:key="id" id="{{item.id}}" bind:tap="focusFacType">
<view class="badge" wx:if="{{activeId===item.id}}">{{badgeNum}}</view>
<image class="icon" src="{{item.logo}}" mode="" />
<view class="text">{{isEn?item.nameEn:item.name}}</view>
</view>
</view>
</scroll-view>
</view>
</view>
\ No newline at end of file
import langBehavior from "../../behaviors/langBehavior";
import poiFocusBehavior from "../../behaviors/poiFocusBehavior";
import modalBehavior from "../../behaviors/modalBehavior";
import {
getShopShareTitle,
getFacilityShareTitle,
authBluetoothAndLocation,
actToRoutePlan,
} from "../../util";
const defaultFloorName = "1F";
Page({
behaviors: [langBehavior, poiFocusBehavior, modalBehavior],
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,
act: null,
acts: [],
resetCount: 0,
userLocation: null,
reseting: false,
facTypes: [],
activeId: null,
currentFloorFacilities: [],
gettingLocation: false,
getLocationSuccess: false,
hasAuth: false,
},
handleTab({ detail: tab }) {
if (tab.text === "我的")
wx.reLaunch({
url: tab.url,
});
else {
this.setData({
isIndex: !this.data.isIndex,
activeId: null,
currentFloorFacilities: [],
});
this.setFloorName(defaultFloorName);
}
},
handleBottomHudViewstate({ detail }) {
this.setData({ viewstate: detail });
},
async handleReset() {
if (this.data.reseting) return;
try {
this.setData({ reseting: true });
if (this.data.focusedPoi) {
getApp().blurPoi();
}
this.getFloor();
} catch (error) {
console.error("复位失败", error);
} finally {
this.setData({ reseting: false });
}
},
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,
key: mapKey,
});
if (data && data.length)
this.setData({
acts: data.map((act) => {
try {
act.t2 = `${act.activity_begin
.substring(0, 10)
.replaceAll("-", ".")}-${act.activity_end
.substring(0, 10)
.replaceAll("-", ".")}`;
} catch (error) {
console.log(error);
}
return act;
}),
});
} catch (error) {
console.error(error);
}
},
async handleActGo() {
const res = await actToRoutePlan(this.data.act);
if (res === "showBluetoothModal") return this.openBluetoothModal();
if (res === "showLocationModal") return this.openLocationModal();
if (res === "showAuthModal") return this.openAuthModal();
},
closeAct() {
this.setData({ act: null });
},
handleAct({ detail }) {
this.setData({ act: detail });
},
handleSearch() {
wx.navigateTo({
url: "/pages/search/index",
});
console.log("handleSearch");
},
handleAi() {
if (!getApp().checkUser()) return;
wx.navigateTo({
url: "/pages/aisearch/index",
});
},
handleFloor({ detail: floorName }) {
if (this.data.focusedPoi) {
getApp().blurPoi();
}
this.setFloorName(floorName);
if (!this.data.isIndex) {
if (this.data.activeId) this.setFacilities(this.data.activeId);
}
},
async getBanner() {
const { get, uploadurl } = getApp();
const {
data: { data },
} = await get(
`${uploadurl}/buyerapi/v1/ads/content/banner?unique_marks=665588`,
{
RequestSource: "WAP",
}
);
const banners = [];
data.forEach((el) => {
try {
el.ads_content.forEach((ad) => {
try {
banners.push(ad);
} catch (error) {
console.error(error);
}
});
} catch (error) {
console.error(error);
}
});
this.setData({ banners, showBanner: banners.length > 0 });
getApp().bannerShowed = true;
},
handleBanner() {
const ad = this.data.banners[0];
if (
(ad.link_type === "app_web" || ad.link_type === "link") &&
ad.link_content
) {
wx.navigateTo({
url: `/pages/wap/index?url=${encodeURIComponent(ad.link_content)}`,
});
} else if (
ad.link_type === "mini_program" &&
ad.link_content &&
ad.link_path
) {
wx.navigateToMiniProgram({ appId: ad.link_content, path: ad.link_path });
}
},
async closeBanner() {
this.setData({ showBanner: false });
},
handleBannerIndex({ detail: { current } }) {
this.setData({ bannerIndex: current });
},
async onLoad({ shopId, facilityId, actId, q, fromshare, mode }) {
const { authSetting } = await wx.getSetting();
this.setData({
hasAuth:
authSetting["scope.bluetooth"] && authSetting["scope.userLocation"],
neverAuthed:
authSetting["scope.bluetooth"] === undefined &&
authSetting["scope.userLocation"] === undefined,
});
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);
}
}
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);
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));
if (act) this.setData({ act });
} catch (error) {
console.error(error);
}
}
if (!hasParam) {
if (!getApp().bannerShowed) {
await this.getBanner();
}
}
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,
getLocationSuccess: true,
});
this.setFloorName(
userLocation &&
userLocation.buildingId === buildingId &&
userLocation.floorName
? userLocation.floorName
: defaultFloorName
);
setTimeout(() => {
this.setData({ getLocationSuccess: false });
}, 2000);
} else if (toIndoor && !this.data.focusedPoi) {
console.log("定位切到室内");
this.setData({
resetCount: this.data.resetCount + 1,
});
this.setFloorName(userLocation?.floorName);
}
};
getApp().events.on("userLocation", this.setUserLocation);
this.setUserLocation();
if (this.data.hasAuth && !hasParam) this.getFloor();
this.appShowHandler = () => {
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);
},
setFloorName(floorName) {
if (floorName !== this.data.floorName) this.setData({ floorName });
},
async getFloor() {
if (this.data.gettingLocation) return;
this.setData({ gettingLocation: true });
await authBluetoothAndLocation();
const { authSetting } = await wx.getSetting();
if (!authSetting["scope.bluetooth"] || !authSetting["scope.userLocation"]) {
this.setData({ gettingLocation: false });
return this.setData({ showAuthModal: true });
}
const { bluetoothEnabled, locationEnabled } = wx.getSystemSetting();
console.log("系统蓝牙状态", bluetoothEnabled);
console.log("系统位置状态", locationEnabled);
if (!bluetoothEnabled) {
this.setData({ gettingLocation: false });
return this.openBluetoothModal();
}
if (!locationEnabled) {
this.setData({ gettingLocation: false });
return this.openLocationModal();
}
const appAuthorizeSetting = wx.getAppAuthorizeSetting();
console.log("微信蓝牙权限", appAuthorizeSetting.bluetoothAuthorized);
console.log("微信位置权限", appAuthorizeSetting.locationAuthorized);
if (appAuthorizeSetting.bluetoothAuthorized !== "authorized") {
this.setData({ gettingLocation: false });
return this.openBluetoothModal();
}
if (appAuthorizeSetting.locationAuthorized !== "authorized") {
this.setData({ gettingLocation: false });
return this.openLocationModal();
}
getApp().ensurePluginInit();
this.getFloorTimeout = setTimeout(() => {
this.setData({
gettingLocation: false,
resetCount: this.data.resetCount + 1,
userLocation: null,
});
this.setFloorName(defaultFloorName);
}, 3000);
},
async getFacTypes() {
const { get, qmurl } = getApp();
try {
const {
data: { data },
} = await get(`${qmurl}/api/v1/applet/getFacilityClassList`);
if (data && data.length !== undefined) {
this.setData({
facTypes: data,
});
}
} catch (error) {
console.log(error);
}
},
handleFacTypeTap({ detail }) {
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]
);
const { floorName } = this.data;
const currentFloorFacilities = facilities.filter(
(fac) => fac.tx?.poi_fl_name === floorName
);
this.setData({ currentFloorFacilities });
},
doNothing() {},
/**
* 生命周期函数--监听页面初次渲染完成
*/
onReady() {},
/**
* 生命周期函数--监听页面显示
*/
onShow() {},
/**
* 生命周期函数--监听页面隐藏
*/
onHide() {},
/**
* 生命周期函数--监听页面卸载
*/
/**
* 页面相关事件处理函数--监听用户下拉动作
*/
onPullDownRefresh() {},
/**
* 页面上拉触底事件的处理函数
*/
onReachBottom() {},
/**
* 用户点击右上角分享
*/
onShareAppMessage() {
if (this.data.act) {
return {
title: this.data.act.name,
path: `/pages/index/index?actId=${this.data.act.id}&fromshare=true`,
};
}
if (!this.data.focusedPoi)
return {
title: this.data.isEn
? "Share Chinagoods Nav District 2 East indoor navigation miniprogram"
: "分享义乌商贸城二区东室内导航小程序,助你场内高效找店",
path: "/pages/index/index",
imageUrl: this.data.isEn
? `https://cdnimg.chinagoods.com/png/2025/08/13/qmzbxkjo3fse9xs5c2uujdc60tsc3wum.png`
: `https://cdnimg.chinagoods.com/png/2025/08/13/mszq3hfw1m6vgnhb4dt9dhapyxr1yt6b.png`,
};
if (this.data.focusedPoi.facility) {
const title = getFacilityShareTitle(this.data.focusedPoi, this.data.isEn);
return {
title,
path: `/pages/index/index?facilityId=${this.data.focusedPoi.facilityId}&fromshare=true`,
};
} else {
const title = getShopShareTitle(this.data.focusedPoi, this.data.isEn);
return {
title,
path: `/pages/index/index?shopId=${this.data.focusedPoi.shopId}&fromshare=true`,
imageUrl: this.data.focusedPoi.ywZh.cover,
};
}
},
});
{
"disableScroll": true,
"usingComponents": {
"map-wrapper": "../../components/map-wrapper/index",
"bottom-hud-index": "./bottom-hud-index/index",
"bottom-hud-surroundings": "./bottom-hud-surroundings/index",
"lang-btn": "../../components/lang-btn/index",
"popup": "../../components/popup/index",
"custom-tab-bar": "../../components/custom-tab-bar/index",
"floors": "../../components/floors/index",
"bluetooth-modal": "../../components/bluetooth-modal/index",
"location-modal": "../../components/location-modal/index",
"auth-modal": "../../components/auth-modal/index"
}
}
/* pages/index/index.wxss */
.index {
position: relative;
width: 100vw;
height: 100vh;
overflow: hidden;
display: flex;
flex-direction: column;
.floors {
position: absolute;
display: flex;
flex-direction: column;
bottom: 421px;
right: 10px;
padding: 7px 4px;
z-index: 2;
gap: 2px;
align-items: center;
border-radius: 8px;
background: var(--VI-0, #fff);
box-shadow: 0px 2px 5px 0px rgba(0, 0, 0, 0.05);
.arrow {
width: 8px;
height: 3px;
}
.list {
display: flex;
flex-direction: column;
gap: 4px;
.floor {
display: flex;
width: 32px;
height: 32px;
align-items: center;
justify-content: center;
color: #323337;
text-align: center;
font-family: DINPro;
font-size: 14px;
font-style: normal;
font-weight: 500;
border-radius: 8px;
&.active {
background: #fff3f3;
color: #e92927;
}
}
}
}
.go {
position: absolute;
top: 0;
left: 0;
right: 0;
height: 200px;
z-index: 2;
background: #fff;
}
.banner {
position: fixed;
top: 0;
left: 0;
z-index: 2000;
background: rgba(0, 0, 0, 0.6);
width: 100vw;
height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 48px;
.close {
width: 32px;
height: 32px;
}
.poster {
width: 304px;
border-radius: 8px;
}
}
.act-bg {
position: fixed;
top: 0;
left: 0;
z-index: 1000;
width: 100vw;
height: 100vh;
}
.act {
position: fixed;
top: 0;
left: 0;
z-index: 2000;
background: rgba(0, 0, 0, 0.6);
width: 100vw;
height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 48px;
.photo-container {
position: relative;
width: 304px;
min-height: 540px;
border-radius: 8px;
background: #fff;
overflow: hidden;
.photo {
width: 304px;
min-height: 540px;
}
.meta {
position: absolute;
bottom: 0;
left: 0;
width: 304px;
background: linear-gradient(
180deg,
rgba(146, 52, 52, 0) 0%,
#923434 33.56%,
#6f1010 100%
);
border-radius: 0 0 8px 8px;
padding: 16px;
.t1 {
color: #fff;
font-family: "PingFang SC";
font-size: 18px;
font-style: normal;
font-weight: 600;
line-height: 24px;
}
.t2 {
display: flex;
align-items: center;
margin-top: 2px;
color: #fff;
font-family: "PingFang SC";
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: 18px;
gap: 4px;
}
.t3 {
overflow: hidden;
color: rgba(255, 255, 255, 0.8);
font-family: "PingFang SC";
font-size: 12px;
font-style: normal;
font-weight: 400;
line-height: 18px;
margin-top: 8px;
}
.btns {
display: flex;
width: 100%;
margin-top: 16px;
height: 40px;
gap: 8px;
.btn1 {
display: flex;
gap: 4px;
padding: 0;
padding-left: 0;
padding-right: 0;
padding-top: 0;
padding-bottom: 0;
background-color: none;
background: #fff;
flex: 1;
height: 40px;
align-items: center;
justify-content: center;
border-radius: 99px;
color: #666;
font-family: "PingFang SC";
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: 18px;
}
.btn2 {
display: flex;
gap: 4px;
flex: 1;
height: 40px;
align-items: center;
justify-content: center;
border-radius: 99px;
background: linear-gradient(90deg, #fa643c 0%, #e92927 100%);
color: #fff;
font-family: "PingFang SC";
font-size: 14px;
font-style: normal;
font-weight: 600;
line-height: 18px;
}
}
}
}
.close {
width: 32px;
height: 32px;
}
}
.size-20 {
width: 20px;
height: 20px;
}
.size-16 {
width: 16px;
height: 16px;
}
}
<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>
</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>
</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>
</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">
<image class="poster" src="{{banners[0].media_address}}" mode="widthFix" catch:tap="handleBanner" />
<image class="close" src="./close.png" mode="" catch:tap="closeBanner" />
</view>
<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>
<textarea wx:if="{{isIndex&&act}}" class="act-bg" disabled=""></textarea>
<view wx:if="{{isIndex&&act}}" class="act" bind:tap="closeAct">
<view class="photo-container" catch:tap="doNothing">
<image src="{{act.photo}}" class="photo" mode="widthFix" />
<view class="meta">
<view class="t1">{{act.name}}</view>
<view class="t2" wx:if="{{act.t2}}">
<image class="size-16" src="./calender.png" mode="" />
{{act.t2}}
</view>
<view class="t3">
{{act.detail}}
</view>
<view class="btns" catch:tap="doNothing">
<button class="btn1" open-type="share">
<image class="size-20" src="./share.png" mode="" />
{{isEn?'Share Location':'分享位置'}}
</button>
<view class="btn2" catch:tap="handleActGo">
<image class="size-20" src="./go.png" mode="" />
{{isEn?'Go Here':'到这去'}}
</view>
</view>
</view>
</view>
<image src="./close.png" class="close" catch:tap="closeAct" mode="" />
</view>
</view>
\ No newline at end of file
import langBehavior from "../../../behaviors/langBehavior";
import modalBehavior from "../../../behaviors/modalBehavior";
import { confirm, toRoutePlan } from "../../../util";
Component({
behaviors: [langBehavior, modalBehavior],
properties: {},
/**
* 组件的初始数据
*/
data: {
itineraryList: [],
notVisitedCount: 0,
deleteIdMap: {},
bulkDeleting: false,
allSelected: false,
listGroupByYmd: [],
swipeDeleteId: null,
touchStartX: 0,
touchStartY: 0,
},
observers: {
itineraryList(list) {
this.setData({
notVisitedCount: list.filter(({ status }) => status !== 1).length,
listGroupByYmd: this.groupByCreateTime(list),
});
},
bulkDeleting() {
this.cleanSwipeDeleteId();
this.setData({ deleteIdMap: {}, allSelected: false });
},
},
methods: {
async handleGo({ currentTarget: { id } }) {
const { isEn, itineraryList } = this.data;
const iti = itineraryList.find((iti) => Number(id) === iti.id);
const poi = getApp().shopIdMap[iti.shopId];
try {
getApp().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,
}),
});
} catch (error) {
console.error("埋点失败", error);
}
try {
const res = await toRoutePlan(poi, isEn);
if (res === "showBluetoothModal")
return this.setData({ showBluetoothModal: true });
if (res === "showLocationModal") return this.openLocationModal();
if (res === "showAuthModal") return this.openAuthModal();
} catch (error) {
console.error(error);
}
},
async handleDelete({ currentTarget: { id } }) {
if (this.data.bulkDeleting) return;
const { isEn, itineraryList } = this.data;
const iti = itineraryList.find((iti) => Number(id) === iti.id);
if (!iti) return;
const confirmed = await confirm({
title: "",
content: isEn
? `Are you sure you want to delete "${iti.goodsTitle}"?`
: `确定删除"${iti.goodsTitle}"吗?`,
confirmText: isEn ? `Ok` : `确认`,
cancelText: isEn ? `Cancel` : `取消`,
});
if (!confirmed) return;
const { itineraryApi } = getApp();
const success = await itineraryApi.bulkDelete([Number(id)]);
if (success) {
wx.showToast({
title: this.data.isEn ? "success" : "删除成功",
icon: "none",
});
getApp().refreshItinerary();
return;
} else {
wx.showToast({
title: this.data.isEn ? "fail" : "删除失败",
icon: "none",
});
}
},
handleSelectAll() {
if (this.data.allSelected) {
this.setData({
deleteIdMap: {},
allSelected: false,
});
} else
this.setData({
deleteIdMap: this.data.itineraryList.reduce(
(acc, nxt) => ({ ...acc, [nxt.id]: true }),
{}
),
allSelected: true,
});
},
handleTouchStart(e) {
if (this.data.bulkDeleting) return;
this.setData({
touchStartX: e.touches[0].clientX,
touchStartY: e.touches[0].clientY,
});
},
handleTouchEnd(e) {
if (this.data.bulkDeleting) return;
const { touchStartX, touchStartY } = this.data;
const endX = e.changedTouches[0].clientX;
const endY = e.changedTouches[0].clientY;
const deltaX = endX - touchStartX;
const deltaY = endY - touchStartY;
// 判断是否为水平滑动
if (Math.abs(deltaX) > Math.abs(deltaY)) {
const itemId = e.currentTarget.id;
if (deltaX > 50) {
const { swipeDeleteId } = this.data;
if (swipeDeleteId === Number(itemId)) {
this.setData({ swipeDeleteId: null });
}
} else if (deltaX < -50) {
this.setData({ swipeDeleteId: Number(itemId) });
}
}
},
cleanSwipeDeleteId() {
this.setData({ swipeDeleteId: null });
},
toggleBulkDeleting() {
this.setData({ bulkDeleting: !this.data.bulkDeleting });
},
async handleRefresh() {
try {
wx.showLoading();
await getApp().refreshItinerary();
} catch (error) {
console.error(error);
} finally {
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")}`;
const result = [];
let currentGroup = null;
dataList.forEach((item) => {
const datePart = item.createTime.split(" ")[0];
const dateKey = datePart === todayStr ? "今天" : datePart;
if (!currentGroup || currentGroup.date !== dateKey) {
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] });
});
return result;
},
async handleBulkDelete() {
const { isEn, deleteIdMap } = this.data;
const ids = Object.entries(deleteIdMap)
.filter(([k, v]) => v)
.map(([k]) => Number(k));
if (!ids.length) return;
const confirmed = await confirm({
title: "",
content: isEn
? `Are you sure you want to bulk delete?`
: `确定批量删除吗?`,
confirmText: isEn ? `Ok` : `确认`,
cancelText: isEn ? `Cancel` : `取消`,
});
if (!confirmed) return;
const { itineraryApi } = getApp();
const success = await itineraryApi.bulkDelete(ids);
if (success) {
this.setData({
bulkDeleting: false,
});
wx.showToast({
title: this.data.isEn ? "success" : "删除成功",
icon: "none",
});
getApp().refreshItinerary();
return;
} else {
wx.showToast({
title: this.data.isEn ? "fail" : "删除失败",
icon: "none",
});
}
},
handleItineraryTap({ currentTarget: { id } }) {
const iti = this.data.itineraryList.find((iti) => Number(id) === iti.id);
if (!iti) return;
if (!this.data.bulkDeleting) return;
this.setData({
deleteIdMap: {
...this.data.deleteIdMap,
[id]: !this.data.deleteIdMap[id],
},
allSelected: false,
});
},
},
lifetimes: {
attached() {
const app = getApp();
this.setList = () => {
this.setData({
itineraryList: app.itineraryList,
});
};
this.setList();
app.events.on("itineraryRefresh", this.setList);
},
detached() {
if (this.setList) {
const app = getApp();
app.events.off("itineraryRefresh", this.setList);
}
},
},
pageLifetimes: {
show() {
console.log("onShow");
getApp().refreshItinerary();
},
},
});
{
"component": true,
"usingComponents": {
"shop-name": "../../../components/shop-name/index",
"shop-addr": "../../../components/shop-addr/index",
"end": "../../../components/end/index",
"empty": "../../../components/empty/index",
"bluetooth-modal": "../../../components/bluetooth-modal/index",
"location-modal": "../../../components/location-modal/index",
"auth-modal": "../../../components/auth-modal/index"
}
}
.itinerary {
position: relative;
width: 100vw;
height: 100vh;
overflow: hidden;
background: #f7f7f7;
.scroller {
position: relative;
width: 100vw;
height: 100vh;
display: flex;
flex-direction: column;
&.bulk {
height: calc(100vh - 90px);
}
.title {
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 10px;
height: 42px;
.left {
display: flex;
align-items: baseline;
gap: 4px;
color: #333;
font-family: "PingFang SC";
font-size: 16px;
font-style: normal;
font-weight: 600;
line-height: 22px;
.nums {
color: #666;
font-family: "PingFang SC";
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: 18px;
.red {
color: #e92927;
}
}
}
.btns {
display: flex;
gap: 17px;
color: #333;
font-family: "PingFang SC";
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: normal;
.red {
color: #e92927;
}
}
}
.groups {
display: flex;
flex-direction: column;
align-items: center;
gap: 8px;
.group {
width: calc(100vw - 20px);
padding: 12px;
border-radius: 8px;
background: #fff;
display: flex;
flex-direction: column;
overflow: hidden;
.date-row {
color: #666;
font-family: "PingFang SC";
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: 18px;
margin-bottom: 10px;
}
.good {
position: relative;
width: calc(100vw - 44px);
height: 96px;
display: flex;
gap: 10px;
transition: margin-left 0.3s ease-in-out;
&.bulk {
margin-left: 34px;
}
&.show-delete {
margin-left: -48px;
}
.check {
position: absolute;
top: 0;
bottom: 0;
left: -34px;
width: 22px;
height: 22px;
margin: auto;
}
.visited {
position: absolute;
top: 0;
left: 0;
height: 16px;
padding: 0 6px;
border-radius: 6px 0;
background: rgba(0, 0, 0, 0.6);
color: #fff;
font-family: "PingFang SC";
font-size: 10px;
font-style: normal;
font-weight: 400;
line-height: normal;
gap: 2px;
}
.delete {
position: absolute;
top: 0;
bottom: 0;
right: -60px;
width: 48px;
background: #e92927;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 4px;
color: #fff;
font-family: "PingFang SC";
font-size: 10px;
font-style: normal;
font-weight: 400;
line-height: 12px;
}
.avatar {
width: 96px;
height: 96px;
border-radius: 6px;
}
.right {
flex: 1;
position: relative;
display: flex;
flex-direction: column;
height: 100%;
justify-content: space-between;
overflow: hidden;
.right-top {
display: flex;
flex-direction: column;
gap: 2px;
.t1 {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
color: rgba(0, 0, 0, 0.9);
font-family: "PingFang SC";
font-size: 15px;
font-style: normal;
font-weight: 600;
line-height: 22px;
}
.t2 {
display: flex;
gap: 1px;
align-items: baseline;
color: #ed383e;
.left {
font-size: 12px;
font-style: normal;
font-weight: 400;
line-height: 16px;
}
.mid {
font-size: 16px;
font-style: normal;
font-weight: 600;
line-height: 22px;
}
.right {
font-size: 12px;
font-style: normal;
font-weight: 600;
line-height: 16px;
}
}
}
.right-bottom {
display: flex;
flex-direction: column;
gap: 4px;
.t3 {
color: #333;
font-family: "PingFang SC";
font-size: 13px;
font-style: normal;
font-weight: 400;
line-height: 16px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.t4 {
color: #666;
font-family: "PingFang SC";
font-size: 10px;
font-style: normal;
font-weight: 400;
line-height: 12px;
}
}
.right-corner {
position: absolute;
bottom: 0;
right: 0;
display: flex;
flex-direction: column;
gap: 6px;
align-items: center;
.t5 {
color: #333;
text-align: center;
font-family: "PingFang SC";
font-size: 12px;
font-style: normal;
font-weight: 600;
line-height: 16px;
}
}
}
}
.hr {
width: calc(100vw - 44px);
height: 1px;
background: #f3f3f3;
margin: 16px 0;
&.bulk {
margin-left: 34px;
margin-right: 0;
}
}
}
}
.empty {
padding-top: calc(50vh - 88px - 42px);
}
}
.bulk-bottom {
position: fixed;
bottom: 0;
left: 0;
width: 100vw;
height: 90px;
display: flex;
justify-content: space-between;
align-items: center;
gap: 37px;
background: #fff;
padding: 9px 12px 43px 12px;
z-index: 1000;
border-top: 1px solid var(--B-3, rgba(0, 0, 0, 0.03));
.left {
display: flex;
align-items: center;
gap: 4px;
color: #333;
font-family: "PingFang SC";
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: 18px;
}
.btn {
display: flex;
align-items: center;
justify-content: center;
gap: 4px;
height: 38px;
flex: 1;
border-radius: 99px;
background: linear-gradient(90deg, #fa643c 0%, #e92927 100%);
color: #fff;
font-family: "PingFang SC";
font-size: 14px;
font-style: normal;
font-weight: 600;
line-height: 18px;
}
}
.size-12 {
width: 12px;
height: 12px;
}
.size-20 {
width: 20px;
height: 20px;
}
.size-22 {
width: 22px;
height: 22px;
}
.size-32 {
width: 32px;
height: 32px;
}
}
<view class="itinerary">
<scroll-view class="scroller {{bulkDeleting?'bulk':''}}" scroll-y>
<view class="title">
<view class="left">{{isEn?'My Itinerary':'我的行程'}}
<text class="nums"><text class="red">{{notVisitedCount}}</text>/{{itineraryList.length}}</text>
</view>
<view class="btns">
<view bind:tap="handleRefresh">{{isEn?'Refresh':'刷新'}}</view>
<block wx:if="{{listGroupByYmd.length}}">
<view wx:if="{{bulkDeleting}}" class="red" bind:tap="toggleBulkDeleting">{{isEn?'Finish':'完成'}}</view>
<view wx:else class="" bind:tap="toggleBulkDeleting">{{isEn?'Edit':'编辑'}}</view>
</block>
</view>
</view>
<view class="groups">
<view class="group" wx:for="{{listGroupByYmd}}" wx:key="date">
<view class="date-row">{{isEn&&item.date==='今天'?'Today':item.date}}</view>
<block wx:for="{{item.list}}" wx:key="id">
<view class="hr {{bulkDeleting?'bulk':''}}" wx:if="{{index!==0}}"></view>
<view class="good {{bulkDeleting?'bulk':''}} {{swipeDeleteId===item.id?'show-delete':''}}" bindtouchstart="handleTouchStart" bindtouchend="handleTouchEnd" id="{{item.id}}" bind:tap="handleItineraryTap">
<block wx:if="{{bulkDeleting}}">
<image wx:if="{{deleteIdMap[item.id]}}" class="check" src="./checked.png" mode="" />
<image wx:else class="check" src="./unchecked.png" mode="" />
</block>
<view wx:if="{{item.status===1}}" class="visited">
<image class="size-12" src="./footprint.png" mode="" />
{{isEn?'visited':'去过'}}
</view>
<image class="avatar" wx:if="{{item.goodsCover}}" src="{{item.goodsCover}}" mode="aspectFill" />
<view class="right">
<view class="right-top">
<view class="t1">{{item.goodsTitle}}</view>
<view class="t2">
<view class="left">¥</view>
<view class="mid">{{item.yuan}}</view>
<view class="right">{{item.fen}}</view>
</view>
</view>
<view class="right-bottom">
<view class="t3">
<shop-name shop="{{item.shop}}"></shop-name>
</view>
<view class="t4">
<shop-addr shop="{{item.shop}}"></shop-addr>
</view>
</view>
<view class="right-corner" wx:if="{{item.shop&&item.shop.tx}}" id="{{item.id}}" bind:tap="handleGo">
<image class="size-32" src="./nav.png" mode="" />
<view class="t5">{{isEn?'Go Here':'到这去'}}</view>
</view>
</view>
<view wx:if="{{swipeDeleteId===item.id}}" class="delete" catch:tap="handleDelete" id="{{item.id}}">
<image src="./delete.png" class="size-20" mode="" />
{{isEn?'Delete':'删除'}}
</view>
</view>
</block>
<view wx:if="{{index===listGroupByYmd.length-1}}" style="margin-top:10px">
<end></end>
</view>
</view>
</view>
<view class="empty">
<empty wx:if="{{itineraryList.length===0}}"></empty>
</view>
</scroll-view>
<view wx:if="{{bulkDeleting}}" class="bulk-bottom">
<view class="left" bind:tap="handleSelectAll">
<image wx:if="{{allSelected}}" src="./checked.png" class="size-22" mode="" />
<image wx:else src="./unchecked.png" class="size-22" mode="" />
{{isEn?'Select All':'全选'}}
</view>
<view class="btn" bind:tap="handleBulkDelete">
<image class="size-20" src="./delete.png" mode="" />
{{isEn?'Bulk Delete':'批量删除'}}
</view>
</view>
<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>
</view>
\ No newline at end of file
{
"usingComponents": {
"itinerary": "./component/itinerary"
}
}
<itinerary></itinerary>
\ No newline at end of file
import langBehavior from "../../behaviors/langBehavior";
Page({
behaviors: [langBehavior],
data: {
checked: false,
phoneLock: false,
showModal: false,
},
/**
* 生命周期函数--监听页面加载
*/
onLoad(options) {},
checkPrivacy() {
this.setData({ checked: !this.data.checked });
},
toPrivacy() {
wx.navigateTo({
url: "/pages/privacy/index",
});
},
back() {
const pages = getCurrentPages();
if (pages.length > 1) {
wx.navigateBack();
} else {
wx.redirectTo({
url: "/pages/index/index",
});
}
},
handleUnchecked() {
this.setData({ showModal: true });
},
closeModal() {
this.setData({ showModal: false });
},
async handleGetPhone({ detail: { code } }) {
this.setData({ phoneLock: true });
try {
if (code) {
const success = await getApp().login(code);
if (success) this.back();
}
} catch (error) {
console.log("error", error);
} finally {
this.setData({ phoneLock: false });
}
},
/**
* 生命周期函数--监听页面初次渲染完成
*/
onReady() {},
/**
* 生命周期函数--监听页面显示
*/
onShow() {},
/**
* 生命周期函数--监听页面隐藏
*/
onHide() {},
/**
* 生命周期函数--监听页面卸载
*/
onUnload() {},
/**
* 页面相关事件处理函数--监听用户下拉动作
*/
onPullDownRefresh() {},
/**
* 页面上拉触底事件的处理函数
*/
onReachBottom() {},
/**
* 用户点击右上角分享
*/
onShareAppMessage() {},
});
{
"usingComponents": {
"lang-btn": "../../components/lang-btn/index"
}
}
.login {
position: relative;
width: 100vw;
height: 100vh;
background: linear-gradient(180deg, #f9f1f1 0%, #f7f7f7 59.85%);
display: flex;
flex-direction: column;
align-items: center;
.bg {
position: absolute;
width: 100vw;
top: 0;
left: 0;
z-index: 1;
.bgleft {
position: absolute;
top: 0;
left: 0;
width: 197px;
height: 332px;
z-index: 1;
}
.logo {
position: absolute;
top: 60px;
left: 0;
right: 0;
margin: auto;
width: 160px;
height: 160px;
z-index: 2;
}
}
.content {
position: absolute;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
z-index: 2;
display: flex;
flex-direction: column;
align-items: center;
justify-content: space-between;
padding-top: 205px;
padding-bottom: 158px;
box-sizing: border-box;
.title {
color: #333;
text-align: center;
font-family: "PingFang SC";
font-size: 20px;
font-style: normal;
font-weight: 600;
line-height: 25px;
}
.btn1 {
display: flex;
width: calc(100vw - 48px);
height: 48px;
border-radius: 99px;
background: linear-gradient(90deg, #fa643c 0%, #e92927 100%);
color: #fff;
font-family: "PingFang SC";
font-size: 14px;
font-style: normal;
font-weight: 600;
line-height: 18px;
align-items: center;
justify-content: center;
}
.btn2 {
display: flex;
width: calc(100vw - 48px);
height: 48px;
border-radius: 99px;
color: #666;
font-family: "PingFang SC";
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: 18px;
align-items: center;
justify-content: center;
}
.meta {
position: relative;
width: calc(100vw - 48px);
margin-top: 24px;
padding-left: 20px;
color: #999;
font-family: "PingFang SC";
font-size: 12px;
font-style: normal;
font-weight: 400;
line-height: 21px;
.radio {
position: absolute;
top: 4px;
left: 0;
width: 14px;
height: 14px;
}
.red {
color: #e92927;
}
}
}
.modal {
position: absolute;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
z-index: 3;
background: rgba(0, 0, 0, 0.6);
.modal-content {
width: 304px;
border-radius: 8px;
background: #fff;
display: flex;
flex-direction: column;
align-items: center;
.r1 {
padding: 30px 24px;
color: #333;
text-align: center;
font-family: "PingFang SC";
font-size: 15px;
font-style: normal;
font-weight: 400;
line-height: normal;
.red {
color: #e92927;
}
}
.r2 {
display: flex;
width: 280px;
height: 50px;
border-top: 0.5px solid #e5e5e5;
.left {
flex: 1;
color: #999;
text-align: center;
font-family: "PingFang SC";
font-size: 18px;
font-style: normal;
font-weight: 400;
display: flex;
align-items: center;
justify-content: center;
}
.right {
flex: 1;
color: #e92927;
text-align: center;
font-family: "PingFang SC";
font-size: 18px;
font-style: normal;
font-weight: 500;
display: flex;
align-items: center;
justify-content: center;
border-left: 0.5px solid #e5e5e5;
background: none;
width: auto;
padding: 0;
}
}
}
}
}
<view class="login">
<view class="bg">
<image src="https://cdnimg.chinagoods.com/png/2025/08/25/vmxpaezkib93hapqzayljg1ladxcegkx.png" class="bgleft" mode="" />
<image class="logo" src="https://cdnimg.chinagoods.com/png/2025/08/25/pctmnrp8pzlrtzvwt7uw6nwab6nnhh4s.png" mode="" />
</view>
<view class="content">
<view class="title">
{{isEn?'Welcome to use Chinagoods Nav':'欢迎使用 小商AI导航'}}
</view>
<view class="bottom">
<button wx:if="{{checked}}" class="btn1" disabled="{{phoneLock}}" open-type="getPhoneNumber" bindgetphonenumber="handleGetPhone">
{{isEn?'Mobile number quick login':'手机号快捷登录'}}
</button>
<view class="btn1" wx:else bind:tap="handleUnchecked">
{{isEn?'Mobile number quick login':'手机号快捷登录'}}
</view>
<view class="btn2" bind:tap="back"> {{isEn?'Skip login':'暂不登录'}}</view>
<view class="meta" bind:tap="checkPrivacy">
<image class="radio" wx:if="{{checked}}" src="./radioActive.png" mode="" />
<image class="radio" wx:else src="./radio.png" mode="" />
<view wx:if="{{isEn}}">I have read and agree to <text class="red" catch:tap="toPrivacy">"User Agreement and Privacy Policy"</text>, unregistered mobile phone number will automatically create an account for you after login.</view>
<view wx:else>我已阅读同意<text class="red" catch:tap="toPrivacy">《用户协议及隐私政策》</text>,未注册手机号在登录后将自动为您创建义乌小商品城账号</view>
</view>
</view>
</view>
<lang-btn type="black"></lang-btn>
<view class="modal" wx:if="{{showModal}}" 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
read and agree to <text class="red" catch:tap="toPrivacy">"User
Agreement and Privacy Policy"</text>
</view>
<view class="r1" wx:else>
请您仔细阅读并充分理解相关条款,点击同意即代表您已阅读并同意<text class="red" catch:tap="toPrivacy">《用户协议及隐私政策》</text>
</view>
<view class="r2">
<view class="left" catch:tap="back">{{isEn?'Cancel': '不同意'}}</view>
<button class="right" disabled="{{phoneLock}}" open-type="getPhoneNumber" bindgetphonenumber="handleGetPhone">{{isEn?'Ok':'同意'}}</button>
</view>
</view>
</view>
</view>
\ No newline at end of file
import collectBehavior from "../../../behaviors/collectBehavior";
import {
confirm,
toRoutePlan,
openBluetooth,
getPoiName,
checkAuth,
} from "../../../util";
Component({
behaviors: [collectBehavior],
properties: { isEn: Boolean },
data: {
poiList: [],
displayList: [],
formats: [{ key: "all", name: "全部收藏", nameEn: "All" }],
key: "all",
swipeDeleteId: null,
touchStartX: 0,
touchStartY: 0,
deleteIdMap: {},
bulkDeleting: false,
allSelected: false,
},
observers: {
collectList() {
this.setPoiList();
},
poiList() {
const { poiList } = this.data;
const formatKeyMap = poiList
.filter(({ poi }) => poi.ywZh)
.map(({ poi }) => poi.ywZh.industryNewKey)
.reduce((acc, nxt) => ({ ...acc, [nxt]: true }), {});
this.setData({
formats: [
{ key: "all", name: "全部收藏", nameEn: "All" },
...getApp().formats.filter(({ key }) => formatKeyMap[key]),
],
});
this.setDisplayList();
},
key() {
this.setDisplayList();
this.cleanSwipeDeleteId();
this.setData({ deleteIdMap: {}, allSelected: false });
},
bulkDeleting() {
this.cleanSwipeDeleteId();
this.setData({ deleteIdMap: {}, allSelected: false });
this.triggerEvent("togglebulkdelete", this.data.bulkDeleting);
},
formats() {
if (
this.data.key !== "all" &&
!this.data.formats.find(({ key }) => key === this.data.key)
) {
this.setData({ key: "all" });
}
},
},
lifetimes: {
attached() {
this.setPoiList();
},
},
/**
* 组件的方法列表
*/
methods: {
handleSelectAll() {
if (this.data.allSelected) {
this.setData({
deleteIdMap: {},
allSelected: false,
});
} else
this.setData({
deleteIdMap: this.data.displayList.reduce(
(acc, nxt) => ({ ...acc, [nxt.id]: true }),
{}
),
allSelected: true,
});
},
async handleBulkDelete() {
const { isEn, deleteIdMap } = this.data;
const ids = Object.entries(deleteIdMap)
.filter(([k, v]) => v)
.map(([k]) => Number(k));
if (!ids.length) return;
const confirmed = await confirm({
title: "",
content: isEn
? `Are you sure you want to bulk delete?`
: `确定批量删除吗?`,
confirmText: isEn ? `Ok` : `确认`,
cancelText: isEn ? `Cancel` : `取消`,
});
if (!confirmed) return;
const { collectionApi } = getApp();
const success = await collectionApi.bulkDelete(ids);
if (success) {
this.setData({
bulkDeleting: false,
});
wx.showToast({
title: this.data.isEn ? "success" : "删除成功",
icon: "none",
});
getApp().refreshCollection();
return;
} else {
wx.showToast({
title: this.data.isEn ? "fail" : "删除失败",
icon: "none",
});
}
},
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 });
},
cleanSwipeDeleteId() {
this.setData({ swipeDeleteId: null });
},
setDisplayList() {
const { key, poiList } = this.data;
this.setData({
displayList:
key === "all"
? poiList
: poiList.filter(({ poi }) => poi.ywZh?.industryNewKey === key),
});
},
handleFormat({ currentTarget: { id } }) {
this.setData({ key: id });
},
async handleGo({ currentTarget: { id: index } }) {
const { isEn, displayList } = this.data;
const { poi } = displayList[index];
try {
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
? {
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,
}),
});
} catch (error) {
console.error("埋点失败", error);
}
try {
const res = await toRoutePlan(poi, isEn);
if (res === "showBluetoothModal")
return this.triggerEvent("showbluetoothmodal");
if (res === "showLocationModal")
return this.triggerEvent("showlocationmodal");
if (res === "showAuthModal") return this.triggerEvent("showauthmodal");
} catch (error) {
console.error(error);
}
},
async handleDelete(e) {
if (this.data.bulkDeleting) return;
const {
currentTarget: { id: index },
} = e;
const { isEn, displayList } = this.data;
const { poi } = displayList[index];
const name = getPoiName(poi, isEn);
const confirmed = await confirm({
title: "",
content: isEn
? `Are you sure you want to delete "${name}"?`
: `确定删除"${name}"吗?`,
confirmText: isEn ? `Ok` : `确认`,
cancelText: isEn ? `Cancel` : `取消`,
});
if (!confirmed) return;
this.toggleCollection(poi);
},
handleTouchStart(e) {
if (this.data.bulkDeleting) return;
this.setData({
touchStartX: e.touches[0].clientX,
touchStartY: e.touches[0].clientY,
});
},
handleTouchEnd(e) {
if (this.data.bulkDeleting) return;
const { touchStartX, touchStartY } = this.data;
const endX = e.changedTouches[0].clientX;
const endY = e.changedTouches[0].clientY;
const deltaX = endX - touchStartX;
const deltaY = endY - touchStartY;
// 判断是否为水平滑动
if (Math.abs(deltaX) > Math.abs(deltaY)) {
const itemId = e.currentTarget.id;
if (deltaX > 50) {
const { swipeDeleteId } = this.data;
if (swipeDeleteId === Number(itemId)) {
this.setData({ swipeDeleteId: null });
}
} else if (deltaX < -50) {
this.setData({ swipeDeleteId: Number(itemId) });
}
}
},
handleShopTap({ currentTarget: { id } }) {
const collect = this.data.displayList.find(
(collect) => Number(id) === collect.id
);
if (!collect) return;
if (!this.data.bulkDeleting) {
const { poi } = collect;
wx.navigateTo({
url: `/pages/poi-map/index${
poi.facilityId
? `?facilityId=${poi.facilityId}`
: `?shopId=${poi.shopId}`
}`,
});
} else {
this.setData({
deleteIdMap: {
...this.data.deleteIdMap,
[id]: !this.data.deleteIdMap[id],
},
allSelected: false,
});
}
},
},
});
{
"component": true,
"usingComponents": {
"shop-name": "../../../components/shop-name/index",
"shop-addr": "../../../components/shop-addr/index",
"shop-maingoods": "../../../components/shop-maingoods/index",
"end": "../../../components/end/index",
"empty": "../../../components/empty/index"
}
}
.collection {
width: calc(100vw - 20px);
display: flex;
flex-direction: column;
padding-top: 12px;
gap: 12px;
border-radius: 8px;
background: #fff;
overflow: hidden;
.size-20 {
width: 20px;
height: 20px;
}
.size-22 {
width: 22px;
height: 22px;
}
.title {
display: flex;
justify-content: space-between;
height: 22px;
color: #333;
font-family: "PingFang SC";
font-size: 16px;
font-style: normal;
font-weight: 600;
padding: 0 12px;
align-items: center;
.left {
flex: 1;
display: flex;
gap: 8px;
white-space: nowrap;
}
.edit {
position: relative;
color: #666;
font-size: 14px;
font-weight: 400;
&::before {
display: block;
content: "";
position: absolute;
top: -10px;
bottom: -10px;
left: -10px;
right: -10px;
}
&.red {
color: #e92927;
}
}
}
.content {
.formats {
color: #999;
font-family: "PingFang SC";
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: 18px;
width: calc(100vw - 20px);
overflow-y: hidden;
overflow-x: auto;
height: 22px;
.list {
display: inline-flex;
gap: 16px;
margin: 0 12px;
.item {
position: relative;
height: 22px;
white-space: nowrap;
&.active {
color: #e92927;
font-weight: 600;
&::after {
content: "";
position: absolute;
left: 0;
right: 0;
bottom: 0;
margin: auto;
width: 16px;
height: 2px;
border-radius: 17px;
background: #ed383e;
}
}
}
}
}
.shops {
.shop {
position: relative;
height: 88px;
display: flex;
width: calc(100vw - 20px);
.main {
position: relative;
flex: 1;
padding: 12px;
display: inline-flex;
gap: 10px;
align-items: center;
overflow: hidden;
.avatar {
width: 64px;
height: 64px;
border-radius: 6px;
object-fit: cover;
}
.texts {
display: flex;
flex-direction: column;
flex: 1;
gap: 4px;
overflow: hidden;
.t1 {
color: #333;
font-family: "PingFang SC";
font-size: 14px;
font-style: normal;
font-weight: 600;
line-height: 18px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.t2 {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
color: #666;
font-family: "PingFang SC";
font-size: 12px;
font-style: normal;
font-weight: 400;
line-height: 16px;
.stick {
display: inline-block;
width: 1px;
height: 8px;
background: #666;
margin: 0 4px;
}
}
.t3 {
white-space: nowrap;
overflow: hidden;
color: #666;
text-overflow: ellipsis;
font-family: "PingFang SC";
font-size: 12px;
font-style: normal;
font-weight: 400;
line-height: 16px;
height: 16px;
}
}
.right {
display: flex;
flex-direction: column;
align-items: center;
gap: 6px;
.icon {
width: 32px;
height: 32px;
}
.text {
color: #333;
text-align: center;
font-family: "PingFang SC";
font-size: 12px;
font-style: normal;
font-weight: 600;
line-height: 16px;
}
}
}
.delete {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 4px;
width: 56px;
height: 100%;
background: #e92927;
color: #fff;
font-family: "PingFang SC";
font-size: 10px;
font-style: normal;
font-weight: 400;
line-height: 12px;
animation: 0.2s ease-in-out slideInRight;
}
@keyframes slideInRight {
from {
transform: translateX(100%);
}
to {
transform: translateX(0);
}
}
}
.border-top {
position: absolute;
top: 0;
left: 12px;
right: 12px;
height: 1px;
background: #f3f3f3;
&.bulk {
left: 46px;
}
}
}
.empty {
padding-top: 40px;
padding-bottom: 48px;
}
}
.bulk-bottom {
position: fixed;
bottom: 0;
left: 0;
width: 100vw;
height: 90px;
display: flex;
justify-content: space-between;
align-items: center;
gap: 37px;
background: #fff;
padding: 9px 12px 43px 12px;
z-index: 1000;
border-top: 1px solid var(--B-3, rgba(0, 0, 0, 0.03));
.left {
position: relative;
display: flex;
align-items: center;
gap: 4px;
color: #333;
font-family: "PingFang SC";
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: 18px;
&::before {
display: block;
content: "";
position: absolute;
top: -10px;
bottom: -10px;
left: -10px;
right: -10px;
}
}
.btn {
display: flex;
align-items: center;
justify-content: center;
gap: 4px;
height: 38px;
flex: 1;
border-radius: 99px;
background: linear-gradient(90deg, #fa643c 0%, #e92927 100%);
color: #fff;
font-family: "PingFang SC";
font-size: 14px;
font-style: normal;
font-weight: 600;
line-height: 18px;
}
}
}
<view class="collection">
<view class="title">
<view class="left">
{{isEn?'My Collection':'我收藏的地点'}}
<view>{{poiList.length}}</view>
</view>
<block wx:if="{{poiList.length}}">
<view wx:if="{{bulkDeleting}}" class="edit red" bind:tap="toggleBulkDeleting">{{isEn?'Finish':'完成'}}</view>
<view wx:else class="edit {{bulkDeleting?'red':''}}" bind:tap="toggleBulkDeleting">{{isEn?'Edit':'编辑'}}</view>
</block>
</view>
<view class="content">
<scroll-view scroll-x enable-flex class="formats" wx:if="{{poiList.length}}">
<view class="list">
<view class="item {{key===item.key?'active':''}}" wx:for="{{formats}}" id="{{item.key}}" wx:key="key" bind:tap="handleFormat">
{{isEn?item.nameEn:item.name}}
</view>
</view>
</scroll-view>
<view class="shops">
<view class="shop" wx:for="{{displayList}}" wx:key="id" bindtouchstart="handleTouchStart" bindtouchend="handleTouchEnd" id="{{item.id}}" bind:tap="handleShopTap">
<view class="main">
<view class="" wx:if="{{bulkDeleting}}">
<image wx:if="{{deleteIdMap[item.id]}}" class="size-22" src="./checked.png" mode="" />
<image wx:else class="size-22" src="./unchecked.png" mode="" />
</view>
<block wx:if="{{item.poi.facility}}">
<image src="{{item.poi.facility.logo}}" class="avatar" mode="" />
<view class="texts">
<view class="t1">{{isEn?item.poi.facility.nameEn:item.poi.facility.name}}</view>
<view class="t2">
{{isEn?item.poi.facility.addressEn:item.poi.facility.address}}
</view>
<view class="t3"></view>
</view>
</block>
<block wx:else>
<image src="{{item.poi.ywZh.cover}}" class="avatar" mode="" />
<view class="texts">
<view class="t1">
<shop-name shop="{{item.poi}}"></shop-name>
</view>
<view class="t2">
<shop-addr shop="{{item.poi}}"></shop-addr>
</view>
<view class="t3">
<shop-maingoods shop="{{item.poi}}"></shop-maingoods>
</view>
</view>
</block>
<view class="right" wx:if="{{item.poi.tx}}" id="{{index}}" catch:tap="handleGo">
<image class="icon" src="./nav.png" mode="" />
<view class="text">{{isEn?'Go':'到这去'}}</view>
</view>
</view>
<view class="delete" wx:if="{{swipeDeleteId===item.id}}" catch:tap="handleDelete" id="{{index}}">
<image class="size-20" src="./delete.png" mode="" />
{{isEn?'Delete':'删除'}}
</view>
<view class="border-top {{bulkDeleting?'bulk':''}}" wx:if="{{index>0}}"></view>
</view>
</view>
<end wx:if="{{poiList.length}}"></end>
<view wx:if="{{poiList.length===0}}" class="empty">
<empty></empty>
</view>
</view>
<view wx:if="{{bulkDeleting}}" class="bulk-bottom">
<view class="left" bind:tap="handleSelectAll">
<image wx:if="{{allSelected}}" src="./checked.png" class="size-20" mode="" />
<image wx:else src="./unchecked.png" class="size-20" mode="" />
{{isEn?'Select All':'全选'}}
</view>
<view class="btn" bind:tap="handleBulkDelete">
<image class="size-20" src="./delete.png" mode="" />
{{isEn?'Bulk Delete':'批量删除'}}
</view>
</view>
</view>
\ No newline at end of file
import langBehavior from "../../behaviors/langBehavior";
import userBehavior from "../../behaviors/userBehavior";
import modalBehavior from "../../behaviors/modalBehavior";
import { confirm } from "../../util";
Page({
behaviors: [langBehavior, userBehavior, modalBehavior],
data: {
phoneLock: false,
phone: null,
bulkDeleting: false,
itineraryCount: 0,
},
toLogin() {
wx.navigateTo({
url: "/pages/login/index",
});
},
async handleLogout() {
const { isEn } = this.data;
const confirmed = await confirm({
title: isEn ? `Confirm logout` : `确定退出账户`,
content: isEn
? `After logging out, you will not be able to view the locations you have visited, collected locations, and coupons in the cloud. You can view them again after logging in.`
: `退出登录后将无法查看云端的去过位置、收藏位置和卡券,重新登录即可查看。`,
confirmText: isEn ? `Ok` : `仍要退出`,
cancelText: isEn ? `Cancel` : `取消`,
});
if (!confirmed) return;
wx.removeStorageSync("user");
getApp().login();
},
toggleBulkDeleting({ detail: bulkDeleting }) {
this.setData({ bulkDeleting });
},
toCouponCenter() {
getApp().sensors?.track("MyCouponClick", {
function_name: "卡券中心",
});
if (!getApp().checkUser()) return;
const token = getApp().globalData.user.access_token;
const url = `https://m.chinagoods.com/member/order/acupon?active=2&token=${token}`;
wx.navigateTo({
url: `/pages/wap/index?url=${encodeURIComponent(url)}`,
});
},
toItinerary() {
if (!getApp().checkUser()) return;
wx.navigateTo({
url: "/pages/itinerary/index",
});
},
onLoad(options) {
this.setItineraryCount = () =>
this.setData({
itineraryCount: getApp().itineraryList.filter(
({ status }) => status !== 1
).length,
});
getApp().events.on("itineraryRefresh", this.setItineraryCount);
this.setItineraryCount();
},
onUnload() {
if (this.setItineraryCount)
getApp().events.off("itineraryRefresh", this.setItineraryCount);
},
handleTab({ detail: tab }) {
wx.reLaunch({
url: tab.url,
});
},
/**
* 生命周期函数--监听页面初次渲染完成
*/
onReady() {},
/**
* 生命周期函数--监听页面显示
*/
onShow() {
wx.hideHomeButton();
getApp().blurPoi();
},
/**
* 生命周期函数--监听页面隐藏
*/
onHide() {},
/**
* 生命周期函数--监听页面卸载
*/
/**
* 页面相关事件处理函数--监听用户下拉动作
*/
onPullDownRefresh() {},
/**
* 页面上拉触底事件的处理函数
*/
onReachBottom() {},
/**
* 用户点击右上角分享
*/
onShareAppMessage() {},
});
{
"homeButton": false,
"usingComponents": {
"lang-btn": "../../components/lang-btn/index",
"phone": "./phone/index",
"collection": "./collection/index",
"custom-tab-bar": "../../components/custom-tab-bar/index",
"bluetooth-modal": "../../components/bluetooth-modal/index",
"location-modal": "../../components/location-modal/index",
"auth-modal": "../../components/auth-modal/index"
}
}
.mine {
position: relative;
width: 100vw;
height: 100vh;
background: #f7f7f7;
.scoller {
width: 100vw;
height: 100vh;
}
.top {
position: relative;
width: 100vw;
height: 156px;
border-radius: 0px 0px 16px 16px;
background: linear-gradient(180deg, #ff8165 0%, #fd443e 100%);
.icon {
position: absolute;
top: 0;
right: 0;
height: 156px;
width: 161px;
}
button {
&.info {
background: none;
display: flex;
width: auto;
padding: initial;
}
}
.info {
position: absolute;
top: 40px;
left: 10px;
display: flex;
gap: 12px;
.avatar {
width: 56px;
height: 56px;
border-radius: 50%;
border: 1px solid rgba(255, 255, 255, 0.8);
object-fit: cover;
object-position: center;
}
.texts {
display: flex;
flex-direction: column;
gap: 4px;
height: 56px;
justify-content: center;
align-items: flex-start;
}
.t1 {
color: #fff;
text-align: center;
font-family: "PingFang SC";
font-size: 20px;
font-style: normal;
font-weight: 600;
line-height: 25px;
}
.t2 {
color: rgba(255, 255, 255, 0.7);
font-family: "PingFang SC";
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: 18px;
}
}
.logout {
position: absolute;
top: 64px;
right: 10px;
padding: 0 16px;
color: #fff;
font-family: "PingFang SC";
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: 32px;
border-radius: 99px;
background: rgba(0, 0, 0, 0.1);
}
}
.content {
position: relative;
padding-top: 36px;
display: flex;
flex-direction: column;
align-items: center;
.btns {
position: absolute;
top: -28px;
left: 0;
right: 0;
margin: auto;
width: calc(100vw - 20px);
display: flex;
gap: 8px;
.btn {
flex: 1;
height: 56px;
border-radius: 8px;
background: var(--W-100, #fff);
display: flex;
padding: 0 12px;
gap: 6px;
align-items: center;
.icon {
width: 32px;
height: 32px;
}
.text {
flex: 1;
color: #333;
font-family: "PingFang SC";
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: 18px;
}
.arrow {
width: 14px;
height: 14px;
}
}
}
}
.bulkDeleting {
position: absolute;
}
}
<view class="mine">
<scroll-view class="scoller" scroll-y>
<view class="top">
<lang-btn type="white"></lang-btn>
<image class="icon" src="./icon.png" mode="" />
<view wx:if="{{user}}" class="info">
<image wx:if="{{user.user.headPortrait}}" src="{{user.user.headPortrait}}" class="avatar" mode="" />
<image wx:else src="./defaultAvatar.png" class="avatar" mode="" />
<view class="texts">
<view class="t1">{{user.user.nickName}}</view>
<view class="t2">
<phone></phone>
</view>
</view>
</view>
<view wx:else class="info" bind:tap="toLogin">
<image src="./defaultAvatar.png" class="avatar" mode="" />
<view class="texts">
<view class="t1">{{isEn?"Login":"您好,请登录"}}</view>
</view>
</view>
<view class="logout" wx:if="{{user}}" bind:tap="handleLogout">{{isEn?"Exit":"退出"}}</view>
<view class="logout" wx:else bind:tap="toLogin">{{isEn?"Login/Registration":"登录/注册"}}</view>
</view>
<view class="content">
<view class="btns">
<view class="btn" bind:tap="toCouponCenter">
<image class="icon" src="./tickets.png" mode="" />
<view class="text">{{isEn?"Card Center":"卡券中心"}}</view>
<image class="arrow" src="./arrow.png" mode="" />
</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>
<image class="arrow" src="./arrow.png" mode="" />
</view>
</view>
<collection wx:if="{{user}}" is-en="{{isEn}}" bind:togglebulkdelete="toggleBulkDeleting" bind:showbluetoothmodal="openBluetoothModal" bind:showlocationmodal="openLocationModal" bind:showauthmodal="openAuthModal"></collection>
</view>
<view style="height: 90px;"></view>
</scroll-view>
<custom-tab-bar wx:if="{{!bulkDeleting}}" current-index="{{2}}" 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>
</view>
\ No newline at end of file
import userBehavior from "../../../behaviors/userBehavior";
import { getStarredPhone } from "../../../util";
Component({
behaviors: [userBehavior],
properties: {},
data: { phone: null },
observers: {
user(user) {
let phone = user?.user?.phone || null;
if (phone) {
phone = getStarredPhone(phone);
}
this.setData({
phone,
});
},
},
lifetimes: {
attached() {
this.setData({
phone: this.data.user?.user?.phone
? getStarredPhone(this.data.user?.user?.phone)
: null,
});
},
},
/**
* 组件的方法列表
*/
methods: {},
});
{
"component": true,
"usingComponents": {}
}
\ No newline at end of file
/* pages/mine/phone/index.wxss */
\ No newline at end of file
<text>{{phone}}</text>
\ No newline at end of file
import langBehavior from "../../behaviors/langBehavior";
import { findNearbyLocations } from "../../util";
Page({
behaviors: [langBehavior],
/**
* 页面的初始数据
*/
data: {
showTip1: false,
showTip2: false,
showTip3: false,
couponId: null,
receivingCoupon: false,
poi: null,
lastNav: null,
kcal: null,
},
/**
* 生命周期函数--监听页面加载
*/
async onLoad(options) {
const { lastNav } = getApp().globalData;
this.setData({ lastNav });
try {
this.setData({
kcal: ((6 * 65 * lastNav.time) / 60).toFixed(1),
});
} 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 });
const rewardConfig = await this.getRewardConfig();
console.log(rewardConfig);
if (!rewardConfig) return;
const { pointStatus, couponStatus, couponId } = rewardConfig;
if (couponStatus === 1 && couponId) {
const isvalid = await this.isCouponOnValid(couponId);
if (isvalid) {
try {
getApp().sensors?.track("ExposureActivityCoupon", {
short_market_name: "二区东",
...(poi.facilityId
? {
booth_floor: poi.tx.poi_fl_seq,
}
: {
booth_addr_street: poi.ywZh.boothAddrStreet,
booth_no: poi.ywZh.boothNo,
industry: poi.ywZh.frontIndustryCategory,
booth_floor: poi.ywZh.addrFloor,
}),
});
} catch (error) {
console.error("埋点失败", error);
}
this.setData({ showTip2: true, couponId });
}
}
if (pointStatus === 1 && poi && poi.shopId) this.receivePoint(poi.shopId);
},
toIndex() {
getApp().globalData.lastNav = null;
wx.reLaunch({
url: "/pages/index/index",
});
},
async isCouponOnValid(couponId) {
try {
const { marketurl, get } = getApp();
const {
data: { code, data },
} = await get(`${marketurl}/coupon/coupon/info?couponId=${couponId}`);
if (code === 200) {
console.log("券详情", data);
if (data.leftQuantity > 0) return true;
return false;
} else {
return false;
}
} catch (error) {
return false;
}
},
async getRewardConfig() {
const { get, qmurl } = getApp();
try {
const {
data: { code, data },
} = await get(`${qmurl}/api/v1/applet/getRewardConfig`);
if (code === 200) {
return data;
}
return null;
} catch (error) {
console.error(error);
return null;
}
},
async collectPoi(poi) {
const { collectionApi } = getApp();
const success = await collectionApi.post(poi);
if (success) getApp().refreshCollection();
console.log(`自动收藏poi${success ? "成功" : "失败"}`);
},
getEndPoi() {
const { txQmMap, globalData, indexData } = getApp();
const { lastNav } = globalData;
const { latitude, longitude, floorName } = lastNav.endPoint;
try {
const list = findNearbyLocations(
latitude,
longitude,
floorName,
indexData
);
if (list.length) {
console.log("txQmMap[list[0]]", txQmMap[list[0]]);
return txQmMap[list[0]];
} else {
return null;
}
} catch (error) {
return null;
}
},
async receivePoint(shopId) {
try {
const { marketurl, post, globalData } = getApp();
const {
data: { code, message },
} = await post(
`${marketurl}/bpmms/map/send/token?shopId=${shopId}`,
{},
{
Authorization: `Bearer ${globalData.user.access_token}`,
userId: globalData.user.user.userId,
}
);
if (code === 20000) {
console.log("积分领取成功" + message);
if (!this.data.showTip2) this.setData({ showTip1: true });
} else {
console.error("积分领取失败" + message);
}
} catch (error) {
console.error(error);
} finally {
this.setData({ receivingCoupon: false });
}
},
async receiveCoupon() {
try {
if (this.data.receivingCoupon) return;
this.setData({ receivingCoupon: true });
try {
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,
}
: {
booth_addr_street: poi.ywZh.boothAddrStreet,
industry: poi.ywZh.frontIndustryCategory,
booth_floor: poi.ywZh.addrFloor,
}),
});
} catch (error) {
console.error("埋点失败", error);
}
const { marketurl, post, globalData } = getApp();
const { couponId } = this.data;
const {
data: { code, message },
} = await post(
`${marketurl}/coupon/record/user/add?conform=2&couponId=${couponId}`,
{},
{
Authorization: `Bearer ${globalData.user.access_token}`,
}
);
if (code === 200) {
this.setData({ showTip2: false, showTip3: true });
setTimeout(() => {
this.setData({ showTip3: false });
}, 3000);
} else {
wx.showToast({
title: message,
icon: "none",
});
}
} catch (error) {
console.error(error);
} finally {
this.setData({ receivingCoupon: false });
}
},
closeTip2() {
try {
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,
}
: {
booth_addr_street: poi.ywZh.boothAddrStreet,
industry: poi.ywZh.frontIndustryCategory,
booth_floor: poi.ywZh.addrFloor,
}),
});
} catch (error) {
console.error("埋点失败", error);
}
this.setData({ showTip2: false });
},
doNothing() {},
onReady() {},
/**
* 生命周期函数--监听页面显示
*/
onShow() {},
/**
* 生命周期函数--监听页面隐藏
*/
onHide() {},
/**
* 生命周期函数--监听页面卸载
*/
onUnload() {
wx.reLaunch({
url: "/pages/index/index",
});
},
/**
* 页面相关事件处理函数--监听用户下拉动作
*/
onPullDownRefresh() {},
/**
* 页面上拉触底事件的处理函数
*/
onReachBottom() {},
/**
* 用户点击右上角分享
*/
onShareAppMessage() {
return {
title: this.data.isEn
? "Share Chinagoods Nav District 2 East indoor navigation miniprogram"
: "分享义乌商贸城二区东室内导航小程序,助你场内高效找店",
path: `/pages/index/index?fromshare=true`,
imageUrl: this.data.isEn
? `https://cdnimg.chinagoods.com/png/2025/08/13/qmzbxkjo3fse9xs5c2uujdc60tsc3wum.png`
: `https://cdnimg.chinagoods.com/png/2025/08/13/mszq3hfw1m6vgnhb4dt9dhapyxr1yt6b.png`,
};
},
});
{
"usingComponents": {}
}
.nav-end {
position: relative;
width: 100vw;
height: 100vh;
background: #f7f7f7;
.bg {
position: absolute;
width: 100vw;
height: 100vh;
z-index: 1;
}
.content {
position: absolute;
top: 306px;
left: 0;
right: 0;
bottom: 0;
border-radius: 8px 8px 0 0;
background: #fff;
z-index: 2;
display: flex;
flex-direction: column;
padding-top: 24px;
padding-left: 26px;
padding-right: 26px;
padding-bottom: 58px;
.r1 {
color: #333;
font-family: "PingFang SC";
font-size: 20px;
font-style: normal;
font-weight: 600;
line-height: 25px;
}
.r2 {
margin-top: 27px;
width: 100%;
display: flex;
flex-direction: column;
gap: 12px;
.row {
width: 100%;
display: flex;
align-items: center;
justify-content: space-between;
.left {
display: inline-flex;
flex-shrink: 0;
gap: 6px;
color: #333;
text-align: center;
font-family: "PingFang SC";
font-size: 13px;
font-style: normal;
font-weight: 400;
line-height: normal;
letter-spacing: -0.076px;
align-items: center;
.green {
width: 6px;
height: 6px;
border-radius: 50%;
background: #28c7a7;
}
.red {
width: 6px;
height: 6px;
border-radius: 50%;
background: #fe5252;
}
}
.right {
display: inline-flex;
color: #999;
text-align: center;
font-family: "PingFang SC";
font-size: 14px;
font-style: normal;
font-weight: 600;
line-height: 18px;
gap: 16px;
align-items: center;
&.end {
color: #333;
}
}
}
.border {
width: 100%;
border-top: 1px dashed #e5e5e5;
}
}
.r3 {
flex: 1;
margin-top: 32px;
padding: 0 13px;
display: flex;
justify-content: space-between;
.col {
display: flex;
flex-direction: column;
align-items: center;
height: 101px;
.c1 {
width: 48px;
height: 48px;
}
.c2 {
margin-top: 8px;
color: #333;
text-align: center;
font-family: "PingFang SC";
font-size: 20px;
font-style: normal;
font-weight: 600;
line-height: 25px;
}
.c3 {
margin-top: 4px;
color: #999;
text-align: center;
/* 说明文字-Caption1/R */
font-family: "PingFang SC";
font-size: 12px;
font-style: normal;
font-weight: 400;
line-height: 16px; /* 133.333% */
}
}
}
.r4 {
display: flex;
justify-content: center;
align-items: center;
gap: 8px;
.r41 {
display: flex;
justify-content: center;
align-items: center;
flex: 1;
height: 38px;
border-radius: 99px;
border: 1px solid #e5e5e5;
color: #666;
text-align: center;
font-family: "PingFang SC";
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: 18px;
}
.r42 {
flex: 1;
display: flex;
justify-content: center;
align-items: center;
width: 100%;
padding: 0;
padding-left: 0;
padding-right: 0;
padding-top: 0;
padding-bottom: 0;
height: 38px;
border-radius: 99px;
background: linear-gradient(90deg, #fa643c 0%, #e92927 100%);
color: #fff;
font-family: "PingFang SC";
font-size: 14px;
font-style: normal;
font-weight: 600;
line-height: 18px;
}
}
}
.tip1 {
position: absolute;
top: 11px;
left: 0;
right: 0;
margin: auto;
display: flex;
flex-direction: column;
width: 264px;
height: 78px;
justify-content: center;
align-items: center;
gap: 10px;
border-radius: 8px;
background: rgba(0, 0, 0, 0.7);
z-index: 3;
.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;
}
}
.tip2 {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.6);
z-index: 4;
.coupon {
position: absolute;
top: 129px;
left: 0;
right: 0;
margin: auto;
width: 300px;
height: 272.5px;
z-index: 1;
}
.btn {
position: absolute;
top: 368px;
left: 0;
right: 0;
margin: auto;
width: 180px;
height: 42px;
z-index: 2;
}
.close {
position: absolute;
display: flex;
top: 128px;
right: 43px;
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;
}
}
.size-20 {
width: 20px;
height: 20px;
}
}
<view class="nav-end">
<image wx:if="{{poi&&poi.ywZh&&poi.ywZh.cover}}" class="bg" src="{{poi.ywZh.cover}}" mode="widthFix" />
<image wx:else class="bg" src="https://cdnimg.chinagoods.com/png/2025/08/14/uatel7r2zww3s0ytfkuo3yy6de3qup8n.png" mode="widthFix" />
<view class="content" wx:if="{{lastNav}}">
<view class="r1">你已到达目的地</view>
<view class="r2">
<view class="row">
<view class="left">
<view class="green"></view>
起点
</view>
<view class="right">
<view>{{lastNav.startPoint.name}}</view>
<view>{{lastNav.startPoint.floorName}}</view>
</view>
</view>
<view class="border"></view>
<view class="row">
<view class="left">
<view class="red"></view>
终点
</view>
<view class="right end">
<view>{{lastNav.endPoint.name}}</view>
<view>{{lastNav.endPoint.floorName}}</view>
</view>
</view>
</view>
<view class="r3">
<view class="col">
<image class="c1" src="./i1.png"></image>
<view class="c2">{{lastNav.distance}}</view>
<view class="c3">里程 m</view>
</view>
<view class="col">
<image class="c1" src="./i2.png"></image>
<view class="c2">{{lastNav.time}}</view>
<view class="c3">用时 min</view>
</view>
<view class="col">
<image class="c1" src="./i3.png"></image>
<view class="c2">{{kcal}}</view>
<view class="c3">卡路里 kcal</view>
</view>
</view>
<view class="r4">
<view class="r41" bind:tap="toIndex">返回首页</view>
<button class="r42" open-type="share">邀请好友使用</button>
</view>
</view>
<view class="tip1" wx:if="{{showTip1}}">
<view class="t1">积分领取成功!</view>
<view class="t2">导航到店积分福利已发放至您的会员账户</view>
</view>
<view class="tip2" bing: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">
<image class="size-20" src="./close.png" mode="" />
</view>
</view>
<view class="tip1" wx:if="{{showTip3}}">
<view class="t1">领取成功!</view>
<view class="t2">活动券将发放您的会员账户</view>
</view>
</view>
\ No newline at end of file
import langBehavior from "../../behaviors/langBehavior";
import poiFocusBehavior from "../../behaviors/poiFocusBehavior";
import modalBehavior from "../../behaviors/modalBehavior";
import { getShopShareTitle, getFacilityShareTitle } from "../../util";
Page({
behaviors: [langBehavior, poiFocusBehavior, modalBehavior],
data: {
buildingId: "3307001549220",
floorName: "1F",
floors: [
{ floorName: "3F", displayName: "3F" },
{ floorName: "2F", displayName: "2F" },
{ floorName: "1F", displayName: "1F" },
],
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];
if (shop) {
if (shop.tx) this.setData({ floorName: shop.tx.poi_fl_name });
app.focusPoi(shop);
}
});
} else {
const shop = app.shopIdMap[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);
}
});
} else {
const facility = app.facilityIdMap[facilityId];
if (facility) {
if (facility.tx) this.setData({ floorName: facility.tx.poi_fl_name });
app.focusPoi(facility);
}
}
}
this.setUserLocation = () => {
this.setData({ userLocation: getApp().userLocation });
};
getApp().events.on("userLocation", this.setUserLocation);
this.setUserLocation();
},
onUnload() {
if (this.setUserLocation)
getApp().events.off("userLocation", this.setUserLocation);
},
doNothing() {},
/**
* 生命周期函数--监听页面初次渲染完成
*/
onReady() {},
/**
* 生命周期函数--监听页面显示
*/
onShow() {},
/**
* 生命周期函数--监听页面隐藏
*/
onHide() {},
/**
* 生命周期函数--监听页面卸载
*/
/**
* 页面相关事件处理函数--监听用户下拉动作
*/
onPullDownRefresh() {},
/**
* 页面上拉触底事件的处理函数
*/
onReachBottom() {},
/**
* 用户点击右上角分享
*/
onShareAppMessage() {
if (this.data.act) {
return {
title: this.data.act.name,
path: `/pages/index/index?actId=${this.data.act.id}&fromshare=true`,
};
}
if (!this.data.focusedPoi) return;
if (this.data.focusedPoi.facility) {
const title = getFacilityShareTitle(this.data.focusedPoi, this.data.isEn);
return {
title,
path: `/pages/index/index?facilityId=${this.data.focusedPoi.facilityId}&fromshare=true`,
};
} else {
const title = getShopShareTitle(this.data.focusedPoi, this.data.isEn);
return {
title,
path: `/pages/index/index?shopId=${this.data.focusedPoi.shopId}&fromshare=true`,
imageUrl: this.data.focusedPoi.ywZh.cover,
};
}
},
});
{
"disableScroll": true,
"usingComponents": {
"map-wrapper": "./map-wrapper/index",
"lang-btn": "../../components/lang-btn/index",
"popup": "../../components/popup/index",
"floors": "../../components/floors/index",
"bluetooth-modal": "../../components/bluetooth-modal/index",
"location-modal": "../../components/location-modal/index",
"auth-modal": "../../components/auth-modal/index"
}
}
/* pages/index/index.wxss */
.index {
position: relative;
width: 100vw;
height: 100vh;
overflow: hidden;
display: flex;
flex-direction: column;
.floors {
position: absolute;
display: flex;
flex-direction: column;
bottom: 421px;
right: 10px;
padding: 7px 4px;
z-index: 2;
gap: 2px;
align-items: center;
border-radius: 8px;
background: var(--VI-0, #fff);
box-shadow: 0px 2px 5px 0px rgba(0, 0, 0, 0.05);
.arrow {
width: 8px;
height: 3px;
}
.list {
display: flex;
flex-direction: column;
gap: 4px;
.floor {
display: flex;
width: 32px;
height: 32px;
align-items: center;
justify-content: center;
color: #323337;
text-align: center;
font-family: DINPro;
font-size: 14px;
font-style: normal;
font-weight: 500;
border-radius: 8px;
&.active {
background: #fff3f3;
color: #e92927;
}
}
}
}
.go {
position: absolute;
top: 0;
left: 0;
right: 0;
height: 200px;
z-index: 2;
background: #fff;
}
.size-20 {
width: 20px;
height: 20px;
}
}
<view class="index">
<map-wrapper viewstate="{{viewstate}}" user-location="{{userLocation}}" reset-count="{{resetCount}}" building-id="{{buildingId}}" floor-name="{{floorName}}" bind:poi="handlePoi"> </map-wrapper>
<lang-btn wx:if="{{viewstate!=='fullscreen'}}"></lang-btn>
<bottom-hud bind:viewstate="handleBottomHudViewstate" bind:search="handleSearch" bind:ai="handleAi">
<floors wx:if="{{viewstate!=='fullscreen'&&!focusedPoi}}" floors="{{floors}}" floor-name="{{floorName}}"></floors>
</bottom-hud>
<popup wx:if="{{focusedPoi}}" is-index="{{true}}" bind:showbluetoothmodal="openBluetoothModal" bind:showlocationmodal="openLocationModal" bind:showauthmodal="openAuthModal">
<floors show-reset="{{false}}" floors="{{floors}}" floor-name="{{floorName}}"></floors>
</popup>
<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>
</view>
\ No newline at end of file
import poiFocusBehavior from "../../../behaviors/poiFocusBehavior";
const defaultScale = 20;
const defaultRotate = 329;
const defaultLatitude = 29.331723;
const defaultLongitude = 120.106838;
const defaultSkew = 30;
Component({
behaviors: [poiFocusBehavior],
properties: {
buildingId: String,
floorName: String,
acts: Array,
resetCount: Number,
userLocation: Object,
},
data: {
latitude: defaultLatitude,
longitude: defaultLongitude,
setting: {
skew: defaultSkew,
rotate: defaultRotate,
scale: defaultScale,
},
},
observers: {
buildingId() {
this.setIndoorFloor();
},
floorName() {
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) {
this.mapContext?.removeMarkers({
markerIds: [123],
fail: console.log,
});
} else {
this.handleFocusedPoi(poi);
}
} catch (error) {
console.error(error);
}
},
},
/**
* 组件的方法列表
*/
methods: {
setIndoorFloor() {
if (this.mapContext && this.data.buildingId && this.data.floorName) {
this.mapContext.setIndoorFloor({
buildingId: this.data.buildingId,
floorName: this.data.floorName,
});
}
},
handleIndoorChange(e) {},
async handleFocusedPoi(poi) {
try {
if (poi?.tx?.poi_location) {
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,
});
setTimeout(() => {
this.setData(
{
latitude: Number(poi.tx.poi_location.latitude),
longitude: Number(poi.tx.poi_location.longitude),
},
() =>
console.log("设置经纬度成功", {
latitude: Number(poi.tx.poi_location.latitude),
longitude: Number(poi.tx.poi_location.longitude),
})
);
}, 500);
}
} catch (error) {
console.error(error);
}
},
calloutTap({ detail: { markerId } }) {
const act = this.data.acts.find(({ id }) => id === markerId);
getApp().blurPoi();
this.triggerEvent("act", act);
},
handleModel() {
try {
if (!this.mapContext) return;
if (this.data.floorName === "1F") {
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 },
}),
});
},
fail: (e) => console.log("addVisualLayer", e),
});
} else {
this.mapContext.removeVisualLayer({
layerId: "08adaf0275db",
success: (e) => console.log("removeVisualLayer", e),
fail: (e) => console.log("removeVisualLayer", e),
});
}
} catch (error) {
console.error(error);
}
},
},
lifetimes: {
attached() {
this.mapContext = wx.createMapContext("poiMap", this);
this.mapContext.setCenterOffset({ offset: [0.5, 0.4] });
this.handleModel();
this.setIndoorFloor();
},
},
});
{
"component": true,
"usingComponents": {}
}
\ No newline at end of file
/* pages/index/bottom-hud/map-wrapper/index.wxss */
.map {
position: absolute;
top: 0;
left: 0;
width: 100vw;
height: calc(100vh - 90px);
z-index: 1;
}
<map id="poiMap" show-compass show-location="{{userLocation&&!(userLocation&&userLocation.floorName&&userLocation.floorName!==floorName)}}" enable-rotate subkey="QUIBZ-OTL6U-T2MVM-GMPIT-EON66-LGBCU" class="map" enable-indoor enable-overlooking enable-auto-max-overlooking setting="{{setting}}" max-scale="22" latitude="{{latitude}}" longitude="{{longitude}}">
</map>
\ No newline at end of file
import langBehavior from "../../behaviors/langBehavior";
Page({
behaviors: [langBehavior],
data: {},
/**
* 生命周期函数--监听页面加载
*/
onLoad(options) {},
/**
* 生命周期函数--监听页面初次渲染完成
*/
onReady() {},
/**
* 生命周期函数--监听页面显示
*/
onShow() {},
/**
* 生命周期函数--监听页面隐藏
*/
onHide() {},
/**
* 生命周期函数--监听页面卸载
*/
onUnload() {},
/**
* 页面相关事件处理函数--监听用户下拉动作
*/
onPullDownRefresh() {},
/**
* 页面上拉触底事件的处理函数
*/
onReachBottom() {},
/**
* 用户点击右上角分享
*/
onShareAppMessage() {},
});
{
"usingComponents": {
"lang-btn": "../../components/lang-btn/index"
}
}
.privacy {
width: 100vw;
height: 100vh;
font-size: 16px;
position: relative;
.scroller {
box-sizing: border-box;
width: 100vw;
height: 100vh;
padding: 0 16px;
}
.t1 {
font-size: 18px;
font-weight: 600;
text-align: center;
}
.t2 {
font-weight: 600;
margin-top: 16px;
}
.br {
height: 16px;
}
}
<view class="privacy">
<lang-btn type="black"></lang-btn>
<scroll-view scroll-y class="scroller" wx:if="{{isEn}}">
<view class="br"></view>
<view class="t1"> User Service Agreement and Privacy Policy</view>
<view class="br"></view>
Welcome to use the Yiwu Market AI Navigation.
<view class="t2">1. Special Reminders</view>
1.1 In order to provide you with better services, please read this agreement carefully. This agreement is the specification of rights and obligations established between you and this application regarding all behaviors involved in your registration and use on the application platform.When you log in and use the platform,it indicates that you have fully agreed to and accepted this agreement and arewilling to abide by all the rules and specifications in this agreement. If you do not agree,you can stop registering or using this application platform. lf you are a minor, you should also ask your guardian to read this agreement carefully and obtain his/their consent.
<view></view>
1.2 To improve users' usage experience and satisfaction, users agree that this application will conduct investigations, research, and analysis on user data based on users' operational behaviors, so as to further optimize the services.
<view class="t2">2.Service Contents</view>
2.1 All illegal and pornographic information is strictly prohibited on the Yiwu Market AI Navigation Mini Program platform.Those who violate the platform operation norms will have their accounts blocked.
<view></view>
2.2 The specific contents of the services provided by this application shall be provided by the application developer according to the actual situation.
<view></view> 2.3 Unless otherwise expressly stipulated in this registration and service agreement,allnew products, new functions, and new services launched by this application shall be subject to this registration and service agreement.
<view></view>
2.4 This application only provides relevant network services.Besides,the equipment related to therelevant network services (such as personal computers, mobile phones,and other devices related to accessing the Internetor mobile networks)and therequired expenses (such as telephone bills and Internet access fees paid for accessing the Internet,and mobile phone fees paid for using mobile networks)shall be borne by users themselves.
<view class="t2">3.Usage Rules</view>
3.1 User Account Registration
<view></view>
3.1.1 For users registering with this application system, only Chinese characters,English letters,numbers,and underscores and their combinations can be used.The use of spaces,various symbols,and special characters is prohibited,and the account name shall not exceed 16 characters (8 Chinese characters). Otherwise, registration will not begranted.
<view></view>
3.1.2 For users logging in through third-party cooperation websites,only Chinese characters,English letters, numbers, and underscores and their combinations can be used.The use of spaces,various symbols, and special characters is prohibited, and the account name shall not exceed 14 characters(7 Chinese characters).Otherwise,the platform has the right to only display the first 14 characters (7 Chinese characters) of the user account (if the user account has the same name as an existing user account of the application, the system will randomly add a character to distinguish it). Otherwise, registration will not be granted.
<view></view>
3.2 If it is found that the user account contains vulgar words or inappropriate names,the application reserves the right to cancel the user's qualification.
<view></view>
3.2.1 Please do not register with the real names, courtesy names, stage names, or pen names of Party and state leaders or other social celebrities.
<view></view>
3.2.2 Please do not register with the names of state institutions or other institutions.
<view></view>
3.2.3 Please do not register accounts with uncivilized, unhealthy names or accounts containing discriminatory, insulting, or obscene words.
<view></view>
3.2.4 Please do not register accounts that are likely to cause ambiguity,misunderstandings among others,or do not comply with legal regulations.
<view></view>
3.3 The ownership of the user account belongs to this application,and the user only has the right to use it.
<view></view>
3.4 Users have the obligation to ensure the security of their passwords and accounts. Users shall bear all responsibilities for any losses or damages caused by all activities conducted by using the passwords and accounts. This application shall not bear any responsibility.If users find that their accounts have been used without authorization or other security issues occur, they should immediately change the account passwords and keep them properly.If necessary,please feedback and notify the application administrator.This application shall not bear any responsibility for illegal use of accounts caused by hacker attacks or users' negligence in account management.
<view></view>
3.5 Users promise that they have complete intellectual property rights over all information (i.e., works stipulated in the Copyright Law of the People's Republic of China,including but not limited to texts,pictures, music, movies, performances, audio and video recordings,and computer programs,etc.published or uploaded by them on this application,or they have obtained legal authorization from relevant right holders. If users violate this provision and cause this application to be claimed by a third party, users shall fully compensate this application for all expenses (including but not limited to various compensation fees,litigation agency fees, and other reasonable expenses incurred).
<view></view>
3.6 When a third party believes that the information published or uploaded by a user on this application infringes its rights and sends a notice of rights to this application according to the Regulations on the Protection of the Right to Network Dissemination of Information or relevant laws, the user agrees that this application can independently judge and decide to delete the suspected infringing information.Unless the user submits written evidence materials to rule out the possibility of infringement, this application will not automatically restore the deleted information.
<view></view>
(1) Do not use the network service system for any illegalpurposes.
<view></view>
(2) Comply with all network agreements,regulations, and procedures related to network services.
<view></view>
(3) Do not use the services of this application to conduct any behaviors that may have an adverse impact on the normal operation of the Internet.
<view></view>
(4) Do not use the services of this application to conduct any behaviors that are unfavorable to this application.
<view></view>
3.7 If a user violates any of the above provisions when using network services, this application has the right to require the user to correct the violation or directly take all necessary measures (including but not limited to deleting the content uploaded by the user, suspending or terminating the user's right to use network services) to mitigate the impact caused by the user's improper behavior.
<view class="t2">4.LiabilityStatements</view>
4.1 Any website, unit, or individual who believes that this application or the relevant contentprovided by this application is suspected of infringing its legitimate rights and interests shall promptly provide this application with a written notice of rights, as well as provide proof of identity,proof of ownership,and detailed proof of infringement.After receiving the above legal documents, this application will cut off the relevant content as soon as possible to ensure the legitimate rights and interests of the relevant website, unit,or individual are protected.
<view></view>
4.2 Users explicitly agree that all risks and consequences existing in their use of the network services of this application shall be borne entirely by the users themselves, and this application shall not bear any responsibility.
<view></view>
4.3 This application cannot guarantee that the network services will definitely meet users' requirements,nor can it guarantee the timeliness, security, and accuracy of network services.
<view></view>
4.4 This application does not guarantee the accuracy and completeness of the external links setup for the convenience of users.Meanwhile,for the content on any web page pointed to by such external links that is not actually controlled by this application, this application shall not bear any responsibility.
<view class="t2">5. Intellectual Property Rights</view>
5.1 The unique logos, layout designs, and arrangement methods of this application are all copyrighted by this application.Without the permission and authorization of this application, they shall not be copied or reproduced at will.
<view></view>
5.2 Information obtained by users from the services of this application shall notbe copied or reproduced at will without the permission of this application.
<view></view>
5.3 The ownership of all contents of this application, including product descriptions, pictures, etc., belongs to the users of the Yiwu Market AI Navigation Mini Program,and no one shall reproduce them.
<view></view>
5.4 All content uploaded byusers of this application only represents the positions and views of the users themselves and has nothing to do with this application. The author shall bear all legal responsibilities.
<view></view>
5.5 The intellectual property rights of the above and other contents included in this service areprotected by law.Without the written permission of this application, users,orrelevant right holders,no one shall use them in any form or create relevant derivative works.
<view class="t2">6. PrivacyProtection</view>
6.1 This application will not disclose or provide individual users'registration information and non-public content stored in the platform when users use network services to third parties, except in the following circumstances:
<view></view>
(1) With the prior explicit authorization of users;
<view></view>
(2) According to relevant laws and regulations;
<view></view>
(3) In accordance with the requirements of relevant government departments;
<view></view>
(4)To safeguard the interests of the public.
<view></view>
6.2 This application may cooperate with third parties to provide relevant network services to users. In this case, if the third party agrees to assume the same responsibility for protecting user privacy as this platform,this platform has the right to provide users registration information and other information to the third party without separately notifying the users.
<view></view>
6.3 Without disclosing the privacy information of individual users,this application has the right to analyze the entire user database and make commercial use of it.
<view class="t2">7. Agreement Modification</view>
7.1 This application has the right to modify any terms of this agreement at anytime.Once the content of this agreement changes, this application will publish the modified agreement content on the application.If users do not agree with the above modifications, they can choose to stop using this application. This application can also choose to notify users of the modification content through other appropriate means (such as system notifications).
<view></view>
7.2 If users do not agree with the modifications made by this application to the relevant terms of this agreement, they have the right to stop using this application. If users continue to use this application, it shall be regarded as their acceptance of the modifications made by this application to the relevant terms of this agreement.
<view class="t2">8. Notice Delivery</view>
8.1 All notices from this application to users under this agreement can be delivered through web announcements, emails, system notifications, private messages, mobile phone text messages, or regular letters.Such notices shall be deemed to have been delivered to the recipients on the date of sending.
<view></view>
8.2 Users' notices to this application shall be delivered through the contact information officially announced by this application, such as the mailing address and email address.
<view class="br"></view>
<view class="br"></view>
</scroll-view>
<scroll-view scroll-y class="scroller" wx:else>
<view class="br"></view>
<view class="t1">用户服务协议及隐私政策</view>
<view class="br"></view>
您好!欢迎您使用小商导航小程序。
<view class="t2">1.特别提示</view>
1.1为了更好地为您提供服务,请您仔细阅读这份协议。本协议是您与本应用就您登录本应用平台进行注册及使用等所涉及的全部行为所订立的权利义务规范。您在登录和使用时,均表明您已完全同意并接受本协议,愿意遵守本协议的各项规则、规范的全部内容,若不同意则可停止注册或使用本应用平台。如您是未成年人,您还应要求您的监护人仔细阅读本协议,并取得他/他们的同意。
<view></view>
1.2为提高用户的使用感受和满意度,用户同意本应用将基于用户的操作行为对用户数据进行调查研究和分析,从而进一步优化服务。
<view class="t2">2.服务内容</view>
2.1小商导航小程序平台内严禁一切非法、涉黄信息,违反社区运营规范者,一律封号处理。
<view></view>
2.2本应用服务的具体内容由本应用制作者根据实际情况提供。
<view></view>
2.3除非本注册及服务协议另有其它明示规定,本应用所推出的新产品、新功能、新服务,均受到本注册及注册协议规范。
<view></view>
2.4本应用仅提供相关的网络服务,除此之外与相关网络服务有关的设备(如个人电脑、手机、及其他与接入互联网或移动网有关的装置)及所需的费用(如为接入互联网而支付的电话费及上网费、为使用移动网而支付的手机费)均应由用户自行负担。
<view class="t2">3.使用规则</view>
3.1用户帐号注册
<view></view>
3.1.1使用本应用系统注册的用户,只能使用汉字、英文字母、数字、下划线及它们的组合,禁止使用空格、各种符号和特殊字符,且最多不超过16个字符(8个汉字)注册,否则将不予注册。
<view></view>
3.1.2使用第三方合作网站登录的用户,只能使用汉字、英文字母、数字、下划线及它们的组合,禁止使用空格、各种符号和特殊字符,且最多不超过14个字符(7个汉字)注册,否则本社区有权只截取前14个字符(7个汉字)予以显示用户帐号(若该用户帐号与应用现有用户帐号重名,系统将随机添加一个字符以示区别),否则将不予注册。
<view></view>
3.2如发现用户帐号中含有不雅文字或不恰当名称的,本社区保留取消其用户资格的权利。
<view></view>
3.2.1请勿以党和国家领导人或其他社会名人的真实姓名、字号、艺名、笔名注册;
<view></view>
3.2.2请勿以国家机构或其他机构的名称注册;
<view></view>
3.2.3请勿注册不文明、不健康名字,或包含歧视、侮辱、猥亵类词语的帐号;
<view></view>
3.2.4请勿注册易产生歧义、引起他人误解或其它不符合法律规定的帐号。
<view></view>
3.3用户帐号的所有权归本应用,用户仅享有使用权。
<view></view>
3.4用户有义务保证密码和帐号的安全,用户利用该密码和帐号所进行的一切活动引起的任何损失或损害,由用户自行承担全部责任,本应用不承担任何责任。如用户发现帐号遭到未授权的使用或发生其他任何安全问题,应立即修改帐号密码并妥善保管,如有必要,请反馈通知本应用管理人员。因黑客行为或用户的保管疏忽导致帐号非法使用,本应用不承担任何责任。
<view></view>
3.5用户承诺对其发表或者上传于本应用的所有信息(即属于《中华人民共和国著作权法》规定的作品,包括但不限于文字、图片、音乐、电影、表演和录音录像制品和电脑程序等)均享有完整的知识产权,或者已经得到相关权利人的合法授权;如用户违反本条规定造成本应用被第三人索赔的,用户应全额补偿本应用的一切费用(包括但不限于各种赔偿费、诉讼代理费及为此支出的其它合理费用);
<view></view>
3.6当第三方认为用户发表或者上传于本应用的信息侵犯其权利,并根据《信息网络传播权保护条例》或者相关法律规定向本应用发送权利通知书时,用户同意本应用可以自行判断决定删除涉嫌侵权信息,除非用户提交书面证据材料排除侵权的可能性,本应用将不会自动恢复上述删除的信息;
<view></view>
(1)不得为任何非法目的而使用网络服务系统;
<view></view>
(2)遵守所有与网络服务有关的网络协议、规定和程序;
<view></view>
(3)不得利用本应用的服务进行任何可能对互联网的正常运转造成不利影响的行为;
<view></view>
(4)不得利用本应用服务进行任何不利于本应用的行为。
<view></view>
3.7如用户在使用网络服务时违反上述任何规定,本应用有权要求用户改正或直接采取一切必要的措施(包括但不限于删除用户上传的内容、暂停或终止用户使用网络服务的权利以减轻用户不当行为而造成的影响。
<view class="t2">4.责任声明</view>
4.1任何网站、单位或者个人如认为本应用或者本应用提供的相关内容涉嫌侵犯其合法权益,应及时向本应用提供书面权力通知,并提供身份证明、权属证明及详细侵权情况证明。本应用在收到上述法律文件后,将会尽快切断相关内容以保证相关网站、单位或者个人的合法权益得到保障。
<view></view>
4.2用户明确同意其使用本应用网络服务所存在的风险及一切后果将完全由用户本人承担,本应用对此不承担任何责任。
<view></view>
4.3本应用无法保证网络服务一定能满足用户的要求,也不保证网络服务的及时性、安全性、准确性。
<view></view>
4.4本应用不保证为方便用户而设置的外部链接的准确性和完整性,同时,对于该等外部链接指向的不由本应用实际控制的任何网页上的内容,本应用不承担任何责任。
<view class="t2">5.知识产权</view>
5.1本应用特有的标识、版面设计、编排方式等版权均属本应用享有,未经本应用许可授权,不得任意复制或转载。
<view></view>
5.2用户从本应用的服务中获得的信息,未经本应用的许可,不得任意复制或转载。
<view></view>
5.3本应用的所有内容,包括商品描述、图片等内容所有权归属于义信购小程序的用户,任何人不得转载。
<view></view>
5.4本应用所有用户上传内容仅代表用户自己的立场和观点,与本应用无关,由作者本人承担一切法律责任。
<view></view>
5.5上述及其他任何本服务包含的内容的知识产权均受到法律保护,未经本应用、用户或相关权利人书面许可,任何人不得以任何形式进行使用或创造相关衍生作品。
<view class="t2">6.隐私保护</view>
6.1本应用不对外公开或向第三方提供单个用户的注册资料及用户在使用网络服务时存储在本社区的非公开内容,但下列情况除外:
<view></view>
(1)事先获得用户的明确授权;
<view></view>
(2)根据有关的法律法规要求;
<view></view>
(3)按照相关政府主管部门的要求;
<view></view>
(4) 为维护社会公众的利益。
<view></view>
6.2本应用可能会与第三方合作向用户提供相关的网络服务,在此情况下,如该第三方同意承担与本社区同等的保护用户隐私的责任,则本社区有权将用户的注册资料等信息提供给该第三方,并无须另行告知用户。
<view></view>
6.3在不透露单个用户隐私资料的前提下,本应用有权对整个用户数据库进行分析并对用户数据库进行商业上的利用。
<view class="t2">7.协议修改</view>
7.1本应用有权随时修改本协议的任何条款,一旦本协议的内容发生变动,本应用将会在本应用上公布修改之后的协议内容,若用户不同意上述修改,则可以选择停止使用本应用。本应用也可选择通过其他适当方式(比如系统通知)向用户通知修改内容。
<view></view>
7.2如果不同意本应用对本协议相关条款所做的修改,用户有权停止使用本应用。如果用户继续使用本应用,则视为用户接受本应用对本协议相关条款所做的修改。
<view></view>
<view class="t2">8.通知送达</view>
8.1本协议项下本应用对于用户所有的通知均可通过网页公告、电子邮件、系统通知、微博管理帐号主动联系、私信、手机短信或常规的信件传送等方式进行;该等通知于发送之日视为已送达收件人。
<view></view>
8.2用户对于本应用的通知应当通过本应用对外正式公布的通信地址、电子邮件地址等联系信息进行送达。
<view class="br"></view>
<view class="br"></view>
</scroll-view>
</view>
\ No newline at end of file
import modalBehavior from "../../behaviors/modalBehavior";
Page({
behaviors: [modalBehavior],
data: {
q: "",
facTypes: [],
userLocation: null,
},
async getFacTypes() {
const { get, qmurl } = getApp();
try {
const {
data: { data },
} = await get(`${qmurl}/api/v1/applet/getFacilityClassList`);
if (data && data.length !== undefined) {
this.setData({
facTypes: data,
});
}
} catch (error) {
console.log(error);
}
},
async onLoad() {
this.getFacTypes();
this.setUserLocation = () => {
this.setData({ userLocation: getApp().userLocation });
};
getApp().events.on("userLocation", this.setUserLocation);
this.setUserLocation();
},
onUnload() {
getApp().blurPoi();
if (this.setUserLocation)
getApp().events.off("userLocation", this.setUserLocation);
},
handleQ({ detail: q }) {
this.setData({ q });
},
handleAi() {
if (!getApp().checkUser()) return;
wx.navigateTo({
url: "/pages/aisearch/index",
});
},
/**
* 生命周期函数--监听页面初次渲染完成
*/
onReady() {},
/**
* 生命周期函数--监听页面显示
*/
onShow() {},
/**
* 生命周期函数--监听页面隐藏
*/
onHide() {},
/**
* 生命周期函数--监听页面卸载
*/
/**
* 页面相关事件处理函数--监听用户下拉动作
*/
onPullDownRefresh() {},
/**
* 页面上拉触底事件的处理函数
*/
onReachBottom() {},
/**
* 用户点击右上角分享
*/
onShareAppMessage() {},
});
{
"usingComponents": {
"search-bar": "./search-bar/index",
"search-default": "./search-default/index",
"search-result": "./search-result/index",
"bluetooth-modal": "../../components/bluetooth-modal/index",
"location-modal": "../../components/location-modal/index",
"auth-modal": "../../components/auth-modal/index"
}
}
.search {
width: 100vw;
height: 100vh;
background: #f7f7f7;
display: flex;
flex-direction: column;
align-items: center;
}
<view class="search">
<search-bar q="{{q}}" bind:q="handleQ" bind:ai="handleAi"></search-bar>
<search-default wx:if="{{!q}}" fac-types="{{facTypes}}" bind:q="handleQ" bind:showbluetoothmodal="openBluetoothModal" bind:showlocationmodal="openLocationModal" bind:showauthmodal="openAuthModal"></search-default>
<search-result wx:if="{{q}}" q="{{q}}" user-location="{{userLocation}}" bind:showbluetoothmodal="openBluetoothModal" bind:showlocationmodal="openLocationModal" bind:showauthmodal="openAuthModal"></search-result>
<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>
</view>
\ No newline at end of file
import langBehavior from "../../../behaviors/langBehavior";
Component({
behaviors: [langBehavior],
properties: { q: String },
/**
* 组件的初始数据
*/
data: {
placeholderIndex: 0,
placeholders: [
{
en: "District 2-East New Energy Product Market Welcome!",
cn: "欢迎光临二区东新能源产品市场",
},
{
en: "Search store number/facilities/block/exit number",
cn: "搜铺号/公共设施/街区号/出入口号",
},
{
en: "Click or search shoplD for click navigation",
cn: "点击或搜索任意商铺可一键导航前往",
},
],
},
observers: {},
methods: {
handleInput({ detail: { value } }) {
this.triggerEvent("q", value);
},
handleClose() {
this.triggerEvent("q", "");
},
aiTap() {
this.triggerEvent("ai");
},
},
lifetimes: {
attached() {
this.interval = setInterval(() => {
this.setData({
placeholderIndex: (this.data.placeholderIndex + 1) % 3,
});
}, 3000);
},
detached() {
if (this.interval) {
clearInterval(this.interval);
this.interval = null;
}
},
},
});
{
"component": true,
"usingComponents": {}
}
\ No newline at end of file
.search-bar {
width: calc(100vw - 20px);
height: 48px;
border-radius: 8px;
background: var(--W-100, #fff);
display: flex;
gap: 8px;
padding: 0 12px;
align-items: center;
margin-top: 8px;
margin-bottom: 12px;
.search {
width: 24px;
height: 24px;
}
.text {
flex: 1;
overflow: hidden;
color: #333;
text-overflow: ellipsis;
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: 18px;
white-space: nowrap;
overflow: hidden;
}
.close {
width: 32px;
height: 32px;
margin: 0 2px;
}
.ai {
position: relative;
width: 40px;
height: 40px;
.icon {
width: 40px;
height: 40px;
}
.text {
position: absolute;
bottom: 0;
left: -7px;
right: -7px;
margin: auto;
font-size: 9px;
font-style: normal;
font-weight: 600;
line-height: 12px;
height: 12px;
text-align: center;
color: #000;
border-radius: 99px;
border: 0.2px solid var(--W-100, #fff);
background: rgba(255, 255, 255, 0.08);
backdrop-filter: blur(33.05883026123047px);
}
}
}
<view class="search-bar">
<image src="./search.png" class="search" mode="" />
<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="" />
<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 collectBehavior from "../../../behaviors/collectBehavior";
import { toRoutePlan, confirm } from "../../../util";
Component({
behaviors: [langBehavior, collectBehavior],
properties: { facTypes: Array },
/**
* 组件的初始数据
*/
data: {
collectPoiList: [],
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 });
},
},
methods: {
handleFacType({ currentTarget: { id: index } }) {
const facType = this.data.facTypes[index];
this.triggerEvent("q", this.data.isEn ? facType.nameEn : facType.name);
},
toMine() {
wx.navigateTo({
url: "/pages/mine/index",
});
},
async handleGo({ currentTarget: { id: index } }) {
const { poi } = this.data.collectPoiList[index];
if (!poi.tx) return;
try {
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),
}
: {}),
...(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,
}),
});
} catch (error) {
console.error("埋点失败", error);
}
try {
const { isEn } = this.data;
const res = await toRoutePlan(poi, isEn);
if (res === "showBluetoothModal")
return this.triggerEvent("showbluetoothmodal");
if (res === "showLocationModal")
return this.triggerEvent("showlocationmodal");
if (res === "showAuthModal") return this.triggerEvent("showauthmodal");
} catch (error) {
console.error(error);
}
},
async getSearchHistory() {
const { post, qmurl, shopIdMap, facilityIdMap, globalData } = getApp();
if (!globalData.user) return;
if (!shopIdMap) return;
try {
const {
data: { data },
} = await post(`${qmurl}/api/v1/applet/getUserSearchHistoryList`, {
userId: globalData.user.user.userId,
});
this.setData({
searchHistory: data
.map(({ shopType, shopId }) =>
shopType === 1
? shopIdMap[shopId]
: shopType === 2
? facilityIdMap[shopId]
: null
)
.filter((el) => el)
.slice(0, 10),
});
} catch (error) {
console.error(error);
}
},
async clearSearchHistory() {
const { isEn } = this.data;
const confirmed = await confirm({
title: "",
content: isEn ? `Whether to clear the history?` : `是否清空历史记录?`,
confirmText: isEn ? `Clear` : `确认`,
cancelText: isEn ? `Cancel` : `取消`,
});
if (!confirmed) return;
const { get, qmurl, globalData } = getApp();
try {
const {
data: { code },
} = await get(
`${qmurl}/api/v1/applet/clearUserSearchHistory?userId=${globalData.user.user.userId}`
);
if (code === 200) {
wx.showToast({
title: this.data.isEn ? "success" : "清空成功",
icon: "none",
});
this.getSearchHistory();
}
} catch (error) {
console.error(error);
}
},
handleShop({ currentTarget: { id: shopId } }) {
wx.navigateTo({
url: `/pages/poi-map/index?shopId=${shopId}`,
});
},
handleFacility({ currentTarget: { id: facilityId } }) {
wx.navigateTo({
url: `/pages/poi-map/index?facilityId=${facilityId}`,
});
},
async handleShopGo({ currentTarget: { id: shopId } }) {
try {
const index = this.data.searchHistory.findIndex(
(poi) => poi.shopId == shopId
);
const poi = this.data.searchHistory[index];
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),
}
: {}),
...(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,
}),
});
} catch (error) {
console.error("埋点失败", error);
}
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");
} catch (error) {
console.error(error);
}
},
async handleFacGo({ currentTarget: { id: facilityId } }) {
try {
const index = this.data.searchHistory.findIndex(
(poi) => poi.facilityId == facilityId
);
const poi = this.data.searchHistory[index];
getApp().sensors?.track("NavEntryBtnClick", {
page_name: "历史搜索记录",
short_market_name: "二区东",
rank: index + 1,
...(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,
}),
});
} catch (error) {
console.error("埋点失败", error);
}
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");
} catch (error) {
console.error(error);
}
},
},
lifetimes: {
attached() {
this.getSearchHistory();
},
},
});
{
"component": true,
"usingComponents": {
"end": "../../../components/end/index",
"shop-name": "../../../components/shop-name/index",
"shop-addr": "../../../components/shop-addr/index",
"shop-maingoods": "../../../components/shop-maingoods/index"
}
}
.search-default {
flex: 1;
height: calc(100vh - 68px);
.list {
display: flex;
flex-direction: column;
align-items: center;
gap: 8px;
width: 100vw;
.fac-scroll {
width: calc(100vw - 20px);
border-radius: 8px;
background: #fff;
height: 88px;
padding: 7px 0;
box-sizing: border-box;
.facs {
display: inline-flex;
gap: 0;
.fac {
display: flex;
padding: 8px 4px 5px 4px;
box-sizing: border-box;
flex-direction: column;
justify-content: center;
align-items: center;
gap: 4px;
width: 64px;
height: 100%;
overflow: hidden;
.icon {
width: 40px;
height: 40px;
}
.txt {
width: 56px;
color: #333;
text-align: center;
font-family: "PingFang SC";
font-size: 12px;
font-style: normal;
font-weight: 400;
line-height: normal;
letter-spacing: -0.08px;
text-align: center;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
}
}
}
.collection {
width: calc(100vw - 20px);
display: flex;
height: 54px;
gap: 8px;
border-radius: 8px;
background: #fff;
align-items: center;
padding: 0 8px 0 12px;
.left {
display: flex;
align-items: center;
gap: 4px;
color: #333;
font-family: "PingFang SC";
font-size: 12px;
font-style: normal;
font-weight: 400;
line-height: 14px; /* 116.667% */
letter-spacing: -0.08px;
.cn {
width: 24px;
}
}
.mid {
flex: 1;
overflow: hidden;
display: inline-flex;
gap: 8px;
.poi {
display: inline-flex;
flex-shrink: 0;
align-items: center;
padding: 0 14px;
height: 32px;
border-radius: 99px;
background: #f3f3f3;
gap: 6px;
color: #333;
font-family: "PingFang SC";
font-size: 12px;
font-style: normal;
font-weight: 400;
line-height: 16px; /* 133.333% */
}
}
.right {
display: flex;
align-items: center;
.badge {
display: flex;
align-items: center;
justify-content: center;
width: 18px;
height: 18px;
border-radius: 99px;
background: #e92927;
color: #fff;
text-align: center;
font-family: "PingFang SC";
font-size: 12px;
font-style: normal;
font-weight: 400;
line-height: 9px; /* 75% */
}
}
}
.search-history {
width: calc(100vw - 20px);
padding: 12px 0;
border-radius: 8px;
background: #fff;
gap: 12px;
display: flex;
flex-direction: column;
align-items: center;
.header {
width: calc(100vw - 44px);
display: flex;
align-items: center;
justify-content: space-between;
color: #333;
font-family: "PingFang SC";
font-size: 16px;
font-style: normal;
font-weight: 600;
line-height: 22px;
.right {
color: #666;
font-family: "PingFang SC";
font-size: 14px;
font-style: normal;
font-weight: 400;
}
}
.shop {
display: flex;
width: calc(100vw - 20px);
padding: 0 12px;
gap: 7px;
overflow: hidden;
.right {
flex: 1;
display: flex;
gap: 8px;
overflow: hidden;
.content {
flex: 1;
overflow: hidden;
display: flex;
flex-direction: column;
gap: 6px;
.t1 {
color: #333;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
font-family: "PingFang SC";
font-size: 16px;
font-style: normal;
font-weight: 600;
line-height: 22px;
}
.t2 {
color: #666;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
font-family: "PingFang SC";
font-size: 12px;
font-style: normal;
font-weight: 400;
line-height: 18px;
}
.meta {
display: flex;
flex-direction: column;
gap: 4px;
}
}
.go {
display: flex;
flex-direction: column;
gap: 6px;
color: #333;
text-align: center;
font-family: "PingFang SC";
font-size: 12px;
font-style: normal;
font-weight: 600;
line-height: 16px;
}
}
}
.border {
width: calc(100vw - 44px);
height: 1px;
background: #f7f7f7;
}
}
}
.size-16 {
width: 16px;
height: 16px;
}
.size-24 {
width: 24px;
height: 24px;
}
.size-32 {
width: 32px;
height: 32px;
}
}
<scroll-view scroll-y class="search-default">
<view class="list">
<scroll-view scroll-x show-scrollbar="{{false}}" enhanced class="fac-scroll" wx:if="{{facTypes.length}}">
<view class="facs">
<view class="fac" wx:for="{{facTypes}}" wx:key="id" id="{{index}}" bind:tap="handleFacType">
<image class="icon" src="{{item.logo}}" mode="" />
<view class="txt">{{isEn?item.nameEn:item.name}}</view>
</view>
</view>
</scroll-view>
<view wx:if="{{collectPoiList.length}}" class="collection" bind:tap="toMine">
<view class="left">
<image src="./star.png" class="size-24" mode="" />
<view class="{{isEn?'':'cn'}}">{{isEn?'Collect':'我的收藏'}}</view>
</view>
<view class="mid">
<view wx:for="{{collectPoiList}}" wx:key="id" class="poi" id="{{index}}" catch:tap="handleGo">
<image src="./shop.png" class="size-16" mode="" />
<shop-name shop="{{item.poi}}"></shop-name>
</view>
</view>
<view class="right">
<view class="badge">{{collectPoiList.length}}</view>
<image class="size-16" src="./arrowright.png" mode="" />
</view>
</view>
<view class="search-history" wx:if="{{searchHistory.length}}">
<view class="header">
{{isEn?'Search History':'历史搜索记录'}}
<view class="right" bind:tap="clearSearchHistory"> {{isEn?'Clear History':'清空记录'}}</view>
</view>
<block wx:for="{{searchHistory}}" wx:key="index">
<view class="border"></view>
<view class="shop" wx:if="{{item.facility}}" bind:tap="handleFacility" id="{{item.facilityId}}">
<image class="size-16" style="margin-top: 3px;" src="./loc.png" mode="" />
<view class="right">
<view class="content">
<view class="t1">
<shop-name shop="{{item}}"></shop-name>
</view>
<view class="meta">
<view class="t2">
{{isEn?item.facility.addressEn:item.facility.address}}
</view>
</view>
</view>
<view class="go" wx:if="{{item.tx}}" id="{{item.facilityId}}" catch:tap="handleFacGo">
<image class="size-32" src="./nav.png" mode="" />
{{isEn?'Go here':'到这去'}}
</view>
</view>
</view>
<view class="shop" wx:else bind:tap="handleShop" id="{{item.shopId}}">
<image class="size-16" style="margin-top: 3px;" src="./loc.png" mode="" />
<view class="right">
<view class="content">
<view class="t1">
<shop-name shop="{{item}}"></shop-name>
</view>
<view class="meta" wx:if="{{item.ywZh}}">
<shop-addr class="t2" shop="{{item}}"></shop-addr>
<view wx:if="{{isEn?item.maingoodsEn:item.maingoods}}" class="t2">
{{isEn?item.maingoodsEn:item.maingoods }}
</view>
</view>
<view wx:else class="meta">
<view class="t2">
{{isEn?item.facility.addressEn:item.facility.address}}
</view>
</view>
</view>
<view class="go" id="{{item.shopId}}" wx:if="{{item.tx}}" catch:tap="handleShopGo">
<image class="size-32" src="./nav.png" mode="" />
{{isEn?'Go here':'到这去'}}
</view>
</view>
</view>
</block>
<end></end>
</view>
</view>
</scroll-view>
\ No newline at end of file
import langBehavior from "../../../behaviors/langBehavior";
import { toRoutePlan, getDistance } from "../../../util";
Component({
behaviors: [langBehavior],
/**
* 组件的属性列表
*/
properties: { q: String, userLocation: Object },
/**
* 组件的初始数据
*/
data: {
list: [],
isLoading: false,
requestCount: 0,
lastQ: "",
pageSize: 10,
pageNo: 1,
hasMore: true,
allList: [], // 添加存储所有数据的数组
},
/**
* 组件的方法列表
*/
methods: {
async getList(loadMore = false) {
const { post, qmurl, shopIdMap, facilityIdMap } = getApp();
const { isEn, q, requestCount, lastQ, pageNo, pageSize } = this.data;
// 如果是加载更多,直接从缓存读取数据
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 (!shopIdMap) return;
if (q === lastQ) return;
if (!q) return;
const currentRequest = requestCount + 1;
this.setData({
requestCount: currentRequest,
isLoading: true,
lastQ: q,
list: [],
pageNo: 1,
hasMore: true,
});
try {
const {
data: { data },
} = await post(
`${qmurl}/api/v1/applet/searchShopFacilityList`,
{ keyword: q },
{ lang: isEn ? "en" : "zh" }
);
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 };
try {
const lat = Number(poi.tx.poi_location.latitude);
const lng = Number(poi.tx.poi_location.longitude);
return {
poi,
distance: Math.ceil(
getDistance(
lat,
lng,
this.data.userLocation.latitude,
this.data.userLocation.longitude
)
),
};
} catch (error) {
return { poi, distance: null };
}
});
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; // 正常按距离升序排序
});
// 缓存所有数据
const currentPageData = allList.slice(0, pageSize);
this.setData({
allList,
list: currentPageData,
isLoading: false,
hasMore: allList.length > pageSize,
pageNo: 2,
});
}
} catch (error) {
console.error(error);
if (currentRequest === this.data.requestCount) {
this.setData({
isLoading: false,
allList: [],
hasMore: false,
});
}
}
},
async addSearchHistory(shopId, isShop) {
const { post, qmurl, globalData } = getApp();
if (!globalData.user) return;
try {
const {
data: { code, msg },
} = await post(`${qmurl}/api/v1/applet/addUserSearchHistory`, {
userId: globalData.user.user.userId,
shopId: shopId,
shopType: isShop ? 1 : 2,
});
if (code === 200) {
console.log("增加搜索历史成功");
} else {
console.log(`增加搜索历史失败[${msg}]`);
}
} catch (error) {
console.error(error);
}
},
handleShop({ currentTarget: { id: shopId } }) {
try {
const index = this.data.list.findIndex(
({ poi }) => poi.shopId == shopId
);
const poi = this.data.list[index].poi;
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),
}
: {}),
...(poi.facilityId
? {
nav_target_type: poi.facility.name,
}
: {
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);
}
this.addSearchHistory(shopId, true);
wx.navigateTo({
url: `/pages/poi-map/index?shopId=${shopId}`,
});
},
handleFacility({ currentTarget: { id: facilityId } }) {
try {
const index = this.data.list.findIndex(
({ poi }) => poi.facilityId == facilityId
);
const poi = this.data.list[index].poi;
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),
}
: {}),
...(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,
}),
});
} catch (error) {
console.error("埋点失败", error);
}
this.addSearchHistory(facilityId, false);
wx.navigateTo({
url: `/pages/poi-map/index?facilityId=${facilityId}`,
});
},
async handleShopGo({ currentTarget: { id: shopId } }) {
try {
const index = this.data.list.findIndex(
({ poi }) => poi.shopId == shopId
);
const poi = this.data.list[index].poi;
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),
}
: {}),
...(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,
}),
});
} catch (error) {
console.error("埋点失败", error);
}
if (!getApp().checkUser()) return;
this.addSearchHistory(shopId, true);
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");
} catch (error) {
console.error(error);
}
},
async handleFacGo({ currentTarget: { id: facilityId } }) {
try {
const index = this.data.list.findIndex(
({ poi }) => poi.facilityId == facilityId
);
const poi = this.data.list[index].poi;
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),
}
: {}),
...(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,
}),
});
} catch (error) {
console.error("埋点失败", error);
}
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);
if (res === "showBluetoothModal")
return this.triggerEvent("showbluetoothmodal");
if (res === "showLocationModal")
return this.triggerEvent("showlocationmodal");
if (res === "showAuthModal") return this.triggerEvent("showauthmodal");
} catch (error) {
console.error(error);
}
},
handleScrollToLower() {
if (this.data.hasMore && !this.data.isLoading) {
this.getList(true);
}
},
},
lifetimes: {
attached() {
const app = getApp();
if (app.shopIdMap) {
this.getList();
} else {
app.events.once("globalData", () => {
this.getList();
});
}
},
},
observers: {
q() {
this.getList();
},
},
});
{
"component": true,
"usingComponents": {
"loading": "../../../components/loading/index",
"end": "../../../components/end/index",
"empty": "../../../components/empty/index",
"shop-name": "../../../components/shop-name/index",
"shop-addr": "../../../components/shop-addr/index",
"highlight-text": "../../../components/highlight-text/index"
}
}
.search-result {
flex: 1;
height: calc(100vh - 68px);
.list {
width: calc(100vw - 20px);
padding: 12px 0;
border-radius: 8px;
background: #fff;
gap: 12px;
display: flex;
flex-direction: column;
align-items: center;
.shop {
display: flex;
width: calc(100vw - 20px);
padding: 0 12px;
gap: 7px;
overflow: hidden;
.right {
flex: 1;
display: flex;
gap: 8px;
overflow: hidden;
.content {
flex: 1;
overflow: hidden;
display: flex;
flex-direction: column;
gap: 6px;
.t1 {
color: #333;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
font-family: "PingFang SC";
font-size: 16px;
font-style: normal;
font-weight: 600;
line-height: 22px;
}
.t2 {
color: #666;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
font-family: "PingFang SC";
font-size: 12px;
font-style: normal;
font-weight: 400;
line-height: 18px;
}
.t3 {
color: #333;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
font-family: "PingFang SC";
font-size: 12px;
font-style: normal;
font-weight: 400;
line-height: 18px;
}
.meta {
display: flex;
flex-direction: column;
gap: 4px;
}
}
.go {
display: flex;
flex-direction: column;
gap: 6px;
color: #333;
text-align: center;
font-family: "PingFang SC";
font-size: 12px;
font-style: normal;
font-weight: 600;
line-height: 16px;
}
}
}
.border {
width: calc(100vw - 44px);
height: 1px;
background: #f7f7f7;
}
}
.empty {
padding-top: calc(50vh - 88px - 68px);
}
.size-16 {
width: 16px;
height: 16px;
}
.size-32 {
width: 32px;
height: 32px;
}
}
<scroll-view scroll-y class="search-result" bindscrolltolower="handleScrollToLower" lower-threshold="200">
<loading wx:if="{{isLoading}}"></loading>
<view class="list" wx:if="{{list.length}}">
<block wx:for="{{list}}" wx:key="index">
<view class="border" wx:if="{{index>0}}"></view>
<view class="shop" wx:if="{{item.poi.facility}}" bind:tap="handleFacility" id="{{item.poi.facilityId}}">
<image class="size-16" style="margin-top: 3px;" src="./loc.png" mode="" />
<view class="right">
<view class="content">
<view class="t1">
<shop-name shop="{{item.poi}}" keyword="{{q}}"></shop-name>
</view>
<view class="meta">
<view class="t2">
<highlight-text text="{{isEn?item.poi.facility.addressEn:item.poi.facility.address}}" keyword="{{q}}"></highlight-text>
</view>
</view>
</view>
<view class="go" wx:if="{{item.poi.tx}}" id="{{item.poi.facilityId}}" catch:tap="handleFacGo">
<image class="size-32" src="./nav.png" mode="" />
{{item.distance!==null? item.distance + 'm' : isEn?'Go here':'到这去'}}
</view>
</view>
</view>
<view class="shop" wx:else bind:tap="handleShop" id="{{item.poi.shopId}}">
<image class="size-16" style="margin-top: 3px;" src="./loc.png" mode="" />
<view class="right">
<view class="content">
<view class="t1">
<shop-name shop="{{item.poi}}" keyword="{{q}}"></shop-name>
</view>
<view class="meta" wx:if="{{item.poi.ywZh}}">
<shop-addr class="t2" shop="{{item.poi}}" keyword="{{q}}"></shop-addr>
<view wx:if="{{isEn?item.poi.maingoodsEn:item.poi.maingoods}}" class="t2">
<highlight-text text="{{isEn?item.poi.maingoodsEn:item.poi.maingoods}}" keyword="{{q}}"></highlight-text>
</view>
<view class="t2" wx:if="{{isEn&&item.poi.ywEn}}">
<highlight-text text="{{item.poi.ywEn.contacter + ' ' + item.poi.contactPhoneEn}}" keyword="{{q}}"></highlight-text>
</view>
<view wx:else class="t2">
<highlight-text text="{{item.poi.ywZh.contacter + ' ' + item.poi.contactPhone}}" keyword="{{q}}"></highlight-text>
</view>
</view>
</view>
<view class="go" id="{{item.poi.shopId}}" 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>
</view>
</view>
</block>
<end wx:if="{{!isLoading&&list.length}}"></end>
</view>
<view class="empty" wx:if="{{!isLoading&&list.length===0}}">
<empty></empty>
</view>
</scroll-view>
\ No newline at end of file
import langBehavior from "../../behaviors/langBehavior";
import collectBehavior from "../../behaviors/collectBehavior";
import modalBehavior from "../../behaviors/modalBehavior";
import { getShopShareTitle, toRoutePlan } from "../../util";
Page({
behaviors: [langBehavior, collectBehavior, modalBehavior],
data: {
shopId: null,
shop: null,
describe: "",
products: [],
collected: false,
},
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() {
if (this.data.shop)
this.setData({ collected: this.isCollected(this.data.shop) });
},
onIsEnChange() {
if (this.data.shopId) this.getDetail(this.data.shopId);
},
back() {
const pages = getCurrentPages();
if (pages.length > 1) {
wx.navigateBack();
} else {
wx.redirectTo({
url: "/pages/index/index",
});
}
},
async getDetail(shopId) {
const { post, get, marketurl, md5, salt, shopIdMap } = getApp();
const { isEn } = this.data;
if (!shopIdMap) return;
const shop = shopIdMap[shopId];
if (!shop) return this.back();
const { boothNo } = shop.ywZh;
try {
let {
data: { data },
} = await post(`${marketurl}/sms/v1/shops/third/getDetail`, {
boothNo,
lang: isEn ? "en" : "zh",
keySecret: md5(md5(salt + boothNo)),
});
if (data) {
this.setData({ describe: data.describe });
}
this.setData({
shop,
});
const {
data: {
data: { data: products },
},
} = await get(
`${marketurl}/fpms/v1/product/hot/shop?shopId=${shop.shopId}&currentPage=1&pageSize=10`
);
try {
products.forEach((product) => {
try {
product.yuan = Math.floor(product.salePrice / 100);
product.fen =
"." + String(product.salePrice % 100).padStart(2, "0");
} catch (error) {
console.error(error);
}
try {
const poi = shop;
getApp().sensors?.track("ExposureGoodsActivity", {
goods_id: product.id,
goods_name: product.goodsTitle,
shop_id: poi.shopId,
shop_name: poi.ywZh.name,
store_name: poi.ywZh.name,
booth_no: poi.ywZh.boothNo,
short_market_name: "二区东",
booth_addr_street: poi.ywZh.boothAddrStreet,
industry: poi.ywZh.frontIndustryCategory,
booth_floor: poi.ywZh.addrFloor,
});
} catch (error) {
console.error("埋点失败", error);
}
});
} catch (error) {
console.error(error);
}
this.setData({
products,
});
} catch (error) {
console.error(error);
}
if (this.data.shop) {
this.setCollected();
}
},
toProduct({ currentTarget: { id } }) {
try {
const poi = this.data.shop;
const product = this.data.products.find((p) => p.id == id);
getApp().sensors?.track("GoodsActivityClick", {
goods_id: product.id,
goods_name: product.goodsTitle,
shop_id: poi.shopId,
shop_name: poi.ywZh.name,
store_name: poi.ywZh.name,
booth_no: poi.ywZh.boothNo,
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/wap/index?url=${encodeURIComponent(
`https://m.chinagoods.com/product/${id}`
)}`,
});
},
toMore() {
wx.navigateTo({
url: `/pages/wap/index?url=${encodeURIComponent(
`https://m.chinagoods.com/shop/${this.data.shopId}`
)}`,
});
},
async handleGo() {
try {
const poi = this.data.shop;
getApp().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,
}),
});
} catch (error) {
console.error("埋点失败", error);
}
try {
const { isEn, shop } = this.data;
const res = await toRoutePlan(shop, isEn);
if (res === "showBluetoothModal")
return this.setData({ showBluetoothModal: true });
if (res === "showLocationModal") return this.openLocationModal();
if (res === "showAuthModal") return this.openAuthModal();
} catch (error) {
console.log(error);
}
},
async handleCollect() {
if (!getApp().checkUser()) return;
this.toggleCollection(this.data.shop);
},
/**
* 生命周期函数--监听页面初次渲染完成
*/
onReady() {},
/**
* 生命周期函数--监听页面显示
*/
onShow() {},
/**
* 生命周期函数--监听页面隐藏
*/
onHide() {},
/**
* 生命周期函数--监听页面卸载
*/
/**
* 页面相关事件处理函数--监听用户下拉动作
*/
onPullDownRefresh() {},
/**
* 页面上拉触底事件的处理函数
*/
onReachBottom() {},
/**
* 用户点击右上角分享
*/
onShareAppMessage() {
if (this.data.shop) {
const title = getShopShareTitle(this.data.shop, this.data.isEn);
return {
title,
path: `/pages/index/index?shopId=${this.data.shop.shopId}&fromshare=true`,
imageUrl: this.data.shop.ywZh.cover,
};
}
},
});
{
"usingComponents": {
"loading": "../../components/loading/index",
"end": "../../components/end/index",
"lang-btn": "../../components/lang-btn/index",
"shop-bottom": "../../components/shop-bottom/index",
"shop-name": "../../components/shop-name/index",
"shop-addr": "../../components/shop-addr/index",
"shop-maingoods": "../../components/shop-maingoods/index",
"bluetooth-modal": "../../components/bluetooth-modal/index",
"location-modal": "../../components/location-modal/index",
"auth-modal": "../../components/auth-modal/index"
}
}
.shop {
position: relative;
width: 100vw;
height: 100vh;
background: #f7f7f7;
.cover {
width: 100vw;
height: 281px;
object-position: top;
}
.scroll {
position: absolute;
width: 100vw;
top: 0;
left: 0;
height: calc(100vh - 90px);
.holder {
width: 100vw;
height: 231px;
}
.content {
display: inline-flex;
flex-direction: column;
align-items: center;
gap: 8px;
position: relative;
background: 0 50px / 100% 100% no-repeat
linear-gradient(180deg, #f7f7f7 0, #f7f7f7 100%);
.b1 {
display: flex;
flex-direction: column;
align-items: center;
width: 100vw;
min-height: 70px;
background: top / 100% 60px no-repeat
linear-gradient(180deg, rgba(255, 255, 255, 0) 3.33%, #f7f7f7 100%);
// backdrop-filter: blur(4px);
.inner {
margin-top: 10px;
width: calc(100vw - 20px);
border-radius: 8px;
background: #fff;
padding: 16px 12px;
}
.c-gap-6 {
display: flex;
flex-direction: column;
gap: 6px;
}
.gap-4 {
display: flex;
gap: 4px;
}
.gap-6 {
display: flex;
gap: 6px;
align-items: center;
}
.shop-name-icon {
flex-shrink: 0;
padding: 2.5px 0;
width: 20px;
height: 25px;
}
.t1 {
color: #333;
font-family: "PingFang SC";
font-size: 20px;
font-style: normal;
font-weight: 600;
line-height: 25px;
}
.t2 {
color: #666;
font-family: "PingFang SC";
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: 18px;
}
.stick {
width: 1px;
height: 11px;
background: #666;
}
.t3 {
color: #666;
font-family: "PingFang SC";
font-size: 13px;
font-style: normal;
font-weight: 400;
line-height: 18px;
}
}
.b2 {
border-radius: 8px;
background: #fff;
padding: 12px;
width: calc(100vw - 20px);
.t1 {
color: #333;
font-family: "PingFang SC";
font-size: 16px;
font-style: normal;
font-weight: 600;
line-height: 22px;
}
.t2 {
color: #333;
font-family: "PingFang SC";
font-size: 13px;
font-style: normal;
font-weight: 400;
line-height: 21px;
margin-top: 4px;
}
}
.b3 {
position: relative;
width: calc(100vw - 20px);
border-radius: 8px;
background: linear-gradient(180deg, #fff 0%, #f7f7f7 41.8%);
overflow: hidden;
.bgtop {
position: absolute;
top: -36px;
height: 43px;
width: 100vw;
left: -10px;
border-radius: 100vw;
background: rgba(255, 188, 157, 0.4);
filter: blur(20px);
}
.border-top {
position: absolute;
top: 0;
left: 0;
right: 0;
height: 1px;
background: linear-gradient(
90deg,
rgba(255, 255, 255, 0.3) 0%,
rgba(255, 255, 255, 0.98) 26.6%,
rgba(255, 255, 255, 0.98) 75.86%,
rgba(255, 255, 255, 0.3) 100%
);
}
.r1 {
display: flex;
padding: 12px;
padding-bottom: 14px;
justify-content: space-between;
align-items: flex-end;
.t1 {
color: #333;
font-family: "PingFang SC";
font-size: 16px;
font-style: normal;
font-weight: 600;
line-height: 22px;
}
.t2 {
display: flex;
align-items: center;
gap: 2px;
color: #666;
font-family: "PingFang SC";
font-size: 12px;
font-style: normal;
font-weight: 400;
line-height: 16px;
}
.size-10 {
width: 10px;
height: 10px;
}
}
.grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 8px;
.product {
width: calc((100vw - 28px) / 2);
overflow: hidden;
border-radius: 8px;
background: #fff;
.avatar {
width: calc((100vw - 28px) / 2);
height: calc((100vw - 28px) / 2);
}
.texts {
padding: 10px;
display: flex;
flex-direction: column;
gap: 4px;
.t1 {
color: #333;
font-family: "PingFang SC";
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: 18px;
}
.t2 {
display: flex;
gap: 1px;
align-items: baseline;
color: #ed383e;
.left {
font-size: 12px;
font-style: normal;
font-weight: 400;
line-height: 16px;
}
.mid {
font-size: 16px;
font-style: normal;
font-weight: 600;
line-height: 22px;
}
.right {
font-size: 12px;
font-style: normal;
font-weight: 600;
line-height: 16px;
}
}
}
}
}
}
}
}
.bottom {
position: absolute;
bottom: 0;
left: 0;
width: 100vw;
height: 90px;
background: #fff;
border-top: 1px solid var(--B-3, rgba(0, 0, 0, 0.03));
padding: 12px;
padding-top: 8px;
display: flex;
gap: 27px;
.btns {
display: flex;
gap: 16px;
height: 40px;
.btn {
display: flex;
flex-direction: column;
align-items: center;
gap: 4px;
width: auto;
padding: 0;
padding-left: 0;
padding-right: 0;
padding-top: 0;
padding-bottom: 0;
background-color: none;
background: none;
color: #999;
font-family: "PingFang SC";
font-size: 10px;
font-style: normal;
font-weight: 400;
line-height: 12px;
.size-24 {
width: 24px;
height: 24px;
}
}
}
.go {
display: flex;
flex: 1;
height: 38px;
border-radius: 99px;
background: linear-gradient(90deg, #fa643c 0%, #e92927 100%);
gap: 4px;
align-items: center;
justify-content: center;
.size-20 {
width: 20px;
height: 20px;
}
color: #fff;
font-family: "PingFang SC";
font-size: 14px;
font-style: normal;
font-weight: 600;
line-height: 18px;
}
}
}
<block wx:if="{{!shop}}" class="shop">
<loading></loading>
</block>
<view wx:else class="shop">
<lang-btn type="black"></lang-btn>
<image class="cover" wx:if="{{shop.ywZh}}" src="{{shop.ywZh.cover}}" mode="widthFix" />
<image class="cover" wx:elif="{{shop.ywEn}}" src="{{shop.ywEn.cover}}" mode="widthFix" />
<scroll-view scroll-y class="scroll">
<view class="holder"></view>
<view class="content">
<view class="b1">
<view class="inner">
<view class="c-gap-6">
<view class="gap-4">
<image src="./shop.png" class="shop-name-icon" mode="" />
<view class="t1">
<shop-name shop="{{shop}}"></shop-name>
</view>
</view>
<view class="gap-6 t2">
<shop-addr shop="{{shop}}" big-gap="{{true}}"></shop-addr>
</view>
<view class="t3">
<shop-maingoods shop="{{shop}}"></shop-maingoods>
</view>
</view>
</view>
</view>
<view class="b2" wx:if="{{describe}}">
<view class="t1">{{isEn?'Shop introduction':'商铺简介'}}</view>
<view class="t2">{{describe}}</view>
</view>
<view class="b3">
<view class="bgtop"></view>
<view class="border-top"></view>
<view class="r1">
<view class="t1">{{isEn?'Top picks':'精彩推荐'}}</view>
<view class="t2" bind:tap="toMore">
{{isEn?'more':'更多'}}
<image class="size-10" src="./right.png" mode="" />
</view>
</view>
<view class="grid">
<view class="product" wx:for="{{products}}" wx:key="id" id="{{item.id}}" bind:tap="toProduct">
<image src="{{item.coverPic[0]}}" class="avatar" mode="" />
<view class="texts">
<view class="t1">{{item.goodsTitle}}</view>
<view class="t2">
<view class="left">¥</view>
<view class="mid">{{item.yuan}}</view>
<view class="right">{{item.fen}}</view>
</view>
</view>
</view>
</view>
</view>
<view style="margin-bottom: 15px">
<end></end>
</view>
</view>
</scroll-view>
<shop-bottom bind:collect="handleCollect" collected="{{collected}}" show-go="{{shop.tx}}" bind:go="handleGo"></shop-bottom>
<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>
</view>
\ No newline at end of file
import langBehavior from "../../../behaviors/langBehavior";
import poiFocusBehavior from "../../../behaviors/poiFocusBehavior";
Component({
behaviors: [langBehavior, poiFocusBehavior],
properties: {
facTypes: Array,
activeId: Number,
badgeNum: Number,
},
/**
* 组件的初始数据
*/
data: {
viewState: "default",
height: 188,
touchStart: null,
doTransition: false,
moveTouches: null,
endTouches: null,
windowHeight: wx.getWindowInfo().windowHeight,
scrollTop: 0,
nextTop: 0,
scrollEndCount: 0,
},
observers: {
focusedPoi(v) {
if (v) {
this.setData({ viewState: "collapsed" });
} else {
this.setData({ viewState: "default" });
}
},
},
methods: {
async getFacTypes() {
const { get, qmurl } = getApp();
try {
const {
data: { data },
} = await get(`${qmurl}/api/v1/applet/getFacilityClassList`);
if (data && data.length !== undefined) {
this.setData({ facTypes: data });
}
} catch (error) {
console.log(error);
}
},
focusFacType({ currentTarget: { id } }) {
this.triggerEvent("factypetap", Number(id));
},
},
lifetimes: {
attached() {},
},
});
{
"component": true,
"usingComponents": {}
}
@keyframes slideInTop {
from {
bottom: -90px;
opacity: 0;
}
to {
bottom: 0;
opacity: 1;
}
}
.bottom-bg {
position: absolute;
bottom: 0;
z-index: 200;
display: flex;
flex-direction: column;
height: 178px;
width: 100vw;
animation: slideInTop 0.5s ease-out;
}
.bottom-hud {
position: absolute;
bottom: 0;
z-index: 200;
height: 178px;
width: 100vw;
background: #fff;
border-radius: 16px 16px 0 0;
animation: slideInTop 0.5s ease-out;
box-shadow: 0 -2px 8px 0 rgba(0, 0, 0, 0.05);
.scroll-view {
position: absolute;
z-index: 1;
left: 0;
right: 0;
top: 12px;
height: 74px;
width: 100vw;
.facs {
padding: 0 16px;
display: inline-flex;
gap: 0;
.fac {
position: relative;
display: flex;
width: 64px;
height: 74px;
padding: 8px 4px 5px 4px;
flex-direction: column;
justify-content: center;
align-items: center;
gap: 4px;
.icon {
width: 40px;
height: 40px;
flex-shrink: 0;
}
.badge {
position: absolute;
padding: 0 4px;
height: 16px;
top: 6px;
left: 42px;
color: #fff;
display: flex;
align-items: center;
justify-content: center;
font-family: "PingFang SC";
font-size: 12px;
font-style: normal;
font-weight: 600;
border-radius: 99px;
border: 1px solid #f8f8f8;
background: linear-gradient(90deg, #fa643c 0%, #e92927 100%);
}
.text {
color: #333;
font-family: "PingFang SC";
font-size: 12px;
font-style: normal;
font-weight: 400;
line-height: normal;
overflow: hidden;
-webkit-line-clamp: 1;
word-break: break-all;
}
&.active {
border-radius: 8px;
background: rgba(0, 0, 0, 0.03);
}
}
}
}
}
<view wx:if="{{facTypes&&facTypes.length}}">
<textarea disabled class="bottom-bg"></textarea>
<view class="bottom-hud">
<slot></slot>
<scroll-view id="scroller" scroll-x enhanced show-scrollbar="{{false}}" bounces="{{false}}" class="scroll-view">
<view class="facs">
<view class="fac {{activeId===item.id?'active':''}}" wx:for="{{facTypes}}" wx:key="id" id="{{item.id}}" bind:tap="focusFacType">
<view class="badge" wx:if="{{activeId===item.id}}">{{badgeNum}}</view>
<image class="icon" src="{{item.logo}}" mode="" />
<view class="text">{{isEn?item.nameEn:item.name}}</view>
</view>
</view>
</scroll-view>
</view>
</view>
\ No newline at end of file
// pages/wap/index.js
Page({
/**
* 页面的初始数据
*/
data: {
url: null,
},
/**
* 生命周期函数--监听页面加载
*/
onLoad({ url }) {
this.setData({ url: decodeURIComponent(url) });
},
/**
* 生命周期函数--监听页面初次渲染完成
*/
onReady() {},
/**
* 生命周期函数--监听页面显示
*/
onShow() {},
/**
* 生命周期函数--监听页面隐藏
*/
onHide() {},
/**
* 生命周期函数--监听页面卸载
*/
onUnload() {},
/**
* 页面相关事件处理函数--监听用户下拉动作
*/
onPullDownRefresh() {},
/**
* 页面上拉触底事件的处理函数
*/
onReachBottom() {},
/**
* 用户点击右上角分享
*/
onShareAppMessage() {},
});
{
"usingComponents": {}
}
\ No newline at end of file
/* pages/wap/index.wxss */
\ No newline at end of file
<web-view src="{{url}}" />
\ No newline at end of file
import { findNearbyLocations } from "./util";
const addNavHistory = async (shopId, isShop) => {
const { post, qmurl, globalData } = getApp();
if (!globalData.user) return;
try {
const {
data: { code, data, msg },
} = await post(`${qmurl}/api/v1/applet/addUserFootPrint`, {
userId: globalData.user.user.userId,
shopId: shopId,
shopType: isShop ? 1 : 2,
});
if (code === 200) {
console.log("增加导航历史成功");
return data;
} else {
console.log(`增加导航历史失败[${msg}]`);
return null;
}
} catch (error) {
console.error(error);
return null;
}
};
const getPoiByPoint = ({ latitude, longitude, floorName }) => {
const { txQmMap, indexData } = getApp();
// latitude = 29.3310262816717;
// longitude = 120.106450377363;
// floorName = "F2";
try {
const list = findNearbyLocations(latitude, longitude, floorName, indexData);
if (list.length) {
return txQmMap[list[0]];
} else return null;
} catch (error) {
console.error(error);
return null;
}
};
const getStarredPhone = (text) => {
return `${text.substring(0, 3)}****${text.substring(7)}`;
};
const pushMsgToShop = async (poi, options) => {
try {
const { post, marketurl, globalData, md5 } = getApp();
if (!globalData.user) return;
if (!poi.ywZh.merchantUserId) return;
const phone = getStarredPhone(globalData.user.user.phone);
const title = `意向采购商正向你店铺赶来,请提前做好接待准备`;
await post(
`${marketurl}/push/common/baidu/push`,
{
title,
content: `【${phone}】看中了你店铺的商品,正向你店铺赶来,${Math.ceil(
options.time
)}分钟后到达您的店铺看品!`,
userId: poi.ywZh.merchantUserId,
sign: md5(title),
},
{
Authorization: `Bearer ${globalData.user.access_token}`,
}
);
} catch (error) {
console.error(error);
}
};
const handleNavStart = async (options) => {
const poi = getPoiByPoint(options.endPoint);
console.log("导航开始poi", poi);
if (!poi) return;
const navHistoryId = await addNavHistory(
poi.facilityId ? poi.facilityId : poi.shopId,
!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();
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),
}
: {}),
nav_route: JSON.stringify(options.line),
nav_estimated_duration: options.time,
nav_distance: options.distance,
enter_nav_duration: options.renderTime / 1000,
from_x: options.startPoint.longitude,
from_y: options.startPoint.latitude,
nav_track_no: navHistoryId,
nav_type: "步行导航",
...(poi.facilityId
? {
nav_target_type: poi.facility.name,
booth_floor: poi.tx.poi_fl_seq,
}
: {
main_product_arr: poi.ywZh.mainGoods,
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);
}
};
const handleNavEnd = async (options) => {
const poi = getPoiByPoint(options.endPoint);
console.log("导航结束poi", poi);
if (!poi) return;
const { lastNavHistoryId } = 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),
}
: {}),
nav_duration: options.time * 60 * 1000,
from_x: options.startPoint.longitude,
from_y: options.startPoint.latitude,
is_reach_destination: options.isArrive,
nav_distance: options.distance,
burn_calories_num: (6 * 65 * options.time) / 60,
nav_type: "步行导航",
...(poi.facilityId
? {
nav_target_type: poi.facility.name,
booth_floor: poi.tx.poi_fl_seq,
}
: {
main_product_arr: poi.ywZh.mainGoods,
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);
}
};
module.exports = {
// 上报参数说明:
// distance 距离(米)
// startPoint 起点
// endPoint 终点
// line 路线
// renderTime 渲染时间(ms)
// time 路线规划时间(分钟)
// type 类型(start_navigation/end_navigation)
// location 用户当前位置
// isArrive 是否到达目的地结束导航
report(options) {
console.error("report 上报:", options);
if (options.type === "start_navigation") {
getApp().lastNavHistoryId = null;
handleNavStart(options);
}
if (options.type === "end_navigation") {
handleNavEnd(options);
if (options.isArrive) {
getApp().globalData.lastNav = options;
}
}
},
};
{
"compileType": "miniprogram",
"libVersion": "trial",
"packOptions": {
"ignore": [],
"include": []
},
"setting": {
"useCompilerPlugins": [
"less"
],
"coverView": true,
"es6": true,
"postcss": true,
"minified": true,
"enhance": true,
"showShadowRootInWxmlPanel": true,
"packNpmRelationList": [],
"babelSetting": {
"ignore": [],
"disablePlugins": [],
"outputPath": ""
}
},
"condition": {},
"editorSetting": {
"tabIndent": "auto",
"tabSize": 2
},
"appid": "wxfd18331fed2fcd31"
}
\ 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",
"setting": {
"compileHotReLoad": true,
"urlCheck": true
},
"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
}
]
}
}
}
\ No newline at end of file
var sa={},saPara={name:"sensors",server_url:"",send_timeout:1e3,show_log:!1,login_id_key:"$identity_login_id",allow_amend_share_path:!0,max_string_length:5e3,datasend_timeout:3e3,source_channel:[],autoTrack:{appLaunch:!0,appShow:!0,appHide:!0,pageShow:!0,pageShare:!0,mpClick:!1,mpFavorite:!0,pageLeave:!1},autotrack_exclude_page:{pageShow:[],pageLeave:[]},is_persistent_save:{share:!1,utm:!1},preset_properties:{url_path:!0},preset_events:{moments_page:!1,defer_track:!1,share_info_use_string:!1},batch_send:!0,sdk_id:"",storage_store_key:"sensorsdata2015_wechat",storage_prepare_data_key:"sensors_mp_prepare_data"};function log(){if(saPara.show_log&&"object"==typeof console&&console.log)try{return console.log.apply(console,arguments)}catch(e){console.log(arguments[0])}}var nativeIsArray=Array.isArray,ObjProto=Object.prototype,ArrayProto=Array.prototype,nativeForEach=ArrayProto.forEach,nativeIndexOf=ArrayProto.indexOf,toString=ObjProto.toString,hasOwnProperty=ObjProto.hasOwnProperty,slice=ArrayProto.slice;function each(e,t,a){if(null==e)return!1;var i={};if(nativeForEach&&e.forEach===nativeForEach)e.forEach(t,a);else if(e.length===+e.length){for(var r=0,n=e.length;r<n;r++)if(r in e&&t.call(a,e[r],r,e)===i)return!1}else for(var s in e)if(hasOwnProperty.call(e,s)&&t.call(a,e[s],s,e)===i)return!1}function isObject(e){return null!=e&&"[object Object]"==toString.call(e)}function isAsyncFunction(e){return"[object AsyncFunction]"===Object.prototype.toString.call(e)}var getRandomBasic=function(){var e=(new Date).getTime();return function(t){return Math.ceil((e=(9301*e+49297)%233280)/233280*t)}}();function getRandom(){if("function"==typeof Uint32Array){var e="";if("undefined"!=typeof crypto?e=crypto:"undefined"!=typeof msCrypto&&(e=msCrypto),isObject(e)&&e.getRandomValues){var t=new Uint32Array(1);return e.getRandomValues(t)[0]/Math.pow(2,32)}}return getRandomBasic(1e19)/1e19}function extend(e){for(var t=1;t<arguments.length;t++){var a=arguments[t];if("[object Object]"===Object.prototype.toString.call(a))for(var i in a)Object.prototype.hasOwnProperty.call(a,i)&&void 0!==a[i]&&(e[i]=a[i])}return e}function extend2Lev(e){return each(slice.call(arguments,1),function(t){for(var a in t)void 0!==t[a]&&null!==t[a]&&(isObject(t[a])&&isObject(e[a])?extend(e[a],t[a]):e[a]=t[a])}),e}function coverExtend(e){return each(slice.call(arguments,1),function(t){for(var a in t)void 0!==t[a]&&void 0===e[a]&&(e[a]=t[a])}),e}var isArray=nativeIsArray||function(e){return"[object Array]"===toString.call(e)};function isFunction(e){if(!e)return!1;var t=Object.prototype.toString.call(e);return"[object Function]"==t||"[object AsyncFunction]"==t}function isArguments(e){return!(!e||!hasOwnProperty.call(e,"callee"))}function toArray(e){return e?e.toArray?e.toArray():isArray(e)?slice.call(e):isArguments(e)?slice.call(e):values(e):[]}function values(e){var t=[];return null==e?t:(each(e,function(e){t[t.length]=e}),t)}function include(e,t){var a=!1;return null==e?a:nativeIndexOf&&e.indexOf===nativeIndexOf?-1!=e.indexOf(t):(each(e,function(e){if(a||(a=e===t))return{}}),a)}function trim(e){return e.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,"")}function isEmptyObject(e){if(isObject(e)){for(var t in e)if(hasOwnProperty.call(e,t))return!1;return!0}return!1}function deepCopy(e){var t={};return function e(t,a){for(var i in a){var r=a[i];isArray(r)?(t[i]=[],e(t[i],r)):isObject(r)?(t[i]={},e(t[i],r)):t[i]=r}}(t,e),t}function isUndefined(e){return void 0===e}function isString(e){return"[object String]"==toString.call(e)}function isDate(e){return"[object Date]"==toString.call(e)}function isBoolean(e){return"[object Boolean]"==toString.call(e)}function isNumber(e){return"[object Number]"==toString.call(e)&&/[\d\\.]+/.test(String(e))}function isJSONString(e){try{JSON.parse(e)}catch(e){return!1}return!0}var isInteger=Number.isInteger||function(e){return"number"==typeof e&&isFinite(e)&&Math.floor(e)===e},isSafeInteger=Number.isSafeInteger||function(e){return isInteger(e)&&Math.abs(e)<=Math.pow(2,53)-1},urlSafeBase64={ENC:{"+":"-","/":"_","=":"."},DEC:{"-":"+",_:"/",".":"="},encode:function(e){return e.replace(/[+\/=]/g,function(e){return urlSafeBase64.ENC[e]})},decode:function(e){return e.replace(/[-_.]/g,function(e){return urlSafeBase64.DEC[e]})},trim:function(e){return e.replace(/[.=]{1,2}$/,"")},isBase64:function(e){return/^[A-Za-z0-9+\/]*[=]{0,2}$/.test(e)},isUrlSafeBase64:function(e){return/^[A-Za-z0-9_-]*[.]{0,2}$/.test(e)}},SOURCE_CHANNEL_STANDARD="utm_source utm_medium utm_campaign utm_content utm_term",LATEST_SOURCE_CHANNEL=["$latest_utm_source","$latest_utm_medium","$latest_utm_campaign","$latest_utm_content","$latest_utm_term","$latest_sa_utm"],LATEST_SHARE_INFO=["$latest_share_distinct_id","$latest_share_url_path","$latest_share_depth","$latest_share_method"],SHARE_INFO_KEY=["sensors_share_d","sensors_share_p","sensors_share_i","sensors_share_m"],MP_FILTER_HOOK={data:1,onLoad:1,onShow:1,onReady:1,onPullDownRefresh:1,onShareAppMessage:1,onShareTimeline:1,onReachBottom:1,onPageScroll:1,onResize:1,onTabItemTap:1,onHide:1,onUnload:1},IDENTITY_KEY={EMAIL:"$identity_email",MOBILE:"$identity_mobile",LOGIN:"$identity_login_id"},LIB_VERSION="1.21.8",LIB_NAME="MiniProgram",meta={init_status:!1,life_state:{app_launched:!1},plugin:{init_list:[],uninitialized_list:[]},privacy:{enable_data_collect:!1},initialState:{queue:[],isComplete:!1},preset_properties:{$lib:LIB_NAME,$lib_version:LIB_VERSION},promise_list:[],query_share_depth:0,page_show_time:Date.now(),mp_show_time:null,share_distinct_id:"",share_method:"",current_scene:"",is_first_launch:!1,wx_sdk_version:"",global_title:{},page_route_map:[]};function getAppInfoSync(){if(wx.getAccountInfoSync){var e=wx.getAccountInfoSync(),t=e&&e.miniProgram?e.miniProgram:{};return{appId:t.appId,appEnv:t.envVersion,appVersion:t.version}}return{}}function getAppId(){var e=getAppInfoSync();return e&&e.appId?e.appId:""}function getOpenidNameByAppid(){var e=getAppId(),t="$identity_mp_openid";return e&&(t="$identity_mp_"+e+"_openid"),t}function rot13defs(e){return rot13obfs(e=String(e),113)}function rot13obfs(e,t){t="number"==typeof t?t:13;for(var a=(e=String(e)).split(""),i=0,r=a.length;i<r;i++){a[i].charCodeAt(0)<126&&(a[i]=String.fromCharCode((a[i].charCodeAt(0)+t)%126))}return a.join("")}var hasOwnProperty$1=Object.prototype.hasOwnProperty,store={storageInfo:null,store_queue:[],getUUID:function(){return Date.now()+"-"+Math.floor(1e7*getRandom())+"-"+getRandom().toString(16).replace(".","")+"-"+String(31242*getRandom()).replace(".","").slice(0,8)},getStorage:function(){return this.storageInfo?this.storageInfo:(this.storageInfo=sa._.getStorageSync(saPara.storage_store_key)||"",this.storageInfo)},_state:{},mem:{mdata:[],getLength:function(){return this.mdata.length},add:function(e){this.mdata.push(e)},clear:function(e){this.mdata.splice(0,e)}},toState:function(e){var t=null,a=this;function i(){t.distinct_id?a._state=t:a.set("distinct_id",a.getUUID())}isJSONString(e)?(t=JSON.parse(e),i()):isObject(e)?(t=e,i()):this.set("distinct_id",this.getUUID());var r=this._state._first_id||this._state.first_id,n=this._state._distinct_id||this._state.distinct_id,s=this._state.openid,o=(this._state.history_login_id?this._state.history_login_id:{}).name;if(this._state.identities&&isString(this._state.identities)){var u=JSON.parse(rot13defs(this._state.identities));this._state.identities=u}var c,p,d=getOpenidNameByAppid();if(this._state.identities&&isObject(this._state.identities)&&!isEmptyObject(this._state.identities)){var l=(c=getAppId(),p="$mp_openid",c&&(p="$mp_"+c+"_openid"),p);hasOwnProperty$1.call(this._state.identities,"$mp_id")&&(this._state.identities.$identity_mp_id=this._state.identities.$mp_id,delete this._state.identities.$mp_id),hasOwnProperty$1.call(this._state.identities,"$mp_unionid")&&(this._state.identities.$identity_mp_unionid=this._state.identities.$mp_unionid,delete this._state.identities.$mp_unionid),hasOwnProperty$1.call(this._state.identities,l)&&(this._state.identities[d]=this._state.identities[l],delete this._state.identities[l])}else this._state.identities={},this._state.identities.$identity_mp_id=this.getUUID();function _(e){for(var t in store._state.identities)hasOwnProperty$1.call(store._state.identities,t)&&"$identity_mp_id"!==t&&t!==e&&delete store._state.identities[t]}s&&(this._state.identities[d]=s),r?o&&hasOwnProperty$1.call(this._state.identities,o)?this._state.identities[o]!==n&&(this._state.identities[o]=n,_(o),this._state.history_login_id.value=n):(this._state.identities[IDENTITY_KEY.LOGIN]=n,_(IDENTITY_KEY.LOGIN),this._state.history_login_id={name:IDENTITY_KEY.LOGIN,value:n}):this._state.history_login_id={name:"",value:""},this.save()},getFirstId:function(){return this._state._first_id||this._state.first_id},getDistinctId:function(){var e=this.getLoginDistinctId();return e||(this._state._distinct_id||this._state.distinct_id)},getUnionId:function(){var e={},t=this._state._first_id||this._state.first_id,a=this.getDistinctId();return t&&a?(e.login_id=a,e.anonymous_id=t):e.anonymous_id=a,e},getIdentities:function(){var e=JSON.parse(JSON.stringify(this._state.identities));return e.$identity_anonymous_id=this.getAnonymousId(),e},getAnonymousId:function(){return this.getUnionId().anonymous_id},getHistoryLoginId:function(){return isObject(this._state.history_login_id)?this._state.history_login_id:null},getLoginDistinctId:function(){var e=this.getHistoryLoginId();return isObject(e)&&e.value?e.name!==IDENTITY_KEY.LOGIN?e.name+"+"+e.value:e.value:null},getProps:function(){return this._state.props||{}},setProps:function(e,t){var a=this._state.props||{};t?this.set("props",e):(extend(a,e),this.set("props",a))},set:function(e,t){var a={};for(var i in"string"==typeof e?a[e]=t:"object"==typeof e&&(a=e),this._state=this._state||{},a)this._state[i]=a[i],"first_id"===i?delete this._state._first_id:"distinct_id"===i&&(delete this._state._distinct_id,sa.events.emit("changeDistinctId"));this.save()},identitiesSet:function(e){var t={};switch(e.type){case"login":t.$identity_mp_id=this._state.identities.$identity_mp_id,t[e.id_name]=e.id;break;case"logout":t.$identity_mp_id=this._state.identities.$identity_mp_id}this.set("identities",t)},change:function(e,t){this._state["_"+e]=t},encryptStorage:function(){var e=this.getStorage(),t="data:enc;";isObject(e)?e=t+rot13obfs(JSON.stringify(e)):isString(e)&&-1===e.indexOf(t)&&(e=t+rot13obfs(e)),sa._.setStorageSync(saPara.storage_store_key,e)},save:function(){var e=deepCopy(this._state),t=rot13obfs(JSON.stringify(e.identities));if(e.identities=t,delete e._first_id,delete e._distinct_id,saPara.encrypt_storage){e="data:enc;"+rot13obfs(JSON.stringify(e))}sa._.setStorageSync(saPara.storage_store_key,e)},init:function(){var e=this.getStorage(),t=store.getUUID();if(e)isString(e)&&-1!==e.indexOf("data:enc;")&&(e=e.substring("data:enc;".length),e=JSON.parse(rot13defs(e))),this.toState(e);else{meta.is_first_launch=!0;var a=new Date,i=a.getTime();a.setHours(23),a.setMinutes(59),a.setSeconds(60),this.set({distinct_id:t,first_visit_time:i,first_visit_day_time:a.getTime(),identities:{$identity_mp_id:t},history_login_id:{name:"",value:""}})}this.checkStoreInit()},checkStoreInit:function(){meta.init_status&&(this.store_queue.length>0&&each(this.store_queue,function(e){sa[e.method].apply(sa,slice.call(e.params))}),this.store_queue=[])}};function _decodeURIComponent(e){var t="";try{t=decodeURIComponent(e)}catch(a){t=e}return t}var hasOwnProperty$2=Object.prototype.hasOwnProperty,decodeURIComponent$1=_decodeURIComponent;function initAppGlobalName(){var e=App;App=function(t){t[saPara.name]=sa,e.apply(this,arguments)}}function getPublicPresetProperties(){var e=getRefPage(),t=getCurrentPageInfo(),a={$referrer:e.route,$referrer_title:e.title,$title:t.title,$url:t.url};return!0===saPara.preset_properties.url_path&&(a.$url_path=t.path),a}function encodeDates(e){return each(e,function(t,a){isDate(t)?e[a]=formatDate(t):isObject(t)&&(e[a]=encodeDates(t))}),e}function formatDate(e){function t(e){return e<10?"0"+e:e}return e.getFullYear()+"-"+t(e.getMonth()+1)+"-"+t(e.getDate())+" "+t(e.getHours())+":"+t(e.getMinutes())+":"+t(e.getSeconds())+"."+t(e.getMilliseconds())}function searchObjDate(e){(isObject(e)||isArray(e))&&each(e,function(t,a){isObject(t)||isArray(t)?searchObjDate(e[a]):isDate(t)&&(e[a]=formatDate(t))})}function formatString(e){return e.length>saPara.max_string_length?(log("\u5b57\u7b26\u4e32\u957f\u5ea6\u8d85\u8fc7\u9650\u5236\uff0c\u5df2\u7ecf\u505a\u622a\u53d6--"+e),e.slice(0,saPara.max_string_length)):e}function searchObjString(e){isObject(e)&&each(e,function(t,a){isObject(t)?searchObjString(e[a]):isString(t)&&(e[a]=formatString(t))})}function parseSuperProperties(e){isObject(e)&&each(e,function(t,a){if(isFunction(t))try{e[a]=t(),isFunction(e[a])&&(log("\u60a8\u7684\u5c5e\u6027- "+a+" \u683c\u5f0f\u4e0d\u6ee1\u8db3\u8981\u6c42\uff0c\u6211\u4eec\u5df2\u7ecf\u5c06\u5176\u5220\u9664"),delete e[a])}catch(t){delete e[a],log("\u60a8\u7684\u5c5e\u6027- "+a+" \u629b\u51fa\u4e86\u5f02\u5e38\uff0c\u6211\u4eec\u5df2\u7ecf\u5c06\u5176\u5220\u9664")}})}function unique(e){for(var t,a=[],i={},r=0;r<e.length;r++)(t=e[r])in i||(i[t]=!0,a.push(t));return a}var check={checkKeyword:function(e){return/^((?!^distinct_id$|^original_id$|^device_id$|^time$|^properties$|^id$|^first_id$|^second_id$|^users$|^events$|^event$|^user_id$|^date$|^datetime$|^user_group|^user_tag)[a-zA-Z_$][a-zA-Z\d_$]{0,99})$/i.test(e)},checkIdLength:function(e){return!(String(e).length>255)||(log("id \u957f\u5ea6\u8d85\u8fc7 255 \u4e2a\u5b57\u7b26\uff01"),!1)}};function strip_sa_properties(e){return isObject(e)?(each(e,function(t,a){if(isArray(t)){var i=[];each(t,function(e){if(isString(e))i.push(e);else if(isUndefined(e))i.push("null");else try{i.push(JSON.stringify(e))}catch(e){log("\u60a8\u7684\u6570\u636e - "+a+":"+t+" - \u7684\u6570\u7ec4\u91cc\u7684\u503c\u6709\u9519\u8bef,\u5df2\u7ecf\u5c06\u5176\u5220\u9664")}}),e[a]=i}if(isObject(t))try{e[a]=JSON.stringify(t)}catch(i){delete e[a],log("\u60a8\u7684\u6570\u636e - "+a+":"+t+" - \u7684\u6570\u636e\u503c\u6709\u9519\u8bef,\u5df2\u7ecf\u5c06\u5176\u5220\u9664")}else isString(t)||isNumber(t)||isDate(t)||isBoolean(t)||isArray(t)||(log("\u60a8\u7684\u6570\u636e - "+a+":"+t+" - \u683c\u5f0f\u4e0d\u6ee1\u8db3\u8981\u6c42\uff0c\u5df2\u7ecf\u5c06\u5176\u5220\u9664"),delete e[a])}),e):e}function strip_empty_properties(e){var t={};return each(e,function(e,a){null==e&&void 0===e||(t[a]=e)}),t}function utf8Encode(e){var t,a,i,r,n="";for(t=a=0,i=(e=(e+"").replace(/\r\n/g,"\n").replace(/\r/g,"\n")).length,r=0;r<i;r++){var s=e.charCodeAt(r),o=null;s<128?a++:o=s>127&&s<2048?String.fromCharCode(s>>6|192,63&s|128):String.fromCharCode(s>>12|224,s>>6&63|128,63&s|128),null!==o&&(a>t&&(n+=e.substring(t,a)),n+=o,t=a=r+1)}return a>t&&(n+=e.substring(t,e.length)),n}function base64Encode(e){var t,a,i,r,n,s="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",o=0,u=0,c="",p=[];if(!e)return e;e=utf8Encode(e);do{t=(n=e.charCodeAt(o++)<<16|e.charCodeAt(o++)<<8|e.charCodeAt(o++))>>18&63,a=n>>12&63,i=n>>6&63,r=63&n,p[u++]=s.charAt(t)+s.charAt(a)+s.charAt(i)+s.charAt(r)}while(o<e.length);switch(c=p.join(""),e.length%3){case 1:c=c.slice(0,-2)+"==";break;case 2:c=c.slice(0,-1)+"="}return c}function btoa(e){for(var t,a,i,r,n="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",s="",o=0,u=(e=String(e)).length%3;o<e.length;)((a=e.charCodeAt(o++))>255||(i=e.charCodeAt(o++))>255||(r=e.charCodeAt(o++))>255)&&log("Failed to execute 'btoa' : The string to be encoded contains characters outside of the Latin1 range."),s+=n.charAt((t=a<<16|i<<8|r)>>18&63)+n.charAt(t>>12&63)+n.charAt(t>>6&63)+n.charAt(63&t);return u?s.slice(0,u-3)+"===".substring(u):s}function urlBase64Encode(e){return btoa(encodeURIComponent(e).replace(/%([0-9A-F]{2})/g,function(e,t){return String.fromCharCode("0x"+t)}))}function getCurrentPage(){var e={};try{var t=getCurrentPages();e=t[t.length-1]}catch(e){log(e)}return e}function getCurrentPath(){var e="\u672a\u53d6\u5230";try{var t=getCurrentPage();e=t?t.route:e}catch(e){log(e)}return e}function getIsFirstDay(){return!!("object"==typeof store._state&&isNumber(store._state.first_visit_day_time)&&store._state.first_visit_day_time>(new Date).getTime())}function getCurrentUrl(e){var t=getCurrentPath(),a="";return isObject(e)&&e.sensors_mp_encode_url_query&&(a=e.sensors_mp_encode_url_query),t?a?t+"?"+a:t:"\u672a\u53d6\u5230"}function getPath(e){return e=isString(e)?e.replace(/^\//,""):"\u53d6\u503c\u5f02\u5e38"}function getCustomUtmFromQuery(e,t,a,i){if(!isObject(e))return{};var r={};if(e.sa_utm)for(var n in e)"sa_utm"!==n?include(saPara.source_channel,n)&&(r[a+n]=e[n]):r[i+n]=e[n];else for(var s in e)-1===(" "+SOURCE_CHANNEL_STANDARD+" ").indexOf(" "+s+" ")?include(saPara.source_channel,s)&&(r[a+s]=e[s]):r[t+s]=e[s];return r}function getObjFromQuery(e){var t=e.split("?"),a=[],i={};return t&&t[1]?(each(t[1].split("&"),function(e){(a=e.split("="))[0]&&a[1]&&(i[a[0]]=a[1])}),i):{}}function setStorageSync(e,t){var a=function(){wx.setStorageSync(e,t)};try{a()}catch(e){log("set Storage fail --",e);try{a()}catch(e){log("set Storage fail again --",e)}}}function getStorageSync(e){var t="";try{t=wx.getStorageSync(e)}catch(e){log("getStorage fail")}return t}function getMPScene(e){return isNumber(e)||isString(e)&&""!==e?e="wx-"+String(e):"\u672a\u53d6\u5230\u503c"}function objToParam(e,t){if(!isObject(e))return log("\u8bf7\u4f20\u5165\u6709\u6548\u5bf9\u8c61"),"";var a=[];for(var i in e)if(hasOwnProperty$2.call(e,i)){var r=e[i];void 0===r?a.push(i+"="):(r=t?encodeURIComponent(r):r,a.push(i+"="+r))}return a.join("&")}function delObjectKey(e){if(isObject(e))for(var t=0;t<SHARE_INFO_KEY.length;t++)delete e[SHARE_INFO_KEY[t]];else log("\u8bf7\u4f20\u5165\u6709\u6548\u5bf9\u8c61")}function shareInfoData(e){var t={},a={};if(saPara.preset_events.share_info_use_string){a=e.query;for(var i=0;i<SHARE_INFO_KEY.length;i++){if(!hasOwnProperty$2.call(a,SHARE_INFO_KEY[i]))return{};a[SHARE_INFO_KEY[i]]=_decodeURIComponent(a[SHARE_INFO_KEY[i]])}t={depth:Number(a.sensors_share_d),path:a.sensors_share_p||"",id:a.sensors_share_i||"",method:a.sensors_share_m||""}}else{if(!e.query.sampshare)return{};if(!isJSONString(a=_decodeURIComponent(e.query.sampshare)))return{};t={depth:(a=JSON.parse(a)).d,path:a.p,id:a.i,method:a.m}}return t}function setShareInfo(e,t){var a={},i={},r=store.getDistinctId(),n=store.getFirstId();if(e&&isObject(e.query)){if(isEmptyObject(a=shareInfoData(e)))return{};var s=a.depth,o=a.path,u=a.id,c=a.method}"string"==typeof u?(t.$share_distinct_id=u,meta.share_distinct_id=u,i.$latest_share_distinct_id=u):t.$share_distinct_id="\u53d6\u503c\u5f02\u5e38","number"==typeof s?!meta.share_distinct_id||meta.share_distinct_id!==r&&meta.share_distinct_id!==n?!meta.share_distinct_id||meta.share_distinct_id===r&&meta.share_distinct_id===n?t.$share_depth="-1":(t.$share_depth=s+1,meta.query_share_depth=s+1,i.$latest_share_depth=s+1):(t.$share_depth=s,meta.query_share_depth=s,i.$latest_share_depth=s):t.$share_depth="-1","string"==typeof o?(t.$share_url_path=o,i.$latest_share_url_path=o):t.$share_url_path="\u53d6\u503c\u5f02\u5e38","string"==typeof c?(t.$share_method=c,i.$latest_share_method=c):t.$share_method="\u53d6\u503c\u5f02\u5e38",setLatestShare(i)}function getShareInfo(){if(saPara.preset_events.share_info_use_string)return objToParam({sensors_share_i:store.getDistinctId()||"\u53d6\u503c\u5f02\u5e38",sensors_share_p:getCurrentPath(),sensors_share_d:meta.query_share_depth,sensors_share_m:meta.share_method},!0);var e=JSON.stringify({i:store.getDistinctId()||"\u53d6\u503c\u5f02\u5e38",p:getCurrentPath(),d:meta.query_share_depth,m:meta.share_method});return"sampshare="+encodeURIComponent(e)}function detectOptionQuery(e){if(!e||!isObject(e.query))return{};var t,a,i,r,n={};return n.query=extend({},e.query),"string"==typeof n.query.scene&&(t=n.query,a=["utm_source","utm_content","utm_medium","utm_campaign","utm_term","sa_utm"].concat(saPara.source_channel),i=new RegExp("("+a.join("|")+")%3D","i"),1===(r=Object.keys(t)).length&&"scene"===r[0]&&i.test(t.scene))&&(n.scene=n.query.scene,delete n.query.scene),e.query.q&&e.query.scancode_time&&"101"===String(e.scene).slice(0,3)&&(n.q=String(n.query.q),delete n.query.q,delete n.query.scancode_time),n}function getMixedQuery(e){var t=detectOptionQuery(e),a=t.scene,i=t.q,r=t.query;for(var n in r)r[n]=_decodeURIComponent(r[n]);return a&&extend(r,getObjFromQuery(a=-1!==(a=_decodeURIComponent(a)).indexOf("?")?"?"+a.replace(/\?/g,""):"?"+a)),i&&extend(r,getObjFromQuery(_decodeURIComponent(i))),r}function setUtm(e,t){var a={},i=getMixedQuery(e),r=getCustomUtmFromQuery(i,"$","_","$"),n=getCustomUtmFromQuery(i,"$latest_","_latest_","$latest_");return a.pre1=r,a.pre2=n,extend(t,r),a}function setSfSource(e,t){isEmptyObject(e.query)||e.query&&e.query._sfs&&(t.$sf_source=e.query._sfs,sa.registerApp({$latest_sf_source:t.$sf_source}))}function setPageSfSource(e){try{var t=getCurrentPage(),a=deepCopy(t?t.options:"");for(var i in a)a[i]=_decodeURIComponent(a[i]);!isEmptyObject(a)&&a._sfs&&(e.$sf_source=a._sfs)}catch(e){log(e)}}function setRefPage(){var e={route:"\u76f4\u63a5\u6253\u5f00",path:"\u76f4\u63a5\u6253\u5f00",title:""};try{var t=getCurrentPage();if(t&&t.route){var a=t.sensors_mp_url_query?"?"+t.sensors_mp_url_query:"",i=t.route,r=getPageTitle(i);e.route=i+a,e.path=i,e.title=r,meta.page_route_map.length>=2?(meta.page_route_map.shift(),meta.page_route_map.push(e)):meta.page_route_map.push(e)}}catch(e){log(e)}}function getRefPage(){var e={route:"\u76f4\u63a5\u6253\u5f00",path:"\u76f4\u63a5\u6253\u5f00",title:""};return meta.page_route_map.length>1&&(e.title=meta.page_route_map[0].title,e.route=meta.page_route_map[0].route,e.path=meta.page_route_map[0].path),e}function getCurrentPageInfo(){var e=getCurrentPage(),t={title:"",url:"",path:"\u672a\u53d6\u5230"};if(e&&e.route){var a=e.sensors_mp_url_query?"?"+e.sensors_mp_url_query:"";t.title=getPageTitle(e.route),t.url=e.route+a,t.path=e.route}return t}function setPageRefData(e,t,a){var i=getRefPage();isObject(e)&&(t?meta.page_route_map.length>0&&t?(a=a?"?"+a:"",e.$referrer=getPath(t)+a,e.$referrer_title=getPageTitle(t)):(e.$referrer="\u76f4\u63a5\u6253\u5f00",e.$referrer_title=""):(e.$referrer=i.route,e.$referrer_title=i.title))}function getPageTitle(e){if("\u672a\u53d6\u5230"===e||!e)return"";var t="";try{if(__wxConfig){var a=__wxConfig,i=__wxConfig.page||{},r=i[e]||i[e+".html"],n={},s={};if(a.global&&a.global.window&&a.global.window.navigationBarTitleText&&(n.titleVal=a.global.window.navigationBarTitleText),r&&r.window&&r.window.navigationBarTitleText&&(s.titleVal=r.window.navigationBarTitleText),!s.titleVal&&__wxAppCode__){var o=__wxAppCode__[e+".json"];o&&o.navigationBarTitleText&&(s.titleVal=o.navigationBarTitleText)}if(each(meta.global_title,function(a,i){if(i===e)return t=a}),0===t.length){var u=extend(n,s);t=u.titleVal||""}}}catch(e){log(e)}return t}function wxrequest(e){if(compareSDKVersion(meta.wx_sdk_version,"2.10.0")>=0)e.timeout=saPara.datasend_timeout,wx.request(e);else{var t=wx.request(e);setTimeout(function(){isObject(t)&&isFunction(t.abort)&&t.abort()},saPara.datasend_timeout)}}function validId(e){return"string"!=typeof e&&"number"!=typeof e||""===e?(log("\u8f93\u5165 ID \u7c7b\u578b\u9519\u8bef"),!1):"number"!=typeof e||(e=String(e),/^\d+$/.test(e))?!!check.checkIdLength(e)&&e:(log("\u8f93\u5165 ID \u7c7b\u578b\u9519\u8bef"),!1)}function compareSDKVersion(e,t){e=e.split("."),t=t.split(".");for(var a=Math.max(e.length,t.length);e.length<a;)e.push("0");for(;t.length<a;)t.push("0");for(var i=0;i<a;i++){var r=parseInt(e[i]),n=parseInt(t[i]);if(r>n)return 1;if(r<n)return-1}return 0}function setUpperCase(e){return isString(e)?e.toLocaleUpperCase():e}function setLatestChannel(e){isEmptyObject(e)||(function(e,t){var a=!1;for(var i in t)e[t[i]]&&(a=!0);return a}(e,LATEST_SOURCE_CHANNEL)&&(sa.clearAppRegister(LATEST_SOURCE_CHANNEL),sa.clearAllProps(LATEST_SOURCE_CHANNEL)),saPara.is_persistent_save.utm?sa.register(e):sa.registerApp(e))}function setLatestShare(e){(e.$latest_share_depth||e.$latest_share_distinct_id||e.$latest_share_url_path||e.$latest_share_method)&&(sa.clearAppRegister(LATEST_SHARE_INFO),sa.clearAllProps(LATEST_SHARE_INFO),saPara.is_persistent_save.share?sa.register(e):sa.registerApp(e))}function setQuery(e,t){if(e&&isObject(e)&&!isEmptyObject(e)){var a=[];return each(e,function(e,i){"q"===i&&isString(e)&&0===e.indexOf("http")||(t?a.push(i+"="+e):a.push(i+"="+_decodeURIComponent(e)))}),a.join("&")}return""}function setNavigationBarTitle(){try{var e=wx.setNavigationBarTitle;Object.defineProperty(wx,"setNavigationBarTitle",{get:function(){return function(t){var a=getCurrentPath();t=isObject(t)?t:{},meta.global_title[a]=t.title,e.call(this,t)}}})}catch(e){log(e)}}function getUtmFromPage(){var e={};try{var t=deepCopy(getCurrentPage().options);for(var a in t)t[a]=_decodeURIComponent(t[a]);e=getCustomUtmFromQuery(t,"$","_","$")}catch(e){log(e)}return e}function isNewLoginId(e,t){return e!==store._state.history_login_id.name||store._state.history_login_id.value!==t}function isSameAndAnonymousID(e){var t=store.getFirstId(),a=store.getDistinctId();return t?e===t:e===a}function isPresetIdKeys(e,t){var a=["$identity_anonymous_id"];for(var i of(isArray(t)&&(a=a.concat(t)),a))if(i===e)return!0;return!1}function encodeTrackData(e){var t=base64Encode(e=JSON.stringify(e));return encodeURIComponent(t)}function setPublicProperties(e){if(e&&e.properties){var t=getRefPage(),a=getCurrentPageInfo(),i={$referrer:t.route,$referrer_title:t.title,$title:a.title,$url:a.url};for(var r in!0===saPara.preset_properties.url_path&&(i.$url_path=a.path),i)hasOwnProperty$2.call(e.properties,r)||(e.properties[r]=i[r])}}function networkStatusChange(){wx.onNetworkStatusChange(function(e){sa.registerApp({$network_type:e.networkType||""})})}function getNetworkType(){return new Promise((e,t)=>{wx.getNetworkType({success:function(t){meta.preset_properties.$network_type=setUpperCase(t.networkType),e()},fail:function(e){log("\u83b7\u53d6\u7f51\u7edc\u4fe1\u606f\u5931\u8d25",e),t()}})})}function getSystemInfo(){var e=meta.preset_properties;return new Promise(t=>{wx.getSystemInfo({success:function(a){var i,r;e.$brand=setUpperCase(a.brand),e.$manufacturer=a.brand,e.$model=a.model,e.$screen_width=Number(a.screenWidth),e.$screen_height=Number(a.screenHeight),e.$os=(i=a.platform,"ios"===(r=i.toLowerCase())?"iOS":"android"===r?"Android":i),e.$os_version=a.system.indexOf(" ")>-1?a.system.split(" ")[1]:a.system,meta.wx_sdk_version=a.SDKVersion,e.$mp_client_app_version=a.version,e.$mp_client_basic_library_version=meta.wx_sdk_version;var n=(new Date).getTimezoneOffset(),s=getAppInfoSync();isNumber(n)&&(e.$timezone_offset=n),s.appId&&(e.$app_id=s.appId),s.appVersion&&(e.$app_version=s.appVersion),t()}})})}var info={currentProps:meta.preset_properties},logger={info:function(){if(saPara.show_log&&"object"==typeof console&&console.log)try{if(3===arguments.length)return console.log(arguments[0],arguments[1],arguments[2]);if(2===arguments.length)return console.log(arguments[0],arguments[1]);if(1===arguments.length)return console.log(arguments[0])}catch(e){console.log(arguments[0])}}};function isValidListener(e){return"function"==typeof e||!(!e||"object"!=typeof e)&&isValidListener(e.listener)}class EventEmitterBase{constructor(){this._events={}}on(e,t){if(!e||!t)return!1;if(!isValidListener(t))throw new Error("listener must be a function");this._events[e]=this._events[e]||[];var a="object"==typeof t;return this._events[e].push(a?t:{listener:t,once:!1}),this}prepend(e,t){if(!e||!t)return!1;if(!isValidListener(t))throw new Error("listener must be a function");this._events[e]=this._events[e]||[];var a="object"==typeof t;return this._events[e].unshift(a?t:{listener:t,once:!1}),this}prependOnce(e,t){return this.prepend(e,{listener:t,once:!0})}once(e,t){return this.on(e,{listener:t,once:!0})}off(e,t){var a=this._events[e];if(!a)return!1;if("number"==typeof t)a.splice(t,1);else if("function"==typeof t)for(var i=0,r=a.length;i<r;i++)a[i]&&a[i].listener===t&&a.splice(i,1);return this}emit(e,t){var a=this._events[e];if(!a)return!1;for(var i=0;i<a.length;i++){var r=a[i];r&&(r.listener.call(this,t||{}),r.once&&this.off(e,i))}return this}removeAllListeners(e){e&&this._events[e]?this._events[e]=[]:this._events={}}listeners(e){return e&&"string"==typeof e?this._events[e]:this._events}}class EventEmitterEx extends EventEmitterBase{constructor(){super(),this.cacheEvents=[],this.maxLen=20}replay(e,t){this.on(e,t),this.cacheEvents.length>0&&this.cacheEvents.forEach(function(a){a.type===e&&t.call(null,a.data)})}emit(e,t){super.emit.apply(this,arguments),this.cacheEvents.push({type:e,data:t}),this.cacheEvents.length>this.maxLen&&this.cacheEvents.shift()}}var _=Object.freeze({__proto__:null,decodeURIComponent:decodeURIComponent$1,encodeDates:encodeDates,formatDate:formatDate,searchObjDate:searchObjDate,formatString:formatString,searchObjString:searchObjString,parseSuperProperties:parseSuperProperties,unique:unique,check:check,getUtmFromPage:getUtmFromPage,setQuery:setQuery,setLatestShare:setLatestShare,setLatestChannel:setLatestChannel,setUpperCase:setUpperCase,compareSDKVersion:compareSDKVersion,validId:validId,wxrequest:wxrequest,getPageTitle:getPageTitle,setPageRefData:setPageRefData,getCurrentPageInfo:getCurrentPageInfo,getRefPage:getRefPage,setRefPage:setRefPage,setPageSfSource:setPageSfSource,setSfSource:setSfSource,setUtm:setUtm,getMixedQuery:getMixedQuery,detectOptionQuery:detectOptionQuery,getShareInfo:getShareInfo,setShareInfo:setShareInfo,shareInfoData:shareInfoData,delObjectKey:delObjectKey,objToParam:objToParam,getMPScene:getMPScene,getStorageSync:getStorageSync,setStorageSync:setStorageSync,getObjFromQuery:getObjFromQuery,getCustomUtmFromQuery:getCustomUtmFromQuery,getPath:getPath,getCurrentUrl:getCurrentUrl,getIsFirstDay:getIsFirstDay,getCurrentPath:getCurrentPath,getCurrentPage:getCurrentPage,urlBase64Encode:urlBase64Encode,btoa:btoa,base64Encode:base64Encode,strip_empty_properties:strip_empty_properties,strip_sa_properties:strip_sa_properties,setNavigationBarTitle:setNavigationBarTitle,networkStatusChange:networkStatusChange,getNetworkType:getNetworkType,getSystemInfo:getSystemInfo,encodeTrackData:encodeTrackData,initAppGlobalName:initAppGlobalName,getPublicPresetProperties:getPublicPresetProperties,setPublicProperties:setPublicProperties,isPresetIdKeys:isPresetIdKeys,isNewLoginId:isNewLoginId,isSameAndAnonymousID:isSameAndAnonymousID,info:info,logger:logger,getAppId:getAppId,getAppInfoSync:getAppInfoSync,getOpenidNameByAppid:getOpenidNameByAppid,rot13defs:rot13defs,rot13obfs:rot13obfs,each:each,isObject:isObject,getRandom:getRandom,extend:extend,extend2Lev:extend2Lev,coverExtend:coverExtend,isArray:isArray,isFunction:isFunction,isAsyncFunction:isAsyncFunction,isArguments:isArguments,toArray:toArray,values:values,include:include,trim:trim,isEmptyObject:isEmptyObject,deepCopy:deepCopy,isUndefined:isUndefined,isString:isString,isDate:isDate,isBoolean:isBoolean,isNumber:isNumber,isJSONString:isJSONString,isInteger:isInteger,isSafeInteger:isSafeInteger,slice:slice,urlSafeBase64:urlSafeBase64,EventEmitterBase:EventEmitterBase,EventEmitterEx:EventEmitterEx,log:log});function onEventSend(){return{}}function processData(e){return e}function batchTrackData(e){var t=Date.now();return e.forEach(function(e){e._flush_time=t}),"data_list="+encodeTrackData(e)}var mergeStorageData={getData:function(e){wx.getStorage({key:saPara.storage_prepare_data_key,complete:function(t){var a=t.data&&isArray(t.data)?t.data:[];mergeStorageData.deleteAesData(a),e&&e()}})},deleteAesData:function(e){var t=[],a=e.length;if(a>0){for(var i=0;i<a;i++)isObject(e[i])&&t.push(e[i]);store.mem.mdata=t.concat(store.mem.mdata)}}};function onceTrackData(e){return"data="+encodeTrackData(e)}var kit={};function setKitTitle(e){if(!isString(e))return!1;var t=meta.page_route_map.length-1;t>=0&&(meta.page_route_map[t].title=e)}kit.setData=function(e){if(!isObject(e))return!1;e.current_title&&setKitTitle(e.current_title)},kit.processData=processData,kit.onceTrackData=onceTrackData,kit.batchTrackData=batchTrackData,kit.onEventSend=onEventSend,kit.onceSend=function(){};var sendStrategy={dataHasSend:!0,dataHasChange:!1,syncStorage:!1,failTime:0,init:function(){this.sendHasInit=!0,mergeStorageData.getData(sendStrategy.syncStorageState.bind(sendStrategy)),this.batchInterval(),this.onAppHide()},syncStorageState:function(){this.syncStorage=!0},onAppHide:function(){var e=this;wx.onAppHide(function(){saPara.batch_send&&e.batchSend()})},send:function(e){this.dataHasChange=!0,store.mem.getLength()>=500&&(log("storage data is too large"),store.mem.mdata.shift()),(e=kit.processData(e))&&store.mem.add(e),this.sendAsOver()},sendAsOver:function(){this.sendHasInit&&store.mem.getLength()>=saPara.batch_send.max_length&&this.batchSend()},wxrequest:function(e){if(isArray(e.data)&&e.data.length>0){var t=kit.batchTrackData(e.data);sa._.wxrequest({url:saPara.server_url,method:"POST",dataType:"text",data:t,header:{"content-type":"text/plain"},success:function(){e.success(e.len)},fail:function(){e.fail()}})}else e.success(e.len)},batchSend:function(){if(this.dataHasSend){var e,t,a=store.mem.mdata;(t=(e=a.length>=100?a.slice(0,100):a).length)>0&&(this.dataHasSend=!1,this.wxrequest({data:e,len:t,success:this.batchRemove.bind(this),fail:this.sendFail.bind(this)}))}},sendFail:function(){this.dataHasSend=!0,this.failTime++},batchRemove:function(e){store.mem.clear(e),this.dataHasSend=!0,this.dataHasChange=!0,this.batchWrite(),this.failTime=0},is_first_batch_write:!0,batchWrite:function(){this.dataHasChange&&(this.dataHasChange=!1,this.syncStorage&&sa._.setStorageSync(saPara.storage_prepare_data_key,store.mem.mdata))},batchInterval:function(){var e=this;!function t(){setTimeout(function(){e.batchWrite(),t()},500)}(),function t(){setTimeout(function(){e.batchSend(),t()},saPara.batch_send.send_timeout*Math.pow(2,e.failTime))}()}};function buildData(e,t){var a={distinct_id:sa.store.getDistinctId(),identities:extend({},sa.store.getIdentities()),lib:{$lib:LIB_NAME,$lib_method:"code",$lib_version:LIB_VERSION},properties:{}};return isObject(t)||(t={}),"track_id_unbind"===e.type&&"$UnbindID"===e.event&&(a.identities=deepCopy(e.unbind_value),delete e.unbind_value),extend(a,sa.store.getUnionId(),e),isObject(e.properties)&&!isEmptyObject(e.properties)&&extend(a.properties,e.properties),"track_id_unbind"===e.type&&"$UnbindID"===e.event&&(a.login_id&&delete a.login_id,a.anonymous_id&&delete a.anonymous_id),e.type&&"profile"===e.type.slice(0,7)||(a._track_id=Number(String(getRandom()).slice(2,5)+String(getRandom()).slice(2,4)+String(Date.now()).slice(-4)),a.properties=extend({},getPublicPresetProperties(),meta.preset_properties,sa.store.getProps(),t,a.properties),"track"===e.type&&(a.properties.$is_first_day=getIsFirstDay())),a.properties.$time&&isDate(a.properties.$time)?(a.time=1*a.properties.$time,delete a.properties.$time):a.time=1*new Date,sa.ee.sdk.emit("createData",a),sa.ee.sdk.emit("beforeBuildCheck",a),sa.ee.data.emit("beforeBuildCheck",a),parseSuperProperties(a.properties),searchObjDate(a),strip_sa_properties(a.properties),searchObjString(a),sa.ee.data.emit("finalAdjustData",a),a}function dataStage(e){if(!saPara.server_url)return!1;if(meta.current_scene&&1154===meta.current_scene&&!sa.para.preset_events.moments_page)return!1;var t=sa._.deepCopy(e),a=buildData(e,kit.onEventSend(t));a?(log(a),sa.events.emit("send",a),sa.para.batch_send?sendStrategy.send(a):sa.kit.onceSend(a)):log("error: \u6570\u636e\u5f02\u5e38 "+a)}sa.popupEmitter={attached:function(){return!1}};var usePlugin=function(e,t){if(!isObject(e)&&!isFunction(e))return log("plugin must be an object",e),!1;if(isFunction(e.init)||log("plugin maybe missing init method",e.plugin_name||e),isString(e.plugin_name)&&e.plugin_name?sa.modules[e.plugin_name]?e=sa.modules[e.plugin_name]:sa.modules[e.plugin_name]=e:log("plugin_name is not defined - ",e.plugin_name||e),isObject(e)&&!0===e.plugin_is_init)return e;if(isObject(e)&&e.plugin_name&&(isString(e.plugin_version)&&e.plugin_version===LIB_VERSION||log("warning!"+e.plugin_name+" plugin version do not match SDK version \uff01\uff01\uff01")),meta.init_status)"function"==typeof e.init&&(e.init(sa,t),e.plugin_is_init=!0,log(e.plugin_name+" plugin is initialized"));else{var a={target:e,para:t};meta.plugin.uninitialized_list.push(a)}return e},use=function(e,t){return isObject(e)||isFunction(e)?(isFunction(e.init)||log("plugin maybe missing init method",e.plugin_name||e),isString(e.plugin_name)&&e.plugin_name?sa.modules[e.plugin_name]?e=sa.modules[e.plugin_name]:sa.modules[e.plugin_name]=e:log("plugin_name is not defined - ",e.plugin_name||e),isObject(e)&&!0===e.plugin_is_init?e:(isObject(e)&&e.plugin_name&&(isString(e.plugin_version)&&e.plugin_version===LIB_VERSION||log("warning!"+e.plugin_name+" plugin version do not match SDK version \uff01\uff01\uff01")),"function"==typeof e.init&&(e.init(sa,t),e.plugin_is_init=!0,log(e.plugin_name+" plugin is initialized")),e)):(log("plugin must be an object",e),!1)},checkPluginInitStatus=function(){if(meta.plugin.uninitialized_list.length>0){for(var e in meta.plugin.uninitialized_list){var t=meta.plugin.uninitialized_list[e];t&&t.target&&"function"==typeof t.target.init&&!t.target.plugin_is_init&&(t.target.init(sa,t.para),isObject(t.target)&&(t.target.plugin_is_init=!0,isString(t.target.plugin_name)&&t.target.plugin_name&&log(t.target.plugin_name+" plugin is initialized")))}meta.plugin.uninitialized_list=[]}};function initAppShowHide(){wx.onAppShow(function(e){if(sa.ee.app.emit("beforeAppShow",e),!meta.life_state.app_launched){var t=wx.getLaunchOptionsSync()||{};sa.autoTrackCustom.appLaunch(t)}sa.autoTrackCustom.appShow(e),sa.ee.app.emit("afterAppShow",e)}),wx.onAppHide(function(){sa.autoTrackCustom.appHide()})}function checkAppLaunch(){if(!meta.life_state.app_launched){var e=wx.getLaunchOptionsSync()||{};sa.autoTrackCustom.appLaunch(e)}}function mpProxy(e,t,a){var i=sa.autoTrackCustom[a];if(e[t]){var r=e[t];e[t]=function(){!sa.para.autoTrackIsFirst||isObject(sa.para.autoTrackIsFirst)&&!sa.para.autoTrackIsFirst[a]?(r.apply(this,arguments),i.apply(this,arguments)):(!0===sa.para.autoTrackIsFirst||isObject(sa.para.autoTrackIsFirst)&&sa.para.autoTrackIsFirst[a])&&(i.apply(this,arguments),r.apply(this,arguments)),sa.ee.page.emit(a)}}else e[t]=function(){i.apply(this,arguments),sa.ee.page.emit(a)}}function clickTrack(e){var t,a={},i={},r=e.currentTarget||{},n=e.target||{};if(isObject(sa.para.framework)&&isObject(sa.para.framework.taro)&&!sa.para.framework.taro.createApp&&n.id&&r.id&&n.id!==r.id)return!1;var s=r.dataset||{};if(t=e.type,a.$element_id=r.id,a.$element_type=s.type,a.$element_content=s.content,a.$element_name=s.name,isObject(e.event_prop)&&(i=e.event_prop),t&&isClick(t)){if(sa.para.preset_events&&sa.para.preset_events.collect_element&&!1===sa.para.preset_events.collect_element(arguments[0]))return!1;a.$url_path=sa._.getCurrentPath(),sa._.setPageRefData(a),a=sa._.extend(a,i),sa.track("$MPClick",a)}}function clickProxy(e,t){var a=e[t];e[t]=function(){var e=a.apply(this,arguments),t=arguments[0];return isObject(t)&&(sa.para.preset_events.defer_track?setTimeout(function(){clickTrack(t)},0):clickTrack(t)),e}}function isClick(e){return!!{tap:1,longpress:1,longtap:1}[e]}function tabProxy(e){var t=e.onTabItemTap;e.onTabItemTap=function(e){t&&t.apply(this,arguments);var a={};e&&(a.$element_content=e.text),a.$element_type="tabBar",a.$url_path=sa._.getCurrentPath(),sa._.setPageRefData(a),sa.track("$MPClick",a)}}function getMethods(e){var t=MP_FILTER_HOOK,a=[];for(var i in e)"function"!=typeof e[i]||t[i]||a.push(i);return a}function initPageProxy(){var e=Page;Page=function(t){try{t||(t={}),monitorClick(t),monitorHooks(t),e.apply(this,arguments)}catch(t){e.apply(this,arguments)}};var t=Component;Component=function(e){try{e||(e={}),e.methods||(e.methods={}),monitorClick(e.methods),monitorHooks(e.methods),t.apply(this,arguments)}catch(e){t.apply(this,arguments)}}}function monitorClick(e){var t=[];if(sa.para.autoTrack&&sa.para.autoTrack.mpClick){t=getMethods(e),tabProxy(e);for(var a=t.length,i=0;i<a;i++)clickProxy(e,t[i])}}function monitorHooks(e){mpProxy(e,"onLoad","pageLoad"),mpProxy(e,"onShow","pageShow"),mpProxy(e,"onHide","pageHide"),mpProxy(e,"onUnload","pageHide"),mpProxy(e,"onAddToFavorites","pageAddFavorites"),"function"==typeof e.onShareAppMessage&&sa.autoTrackCustom.pageShare(e),"function"==typeof e.onShareTimeline&&sa.autoTrackCustom.pageShareTimeline(e)}var eventEmitter=function(){this.sub=[]};eventEmitter.prototype={add:function(e){this.sub.push(e)},emit:function(e,t){this.sub.forEach(function(a){a.on(e,t)})}};var eventSub=function(e){sa.events.add(this),this._events=[],this.handle=e,this.ready=!1};eventSub.prototype={on:function(e,t){if(this.ready){if(isFunction(this.handle))try{this.handle(e,t)}catch(e){log(e)}}else this._events.push({event:e,data:t})},isReady:function(){var e=this;e.ready=!0,e._events.forEach(function(t){if(isFunction(e.handle))try{e.handle(t.event,t.data)}catch(e){log(e)}})}};var ee={};function checkPrivacyStatus(){var e;return global&&global.sensors_data_pre_config&&(e=!!global.sensors_data_pre_config.is_compliance_enabled&&global.sensors_data_pre_config.is_compliance_enabled),!e||(!!meta.init_status||!!meta.privacy.enable_data_collect)}function enableDataCollect(){meta.privacy.enable_data_collect=!0}function apiStaging(){each(["resetAnonymousIdentity","setProfile","setOnceProfile","track","quick","incrementProfile","appendProfile","login","logout","registerApp","register","clearAllRegister","clearAllProps","clearAppRegister","bind","unbind","bindOpenid","unbindOpenid","bindUnionid","unbindUnionid"],function(e){var t=sa[e];sa[e]=function(){return!!checkPrivacyStatus()&&((!isFunction(sa.getDisabled)||!sa.getDisabled())&&void(meta.initialState.isComplete?t.apply(sa,arguments):meta.initialState.queue.push([e,arguments])))}})}function registerApp(e){isObject(e)&&!isEmptyObject(e)&&(meta.preset_properties=extend(meta.preset_properties,e))}function register(e){isObject(e)&&!isEmptyObject(e)&&store.setProps(e)}function clearAllRegister(){store.setProps({},!0)}function clearAppRegister(e){isArray(e)&&each(meta.preset_properties,function(t,a){include(e,a)&&delete meta.preset_properties[a]})}function clearAllProps(e){var t=store.getProps(),a={};isArray(e)&&(each(t,function(t,i){include(e,i)||(a[i]=t)}),store.setProps(a,!0))}ee.sdk=new EventEmitterEx,ee.data=new EventEmitterEx,ee.page=new EventEmitterEx,ee.app=new EventEmitterEx;var hasOwnProperty$3=Object.prototype.hasOwnProperty;function setProfile(e,t){dataStage({type:"profile_set",properties:e})}function setOnceProfile(e,t){dataStage({type:"profile_set_once",properties:e})}function appendProfile(e,t){if(!isObject(e))return!1;each(e,function(t,a){isString(t)?e[a]=[t]:isArray(t)?e[a]=t:(delete e[a],log("appendProfile\u5c5e\u6027\u7684\u503c\u5fc5\u987b\u662f\u5b57\u7b26\u4e32\u6216\u8005\u6570\u7ec4"))}),dataStage({type:"profile_append",properties:e})}function incrementProfile(e,t){if(!isObject(e))return!1;var a=e;isString(e)&&((e={})[a]=1),dataStage({type:"profile_increment",properties:e})}function track(e,t,a){dataStage({type:"track",event:e,properties:t})}function identify(e){if(!checkPrivacyStatus())return!1;if(!meta.init_status)return store.store_queue.push({method:"identify",params:arguments}),!1;(e=validId(e))&&(store.getFirstId()?store.set("first_id",e):store.set("distinct_id",e))}function resetAnonymousIdentity(e){if(store.getFirstId())return log("resetAnonymousIdentity must be used in a logout state \uff01"),!1;if("number"==typeof e&&(e=String(e)),void 0===e){var t=store.getUUID();store._state.identities.$identity_mp_id=t,store.set("distinct_id",t)}else validId(e)&&(store._state.identities.$identity_mp_id=e,store.set("distinct_id",e))}function trackSignup(e,t,a,i){var r,n,s,o;isObject(e)?(r=e.id,n=e.event_name,s=e.id_name):(r=e,n=t),store.set("distinct_id",r),o=s&&s!==IDENTITY_KEY.LOGIN?s+"+"+r:r,dataStage({original_id:store.getFirstId()||store.getDistinctId(),distinct_id:o,login_id:o,type:"track_signup",event:n,properties:a})}function login(e){if(!(e=validId(e)))return!1;if(isSameAndAnonymousID(e))return!1;var t=store.getFirstId(),a=store.getDistinctId(),i=IDENTITY_KEY.LOGIN;isNewLoginId(i,e)&&(store._state.identities[i]=e,store.set("history_login_id",{name:i,value:e}),t||store.set("first_id",a),sa.trackSignup({id:e,event_name:"$SignUp"}),store.identitiesSet({type:"login",id:e,id_name:i}))}function loginWithKey(e,t){if(log("loginWithKey is deprecated !!!"),!isString(e))return log("Key must be String"),!1;if(!check.checkKeyword(e)&&e.length>100)log("Key ["+e+"] is invalid");else if(!check.checkKeyword(e))return log("Key ["+e+"] is invalid"),!1;if(isPresetIdKeys(e,["$mp_openid","$identity_mp_openid","$identity_mp_unionid","$mp_unionid","$mp_id","$identity_mp_id"]))return log("Key ["+e+"] is invalid"),!1;if(!(t=validId(t)))return!1;if(isSameAndAnonymousID(t))return!1;var a=store.getFirstId(),i=store.getDistinctId();isNewLoginId(e,t)&&(store._state.identities[e]=t,store.set("history_login_id",{name:e,value:t}),a||store.set("first_id",i),sa.trackSignup({id:t,event_name:"$SignUp",id_name:e}),store.identitiesSet({type:"login",id:t,id_name:e}))}function getAnonymousID(){if(!isEmptyObject(store._state))return store._state._first_id||store._state.first_id||store._state._distinct_id||store._state.distinct_id;log("\u8bf7\u5148\u521d\u59cb\u5316SDK")}function getIdentities(){return isEmptyObject(store._state)?(log("\u8bf7\u5148\u521d\u59cb\u5316SDK"),null):store.getIdentities()||null}function logout(e){var t=store.getFirstId();store.identitiesSet({type:"logout"}),store.set("history_login_id",{name:"",value:""}),t?(store.set("first_id",""),!0===e?store.set("distinct_id",store.getUUID()):store.set("distinct_id",t)):log("\u6ca1\u6709first_id\uff0clogout\u5931\u8d25")}function getPresetProperties(){if(meta.preset_properties&&meta.preset_properties.$lib){var e={};each(meta.preset_properties,function(t,a){0===a.indexOf("$")&&(e[a]=t)});var t={$url_path:getCurrentPath(),$is_first_day:getIsFirstDay(),$is_first_time:meta.is_first_launch},a=extend(e,t,meta.preset_properties,store.getProps());return delete a.$lib,a}return{}}function bindOpenid(e){if(!(e=validId(e)))return!1;var t=getOpenidNameByAppid();this.bind(t,e)}function unbindOpenid(e){if(!validId(e))return!1;var t=getOpenidNameByAppid();this.unbind(t,e)}function setUnionid(e){log("setUnionid is deprecated !!!");var t=validId(e);t&&bind("$identity_mp_unionid",t)}function unsetUnionid(e){log("unsetUnionid is deprecated !!!");var t=validId(e);if(t){if(hasOwnProperty$3.call(store._state.identities,"$identity_mp_unionid")&&t===store._state.identities.$identity_mp_unionid){var a=getOpenidNameByAppid();hasOwnProperty$3.call(store._state.identities,a)&&(delete store._state.identities[a],delete store._state.openid,store.save())}unbind("$identity_mp_unionid",t)}}function bind(e,t){if(isNumber(t)){if(isInteger(t)&&!1===isSafeInteger(t))return log("Value must be String"),!1;t=String(t)}if(!isString(e))return log("Key must be String"),!1;var a=store.getHistoryLoginId(),i=a?a.name:"";return!check.checkKeyword(e)||isPresetIdKeys(e,[IDENTITY_KEY.LOGIN,i,"$mp_id","$identity_mp_id"])?(log("Key ["+e+"] is invalid"),!1):t&&""!==t?isString(t)?!!check.checkIdLength(t)&&(store._state.identities[e]=t,store.save(),void dataStage({type:"track_id_bind",event:"$BindID"})):(log("Value must be String"),!1):(log("Value is empty or null"),!1)}function unbind(e,t){if(isNumber(t)){if(isInteger(t)&&!1===isSafeInteger(t))return log("Value must be String"),!1;t=String(t)}if(!isString(e))return log("Key must be String"),!1;if(!sa._.check.checkKeyword(e)||isPresetIdKeys(e,[IDENTITY_KEY.LOGIN]))return log("Key ["+e+"] is invalid"),!1;if(!t||""===t)return log("Value is empty or null"),!1;if(!isString(t))return log("Value must be String"),!1;if(!sa._.check.checkIdLength(t))return!1;hasOwnProperty$3.call(store._state.identities,e)&&t===store._state.identities[e]&&("$mp_id"!==e&&"$identity_mp_id"!==e&&delete store._state.identities[e],store.save());var a=store.getDistinctId(),i=store.getFirstId();a===e+"+"+t&&(store.set("first_id",""),store.set("distinct_id",i),store.set("history_login_id",{name:"",value:""}));var r={};r[e]=t,dataStage({type:"track_id_unbind",event:"$UnbindID",unbind_value:r})}function setWebViewUrl(e,t){if(log("setWebViewUrl \u65b9\u6cd5\u5df2\u4ece 2022-9-23 \u5f00\u59cb\u5e9f\u5f03\uff0c\u8bf7\u5c3d\u5feb\u53bb\u9664\u8be5 API \u7684\u8c03\u7528\uff0c\u5e76\u4f7f\u7528 use \u63d2\u4ef6 \u4ee3\u66ff"),!isString(e)||""===e)return log("error:\u8bf7\u4f20\u5165\u6b63\u786e\u7684 URL \u683c\u5f0f"),!1;if(!/^http(s)?:\/\//.test(e))return log("warning: \u8bf7\u4f20\u5165\u6b63\u786e\u7684 URL \u683c\u5f0f"),!1;var a=/([^?#]+)(\?[^#]*)?(#.*)?/.exec(e);if(!a)return!1;var i,r=a[1]||"",n=a[2]||"",s=a[3]||"",o="",u=store.getDistinctId()||"",c=store.getFirstId()||"";urlSafeBase64&&urlSafeBase64.encode?u=u?urlSafeBase64.trim(urlSafeBase64.encode(urlBase64Encode(u))):"":rot13obfs&&(u=u?rot13obfs(u):""),u=encodeURIComponent(u);var p=c?"f"+u:"d"+u;t?(i=s.indexOf("_sasdk"),o=s.indexOf("?")>-1?i>-1?r+n+"#"+s.substring(1,i)+"_sasdk="+p:r+n+"#"+s.substring(1)+"&_sasdk="+p:r+n+"#"+s.substring(1)+"?_sasdk="+p):(i=n.indexOf("_sasdk"),o=/^\?(\w)+/.test(n)?i>-1?r+n.replace(/(_sasdk=)([^&]*)/gi,"_sasdk="+p)+s:r+"?"+n.substring(1)+"&_sasdk="+p+s:r+"?"+n.substring(1)+"_sasdk="+p+s);return o}function quick(){var e=arguments[0],t=arguments[1],a=arguments[2],i=isObject(a)?a:{};if("getAnonymousID"===e){if(!isEmptyObject(store._state))return store._state._first_id||store._state.first_id||store._state._distinct_id||store._state.distinct_id;log("\u8bf7\u5148\u521d\u59cb\u5316SDK")}else"appLaunch"===e||"appShow"===e?t?sa.autoTrackCustom[e](t,i):log("App\u7684launch\u548cshow\uff0c\u5728sensors.quick\u7b2c\u4e8c\u4e2a\u53c2\u6570\u5fc5\u987b\u4f20\u5165App\u7684options\u53c2\u6570"):"appHide"===e&&(i=isObject(t)?t:{},sa.autoTrackCustom[e](i))}function appLaunch(e,t){var a={};e&&e.scene?(meta.current_scene=e.scene,a.$scene=getMPScene(e.scene)):a.$scene="\u672a\u53d6\u5230\u503c",e&&e.scene&&1010===e.scene&&e.query&&(e.query.sampshare&&delete e.query.sampshare,delObjectKey(e.query)),e&&e.path&&(a.$url_path=getPath(e.path),a.$title=getPageTitle(e.path)),setShareInfo(e,a);var i=setUtm(e,a);meta.is_first_launch?(a.$is_first_time=!0,isEmptyObject(i.pre1)||sa.setOnceProfile(i.pre1)):a.$is_first_time=!1,setLatestChannel(i.pre2),setSfSource(e,a),sa.registerApp({$latest_scene:a.$scene}),a.$url_query=setQuery(e.query),a.$url=e.path+(a.$url_query?"?"+a.$url_query:""),setPageRefData(t),isObject(t)&&(a=extend(a,t)),sa.track("$MPLaunch",a)}function appShow(e,t){var a={};meta.mp_show_time=(new Date).getTime(),e&&e.scene?(meta.current_scene=e.scene,a.$scene=getMPScene(e.scene)):a.$scene="\u672a\u53d6\u5230\u503c",e&&e.scene&&1010===e.scene&&e.query&&(e.query.sampshare&&delete e.query.sampshare,delObjectKey(e.query)),e&&e.path&&(a.$url_path=getPath(e.path),a.$title=getPageTitle(e.path)),setShareInfo(e,a),setLatestChannel(setUtm(e,a).pre2),setSfSource(e,a),sa.registerApp({$latest_scene:a.$scene}),a.$url_query=setQuery(e.query),e&&e.path&&(a.$url=e.path+(a.$url_query?"?"+a.$url_query:"")),setPageRefData(a,e.path,a.$url_query),isObject(t)&&(a=extend(a,t)),sa.track("$MPShow",a)}function appHide(e){var t=(new Date).getTime(),a={};a.$url_path=getCurrentPath(),meta.mp_show_time&&t-meta.mp_show_time>0&&(t-meta.mp_show_time)/36e5<24&&(a.event_duration=(t-meta.mp_show_time)/1e3),setPageRefData(a),isObject(e)&&(a=extend(a,e)),sa.track("$MPHide",a),sa.sendStrategy.onAppHide()}function pageShow(e){var t={},a=getCurrentPath(),i=getPageTitle(a),r=getCurrentPage();i&&(t.$title=i),t.$url_path=a,t.$url_query=r.sensors_mp_url_query?r.sensors_mp_url_query:"",setPageSfSource(t=extend(t,getUtmFromPage())),setPageRefData(t),isObject(e)&&(t=extend(t,e)),sa.track("$MPViewScreen",t)}function setPara(e){sa.para=extend2Lev(saPara,e);var t=[];if(isArray(saPara.source_channel))for(var a=saPara.source_channel.length,i=0;i<a;i++)-1===" utm_source utm_medium utm_campaign utm_content utm_term sa_utm ".indexOf(" "+saPara.source_channel[i]+" ")&&t.push(saPara.source_channel[i]);saPara.source_channel=t,isObject(saPara.register)&&extend(meta.preset_properties,saPara.register),saPara.openid_url||(saPara.openid_url=saPara.server_url.replace(/([^\\\/])\/(sa)(\.gif){0,1}/,"$1/mp_login")),"number"!=typeof saPara.send_timeout&&(saPara.send_timeout=1e3);var r={send_timeout:6e3,max_length:6};e&&e.datasend_timeout||saPara.batch_send&&(saPara.datasend_timeout=1e4),!0===saPara.batch_send?saPara.batch_send=extend({},r):isObject(saPara.batch_send)&&(saPara.batch_send=extend({},r,saPara.batch_send));var n={share:!1,utm:!1};!0===saPara.is_persistent_save?(saPara.is_persistent_save=extend({},n),saPara.is_persistent_save.utm=!0):isObject(saPara.is_persistent_save)&&(saPara.is_persistent_save=extend({},n,saPara.is_persistent_save)),saPara.server_url?(saPara.preset_properties=isObject(saPara.preset_properties)?saPara.preset_properties:{},isObject(saPara.autotrack_exclude_page)||(saPara.autotrack_exclude_page={pageShow:[],pageLeave:[]}),isArray(saPara.autotrack_exclude_page.pageShow)||(saPara.autotrack_exclude_page.pageShow=[]),isArray(saPara.autotrack_exclude_page.pageLeave)||(saPara.autotrack_exclude_page.pageLeave=[]),saPara.sdk_id&&(saPara.storage_store_key+=saPara.sdk_id,saPara.storage_prepare_data_key+=saPara.sdk_id)):log("\u8bf7\u4f7f\u7528 setPara() \u65b9\u6cd5\u8bbe\u7f6e server_url \u6570\u636e\u63a5\u6536\u5730\u5740,\u8be6\u60c5\u53ef\u67e5\u770bhttps://www.sensorsdata.cn/manual/mp_sdk_new.html#112-%E5%BC%95%E5%85%A5%E5%B9%B6%E9%85%8D%E7%BD%AE%E5%8F%82%E6%95%B0")}function getServerUrl(){return saPara.server_url}var autoTrackCustom={trackCustom:function(e,t,a){var i=saPara.autoTrack[e],r="";saPara.autoTrack&&i&&("function"==typeof i?isObject(r=i())&&extend(t,r):isObject(i)&&(extend(t,i),saPara.autoTrack[e]=!0),sa.track(a,t))},appLaunch:function(e,t){if(!checkPrivacyStatus())return!1;if(!meta.initialState.isComplete)return meta.initialState.queue.push(["appLaunch",arguments]),meta.life_state.app_launched=!0,!1;meta.life_state.app_launched=!0;var a={};if(e&&e.scene?(meta.current_scene=e.scene,a.$scene=getMPScene(e.scene)):a.$scene="\u672a\u53d6\u5230\u503c",e&&e.scene&&1010===e.scene&&e.query&&(e.query.sampshare&&delete e.query.sampshare,delObjectKey(e.query)),e&&e.path&&(a.$url_path=getPath(e.path),a.$title=getPageTitle(e.path),e.query&&isObject(e.query))){var i=setQuery(e.query);i=i?"?"+i:"",a.$url=a.$url_path+i}setShareInfo(e,a);var r=setUtm(e,a);meta.is_first_launch?(a.$is_first_time=!0,isEmptyObject(r.pre1)||sa.setOnceProfile(r.pre1)):a.$is_first_time=!1,setLatestChannel(r.pre2),setSfSource(e,a),sa.registerApp({$latest_scene:a.$scene}),a.$url_query=setQuery(e.query),setPageRefData(a),t?(a=extend(a,t),sa.track("$MPLaunch",a)):saPara.autoTrack&&saPara.autoTrack.appLaunch&&sa.autoTrackCustom.trackCustom("appLaunch",a,"$MPLaunch")},appShow:function(e,t){var a={};meta.mp_show_time=(new Date).getTime(),e&&e.scene?(meta.current_scene=e.scene,a.$scene=getMPScene(e.scene)):a.$scene="\u672a\u53d6\u5230\u503c",e&&e.scene&&1010===e.scene&&e.query&&(e.query.sampshare&&delete e.query.sampshare,delObjectKey(e.query)),e&&e.path&&(a.$url_path=getPath(e.path),a.$title=getPageTitle(e.path)),setShareInfo(e,a),setLatestChannel(setUtm(e,a).pre2),setSfSource(e,a),sa.registerApp({$latest_scene:a.$scene}),a.$url_query=setQuery(e.query),setPageRefData(a,e.path,a.$url_query),e&&e.path&&(a.$url=e.path+(a.$url_query?"?"+a.$url_query:"")),t?(a=extend(a,t),sa.track("$MPShow",a)):saPara.autoTrack&&saPara.autoTrack.appShow&&sa.autoTrackCustom.trackCustom("appShow",a,"$MPShow")},appHide:function(e){var t=(new Date).getTime(),a={};a.$url_path=getCurrentPath(),meta.mp_show_time&&t-meta.mp_show_time>0&&(t-meta.mp_show_time)/36e5<24&&(a.event_duration=(t-meta.mp_show_time)/1e3),setPageRefData(a),e?(a=extend(a,e),sa.track("$MPHide",a)):saPara.autoTrack&&saPara.autoTrack.appHide&&sa.autoTrackCustom.trackCustom("appHide",a,"$MPHide")},pageLoad:function(e){meta.current_scene&&1010===meta.current_scene&&e&&(e.sampshare&&delete e.sampshare,delObjectKey(e)),e&&isObject(e)&&(this.sensors_mp_url_query=setQuery(e),this.sensors_mp_encode_url_query=setQuery(e,!0))},pageShow:function(){meta.page_show_time=Date.now();var e={},t=getCurrentPath(),a=getPageTitle(t);setRefPage(),e.$url_path=t,e.$url_query=this.sensors_mp_url_query?this.sensors_mp_url_query:"",setPageRefData(e=extend(e,getUtmFromPage())),setPageSfSource(e),a&&(e.$title=a),saPara.onshow?saPara.onshow(sa,t,this):-1===saPara.autotrack_exclude_page.pageShow.indexOf(t)&&sa.autoTrackCustom.trackCustom("pageShow",e,"$MPViewScreen")},pageShare:function(e){var t=e.onShareAppMessage;function a(e,t){return isObject(e)||(e={}),(isUndefined(e.path)||""===e.path)&&(e.path=getCurrentUrl(t)),isString(e.path)&&(-1===e.path.indexOf("?")?e.path=e.path+"?":"&"!==e.path.slice(-1)&&(e.path=e.path+"&")),e.path=e.path+getShareInfo(),e}function i(){if(saPara.autoTrack&&saPara.autoTrack.pageShare){var e={$url_path:getCurrentPath(),$share_depth:meta.query_share_depth,$share_method:meta.share_method};setPageRefData(e),sa.autoTrackCustom.trackCustom("pageShare",e,"$MPShare")}}isAsyncFunction(t)?e.onShareAppMessage=new Proxy(t,{apply:async function(e,t,r){meta.share_method="\u8f6c\u53d1\u6d88\u606f\u5361\u7247",i();var n=await e.apply(t,r);return sa.para.allow_amend_share_path&&(n=a(n,t)),n}}):e.onShareAppMessage=function(){var e=this;meta.share_method="\u8f6c\u53d1\u6d88\u606f\u5361\u7247";var r,n=t.apply(this,arguments);if(i(),sa.para.allow_amend_share_path&&isObject(n=a(n,e)))for(var s in n)if((r=n[s])&&isFunction(r.then)&&isFunction(r.catch))try{n[s]=n[s].then(function(t){return a(t,e)})}catch(e){log("onShareAppMessage: "+e)}return n}},pageShareTimeline:function(e){var t=e.onShareTimeline;e.onShareTimeline=function(){meta.share_method="\u670b\u53cb\u5708\u5206\u4eab";var e=t.apply(this,arguments);if(saPara.autoTrack&&saPara.autoTrack.pageShare){var a={$url_path:getCurrentPath(),$share_depth:meta.query_share_depth,$share_method:meta.share_method};setPageRefData(a),sa.autoTrackCustom.trackCustom("pageShare",a,"$MPShare")}return saPara.allow_amend_share_path&&("object"!=typeof e&&(e={}),"object"==typeof e&&void 0===e.query&&(e.query=""),"object"==typeof e&&"string"==typeof e.query&&""!==e.query&&"&"!==e.query.slice(-1)&&(e.query=e.query+"&"),e.query=e.query+getShareInfo()),e}},pageAddFavorites:function(){var e={};e.$url_path=getCurrentPath(),saPara.autoTrack&&saPara.autoTrack.mpFavorite&&sa.autoTrackCustom.trackCustom("mpFavorite",e,"$MPAddFavorites")},pageHide:function(){sa.para.autoTrack&&sa.para.autoTrack.pageLeave&&sendPageLeave()}};function sendPageLeave(){var e={},t="";try{t=(e=getCurrentPage())?e.route:""}catch(e){log(e)}if(meta.page_show_time>=0&&""!==t){var a={},i=getPageTitle(t),r=(Date.now()-meta.page_show_time)/1e3;(isNaN(r)||r<0)&&(r=0),a.$url_query=e.sensors_mp_url_query?e.sensors_mp_url_query:"",a.$url_path=t,a.$title=i,a.event_duration=r,-1===saPara.autotrack_exclude_page.pageLeave.indexOf(t)&&sa.track("$MPPageLeave",a),meta.page_show_time=-1}}var functions=Object.freeze({__proto__:null,setProfile:setProfile,setOnceProfile:setOnceProfile,appendProfile:appendProfile,incrementProfile:incrementProfile,track:track,identify:identify,resetAnonymousIdentity:resetAnonymousIdentity,trackSignup:trackSignup,login:login,loginWithKey:loginWithKey,getAnonymousID:getAnonymousID,getIdentities:getIdentities,logout:logout,getPresetProperties:getPresetProperties,bindOpenid:bindOpenid,unbindOpenid:unbindOpenid,setUnionid:setUnionid,unsetUnionid:unsetUnionid,bindUnionid:setUnionid,unbindUnionid:unsetUnionid,bind:bind,unbind:unbind,setWebViewUrl:setWebViewUrl,quick:quick,appLaunch:appLaunch,appShow:appShow,appHide:appHide,pageShow:pageShow,setPara:setPara,getServerUrl:getServerUrl,sendPageLeave:sendPageLeave,autoTrackCustom:autoTrackCustom,registerApp:registerApp,register:register,clearAllRegister:clearAllRegister,clearAppRegister:clearAppRegister,clearAllProps:clearAllProps});function buildAPI(e){for(var t in functions)e[t]=functions[t]}function setFirstVisitTime(){meta.is_first_launch&&sa.setOnceProfile({$first_visit_time:new Date})}function checkIsComplete(){meta.initialState.isComplete=!0,meta.initialState.queue.length>0&&(each(meta.initialState.queue,function(e){"appLaunch"===e[0]?sa.autoTrackCustom.appLaunch.apply(sa.autoTrackCustom,slice.call(e[1])):sa[e[0]].apply(sa,slice.call(e[1]))}),meta.initialState.queue=[])}function init(e){if(!0===meta.init_status)return!1;function t(){checkIsComplete(),checkAppLaunch(),sa.ee.sdk.emit("ready")}meta.init_status=!0,sa.ee.sdk.emit("beforeInit"),e&&isObject(e)&&sa.setPara(e),sa.ee.sdk.emit("initPara"),sa.ee.sdk.emit("afterInitPara"),sa.store.init(),sa.checkPluginInitStatus(),setFirstVisitTime(),sa.para.batch_send&&sa.sendStrategy.init();var a=[getNetworkType(),getSystemInfo()].concat(meta.promise_list);Promise.all(a).then(()=>{t()}).catch(()=>{t()})}function registerPropertyPlugin(e){isFunction(e.properties)?!e.isMatchedWithFilter||isFunction(e.isMatchedWithFilter)?sa.ee.data.on("finalAdjustData",function(t){try{isFunction(e.isMatchedWithFilter)?e.isMatchedWithFilter(t)&&e.properties(t):e.properties(t)}catch(e){log("execute registerPropertyPlugin callback error:"+e)}}):log("registerPropertyPlugin arguments error, isMatchedWithFilter must be function"):log("registerPropertyPlugin arguments error, properties must be function")}sa._=_,sa.modules={},sa.meta=meta,sa.kit=kit,sa.mergeStorageData=mergeStorageData,sa.dataStage=dataStage,sa.sendStrategy=sendStrategy,sa.store=store,sa.usePlugin=usePlugin,sa.use=use,sa.checkPluginInitStatus=checkPluginInitStatus,sa.eventSub=eventSub,sa.events=new eventEmitter,sa.ee=ee,sa.registerPropertyPlugin=registerPropertyPlugin,sa.enableDataCollect=enableDataCollect,sa.initialState=meta.initialState,sa.IDENTITY_KEY={EMAIL:IDENTITY_KEY.EMAIL,MOBILE:IDENTITY_KEY.MOBILE},buildAPI(sa),apiStaging(),setNavigationBarTitle(),networkStatusChange(),initAppGlobalName(),initAppShowHide(),initPageProxy(),sa.init=init;var base={plugin_version:"1.21.8"};function createPlugin(e){if("object"==typeof e&&"string"==typeof e.plugin_name&&""!==e.plugin_name)return e.plugin_version=base.plugin_version,e.log=e.log||function(){"object"==typeof console&&"function"==typeof console.log&&console.log.apply(console,arguments)},e;"object"==typeof console&&"function"==typeof console.error&&console.error('plugin must contain proprerty "plugin_name"')}var disableSDK={init(e){e.disableSDK=this.disableSDK.bind(this),e.enableSDK=this.enableSDK.bind(this),e.getDisabled=this.getDisabled.bind(this)},plugin_name:"DisableSDK",disabled:!1,disableSDK(){this.disabled=!0},enableSDK(){this.disabled=!1},getDisabled(){return this.disabled}},DisableSDK=createPlugin(disableSDK),oldOpenid={init(e){var t={getRequest:function(t){wx.login({success:function(a){a.code&&e.para.appid&&e.para.openid_url?e._.wxrequest({url:e.para.openid_url+"&code="+a.code+"&appid="+e.para.appid,method:"GET",complete:function(a){e._.isObject(a)&&e._.isObject(a.data)&&a.data.openid?t(a.data.openid):t()}}):t()}})},getWXStorage:function(){var t=e.store.getStorage();if(t&&e._.isObject(t))return t.openid},getOpenid:function(t){if(!e.para.appid)return t(),!1;var a=this.getWXStorage();a?t(a):this.getRequest(t)}};e.unsetOpenid=function(t){var a=e._.validId(t);if(!a)return!1;var i=e.store._state.openid;i===a&&e.store.set("openid","");var r=e._.getOpenidNameByAppid();if(Object.prototype.hasOwnProperty.call(e.store._state.identities,r)&&a===e.store._state.identities[r]){delete e.store._state.identities[r];var n=e.store.getFirstId(),s=e.store.getDistinctId(),o=e.store._state&&e.store._state.identities&&e.store._state.identities.$identity_mp_id;n&&n===i&&o&&e.store.change("first_id",o),s&&s===i&&o&&e.store.change("distinct_id",o),e.store.save()}},e.setOpenid=function(t){if(!(t=e._.validId(t)))return!1;if(!e.meta.init_status)return e.store.store_queue.push({method:"setOpenid",params:arguments}),!1;e.store.set("openid",t),e.identify(t);var a=e._.getOpenidNameByAppid();e.store._state.identities[a]=t,e.store.save()},e.initWithOpenid=function(a,i){(a=a||{}).appid&&(e.para.appid=a.appid),t.getOpenid(function(t){t&&e.setOpenid(t,a.isCoverLogin),i&&e._.isFunction(i)&&i(t),e.init(a)})}},plugin_name:"OldOpenid"},OldOpenid=createPlugin(oldOpenid),onceSend={init(e){e.kit.onceSend=function(t){t._flush_time=Date.now();var a=e.kit.onceTrackData(t),i=e.para.server_url+"?"+a;-1!==e.para.server_url.indexOf("?")&&(i=e.para.server_url+"&"+a),e._.wxrequest({url:i,method:"GET"})}},plugin_name:"OnceSend"},OnceSend=createPlugin(onceSend);sa.use(DisableSDK),sa.use(OldOpenid),sa.use(OnceSend);export default sa;
\ No newline at end of file
{
"desc": "关于本文件的更多信息,请参考文档 https://developers.weixin.qq.com/miniprogram/dev/framework/sitemap.html",
"rules": [{
"action": "allow",
"page": "*"
}]
}
\ No newline at end of file
const showTimeoutToast = () => {
wx.showToast({
icon: "none",
title: getApp().globalData.isEn
? `Unstable network.Try again later.`
: `当前网络异常,请稍后再试`,
});
};
export const get = (url, header = {}) =>
new Promise((resolve, reject) => {
wx.request({
url,
header,
timeout: 10000,
success: resolve,
fail: (e) => {
showTimeoutToast();
reject(e);
},
});
});
export const post = (url, data, header = {}) =>
new Promise((resolve, reject) => {
wx.request({
url,
header,
data,
method: "POST",
timeout: 10000,
success: resolve,
fail: (e) => {
showTimeoutToast();
reject(e);
},
});
});
export class EventEmitter {
constructor() {
this._events = {};
}
// 监听事件(on)
on(type, listener) {
if (!this._events[type]) {
this._events[type] = [];
}
this._events[type].push(listener);
return this;
}
// 移除事件(off)
off(type, listener) {
if (!this._events[type]) return this;
if (listener) {
// 移除指定监听器
this._events[type] = this._events[type].filter((fn) => fn !== listener);
} else {
// 移除所有同类型监听器
delete this._events[type];
}
return this;
}
// 一次性监听事件(once)
once(type, listener) {
const onceWrapper = (...args) => {
listener(...args);
this.off(type, onceWrapper); // 自动移除监听器
};
this.on(type, onceWrapper);
return this;
}
// 触发事件(emit)
emit(type, ...args) {
if (!this._events[type]) return;
this._events[type].forEach((listener) => {
listener(...args);
});
return this;
}
}
function getCorridorNumber(boothNo) {
if (boothNo.startsWith("F2-")) {
const number = parseInt(boothNo.substring(3), 10);
if (number >= 6 && number <= 10) {
return 9;
} else if (number >= 1 && number <= 5) {
return 5;
} else {
return null;
}
}
// 三楼(20629 和 20728)的规则
if (boothNo.startsWith("20629-") || boothNo.startsWith("20728-")) {
const prefix = boothNo.substring(0, 6);
const number = parseInt(boothNo.substring(6), 10);
if (prefix === "20629-" && number >= 1 && number <= 16) {
return 5;
} else if (prefix === "20728-" && number >= 1 && number <= 16) {
return 9;
} else {
return null;
}
}
return null;
}
export const getAddr = (shop, isEn) => {
let isCorridor = false;
let enAddr = "";
let buildingFloor = "";
let street = "";
try {
isCorridor =
shop.ywZh.boothAddrStreet !== "/"
? null
: getCorridorNumber(shop.ywZh.boothNo);
} catch (error) {
console.error(error);
}
if (isEn) {
try {
enAddr = `${shop.ywZh.boothNo},${
isCorridor
? `Corridor.${isCorridor}`
: `St.${shop.ywZh.boothAddrStreet}`
},${shop.ywZh.addrFloor}F,${
shop.ywZh.marketNameDtl === "二区东" ? "District 2-East" : ""
}`;
} catch (error) {
console.error(error);
}
return { enAddr, buildingFloor, street };
}
try {
buildingFloor =
shop.ywZh.marketNameDtl === "二区东"
? `二区东${shop.ywZh.addrFloor}F`
: "";
} catch (error) {
console.error(error);
}
try {
street = isCorridor
? `${isCorridor}号连廊`
: `${shop.ywZh.boothAddrStreet}街`;
} catch (error) {
console.error(error);
}
return { enAddr, buildingFloor, street };
};
export const getShopName = (shop, isEn) => {
return isEn && shop.ywEn
? shop.ywEn.name
: isEn && !shop.ywEn
? shop.ywZh.boothNo
: shop.ywZh.name;
};
export const getPoiName = (poi, isEn) => {
return poi.facility
? isEn
? poi.facility.nameEn
: poi.facility.name
: getShopName(poi, isEn);
};
export const getShopShareTitle = (shop, isEn) => {
const { enAddr, buildingFloor, street } = getAddr(shop, isEn);
const name = getShopName(shop, isEn);
return isEn
? `Friend Shared Location:${enAddr}/${name}- Click to View`
: `好友分享位置:${buildingFloor}|${street}/${name},点击查看`;
};
export const getFacilityShareTitle = (facility, isEn) => {
return isEn
? `Friend Shared Location:${facility.facility.addressEn}/${facility.facility.nameEn}- Click to View`
: `好友分享位置:${facility.facility.address}/${facility.facility.name},点击查看`;
};
export const confirm = ({ title, content, confirmText, cancelText }) =>
new Promise((resolve) => {
wx.showModal({
title,
content,
confirmColor: "#E92927",
confirmText,
cancelText,
success(res) {
if (res.confirm) {
resolve(true);
} else if (res.cancel) {
resolve(false);
}
resolve(false);
},
});
});
export const getShopMainGoods = (shop, isEn) => {
return isEn && shop.ywEn && shop.ywEn.mainGoods?.length
? `Business: ${shop.ywEn.mainGoods.join(",")}`
: !isEn && shop.ywZh.mainGoods?.length
? `主营: ${shop.ywZh.mainGoods.join("、")}`
: "";
};
export const getStarredPhone = (text) => {
return `${text.substring(0, 3)}****${text.substring(7)}`;
};
export const toRoutePlan = async (poi, isEn) => {
if (getApp().toRoutePlaning) return;
getApp().toRoutePlaning = true;
try {
await authBluetoothAndLocation();
const { authSetting } = await wx.getSetting();
console.log("蓝牙授权状态", authSetting["scope.bluetooth"]);
console.log("位置授权状态", authSetting["scope.userLocation"]);
if (!authSetting["scope.bluetooth"] || !authSetting["scope.userLocation"]) {
return "showAuthModal";
}
const bluetoothOpenSuccess = await openBluetooth();
console.log("尝试开启蓝牙", bluetoothOpenSuccess);
if (!bluetoothOpenSuccess) return "showBluetoothModal";
const { bluetoothEnabled, locationEnabled } = wx.getSystemSetting();
console.log("系统蓝牙状态", bluetoothEnabled);
console.log("系统位置状态", locationEnabled);
if (!bluetoothEnabled) return "showBluetoothModal";
if (!locationEnabled) return "showLocationModal";
const appAuthorizeSetting = wx.getAppAuthorizeSetting();
console.log("微信蓝牙权限", appAuthorizeSetting.bluetoothAuthorized);
console.log("微信位置权限", appAuthorizeSetting.locationAuthorized);
if (appAuthorizeSetting.bluetoothAuthorized !== "authorized")
return "showBluetoothModal";
if (appAuthorizeSetting.locationAuthorized !== "authorized")
return "showLocationModal";
if (!getApp().checkUser()) return "login needed";
getApp().ensurePluginInit();
const name = getPoiName(poi, isEn);
const {
poi_location: { latitude, longitude },
poi_fl_name: floorName,
} = 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",
buildingName: "义乌国际商贸城",
})
);
wx.navigateTo({
url: `plugin://indoormap/routeplan?banNav=false&transitDisable=true&buildingId=3307001549220&buildingName=义乌国际商贸城&destPoint=${destPoint}`,
});
} catch (error) {
console.error(error);
} finally {
getApp().toRoutePlaning = false;
}
};
export const actToRoutePlan = async (act) => {
if (getApp().toRoutePlaning) return;
getApp().toRoutePlaning = true;
try {
await authBluetoothAndLocation();
const { authSetting } = await wx.getSetting();
console.log("蓝牙授权状态", authSetting["scope.bluetooth"]);
console.log("位置授权状态", authSetting["scope.userLocation"]);
if (!authSetting["scope.bluetooth"] || !authSetting["scope.userLocation"]) {
return "showAuthModal";
}
const bluetoothOpenSuccess = await openBluetooth();
console.log("尝试开启蓝牙", bluetoothOpenSuccess);
if (!bluetoothOpenSuccess) return "showBluetoothModal";
const { bluetoothEnabled, locationEnabled } = wx.getSystemSetting();
console.log("系统蓝牙状态", bluetoothEnabled);
console.log("系统位置状态", locationEnabled);
if (!bluetoothEnabled) return "showBluetoothModal";
if (!locationEnabled) return "showLocationModal";
const appAuthorizeSetting = wx.getAppAuthorizeSetting();
console.log("微信蓝牙权限", appAuthorizeSetting.bluetoothAuthorized);
console.log("微信位置权限", appAuthorizeSetting.locationAuthorized);
if (appAuthorizeSetting.bluetoothAuthorized !== "authorized")
return "showBluetoothModal";
if (appAuthorizeSetting.locationAuthorized !== "authorized")
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 destPoint = encodeURIComponent(
JSON.stringify({
name,
latitude: Number(latitude),
longitude: Number(longitude),
floorName: floor_name,
buildingId: "3307001549220",
buildingName: "义乌国际商贸城",
})
);
wx.navigateTo({
url: `plugin://indoormap/routeplan?banNav=false&transitDisable=true&buildingId=3307001549220&buildingName=义乌国际商贸城&destPoint=${destPoint}`,
});
} catch (error) {
console.error(error);
} finally {
getApp().toRoutePlaning = false;
}
};
/**
* 计算两个经纬度之间的直线距离(单位:米)
* @param {number} lat1 第一个点的纬度
* @param {number} lng1 第一个点的经度
* @param {number} lat2 第二个点的纬度
* @param {number} lng2 第二个点的经度
* @returns {number} 返回两点之间的距离(米)
*/
export const getDistance = (lat1, lng1, lat2, lng2) => {
const R = 6371000; // 地球半径(米)
const rad = Math.PI / 180;
// 转换为弧度
const radLat1 = lat1 * rad;
const radLat2 = lat2 * rad;
const deltaLat = (lat2 - lat1) * rad;
const deltaLng = (lng2 - lng1) * rad;
const a =
Math.sin(deltaLat / 2) * Math.sin(deltaLat / 2) +
Math.cos(radLat1) *
Math.cos(radLat2) *
Math.sin(deltaLng / 2) *
Math.sin(deltaLng / 2);
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
// 返回距离(米)
return R * c;
};
export const authBluetoothAndLocation = async () => {
const { authSetting } = await wx.getSetting();
getApp().events.emit("authed");
try {
console.log(
`蓝牙授权状态[${
authSetting["scope.bluetooth"]
? "允许"
: authSetting["scope.bluetooth"] === undefined
? "没授权过"
: "拒绝"
}]`
);
if (authSetting["scope.bluetooth"] === undefined) {
getApp().sensors?.track("OpenSystemLocationSvcDialog", {
is_turn_on_bluetooth: true,
});
const res = await wx.authorize({
scope: "scope.bluetooth",
});
getApp().sensors?.track("ConfirmTurnOnSystemLocationSvcClick", {
btn_name: "允许",
is_turn_on_bluetooth: true,
});
console.log(res);
}
} catch (error) {
getApp().sensors?.track("ConfirmTurnOnSystemLocationSvcClick", {
btn_name: "拒绝",
is_turn_on_bluetooth: true,
});
console.error("用户拒绝蓝牙授权" + error);
}
try {
console.log(
`定位授权状态[${
authSetting["scope.userLocation"]
? "允许"
: authSetting["scope.userLocation"] === undefined
? "没授权过"
: "拒绝"
}]`
);
if (authSetting["scope.userLocation"] === undefined) {
getApp().sensors?.track("OpenSystemLocationSvcDialog", {
is_turn_on_bluetooth: false,
});
const res = await wx.authorize({
scope: "scope.userLocation",
});
getApp().sensors?.track("ConfirmTurnOnSystemLocationSvcClick", {
btn_name: "允许",
is_turn_on_bluetooth: false,
});
console.log(res);
}
} catch (error) {
getApp().sensors?.track("ConfirmTurnOnSystemLocationSvcClick", {
btn_name: "拒绝",
is_turn_on_bluetooth: false,
});
console.error("用户拒绝位置授权" + error);
}
};
export const openBluetooth = async () => {
return await new Promise((resolve) => {
wx.openBluetoothAdapter({
success: () => {
console.log("bluetoothOpened");
resolve(true);
},
fail: ({ errno, errCode }) => {
console.warn("蓝牙未开启", errCode || errno);
resolve(false);
},
});
});
};
/**
* 创建网格索引
* @param {Array} locations 位置列表
* @param {number} gridSize 网格大小(米),默认20米
* @returns {Object|null} 网格索引数据,发生错误时返回null
*/
export function createGridIndex(locations, gridSize = 20) {
try {
// 参数验证
if (!Array.isArray(locations)) {
console.warn("createGridIndex: locations必须是数组");
return null;
}
if (locations.length === 0) {
console.warn("createGridIndex: locations不能为空数组");
return null;
}
// gridSize参数验证
gridSize = Number(gridSize);
if (isNaN(gridSize) || gridSize <= 0) {
console.warn("createGridIndex: gridSize必须是正数");
return null;
}
// 计算边界
const bounds = locations.reduce(
(acc, loc) => {
// 验证必要字段,如果数据无效则跳过
if (!loc?.poi_location?.latitude || !loc?.poi_location?.longitude) {
console.warn("跳过无效位置数据:", loc);
return acc;
}
const lat = Number(loc.poi_location.latitude);
const lon = Number(loc.poi_location.longitude);
// 验证经纬度值,如果无效则跳过
if (
isNaN(lat) ||
isNaN(lon) ||
lat < -90 ||
lat > 90 ||
lon < -180 ||
lon > 180
) {
console.warn("跳过无效经纬度数据:", { lat, lon });
return acc;
}
return {
minLat: Math.min(acc.minLat, lat),
maxLat: Math.max(acc.maxLat, lat),
minLon: Math.min(acc.minLon, lon),
maxLon: Math.max(acc.maxLon, lon),
};
},
{
minLat: Infinity,
maxLat: -Infinity,
minLon: Infinity,
maxLon: -Infinity,
}
);
// 计算网格转换系数
const latGrid = gridSize / 111000;
const lonGrid =
gridSize / (111000 * Math.cos((bounds.minLat * Math.PI) / 180));
// 建立网格索引
const grid = {};
locations.forEach((loc, index) => {
try {
const lat = Number(loc.poi_location.latitude);
const lon = Number(loc.poi_location.longitude);
const gridX = Math.floor((lon - bounds.minLon) / lonGrid);
const gridY = Math.floor((lat - bounds.minLat) / latGrid);
const key = `${gridX},${gridY}`;
if (!grid[key]) grid[key] = [];
grid[key].push({ ...loc, index });
} catch (err) {
console.warn(`处理第${index}个位置时发生错误:`, err);
// 继续处理下一个位置
}
});
return { grid, bounds, latGrid, lonGrid, locations };
} catch (err) {
console.error("createGridIndex 发生错误:", err);
return null;
}
}
/**
* 查找附近位置
* @param {number} targetLat 目标纬度
* @param {number} targetLon 目标经度
* @param {string} floorName 楼层名称
* @param {Object} indexData 网格索引数据
* @param {Array} originalLocations 原始位置列表
* @param {number} radius 搜索半径(米)
* @returns {Array} shinei_id列表
*/
export function findNearbyLocations(
targetLat,
targetLon,
floorName,
indexData,
radius = 10
) {
try {
// 参数验证
if (
typeof targetLat !== "number" ||
typeof targetLon !== "number" ||
isNaN(targetLat) ||
isNaN(targetLon)
) {
console.warn("findNearbyLocations: 无效的经纬度参数");
return [];
}
if (!floorName || typeof floorName !== "string") {
console.warn("findNearbyLocations: 无效的楼层参数");
return [];
}
if (
!indexData?.grid ||
!indexData?.bounds ||
!indexData?.latGrid ||
!indexData?.lonGrid ||
!indexData?.locations
) {
console.warn("findNearbyLocations: 无效的索引数据");
return [];
}
radius = Number(radius);
if (isNaN(radius) || radius <= 0) {
console.warn("findNearbyLocations: 无效的搜索半径");
return [];
}
const { grid, bounds, latGrid, lonGrid, locations } = indexData;
// 计算目标点所在网格
const centerX = Math.floor((targetLon - bounds.minLon) / lonGrid);
const centerY = Math.floor((targetLat - bounds.minLat) / latGrid);
// 经纬度初筛参数
const latDiff = radius / 111000;
const lonDiff = radius / (111000 * Math.cos((targetLat * Math.PI) / 180));
// 收集周边网格中符合条件的点
const candidates = new Set();
for (let dx = -1; dx <= 1; dx++) {
for (let dy = -1; dy <= 1; dy++) {
const key = `${centerX + dx},${centerY + dy}`;
if (!grid[key]) continue;
grid[key].forEach((loc) => {
try {
if (!locations[loc.index]) return;
const location = locations[loc.index];
// 数据完整性检查
if (
!location?.poi_fl_name ||
!location?.poi_location?.latitude ||
!location?.poi_location?.longitude ||
!location?.shinei_id
) {
return;
}
// 楼层过滤
if (location.poi_fl_name !== floorName) return;
// 经纬度初筛
const lat = Number(location.poi_location.latitude);
const lon = Number(location.poi_location.longitude);
if (isNaN(lat) || isNaN(lon)) return;
const latDelta = Math.abs(lat - targetLat);
const lonDelta = Math.abs(lon - targetLon);
if (latDelta <= latDiff && lonDelta <= lonDiff) {
candidates.add(loc.index);
}
} catch (err) {
console.warn(`处理位置数据时出错:`, err);
}
});
}
}
return Array.from(candidates)
.map((index) => {
try {
const loc = locations[index];
const distance = getDistance(
targetLat,
targetLon,
Number(loc.poi_location.latitude),
Number(loc.poi_location.longitude)
);
return { ...loc, distance };
} catch (err) {
console.warn(`计算距离时出错:`, err);
return null;
}
})
.filter((loc) => loc && loc.distance <= radius && loc.shinei_id)
.sort((a, b) => a.distance - b.distance)
.map((loc) => loc.shinei_id);
} catch (err) {
console.error("findNearbyLocations 发生错误:", err);
return [];
}
}
export const checkAuth = async () => {
await authBluetoothAndLocation();
const { authSetting } = await wx.getSetting();
if (!authSetting["scope.bluetooth"] || !authSetting["scope.userLocation"]) {
return false;
}
return true;
};
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论