feat: 优化腾讯地图地址选点插件,完善订单管理与分销服务
这个提交包含在:
15
addons/address/bootstrap.js
vendored
15
addons/address/bootstrap.js
vendored
@@ -11,8 +11,19 @@ require([], function () {
|
||||
var lat = lat_id ? $("#" + lat_id).val() : '';
|
||||
var lng = lng_id ? $("#" + lng_id).val() : '';
|
||||
var zoom = zoom_id ? $("#" + zoom_id).val() : '';
|
||||
var url = "/addons/address/index/select";
|
||||
url += (lat && lng) ? '?lat=' + lat + '&lng=' + lng + (input_id ? "&address=" + $("#" + input_id).val() : "") + (zoom ? "&zoom=" + zoom : "") : '';
|
||||
var address = input_id ? $("#" + input_id).val() : ($(that).data("address") || '');
|
||||
var query = [];
|
||||
if (lat && lng) {
|
||||
query.push('lat=' + encodeURIComponent(lat));
|
||||
query.push('lng=' + encodeURIComponent(lng));
|
||||
}
|
||||
if (address) {
|
||||
query.push('address=' + encodeURIComponent(address));
|
||||
}
|
||||
if (zoom) {
|
||||
query.push('zoom=' + encodeURIComponent(zoom));
|
||||
}
|
||||
var url = "/addons/address/index/select" + (query.length ? '?' + query.join('&') : '');
|
||||
Fast.api.open(url, '位置选择', {
|
||||
callback: function (res) {
|
||||
input_id && $("#" + input_id).val(res.address).trigger("change");
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "PingFang SC", "Microsoft YaHei", sans-serif;
|
||||
}
|
||||
|
||||
#container {
|
||||
@@ -19,72 +20,173 @@
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
.confirm {
|
||||
position: absolute;
|
||||
bottom: 30px;
|
||||
right: 4%;
|
||||
z-index: 99;
|
||||
height: 50px;
|
||||
width: 50px;
|
||||
line-height: 50px;
|
||||
font-size: 15px;
|
||||
text-align: center;
|
||||
background-color: white;
|
||||
background: #1ABC9C;
|
||||
color: white;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.search {
|
||||
position: absolute;
|
||||
width: 400px;
|
||||
top: 0;
|
||||
width: 440px;
|
||||
max-width: 92%;
|
||||
top: 12px;
|
||||
left: 50%;
|
||||
padding: 5px;
|
||||
margin-left: -200px;
|
||||
transform: translateX(-50%);
|
||||
z-index: 100;
|
||||
background: #fff;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 4px 16px rgba(15, 23, 42, 0.14);
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
.search .form-control {
|
||||
border: none;
|
||||
box-shadow: none;
|
||||
height: 36px;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.search .btn-success {
|
||||
background: #00d26a;
|
||||
border-color: #00d26a;
|
||||
height: 36px;
|
||||
border-radius: 6px !important;
|
||||
padding: 0 14px;
|
||||
}
|
||||
|
||||
.search .btn-success:hover {
|
||||
background: #00a854;
|
||||
border-color: #00a854;
|
||||
}
|
||||
|
||||
.map-tip-banner {
|
||||
position: absolute;
|
||||
top: 64px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
z-index: 90;
|
||||
background: rgba(15, 23, 42, 0.75);
|
||||
color: #fff;
|
||||
font-size: 12px;
|
||||
padding: 5px 14px;
|
||||
border-radius: 20px;
|
||||
pointer-events: none;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.selected-panel {
|
||||
position: absolute;
|
||||
left: 16px;
|
||||
right: 16px;
|
||||
bottom: 16px;
|
||||
z-index: 99;
|
||||
background: #ffffff;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 6px 24px rgba(15, 23, 42, 0.16);
|
||||
padding: 12px 16px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 14px;
|
||||
}
|
||||
|
||||
.selected-panel__info {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.selected-panel__addr {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #1e293b;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
margin-bottom: 3px;
|
||||
}
|
||||
|
||||
.selected-panel__coord {
|
||||
font-size: 12px;
|
||||
color: #64748b;
|
||||
}
|
||||
|
||||
.confirm {
|
||||
flex-shrink: 0;
|
||||
height: 38px;
|
||||
padding: 0 22px;
|
||||
line-height: 38px;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
text-align: center;
|
||||
background: linear-gradient(135deg, #00e070, #00a854);
|
||||
color: #ffffff;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 4px 12px rgba(0, 210, 106, 0.28);
|
||||
transition: filter 0.15s ease;
|
||||
}
|
||||
|
||||
.confirm:hover {
|
||||
filter: brightness(0.96);
|
||||
}
|
||||
|
||||
.autocomplete-search {
|
||||
text-align: left;
|
||||
cursor: default;
|
||||
background: #fff;
|
||||
border-radius: 2px;
|
||||
-webkit-box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175);
|
||||
-moz-box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175);
|
||||
box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175);
|
||||
background-clip: padding-box;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 8px 24px rgba(15, 23, 42, 0.16);
|
||||
position: absolute;
|
||||
display: none;
|
||||
z-index: 1036;
|
||||
max-height: 254px;
|
||||
max-height: 280px;
|
||||
overflow: hidden;
|
||||
overflow-y: auto;
|
||||
box-sizing: border-box;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.autocomplete-search .autocomplete-suggestion {
|
||||
padding: 5px;
|
||||
padding: 8px 12px;
|
||||
font-size: 13px;
|
||||
color: #1e293b;
|
||||
border-bottom: 1px solid #f1f5f9;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.autocomplete-search .autocomplete-suggestion:hover {
|
||||
background: #f0f0f0;
|
||||
.autocomplete-search .autocomplete-suggestion:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.autocomplete-search .autocomplete-suggestion:hover,
|
||||
.autocomplete-search .autocomplete-suggestion.selected {
|
||||
background: #f2fef7;
|
||||
color: #00a854;
|
||||
}
|
||||
|
||||
.autocomplete-suggestion__sub {
|
||||
display: block;
|
||||
font-size: 11px;
|
||||
color: #94a3b8;
|
||||
margin-top: 2px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="search">
|
||||
<div class="input-group">
|
||||
<input type="text" id="place" name="q" class="form-control" placeholder="输入地点"/>
|
||||
<input type="text" id="place" name="q" class="form-control" autocomplete="off" placeholder="搜索全国城市、区县、街道或地标(回车搜索)"/>
|
||||
<span class="input-group-btn">
|
||||
<button type="button" name="search" id="search-btn" class="btn btn-success">
|
||||
<i class="fa fa-search"></i>
|
||||
<i class="fa fa-search"></i> 搜索
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="confirm">确定</div>
|
||||
<div class="map-tip-banner"><i class="fa fa-hand-pointer-o"></i> 直接点击地图任意位置或拖动红标即可选点</div>
|
||||
<div class="selected-panel">
|
||||
<div class="selected-panel__info">
|
||||
<div class="selected-panel__addr" id="selected-addr">点击地图选择位置...</div>
|
||||
<div class="selected-panel__coord" id="selected-coord">坐标:--</div>
|
||||
</div>
|
||||
<button type="button" class="confirm"><i class="fa fa-check"></i> 确定选点</button>
|
||||
</div>
|
||||
<div id="container"></div>
|
||||
|
||||
<script charset="utf-8" src="//map.qq.com/api/js?v=2.exp&libraries=place&key={$config.tencentkey|default=''}"></script>
|
||||
@@ -94,72 +196,274 @@
|
||||
|
||||
<script type="text/javascript">
|
||||
$(function () {
|
||||
var map, marker, geocoder, infoWin, searchService, keyword, address, fromtype, totype;
|
||||
address = "{$address|htmlentities}";
|
||||
var map, marker, geocoder, infoWin, xhr;
|
||||
var initialAddress = "{$address|htmlentities}";
|
||||
var hasUrlCoord = location.search.indexOf('lat=') !== -1 && location.search.indexOf('lng=') !== -1;
|
||||
var lng = Number("{$lng}");
|
||||
var lat = Number("{$lat}");
|
||||
fromtype = "GCJ02";
|
||||
totype = "{$config.coordtype|default='DEFAULT'}"
|
||||
var fromtype = "GCJ02";
|
||||
var totype = "{$config.coordtype|default='DEFAULT'}";
|
||||
totype = totype === 'DEFAULT' ? "GCJ02" : totype;
|
||||
|
||||
if (lng && lat && fromtype !== totype) {
|
||||
var result = gcoord.transform([lng, lat], gcoord[totype], gcoord[fromtype]);
|
||||
lng = result[0] || lng;
|
||||
lat = result[1] || lat;
|
||||
var converted = gcoord.transform([lng, lat], gcoord[totype], gcoord[fromtype]);
|
||||
lng = converted[0] || lng;
|
||||
lat = converted[1] || lat;
|
||||
}
|
||||
|
||||
// 当前选点状态(确保无论逆地址解析是否成功,随时可点击确定返回所选点位)
|
||||
var selectedState = {
|
||||
lat: lat,
|
||||
lng: lng,
|
||||
address: initialAddress || '',
|
||||
province: '',
|
||||
city: '',
|
||||
district: '',
|
||||
rawParts: []
|
||||
};
|
||||
|
||||
/**
|
||||
* 提取 LatLng 对象的数值坐标
|
||||
* @param {Object} latLng 腾讯地图 LatLng 或普通 {lat, lng} 对象
|
||||
* @return {{lat: number, lng: number}}
|
||||
*/
|
||||
var extractLatLng = function (latLng) {
|
||||
var parsedLat = typeof latLng.getLat === 'function' ? latLng.getLat() : Number(latLng.lat);
|
||||
var parsedLng = typeof latLng.getLng === 'function' ? latLng.getLng() : Number(latLng.lng);
|
||||
return {
|
||||
lat: parsedLat,
|
||||
lng: parsedLng
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* 更新底部状态栏与地图信息窗体展示
|
||||
* @param {{lat: number, lng: number}} pos 坐标
|
||||
* @param {string} title 地址文本
|
||||
*/
|
||||
var updateSelectedDisplay = function (pos, title) {
|
||||
var addrText = title || selectedState.address || ('已选坐标 (' + pos.lat.toFixed(5) + ', ' + pos.lng.toFixed(5) + ')');
|
||||
$('#selected-addr').text(addrText);
|
||||
$('#selected-coord').text('纬度:' + pos.lat.toFixed(6) + ' ,经度:' + pos.lng.toFixed(6));
|
||||
showInfoWin(pos, addrText);
|
||||
};
|
||||
|
||||
/**
|
||||
* 解析 Nominatim 返回的中文地址层级
|
||||
* @param {Object} ret Nominatim 响应对象
|
||||
* @return {{address: string, province: string, city: string, district: string, rawParts: Array}}
|
||||
*/
|
||||
var parseNominatimResult = function (ret) {
|
||||
var displayName = (ret && ret.display_name) ? String(ret.display_name) : '';
|
||||
var rawParts = displayName.split(',').map(function (s) {
|
||||
return $.trim(s);
|
||||
}).filter(function (s) {
|
||||
return s !== '' && s !== '中国' && !/^\d{5,6}$/.test(s);
|
||||
}).reverse();
|
||||
|
||||
var addrObj = (ret && ret.address) ? ret.address : {};
|
||||
var province = addrObj.state || rawParts[0] || '';
|
||||
var city = '';
|
||||
var district = '';
|
||||
|
||||
// 从反转后的层级中提取省、市、区县
|
||||
for (var i = 0; i < rawParts.length; i++) {
|
||||
var part = rawParts[i];
|
||||
if (!province && /(省|自治区|特别行政区)$/.test(part)) {
|
||||
province = part;
|
||||
} else if (!city && /(市|自治州|地区|盟)$/.test(part) && part !== province) {
|
||||
city = part;
|
||||
} else if (!district && /(区|县|旗|市)$/.test(part) && part !== province && part !== city) {
|
||||
district = part;
|
||||
}
|
||||
}
|
||||
if (!city && /^(北京市|上海市|天津市|重庆市|北京|上海|天津|重庆)/.test(province)) {
|
||||
city = province.indexOf('市') === -1 ? (province + '市') : province;
|
||||
}
|
||||
if (!district && addrObj.county) {
|
||||
district = addrObj.county;
|
||||
}
|
||||
|
||||
var fullAddress = rawParts.length ? rawParts.join('') : displayName;
|
||||
return {
|
||||
address: fullAddress,
|
||||
province: province,
|
||||
city: city,
|
||||
district: district,
|
||||
rawParts: rawParts
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* 选中地图上某个坐标点:立即移动图标并异步解析中文地址
|
||||
* @param {Object} latLng 坐标点
|
||||
* @param {string} [presetAddress] 预设地址名(如搜索结果标题)
|
||||
* @param {boolean} [panCenter] 是否移动地图中心
|
||||
*/
|
||||
var selectPoint = function (latLng, presetAddress, panCenter) {
|
||||
var pos = extractLatLng(latLng);
|
||||
if (!isFinite(pos.lat) || !isFinite(pos.lng)) {
|
||||
return;
|
||||
}
|
||||
selectedState.lat = pos.lat;
|
||||
selectedState.lng = pos.lng;
|
||||
if (presetAddress) {
|
||||
selectedState.address = presetAddress;
|
||||
}
|
||||
|
||||
// 1. 立即放置/移动红标并显示坐标,不等待接口返回
|
||||
showMarker(pos, presetAddress || '已选位置');
|
||||
updateSelectedDisplay(pos, presetAddress || '正在解析地点名称...');
|
||||
|
||||
if (panCenter && map) {
|
||||
map.setCenter(new qq.maps.LatLng(pos.lat, pos.lng));
|
||||
}
|
||||
|
||||
// 2. 异步逆地址解析
|
||||
geocoder.getAddress(pos, presetAddress);
|
||||
};
|
||||
|
||||
/**
|
||||
* 通过关键词搜索地点并自动定位到第一个匹配结果
|
||||
* @param {string} keyword 搜索关键词
|
||||
*/
|
||||
var searchAndLocate = function (keyword) {
|
||||
var kw = $.trim(String(keyword || ''));
|
||||
if (!kw) {
|
||||
return;
|
||||
}
|
||||
var wgs2gcj = function (wgsLng, wgsLat) {
|
||||
var res = gcoord.transform([Number(wgsLng), Number(wgsLat)], gcoord.WGS84, gcoord.GCJ02);
|
||||
return {
|
||||
lng: res[0] || Number(wgsLng),
|
||||
lat: res[1] || Number(wgsLat)
|
||||
};
|
||||
};
|
||||
|
||||
$.ajax({
|
||||
url: "https://nominatim.openstreetmap.org/search",
|
||||
dataType: "jsonp",
|
||||
jsonp: "json_callback",
|
||||
data: {
|
||||
format: "json",
|
||||
q: kw,
|
||||
addressdetails: 1,
|
||||
limit: 5,
|
||||
"accept-language": "zh-CN",
|
||||
countrycodes: "cn"
|
||||
},
|
||||
success: function (list) {
|
||||
if ($.isArray(list) && list.length > 0) {
|
||||
var first = list[0];
|
||||
var gcj = wgs2gcj(first.lon, first.lat);
|
||||
var parsed = parseNominatimResult(first);
|
||||
selectedState.province = parsed.province;
|
||||
selectedState.city = parsed.city;
|
||||
selectedState.district = parsed.district;
|
||||
selectedState.rawParts = parsed.rawParts;
|
||||
selectPoint(gcj, parsed.address || first.name || kw, true);
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 初始化腾讯地图与交互事件
|
||||
*/
|
||||
var init = function () {
|
||||
var center = new qq.maps.LatLng(lat, lng);
|
||||
map = new qq.maps.Map(document.getElementById('container'), {
|
||||
center: center,
|
||||
zoom: parseInt("{$config.zoom}")
|
||||
zoom: parseInt("{$config.zoom}") || 12
|
||||
});
|
||||
|
||||
//实例化信息窗口
|
||||
infoWin = new qq.maps.InfoWindow({
|
||||
map: map
|
||||
});
|
||||
|
||||
geocoder = {
|
||||
getAddress: function (latLng) {
|
||||
/**
|
||||
* 逆地址解析:优先尝试腾讯接口,未授权时自动降级为 Nominatim
|
||||
* @param {{lat: number, lng: number}} pos GCJ02坐标
|
||||
* @param {string} [fallbackTitle] 预设标题
|
||||
*/
|
||||
getAddress: function (pos, fallbackTitle) {
|
||||
var fallbackToNominatim = function () {
|
||||
var wgs = gcoord.transform([pos.lng, pos.lat], gcoord.GCJ02, gcoord.WGS84);
|
||||
$.ajax({
|
||||
url: "https://apis.map.qq.com/ws/geocoder/v1/?location=" + latLng.lat + "," + latLng.lng + "&key={$config.tencentkey|default=''}&output=jsonp",
|
||||
url: "https://nominatim.openstreetmap.org/reverse",
|
||||
dataType: "jsonp",
|
||||
jsonp: "json_callback",
|
||||
data: {
|
||||
format: "json",
|
||||
lat: wgs[1],
|
||||
lon: wgs[0],
|
||||
zoom: 18,
|
||||
addressdetails: 1,
|
||||
"accept-language": "zh-CN"
|
||||
},
|
||||
success: function (ret) {
|
||||
if (ret && ret.display_name) {
|
||||
var parsed = parseNominatimResult(ret);
|
||||
selectedState.address = parsed.address || fallbackTitle || '';
|
||||
selectedState.province = parsed.province;
|
||||
selectedState.city = parsed.city;
|
||||
selectedState.district = parsed.district;
|
||||
selectedState.rawParts = parsed.rawParts;
|
||||
updateSelectedDisplay(pos, selectedState.address);
|
||||
} else {
|
||||
updateSelectedDisplay(pos, fallbackTitle || ('坐标 (' + pos.lat.toFixed(5) + ', ' + pos.lng.toFixed(5) + ')'));
|
||||
}
|
||||
},
|
||||
error: function () {
|
||||
updateSelectedDisplay(pos, fallbackTitle || ('坐标 (' + pos.lat.toFixed(5) + ', ' + pos.lng.toFixed(5) + ')'));
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
$.ajax({
|
||||
url: "https://apis.map.qq.com/ws/geocoder/v1/?location=" + pos.lat + "," + pos.lng + "&key={$config.tencentkey|default=''}&output=jsonp",
|
||||
dataType: "jsonp",
|
||||
type: 'GET',
|
||||
cache: true,
|
||||
crossDomain: true,
|
||||
success: function (ret) {
|
||||
console.log("getAddress:", ret)
|
||||
if (ret.status === 0) {
|
||||
var component = ret.result.address_component;
|
||||
if (ret && ret.status === 0 && ret.result) {
|
||||
var component = ret.result.address_component || {};
|
||||
var resolvedAddr = '';
|
||||
if (ret.result.formatted_addresses && ret.result.formatted_addresses.recommend) {
|
||||
var recommend = ret.result.formatted_addresses.recommend;
|
||||
var standard_address = ret.result.formatted_addresses.standard_address;
|
||||
var address = component.province !== component.city ? component.province + component.city : component.province;
|
||||
|
||||
address = address + (recommend.indexOf(component.district) === 0 ? '' : component.district) + recommend;
|
||||
resolvedAddr = (component.province !== component.city ? (component.province + component.city) : component.province)
|
||||
+ (recommend.indexOf(component.district) === 0 ? '' : component.district) + recommend;
|
||||
} else {
|
||||
address = ret.result.address;
|
||||
resolvedAddr = ret.result.address || '';
|
||||
}
|
||||
showMarker(ret.result.location, address);
|
||||
showInfoWin(ret.result.location, address);
|
||||
selectedState.address = resolvedAddr || fallbackTitle || '';
|
||||
selectedState.province = component.province || '';
|
||||
selectedState.city = component.city || '';
|
||||
selectedState.district = component.district || '';
|
||||
updateSelectedDisplay(pos, selectedState.address);
|
||||
} else {
|
||||
fallbackToNominatim();
|
||||
}
|
||||
},
|
||||
error: function (e) {
|
||||
console.log(e, 'error')
|
||||
error: function () {
|
||||
fallbackToNominatim();
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
//初始化marker
|
||||
showMarker(center);
|
||||
if (address) {
|
||||
showInfoWin(center, address);
|
||||
} else {
|
||||
geocoder.getAddress(center);
|
||||
// 初始选点渲染
|
||||
selectPoint({lat: lat, lng: lng}, initialAddress, false);
|
||||
|
||||
// 若打开时未传自定义坐标但带有区域地址,自动定位到该区域
|
||||
if (!hasUrlCoord && initialAddress) {
|
||||
$('#place').val(initialAddress);
|
||||
searchAndLocate(initialAddress);
|
||||
}
|
||||
|
||||
// 搜索框自动联想(腾讯 Suggestion + Nominatim 双通道自动降级)
|
||||
var place = $("#place");
|
||||
place.autoComplete({
|
||||
minChars: 1,
|
||||
@@ -167,121 +471,196 @@
|
||||
menuClass: 'autocomplete-search',
|
||||
source: function (term, response) {
|
||||
try {
|
||||
xhr.abort();
|
||||
xhr && xhr.abort();
|
||||
} catch (e) {
|
||||
}
|
||||
var searchByNominatim = function () {
|
||||
xhr = $.ajax({
|
||||
url: "https://apis.map.qq.com/ws/place/v1/suggestion?keyword=" + term + "&key={$config.tencentkey|default=''}&output=jsonp",
|
||||
url: "https://nominatim.openstreetmap.org/search",
|
||||
dataType: "jsonp",
|
||||
jsonp: "json_callback",
|
||||
data: {
|
||||
format: "json",
|
||||
q: term,
|
||||
addressdetails: 1,
|
||||
limit: 8,
|
||||
"accept-language": "zh-CN",
|
||||
countrycodes: "cn"
|
||||
},
|
||||
success: function (list) {
|
||||
if (!$.isArray(list) || !list.length) {
|
||||
$(".autocomplete-suggestions.autocomplete-search").html('');
|
||||
response([]);
|
||||
return;
|
||||
}
|
||||
var mapped = [];
|
||||
for (var i = 0; i < list.length; i++) {
|
||||
var row = list[i];
|
||||
var gcj = gcoord.transform([Number(row.lon), Number(row.lat)], gcoord.WGS84, gcoord.GCJ02);
|
||||
var parsed = parseNominatimResult(row);
|
||||
mapped.push({
|
||||
title: row.name || parsed.district || parsed.city || term,
|
||||
address: parsed.address,
|
||||
province: parsed.province,
|
||||
city: parsed.city,
|
||||
district: parsed.district,
|
||||
rawParts: parsed.rawParts,
|
||||
location: {
|
||||
lat: gcj[1] || Number(row.lat),
|
||||
lng: gcj[0] || Number(row.lon)
|
||||
}
|
||||
});
|
||||
}
|
||||
response(mapped);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
xhr = $.ajax({
|
||||
url: "https://apis.map.qq.com/ws/place/v1/suggestion?keyword=" + encodeURIComponent(term) + "&key={$config.tencentkey|default=''}&output=jsonp",
|
||||
dataType: "jsonp",
|
||||
type: 'GET',
|
||||
cache: true,
|
||||
success: function (ret) {
|
||||
if (ret.status === 0) {
|
||||
if (ret && ret.status === 0 && $.isArray(ret.data)) {
|
||||
if (ret.data.length === 0) {
|
||||
$(".autocomplete-suggestions.autocomplete-search").html('');
|
||||
}
|
||||
response(ret.data);
|
||||
} else {
|
||||
console.log(ret);
|
||||
searchByNominatim();
|
||||
}
|
||||
},
|
||||
error: function () {
|
||||
searchByNominatim();
|
||||
}
|
||||
});
|
||||
},
|
||||
renderItem: function (item, search) {
|
||||
search = search.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
|
||||
var regexp = new RegExp("(" + search.replace(/[\,|\u3000|\uff0c]/, ' ').split(' ').join('|') + ")", "gi");
|
||||
return "<div class='autocomplete-suggestion' data-item='" + JSON.stringify(item) + "' data-title='" + item.title + "' data-val='" + item.title + "'>" + item.title.replace(regexp, "<b>$1</b>") + "</div>";
|
||||
var safeSearch = search.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
|
||||
var regexp = new RegExp("(" + safeSearch.replace(/[\,|\u3000|\uff0c]/, ' ').split(' ').join('|') + ")", "gi");
|
||||
var sub = item.address ? ("<span class='autocomplete-suggestion__sub'>" + item.address + "</span>") : "";
|
||||
var jsonStr = JSON.stringify(item).replace(/'/g, "'");
|
||||
return "<div class='autocomplete-suggestion' data-item='" + jsonStr + "' data-title='" + item.title + "' data-val='" + item.title + "'>" + item.title.replace(regexp, "<b>$1</b>") + sub + "</div>";
|
||||
},
|
||||
onSelect: function (e, term, sel) {
|
||||
e.preventDefault();
|
||||
var item = $(sel).data("item");
|
||||
//调用获取位置方法
|
||||
geocoder.getAddress(item.location);
|
||||
|
||||
var position = new qq.maps.LatLng(item.location.lat, item.location.lng);
|
||||
map.setCenter(position);
|
||||
if (item && item.location) {
|
||||
if (item.province) selectedState.province = item.province;
|
||||
if (item.city) selectedState.city = item.city;
|
||||
if (item.district) selectedState.district = item.district;
|
||||
if (item.rawParts) selectedState.rawParts = item.rawParts;
|
||||
selectPoint(item.location, item.address || item.title, true);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
//地图点击
|
||||
// 点击地图任意地点立即选点
|
||||
qq.maps.event.addListener(map, 'click', function (event) {
|
||||
try {
|
||||
//调用获取位置方法
|
||||
geocoder.getAddress(event.latLng);
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
if (event && event.latLng) {
|
||||
selectPoint(event.latLng, '', false);
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
//显示info窗口
|
||||
/**
|
||||
* 显示地图信息气泡
|
||||
* @param {{lat: number, lng: number}} latLng 坐标
|
||||
* @param {string} title 显示文本
|
||||
*/
|
||||
var showInfoWin = function (latLng, title) {
|
||||
var position = new qq.maps.LatLng(latLng.lat, latLng.lng);
|
||||
infoWin.open();
|
||||
infoWin.setContent(title);
|
||||
infoWin.setContent('<div style="padding:2px 4px;font-size:12px;color:#1e293b;max-width:260px;">' + (title || '当前选点') + '</div>');
|
||||
infoWin.setPosition(position);
|
||||
};
|
||||
|
||||
//实例化marker和监听拖拽结束事件
|
||||
/**
|
||||
* 放置可拖拽红标并监听拖拽结束选点
|
||||
* @param {{lat: number, lng: number}} latLng 坐标
|
||||
* @param {string} title 提示文字
|
||||
*/
|
||||
var showMarker = function (latLng, title) {
|
||||
console.log("showMarker", latLng, title)
|
||||
var position = new qq.maps.LatLng(latLng.lat, latLng.lng);
|
||||
marker && marker.setMap(null);
|
||||
marker = new qq.maps.Marker({
|
||||
map: map,
|
||||
position: position,
|
||||
draggable: true,
|
||||
title: title || '拖动图标选择位置'
|
||||
title: title || '点击地图或拖动图标选择位置'
|
||||
});
|
||||
|
||||
//监听拖拽结束
|
||||
qq.maps.event.addListener(marker, 'dragend', function (event) {
|
||||
//调用获取位置方法
|
||||
geocoder.getAddress(event.latLng);
|
||||
if (event && event.latLng) {
|
||||
selectPoint(event.latLng, '', false);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 关闭弹窗并向父窗口回传选点数据
|
||||
* @param {Object} data 选点数据
|
||||
*/
|
||||
var close = function (data) {
|
||||
var index = parent.Layer.getFrameIndex(window.name);
|
||||
var callback = parent.$("#layui-layer" + index).data("callback");
|
||||
//再执行关闭
|
||||
parent.Layer.close(index);
|
||||
//再调用回传函数
|
||||
if (typeof callback === 'function') {
|
||||
callback.call(undefined, data);
|
||||
}
|
||||
};
|
||||
|
||||
//点击确定后执行回调赋值
|
||||
// 点击「确定选点」回传坐标与解析出的省市区信息
|
||||
$(document).on('click', '.confirm', function () {
|
||||
var zoom = map.getZoom();
|
||||
var data = {lat: infoWin.position.lat.toFixed(5), lng: infoWin.position.lng.toFixed(5), zoom: zoom, address: infoWin.content};
|
||||
var zoom = map ? map.getZoom() : 12;
|
||||
var outLat = Number(selectedState.lat);
|
||||
var outLng = Number(selectedState.lng);
|
||||
if (fromtype !== totype) {
|
||||
var result = gcoord.transform([data.lng, data.lat], gcoord[fromtype], gcoord[totype]);
|
||||
data.lng = (result[0] || data.lng).toFixed(5);
|
||||
data.lat = (result[1] || data.lat).toFixed(5);
|
||||
console.log(data, result, fromtype, totype);
|
||||
var result = gcoord.transform([outLng, outLat], gcoord[fromtype], gcoord[totype]);
|
||||
outLng = Number(result[0] || outLng);
|
||||
outLat = Number(result[1] || outLat);
|
||||
}
|
||||
|
||||
var data = {
|
||||
lat: outLat.toFixed(6),
|
||||
lng: outLng.toFixed(6),
|
||||
zoom: zoom,
|
||||
address: selectedState.address || '',
|
||||
province: selectedState.province || '',
|
||||
city: selectedState.city || '',
|
||||
district: selectedState.district || '',
|
||||
rawParts: selectedState.rawParts || []
|
||||
};
|
||||
close(data);
|
||||
});
|
||||
|
||||
//点击搜索按钮
|
||||
// 点击搜索按钮或回车搜索
|
||||
$(document).on('click', '#search-btn', function () {
|
||||
if ($("#place").val() === '')
|
||||
return;
|
||||
var first = $(".autocomplete-search > .autocomplete-suggestion:first");
|
||||
if (!first.length) {
|
||||
var kw = $.trim($("#place").val());
|
||||
if (!kw) {
|
||||
return;
|
||||
}
|
||||
var first = $(".autocomplete-search > .autocomplete-suggestion:first");
|
||||
if (first.length && first.is(':visible')) {
|
||||
var item = first.data("item");
|
||||
if (item && item.location) {
|
||||
if (item.province) selectedState.province = item.province;
|
||||
if (item.city) selectedState.city = item.city;
|
||||
if (item.district) selectedState.district = item.district;
|
||||
if (item.rawParts) selectedState.rawParts = item.rawParts;
|
||||
selectPoint(item.location, item.address || item.title, true);
|
||||
$(".autocomplete-search").hide();
|
||||
return;
|
||||
}
|
||||
}
|
||||
searchAndLocate(kw);
|
||||
});
|
||||
|
||||
//调用获取位置方法
|
||||
geocoder.getAddress(item.location);
|
||||
|
||||
var position = new qq.maps.LatLng(item.location.lat, item.location.lng);
|
||||
map.setCenter(position);
|
||||
$('#place').on('keydown', function (e) {
|
||||
if (e.keyCode === 13) {
|
||||
e.preventDefault();
|
||||
$('#search-btn').trigger('click');
|
||||
}
|
||||
});
|
||||
|
||||
init();
|
||||
|
||||
@@ -80,40 +80,70 @@ class Order extends Backend
|
||||
}
|
||||
|
||||
/**
|
||||
* 订单补单
|
||||
* 订单补单(含权益发放与分销结算)
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function supplement()
|
||||
{
|
||||
$orderNo = $this->request->post('orderNo');
|
||||
if (trim((string)$orderNo) === '') {
|
||||
$orderNo = trim((string)$this->request->post('orderNo', ''));
|
||||
if ($orderNo === '') {
|
||||
$this->error('订单号不能为空');
|
||||
}
|
||||
$order = Db::name('fc_order')->where('order_no', $orderNo)->find();
|
||||
if (!$order) {
|
||||
$this->error('订单不存在');
|
||||
}
|
||||
if ($order['status'] === 'paid') {
|
||||
if ((string)$order['status'] === 'paid') {
|
||||
$this->success('订单已支付');
|
||||
}
|
||||
Db::name('fc_order')->where('id', $order['id'])->update([
|
||||
if ((string)$order['status'] !== 'pending') {
|
||||
$this->error('订单状态不支持补单(当前:' . $order['status'] . ')');
|
||||
}
|
||||
|
||||
$now = time();
|
||||
try {
|
||||
Db::transaction(function () use ($order, $now) {
|
||||
$locked = Db::name('fc_order')->where('id', (int)$order['id'])->lock(true)->find();
|
||||
if (!$locked || (string)$locked['status'] !== 'pending') {
|
||||
return;
|
||||
}
|
||||
|
||||
$affected = Db::name('fc_order')
|
||||
->where('id', (int)$order['id'])
|
||||
->where('status', 'pending')
|
||||
->update([
|
||||
'status' => 'paid',
|
||||
'paytime' => time(),
|
||||
'updatetime' => time(),
|
||||
'paytime' => $now,
|
||||
'updatetime' => $now,
|
||||
]);
|
||||
if (!$affected) {
|
||||
return;
|
||||
}
|
||||
|
||||
$locked['status'] = 'paid';
|
||||
$locked['paytime'] = $now;
|
||||
|
||||
Db::name('fc_finance_flow')->insert([
|
||||
'user_id' => (int)$order['user_id'],
|
||||
'flow_no' => 'SK' . date('YmdHis') . $order['id'],
|
||||
'user_id' => (int)$locked['user_id'],
|
||||
'flow_no' => 'SK' . date('YmdHis') . $locked['id'],
|
||||
'type' => 'income',
|
||||
'scene' => '订单补单',
|
||||
'amount' => $order['amount'],
|
||||
'amount' => $locked['amount'],
|
||||
'balance' => 0,
|
||||
'related_id' => $order['id'],
|
||||
'related_id' => $locked['id'],
|
||||
'remark' => '订单补单',
|
||||
'createtime' => time(),
|
||||
'createtime' => $now,
|
||||
]);
|
||||
|
||||
// 统一发放订单权益(会员开通 / 余额充值入账 / 付费查看解锁 / 发帖激活)并触发分销佣金结算
|
||||
\app\common\library\OrderFulfill::handle($locked);
|
||||
});
|
||||
} catch (\Throwable $e) {
|
||||
\think\Log::record('后台订单补单失败:订单 ' . $orderNo . ',' . $e->getMessage(), 'error');
|
||||
$this->error('补单失败:' . $e->getMessage());
|
||||
}
|
||||
|
||||
$this->success('补单成功');
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -110,7 +110,7 @@
|
||||
|
||||
<input type="hidden" id="area-options" value="{$areaOptionsJson|htmlentities}">
|
||||
|
||||
<div class="fc-area-actions">
|
||||
<button type="submit" class="btn btn-primary btn-embossed disabled">保存</button>
|
||||
<div class="fc-area-actions normal-footer">
|
||||
<button type="submit" class="btn btn-success btn-embossed fc-area-submit-btn"><i class="fa fa-check"></i> 保存</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@@ -110,7 +110,7 @@
|
||||
|
||||
<input type="hidden" id="area-options" data-current-id="{$row.id|htmlentities}" value="{$areaOptionsJson|htmlentities}">
|
||||
|
||||
<div class="fc-area-actions">
|
||||
<button type="submit" class="btn btn-primary btn-embossed disabled">保存</button>
|
||||
<div class="fc-area-actions normal-footer">
|
||||
<button type="submit" class="btn btn-success btn-embossed fc-area-submit-btn"><i class="fa fa-check"></i> 保存</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@@ -221,7 +221,7 @@ class Distribution extends V1
|
||||
/**
|
||||
* D4 分销海报生成要素
|
||||
* GET /api/v1/distribution/poster
|
||||
* 获取用于前端合成或分享专属推广海报的元数据
|
||||
* 获取用于前端合成或分享专属推广海报的元数据(含官方小程序码 URL,支持传入 postId 生成详情页小程序码)
|
||||
*
|
||||
* @return void 输出海报配置与素材信息的JSON
|
||||
*/
|
||||
@@ -234,6 +234,16 @@ class Distribution extends V1
|
||||
$siteName = (string)$this->cfg('app.name', '同城分类信息');
|
||||
$slogan = (string)$this->cfg('dist.slogan', '同城便民生活 · 随时随地发布与求助');
|
||||
$code = (string)$ext['invite_code'];
|
||||
$postId = (int)$this->input('postId', 0);
|
||||
|
||||
if ($postId > 0) {
|
||||
$scene = 'id_' . $postId . '_u_' . $uid;
|
||||
$page = 'pages/detail/detail';
|
||||
} else {
|
||||
$scene = 'ic_' . $code;
|
||||
$page = 'pages/index/index';
|
||||
}
|
||||
$wxacodeUrl = \app\common\library\WxMini::wxacodeUnlimit($scene, $page, 280);
|
||||
|
||||
$this->ok([
|
||||
'inviteCode' => $code,
|
||||
@@ -241,7 +251,8 @@ class Distribution extends V1
|
||||
'slogan' => $slogan,
|
||||
'nickname' => (string)(isset($user['nickname']) && $user['nickname'] !== '' ? $user['nickname'] : '同城合伙人'),
|
||||
'avatar' => (string)(isset($user['avatar']) ? $user['avatar'] : ''),
|
||||
'path' => 'pages/index/index?ic=' . $code,
|
||||
'path' => $postId > 0 ? ('pages/detail/detail?id=' . $postId . '&ic=' . $code) : ('pages/index/index?ic=' . $code),
|
||||
'wxacodeUrl' => $wxacodeUrl,
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
@@ -578,6 +578,7 @@ class V1 extends Api
|
||||
'memberLevel' => $isMember ? (string)$ext['member_level'] : '',
|
||||
'memberExpireAt' => $ext['member_expiretime'] ? $this->iso($ext['member_expiretime']) : null,
|
||||
'balance' => (float)$user['money'],
|
||||
'inviteCode' => isset($ext['invite_code']) ? (string)$ext['invite_code'] : '',
|
||||
'realnameStatus' => (int)$ext['realname_status'],
|
||||
'realname' => isset($ext['realname']) ? (string)$ext['realname'] : '',
|
||||
'isMerchant' => (bool)$isMerchant,
|
||||
|
||||
@@ -185,9 +185,12 @@ class DistributionService
|
||||
'createtime' => $now,
|
||||
]);
|
||||
|
||||
// 发送分销佣金入账站内信通知
|
||||
// 发送分销佣金入账站内信通知(区分待结算周期与实时结算)
|
||||
$levelText = ($level === 1) ? '直接下线' : '间接下线';
|
||||
$noticeContent = '您的 ' . $levelText . ' 产生了一笔消费(¥' . $amount . '),为您带来分销佣金 ¥' . $commissionAmount . ',已计入可提现资产。';
|
||||
$settleTip = ($settleDays > 0)
|
||||
? '已计入待结算佣金(预计 T+' . $settleDays . ' 自动结算转入可提现资产)。'
|
||||
: '已计入可提现资产。';
|
||||
$noticeContent = '您的 ' . $levelText . ' 产生了一笔消费(¥' . $amount . '),为您带来分销佣金 ¥' . $commissionAmount . ',' . $settleTip;
|
||||
Db::name('fc_message')->insert([
|
||||
'user_id' => $parentId,
|
||||
'type' => 'interact',
|
||||
|
||||
@@ -207,6 +207,60 @@ class WxMini
|
||||
return isset($token['access_token']) ? (string)$token['access_token'] : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成并缓存不限次数微信小程序码(wxacode.getUnlimited)
|
||||
* 若未配置小程序或线上尚未发布正式版,静默返回空字符串以便前端自动回退至普通二维码
|
||||
*
|
||||
* @param string $scene 场景值(最长32个可见字符,如 ic_ABCD12)
|
||||
* @param string $page 小程序页面路径(不带前导 /,如 pages/index/index)
|
||||
* @param int $width 二维码宽度(px)
|
||||
* @return string 成功返回相对 URL(如 /uploads/wxacode/xxx.png),失败返回空字符串
|
||||
*/
|
||||
public static function wxacodeUnlimit($scene, $page = 'pages/index/index', $width = 280)
|
||||
{
|
||||
$scene = substr(trim((string)$scene), 0, 32);
|
||||
$page = ltrim(trim((string)$page), '/');
|
||||
if ($scene === '' || !self::isConfigured()) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$hash = md5($scene . '|' . $page . '|' . (int)$width);
|
||||
$relDir = '/uploads/wxacode';
|
||||
$absDir = ROOT_PATH . 'public' . DS . 'uploads' . DS . 'wxacode';
|
||||
$relUrl = $relDir . '/' . $hash . '.png';
|
||||
$absFile = $absDir . DS . $hash . '.png';
|
||||
|
||||
if (is_file($absFile) && filesize($absFile) > 200) {
|
||||
return $relUrl;
|
||||
}
|
||||
|
||||
try {
|
||||
$response = self::app()->app_code->getUnlimit($scene, [
|
||||
'page' => $page !== '' ? $page : 'pages/index/index',
|
||||
'width' => max(280, min(1280, (int)$width)),
|
||||
'check_path' => false,
|
||||
]);
|
||||
|
||||
if ($response instanceof \EasyWeChat\Kernel\Http\StreamResponse) {
|
||||
$body = $response->getBody()->getContents();
|
||||
// 校验 PNG (\x89PNG) 或 JPEG (\xFF\xD8) 魔数,避免把微信返回的 JSON 错误当图片落盘
|
||||
if (strlen($body) > 200 && (substr($body, 0, 4) === "\x89PNG" || substr($body, 0, 2) === "\xFF\xD8")) {
|
||||
if (!is_dir($absDir)) {
|
||||
@mkdir($absDir, 0755, true);
|
||||
}
|
||||
@file_put_contents($absFile, $body);
|
||||
if (is_file($absFile) && filesize($absFile) > 200) {
|
||||
return $relUrl;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
// 静默降级:小程序未发布或网络受限时由前端自动回退绘制二维码
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/* ==================== 内部工具 ==================== */
|
||||
|
||||
/** SDK 返回 Collection / array / object 统一转数组 */
|
||||
|
||||
@@ -258,16 +258,17 @@
|
||||
height: 40px;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
background: linear-gradient(135deg, #00e070, #04874a);
|
||||
color: #04301e;
|
||||
font-weight: 700;
|
||||
box-shadow: 0 4px 12px rgba(0, 224, 112, 0.2);
|
||||
background: linear-gradient(135deg, #00d26a, #00a854);
|
||||
color: #ffffff;
|
||||
font-weight: 600;
|
||||
box-shadow: 0 4px 12px rgba(0, 210, 106, 0.24);
|
||||
transition: filter 0.15s ease, box-shadow 0.15s ease;
|
||||
}
|
||||
|
||||
.fc-area-map-btn:hover,
|
||||
.fc-area-map-btn:focus {
|
||||
color: #04301e;
|
||||
filter: brightness(0.98);
|
||||
color: #ffffff;
|
||||
filter: brightness(0.96);
|
||||
}
|
||||
|
||||
.fc-area-actions {
|
||||
@@ -276,11 +277,28 @@
|
||||
padding: 4px 0 8px;
|
||||
}
|
||||
|
||||
.fc-area-actions .btn {
|
||||
min-width: 120px;
|
||||
.fc-area-actions .btn,
|
||||
.fc-area-actions .fc-area-submit-btn {
|
||||
min-width: 128px;
|
||||
height: 40px;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
font-weight: 700;
|
||||
background: linear-gradient(135deg, #00d26a, #00a854) !important;
|
||||
color: #ffffff !important;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
box-shadow: 0 4px 14px rgba(0, 210, 106, 0.28);
|
||||
opacity: 1 !important;
|
||||
transition: filter 0.15s ease, transform 0.15s ease;
|
||||
}
|
||||
|
||||
.fc-area-actions .btn:hover,
|
||||
.fc-area-actions .btn:focus,
|
||||
.fc-area-actions .fc-area-submit-btn:hover,
|
||||
.fc-area-actions .fc-area-submit-btn:focus {
|
||||
background: linear-gradient(135deg, #00c462, #00964b) !important;
|
||||
color: #ffffff !important;
|
||||
filter: brightness(0.98);
|
||||
}
|
||||
|
||||
@media (max-width: 767px) {
|
||||
|
||||
@@ -12,8 +12,19 @@ define([], function () {
|
||||
var lat = lat_id ? $("#" + lat_id).val() : '';
|
||||
var lng = lng_id ? $("#" + lng_id).val() : '';
|
||||
var zoom = zoom_id ? $("#" + zoom_id).val() : '';
|
||||
var url = "/addons/address/index/select";
|
||||
url += (lat && lng) ? '?lat=' + lat + '&lng=' + lng + (input_id ? "&address=" + $("#" + input_id).val() : "") + (zoom ? "&zoom=" + zoom : "") : '';
|
||||
var address = input_id ? $("#" + input_id).val() : ($(that).data("address") || '');
|
||||
var query = [];
|
||||
if (lat && lng) {
|
||||
query.push('lat=' + encodeURIComponent(lat));
|
||||
query.push('lng=' + encodeURIComponent(lng));
|
||||
}
|
||||
if (address) {
|
||||
query.push('address=' + encodeURIComponent(address));
|
||||
}
|
||||
if (zoom) {
|
||||
query.push('zoom=' + encodeURIComponent(zoom));
|
||||
}
|
||||
var url = "/addons/address/index/select" + (query.length ? '?' + query.join('&') : '');
|
||||
Fast.api.open(url, '位置选择', {
|
||||
callback: function (res) {
|
||||
input_id && $("#" + input_id).val(res.address).trigger("change");
|
||||
|
||||
@@ -321,7 +321,50 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'backend/format'], fu
|
||||
};
|
||||
|
||||
/**
|
||||
* 刷新完整区域实时预览文案
|
||||
* 将 WGS84 坐标转换为 GCJ02 坐标(中国大陆标准)
|
||||
* @param {number} wgsLng WGS84经度
|
||||
* @param {number} wgsLat WGS84纬度
|
||||
* @return {{lat: number, lng: number}}
|
||||
*/
|
||||
var wgs84ToGcj02 = function (wgsLng, wgsLat) {
|
||||
var lon = Number(wgsLng);
|
||||
var lat = Number(wgsLat);
|
||||
if (!isFinite(lon) || !isFinite(lat)) {
|
||||
return {lat: lat, lng: lon};
|
||||
}
|
||||
var a = 6378245.0;
|
||||
var ee = 0.00669342162296594323;
|
||||
var pi = Math.PI;
|
||||
var transformLat = function (x, y) {
|
||||
var ret = -100.0 + 2.0 * x + 3.0 * y + 0.2 * y * y + 0.1 * x * y + 0.2 * Math.sqrt(Math.abs(x));
|
||||
ret += (20.0 * Math.sin(6.0 * x * pi) + 20.0 * Math.sin(2.0 * x * pi)) * 2.0 / 3.0;
|
||||
ret += (20.0 * Math.sin(y * pi) + 40.0 * Math.sin(y / 3.0 * pi)) * 2.0 / 3.0;
|
||||
ret += (160.0 * Math.sin(y / 12.0 * pi) + 320 * Math.sin(y * pi / 30.0)) * 2.0 / 3.0;
|
||||
return ret;
|
||||
};
|
||||
var transformLon = function (x, y) {
|
||||
var ret = 300.0 + x + 2.0 * y + 0.1 * x * x + 0.1 * x * y + 0.1 * Math.sqrt(Math.abs(x));
|
||||
ret += (20.0 * Math.sin(6.0 * x * pi) + 20.0 * Math.sin(2.0 * x * pi)) * 2.0 / 3.0;
|
||||
ret += (20.0 * Math.sin(x * pi) + 40.0 * Math.sin(x / 3.0 * pi)) * 2.0 / 3.0;
|
||||
ret += (150.0 * Math.sin(x / 12.0 * pi) + 300.0 * Math.sin(x / 30.0 * pi)) * 2.0 / 3.0;
|
||||
return ret;
|
||||
};
|
||||
var dLat = transformLat(lon - 105.0, lat - 35.0);
|
||||
var dLon = transformLon(lon - 105.0, lat - 35.0);
|
||||
var radLat = lat / 180.0 * pi;
|
||||
var magic = Math.sin(radLat);
|
||||
magic = 1 - ee * magic * magic;
|
||||
var sqrtMagic = Math.sqrt(magic);
|
||||
dLat = (dLat * 180.0) / ((a * (1 - ee)) / (magic * sqrtMagic) * pi);
|
||||
dLon = (dLon * 180.0) / (a / sqrtMagic * Math.cos(radLat) * pi);
|
||||
return {
|
||||
lat: lat + dLat,
|
||||
lng: lon + dLon
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* 刷新完整区域实时预览文案并同步地图选点按钮默认地址
|
||||
*/
|
||||
var refreshPreview = function () {
|
||||
var parts = [
|
||||
@@ -332,6 +375,7 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'backend/format'], fu
|
||||
return item !== '';
|
||||
});
|
||||
$('#area-full-name').text(parts.length ? parts.join(' / ') : '未选择');
|
||||
$('.fc-area-map-btn').data('address', parts.join(''));
|
||||
syncWrapState();
|
||||
};
|
||||
|
||||
@@ -354,7 +398,7 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'backend/format'], fu
|
||||
};
|
||||
|
||||
/**
|
||||
* 根据所选省市区自动解析中心经纬度(腾讯地图Place API JSONP)
|
||||
* 根据所选省市区自动解析中心经纬度(腾讯Place接口 + Nominatim自动降级)
|
||||
*/
|
||||
var autoResolveCoordinates = function () {
|
||||
var p = String($province.val() || '').trim();
|
||||
@@ -375,13 +419,37 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'backend/format'], fu
|
||||
clearTimeout(geocodeTimer);
|
||||
}
|
||||
geocodeTimer = setTimeout(function () {
|
||||
var queryAddress = (p === c ? p : (p + c)) + d;
|
||||
var queryAddress = (p === c ? p : (p + ' ' + c)) + ' ' + d;
|
||||
var resolveByNominatim = function () {
|
||||
$.ajax({
|
||||
url: 'https://nominatim.openstreetmap.org/search',
|
||||
dataType: 'jsonp',
|
||||
jsonp: 'json_callback',
|
||||
data: {
|
||||
format: 'json',
|
||||
q: queryAddress,
|
||||
limit: 1,
|
||||
'accept-language': 'zh-CN',
|
||||
countrycodes: 'cn'
|
||||
},
|
||||
success: function (list) {
|
||||
if ($.isArray(list) && list.length > 0 && list[0].lat && list[0].lon) {
|
||||
var gcj = wgs84ToGcj02(list[0].lon, list[0].lat);
|
||||
coordAutoFilled = true;
|
||||
$lat.val(Number(gcj.lat).toFixed(6));
|
||||
$lng.val(Number(gcj.lng).toFixed(6));
|
||||
refreshCoordTip(true);
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
var tencentKey = 'WM7BZ-ZAYLL-MZEPZ-MRZBT-MBYJE-ZAFIT';
|
||||
$.ajax({
|
||||
url: 'https://apis.map.qq.com/ws/place/v1/suggestion',
|
||||
dataType: 'jsonp',
|
||||
data: {
|
||||
keyword: queryAddress,
|
||||
keyword: (p === c ? p : (p + c)) + d,
|
||||
region: c,
|
||||
key: tencentKey,
|
||||
output: 'jsonp'
|
||||
@@ -394,8 +462,13 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'backend/format'], fu
|
||||
$lat.val(Number(loc.lat).toFixed(6));
|
||||
$lng.val(Number(loc.lng).toFixed(6));
|
||||
refreshCoordTip(true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
resolveByNominatim();
|
||||
},
|
||||
error: function () {
|
||||
resolveByNominatim();
|
||||
}
|
||||
});
|
||||
}, 180);
|
||||
@@ -847,6 +920,66 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'backend/format'], fu
|
||||
closeAllDropdowns();
|
||||
}
|
||||
});
|
||||
|
||||
// 地图选点完成回调:不仅填充坐标,还可根据地图所选位置自动反填省 / 市 / 区县
|
||||
$('.fc-area-map-btn').data('callback', function (res) {
|
||||
if (!res) {
|
||||
return;
|
||||
}
|
||||
var fullText = String(res.address || '') + ' ' + ($.isArray(res.rawParts) ? res.rawParts.join(' ') : '');
|
||||
var matchedProv = resolveProvinceKey(res.province || '');
|
||||
if (!matchedProv && fullText) {
|
||||
for (var pIdx = 0; pIdx < tree.provinces.length; pIdx++) {
|
||||
var pCandidate = tree.provinces[pIdx];
|
||||
var pShort = pCandidate.replace(/省|市|自治区|特别行政区|壮族|回族|维吾尔/g, '');
|
||||
if (fullText.indexOf(pCandidate) !== -1 || (pShort.length >= 2 && fullText.indexOf(pShort) !== -1)) {
|
||||
matchedProv = pCandidate;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var matchedCity = String(res.city || '').trim();
|
||||
if (matchedProv && tree.cityMap[matchedProv]) {
|
||||
var cCandidates = tree.cityMap[matchedProv];
|
||||
if (cCandidates.length === 1) {
|
||||
matchedCity = cCandidates[0];
|
||||
} else if (!matchedCity || cCandidates.indexOf(matchedCity) === -1) {
|
||||
for (var cIdx = 0; cIdx < cCandidates.length; cIdx++) {
|
||||
var cItem = cCandidates[cIdx];
|
||||
var cShort = cItem.replace(/市|自治州|地区|盟/g, '');
|
||||
if (fullText.indexOf(cItem) !== -1 || (cShort.length >= 2 && fullText.indexOf(cShort) !== -1)) {
|
||||
matchedCity = cItem;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var matchedDist = String(res.district || '').trim();
|
||||
if (matchedProv && matchedCity && tree.districtMap[matchedProv + '|' + matchedCity]) {
|
||||
var dCandidates = tree.districtMap[matchedProv + '|' + matchedCity];
|
||||
if (!matchedDist || dCandidates.indexOf(matchedDist) === -1) {
|
||||
for (var dIdx = 0; dIdx < dCandidates.length; dIdx++) {
|
||||
if (fullText.indexOf(dCandidates[dIdx]) !== -1) {
|
||||
matchedDist = dCandidates[dIdx];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (matchedProv) {
|
||||
$province.val(matchedProv).trigger('change').trigger('validate');
|
||||
}
|
||||
if (matchedCity) {
|
||||
$city.val(matchedCity).trigger('change').trigger('validate');
|
||||
}
|
||||
if (matchedDist) {
|
||||
$district.val(matchedDist).trigger('change').trigger('validate');
|
||||
}
|
||||
refreshPreview();
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,8 +31,14 @@ export default {
|
||||
ic = sceneStr.slice(3)
|
||||
} else if (sceneStr.indexOf('uid_') === 0) {
|
||||
inviterId = sceneStr.slice(4)
|
||||
} else if (/^id_\d+_u_(\d+)$/.test(sceneStr)) {
|
||||
const m = sceneStr.match(/^id_\d+_u_(\d+)$/)
|
||||
if (m && m[1]) inviterId = m[1]
|
||||
} else if (sceneStr.indexOf('uid=') === 0) {
|
||||
inviterId = sceneStr.slice(4)
|
||||
} else if (/^id_\d+_u_(\d+)$/.test(sceneStr)) {
|
||||
const m = sceneStr.match(/^id_\d+_u_(\d+)$/)
|
||||
if (m && m[1]) inviterId = m[1]
|
||||
} else if (/^[A-Za-z0-9]{4,16}$/.test(sceneStr)) {
|
||||
ic = sceneStr
|
||||
}
|
||||
|
||||
@@ -11,8 +11,8 @@ export const distributionApi = {
|
||||
team: (params = {}) => get('/distribution/team', params, { silent: true }),
|
||||
/** 分销收益明细账单(佣金流水) */
|
||||
commissions: (params = {}) => get('/distribution/commissions', params, { silent: true }),
|
||||
/** 专属推广海报要素 */
|
||||
poster: () => get('/distribution/poster', {}, { silent: true }),
|
||||
/** 专属推广海报要素(支持传入 postId 获取详情页小程序码) */
|
||||
poster: (params = {}) => get('/distribution/poster', params, { silent: true }),
|
||||
/** 申请提现(提现至微信零钱) */
|
||||
withdraw: (payload) => post('/distribution/withdraw', payload, { silent: true }),
|
||||
/** 提现记录 */
|
||||
|
||||
@@ -471,19 +471,16 @@
|
||||
<text class="detail__poster-tip">点击大图可全屏预览,支持长按直接转发</text>
|
||||
</view>
|
||||
|
||||
<!-- 底部三大快捷分享操作 -->
|
||||
<!-- 底部快捷分享操作 -->
|
||||
<view class="detail__poster-actions">
|
||||
<view class="detail__poster-btn detail__poster-btn--primary" @click="savePoster">
|
||||
<image class="fc-icon fc-icon--sm" :src="iconUrl('image-white')" mode="aspectFit" />
|
||||
<text class="detail__poster-btn-text">保存到手机相册</text>
|
||||
</view>
|
||||
<button class="detail__poster-btn detail__poster-btn--share" open-type="share">
|
||||
<button class="detail__poster-btn detail__poster-btn--share" @click="sharePosterImage">
|
||||
<image class="fc-icon fc-icon--sm" :src="iconUrl('bar-share')" mode="aspectFit" />
|
||||
<text class="detail__poster-btn-text">发给微信好友</text>
|
||||
</button>
|
||||
<view class="detail__poster-btn detail__poster-btn--ghost" @click="copyPosterText">
|
||||
<text>复制文案</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
@@ -503,6 +500,7 @@ import postApi from '@/common/api/post.js'
|
||||
import favoriteApi from '@/common/api/favorite.js'
|
||||
import reportApi from '@/common/api/report.js'
|
||||
import memberApi from '@/common/api/member.js'
|
||||
import distributionApi from '@/common/api/distribution.js'
|
||||
import { REPORT_REASONS } from '@/common/constants/report.js'
|
||||
import { iconUrl, categoryIconUrl, loadUiConfig, uiState } from '@/common/store/ui.js'
|
||||
import { fromNow, assetUrl } from '@/common/utils/format.js'
|
||||
@@ -691,8 +689,29 @@ export default {
|
||||
}
|
||||
},
|
||||
},
|
||||
/**
|
||||
* 页面加载:提取帖子ID与分销裂变参数
|
||||
* @param {Object} query 路由入参
|
||||
*/
|
||||
onLoad(query) {
|
||||
this.id = Number(query.id || 101)
|
||||
let postIdFromScene = 0
|
||||
if (query) {
|
||||
const ic = query.ic || query.inviteCode || query.invite_code
|
||||
let inviterId = query.inviterId || query.inviter_id
|
||||
if (query.scene) {
|
||||
try {
|
||||
const s = decodeURIComponent(query.scene)
|
||||
const m = s.match(/^id_(\d+)(?:_u_(\d+))?$/)
|
||||
if (m) {
|
||||
postIdFromScene = Number(m[1] || 0)
|
||||
if (m[2]) inviterId = m[2]
|
||||
}
|
||||
} catch (e) {}
|
||||
}
|
||||
if (ic) uni.setStorageSync('fc_invite_code', String(ic))
|
||||
if (inviterId) uni.setStorageSync('fc_inviter_id', Number(inviterId))
|
||||
}
|
||||
this.id = Number((query && query.id) || postIdFromScene || 101)
|
||||
this.posterImagePath = ''
|
||||
loadUiConfig()
|
||||
this.fetch()
|
||||
@@ -701,19 +720,21 @@ export default {
|
||||
if (this.loaded) this.refreshAccess()
|
||||
},
|
||||
/**
|
||||
* 转发给微信好友(携带分销邀请人与精美封面图)
|
||||
* 转发给微信好友(携带分销邀请码/邀请人与精美封面图)
|
||||
*/
|
||||
onShareAppMessage() {
|
||||
let inviterId = 0
|
||||
let inviteCode = ''
|
||||
try {
|
||||
const user = this.currentUser
|
||||
if (user && user.id) inviterId = user.id
|
||||
if (user && user.inviteCode) inviteCode = user.inviteCode
|
||||
} catch (e) {}
|
||||
|
||||
const shareTitle = this.post.title
|
||||
? `【${this.post.categoryName || '同城信息'}】${this.post.title}`
|
||||
: (uiState.shareTitle || uiState.siteName || '同城便民信息')
|
||||
const sharePath = `/pages/detail/detail?id=${this.id}${inviterId ? '&inviterId=' + inviterId : ''}`
|
||||
const sharePath = `/pages/detail/detail?id=${this.id}${inviteCode ? '&ic=' + encodeURIComponent(inviteCode) : ''}${inviterId ? '&inviterId=' + inviterId : ''}`
|
||||
const shareImg = this.posterImagePath || (this.imageUrls && this.imageUrls.length ? this.imageUrls[0] : (this.post.cover ? assetUrl(this.post.cover) : ''))
|
||||
|
||||
return {
|
||||
@@ -723,13 +744,15 @@ export default {
|
||||
}
|
||||
},
|
||||
/**
|
||||
* 转发到微信朋友圈
|
||||
* 转发到微信朋友圈(携带分销邀请码/邀请人)
|
||||
*/
|
||||
onShareTimeline() {
|
||||
let inviterId = 0
|
||||
let inviteCode = ''
|
||||
try {
|
||||
const user = this.currentUser
|
||||
if (user && user.id) inviterId = user.id
|
||||
if (user && user.inviteCode) inviteCode = user.inviteCode
|
||||
} catch (e) {}
|
||||
|
||||
const shareTitle = this.post.title
|
||||
@@ -739,7 +762,7 @@ export default {
|
||||
|
||||
return {
|
||||
title: shareTitle,
|
||||
query: `id=${this.id}${inviterId ? '&inviterId=' + inviterId : ''}`,
|
||||
query: `id=${this.id}${inviteCode ? '&ic=' + encodeURIComponent(inviteCode) : ''}${inviterId ? '&inviterId=' + inviterId : ''}`,
|
||||
imageUrl: shareImg || undefined,
|
||||
}
|
||||
},
|
||||
@@ -1693,8 +1716,22 @@ export default {
|
||||
} catch (e) {}
|
||||
const qrUrl = `https://fenleixinxi.gzyakj.com.cn/pages/detail/detail?id=${this.id}${inviterId ? '&inviterId=' + inviterId : ''}`
|
||||
|
||||
// 绘制高识别度合规二维码(124 x 124)
|
||||
// 优先拉取并绘制微信官方小程序码,未发布或受限时无缝回退至自绘二维码(124 x 124)
|
||||
let wxacodeLocal = ''
|
||||
if (this.currentUser) {
|
||||
try {
|
||||
const posterMeta = await distributionApi.poster({ postId: this.id })
|
||||
if (posterMeta && posterMeta.wxacodeUrl) {
|
||||
wxacodeLocal = await this.getImageLocalPath(assetUrl(posterMeta.wxacodeUrl))
|
||||
}
|
||||
} catch (e) {}
|
||||
}
|
||||
if (wxacodeLocal) {
|
||||
this.drawRoundRect(ctx, 56, 830, 124, 124, 10, '#FFFFFF', '#DFF2E8', 1)
|
||||
ctx.drawImage(wxacodeLocal, 60, 834, 116, 116)
|
||||
} else {
|
||||
this.drawRealQRCode(ctx, qrUrl, 56, 830, 124)
|
||||
}
|
||||
|
||||
// 右侧指引文案(显式左对齐,彻底杜绝对齐错乱)
|
||||
this.drawSafeText(ctx, '微信长按识别小程序', 198, 842, {
|
||||
@@ -1743,6 +1780,29 @@ export default {
|
||||
}, 300)
|
||||
})
|
||||
},
|
||||
/**
|
||||
* 唤起微信原生图片分享面板(一键发给好友/发朋友圈/收藏),不支持时回退全屏预览长按转发
|
||||
*/
|
||||
sharePosterImage() {
|
||||
if (!this.posterImagePath) {
|
||||
uni.showToast({ title: '海报准备中,请稍候', icon: 'none' })
|
||||
return
|
||||
}
|
||||
// #ifdef MP-WEIXIN
|
||||
if (typeof wx !== 'undefined' && typeof wx.showShareImageMenu === 'function') {
|
||||
wx.showShareImageMenu({
|
||||
path: this.posterImagePath,
|
||||
fail: (err) => {
|
||||
if (err && err.errMsg && !err.errMsg.includes('cancel')) {
|
||||
this.previewPoster()
|
||||
}
|
||||
},
|
||||
})
|
||||
return
|
||||
}
|
||||
// #endif
|
||||
this.previewPoster()
|
||||
},
|
||||
/**
|
||||
* 预览海报大图
|
||||
*/
|
||||
|
||||
@@ -512,14 +512,11 @@
|
||||
<text>保存到相册</text>
|
||||
</view>
|
||||
<!-- #ifdef MP-WEIXIN -->
|
||||
<button class="dist__poster-act-btn dist__poster-act-btn--share" open-type="share">
|
||||
<button class="dist__poster-act-btn dist__poster-act-btn--share" @click="sharePosterImage">
|
||||
<image class="dist__btn-icon" :src="iconUrl('share')" mode="aspectFit" />
|
||||
<text>发给好友</text>
|
||||
</button>
|
||||
<!-- #endif -->
|
||||
<view class="dist__poster-act-btn dist__poster-act-btn--ghost" @click="copyPosterText">
|
||||
<text>复制文案</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
@@ -528,7 +525,7 @@
|
||||
<canvas
|
||||
canvas-id="distPosterCanvas"
|
||||
id="distPosterCanvas"
|
||||
style="width: 600px; height: 880px; position: fixed; left: -9999px; top: -9999px; z-index: -999;"
|
||||
style="width: 600px; height: 740px; position: fixed; left: -9999px; top: -9999px; z-index: -999;"
|
||||
/>
|
||||
</view>
|
||||
</template>
|
||||
@@ -1134,7 +1131,7 @@ export default {
|
||||
}, 6000)
|
||||
|
||||
const W = 600
|
||||
const H = 880
|
||||
const H = 740
|
||||
const ctx = uni.createCanvasContext('distPosterCanvas', this)
|
||||
|
||||
try {
|
||||
@@ -1158,7 +1155,7 @@ export default {
|
||||
ctx.fillRect(0, 0, W, H)
|
||||
|
||||
// 2. 纯白立体高光主卡片(圆角 24px,细边框 #D1FAE5)
|
||||
this.drawRoundRect(ctx, 20, 20, 560, 840, 24, '#FFFFFF', '#D1FAE5', 1.5)
|
||||
this.drawRoundRect(ctx, 20, 20, 560, 700, 24, '#FFFFFF', '#D1FAE5', 1.5)
|
||||
|
||||
// 3. 顶部 Header 栏(y: 44 ~ 76)
|
||||
// 左侧绿色品牌胶囊
|
||||
@@ -1291,7 +1288,16 @@ export default {
|
||||
const qrX = 48
|
||||
const qrY = 546
|
||||
const qrSize = 150
|
||||
let wxacodeLocal = ''
|
||||
if (meta && meta.wxacodeUrl) {
|
||||
wxacodeLocal = await this.getImageLocalPath(meta.wxacodeUrl, 1500)
|
||||
}
|
||||
if (wxacodeLocal) {
|
||||
this.drawRoundRect(ctx, qrX, qrY, qrSize, qrSize, 12, '#FFFFFF', '#DFF2E8', 1)
|
||||
ctx.drawImage(wxacodeLocal, qrX + 6, qrY + 6, qrSize - 12, qrSize - 12)
|
||||
} else {
|
||||
this.drawRealQRCode(ctx, qrUrl, qrX, qrY, qrSize)
|
||||
}
|
||||
|
||||
// 二维码右侧文字说明(从 x = 216 开始,宽度 344px 极为充裕,排版整洁绝不重叠)
|
||||
const textX = 218
|
||||
@@ -1362,6 +1368,33 @@ export default {
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 全屏预览生成的海报大图
|
||||
*/
|
||||
/**
|
||||
* 唤起微信原生图片转发菜单(一键发给好友/发朋友圈/收藏),不支持时回退全屏预览长按转发
|
||||
*/
|
||||
sharePosterImage() {
|
||||
if (!this.posterImagePath) {
|
||||
uni.showToast({ title: '海报正在生成,请稍候', icon: 'none' })
|
||||
return
|
||||
}
|
||||
// #ifdef MP-WEIXIN
|
||||
if (typeof wx !== 'undefined' && typeof wx.showShareImageMenu === 'function') {
|
||||
wx.showShareImageMenu({
|
||||
path: this.posterImagePath,
|
||||
fail: (err) => {
|
||||
if (err && err.errMsg && !err.errMsg.includes('cancel')) {
|
||||
this.previewPoster()
|
||||
}
|
||||
},
|
||||
})
|
||||
return
|
||||
}
|
||||
// #endif
|
||||
this.previewPoster()
|
||||
},
|
||||
|
||||
/**
|
||||
* 全屏预览生成的海报大图
|
||||
*/
|
||||
@@ -2422,7 +2455,7 @@ export default {
|
||||
}
|
||||
.dist__poster-img {
|
||||
width: 500rpx;
|
||||
height: 733rpx;
|
||||
height: 616rpx;
|
||||
border-radius: 20rpx;
|
||||
box-shadow: 0 16rpx 40rpx rgba(4, 48, 30, 0.16);
|
||||
background: #ffffff;
|
||||
|
||||
@@ -356,12 +356,7 @@ export default {
|
||||
*/
|
||||
vipTitle() {
|
||||
if (this.isMember) return '会员权益已生效'
|
||||
const b = this.benefits || {}
|
||||
if (b.postMode === 'free') {
|
||||
return '开通会员,尊享 VIP 专属特权'
|
||||
}
|
||||
const discount = b.postDiscount
|
||||
return discount ? `开通会员,享专属发帖与查看特权` : '开通会员,享专属特权'
|
||||
return '开通会员 · 享免费发帖看号'
|
||||
},
|
||||
/**
|
||||
* 顶栏显示的城市或区域文案(优先当前选择区县,其次城市,缺省回退到同城服务)
|
||||
@@ -472,16 +467,44 @@ export default {
|
||||
onUnload() {
|
||||
uni.$off('refreshUnreadCount')
|
||||
},
|
||||
/**
|
||||
* 微信好友转发(携带当前登录用户的分销邀请码/ID)
|
||||
*/
|
||||
onShareAppMessage() {
|
||||
let queryStr = ''
|
||||
try {
|
||||
const store = useUserStore()
|
||||
const user = (store.userInfo && store.userInfo.value) || store.userInfo || (store.state && store.state.userInfo)
|
||||
if (user) {
|
||||
const params = []
|
||||
if (user.inviteCode) params.push('ic=' + encodeURIComponent(user.inviteCode))
|
||||
if (user.id) params.push('inviterId=' + encodeURIComponent(user.id))
|
||||
if (params.length) queryStr = '?' + params.join('&')
|
||||
}
|
||||
} catch (e) {}
|
||||
return {
|
||||
title: uiState.shareTitle || uiState.siteName || '同城分类信息',
|
||||
path: '/pages/index/index',
|
||||
path: '/pages/index/index' + queryStr,
|
||||
}
|
||||
},
|
||||
/**
|
||||
* 微信朋友圈转发(携带当前登录用户的分销邀请码/ID)
|
||||
*/
|
||||
onShareTimeline() {
|
||||
let queryStr = ''
|
||||
try {
|
||||
const store = useUserStore()
|
||||
const user = (store.userInfo && store.userInfo.value) || store.userInfo || (store.state && store.state.userInfo)
|
||||
if (user) {
|
||||
const params = []
|
||||
if (user.inviteCode) params.push('ic=' + encodeURIComponent(user.inviteCode))
|
||||
if (user.id) params.push('inviterId=' + encodeURIComponent(user.id))
|
||||
if (params.length) queryStr = params.join('&')
|
||||
}
|
||||
} catch (e) {}
|
||||
return {
|
||||
title: uiState.shareTitle || uiState.siteName || '同城分类信息',
|
||||
query: '',
|
||||
query: queryStr,
|
||||
}
|
||||
},
|
||||
/**
|
||||
@@ -1208,35 +1231,52 @@ export default {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 16rpx;
|
||||
box-shadow: $fc-shadow;
|
||||
}
|
||||
.home__vip-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
.home__vip-left .fc-icon {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.home__vip-texts {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-left: 12rpx;
|
||||
margin-left: 14rpx;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
.home__vip-title {
|
||||
color: #ffffff;
|
||||
font-size: $fc-font-lg;
|
||||
font-size: 30rpx;
|
||||
font-weight: 700;
|
||||
line-height: $fc-leading-tight;
|
||||
line-height: 1.3;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.home__vip-sub {
|
||||
color: rgba(255, 255, 255, 0.9);
|
||||
font-size: $fc-font-xs;
|
||||
margin-top: 8rpx;
|
||||
color: rgba(255, 255, 255, 0.92);
|
||||
font-size: 22rpx;
|
||||
margin-top: 6rpx;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.home__vip-btn {
|
||||
flex-shrink: 0;
|
||||
white-space: nowrap;
|
||||
padding: 12rpx 28rpx;
|
||||
border-radius: $fc-radius-pill;
|
||||
background: #ffffff;
|
||||
color: $fc-primary-text;
|
||||
font-size: $fc-font-sm;
|
||||
font-size: 24rpx;
|
||||
font-weight: 700;
|
||||
line-height: 1.2;
|
||||
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
|
||||
@@ -179,6 +179,7 @@ import FcEmpty from '@/components/fc-empty/fc-empty.vue'
|
||||
import { merchantApi } from '@/common/api/merchant.js'
|
||||
import { iconUrl, loadUiConfig } from '@/common/store/ui.js'
|
||||
import { assetUrl } from '@/common/utils/format.js'
|
||||
import { useUserStore } from '@/common/store/index.js'
|
||||
|
||||
export default {
|
||||
components: { FcNavbar, FcPostCard, FcEmpty },
|
||||
@@ -232,7 +233,13 @@ export default {
|
||||
* @param {Object} query 路由入参
|
||||
*/
|
||||
onLoad(query) {
|
||||
this.merchantId = Number(query.id || query.userId || 0)
|
||||
if (query) {
|
||||
const ic = query.ic || query.inviteCode || query.invite_code
|
||||
const inviterId = query.inviterId || query.inviter_id
|
||||
if (ic) uni.setStorageSync('fc_invite_code', String(ic))
|
||||
if (inviterId) uni.setStorageSync('fc_inviter_id', Number(inviterId))
|
||||
}
|
||||
this.merchantId = Number((query && (query.id || query.userId)) || 0)
|
||||
this.postId = Number(query.postId || 0)
|
||||
loadUiConfig()
|
||||
this.fetchLocation()
|
||||
@@ -260,11 +267,44 @@ export default {
|
||||
onShareAppMessage() {
|
||||
return {
|
||||
title: `【认证商家】${this.merchant.name || '同城实体门店'}`,
|
||||
path: `/pages/merchant/detail?id=${this.merchantId}`,
|
||||
path: this.buildSharePath(),
|
||||
imageUrl: this.merchant.logo ? assetUrl(this.merchant.logo) : undefined,
|
||||
}
|
||||
},
|
||||
/**
|
||||
* 微信朋友圈分享配置(携带当前登录用户的分销邀请码/ID)
|
||||
*/
|
||||
onShareTimeline() {
|
||||
return {
|
||||
title: `【认证商家】${this.merchant.name || '同城实体门店'}`,
|
||||
query: this.buildShareQuery(),
|
||||
imageUrl: this.merchant.logo ? assetUrl(this.merchant.logo) : undefined,
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* 构造带分销参数的商家详情页分享 query
|
||||
* @returns {string}
|
||||
*/
|
||||
buildShareQuery() {
|
||||
const params = [`id=${this.merchantId}`]
|
||||
try {
|
||||
const store = useUserStore()
|
||||
const user = (store.userInfo && store.userInfo.value) || store.userInfo || (store.state && store.state.userInfo)
|
||||
if (user) {
|
||||
if (user.inviteCode) params.push('ic=' + encodeURIComponent(user.inviteCode))
|
||||
if (user.id) params.push('inviterId=' + encodeURIComponent(user.id))
|
||||
}
|
||||
} catch (e) {}
|
||||
return params.join('&')
|
||||
},
|
||||
/**
|
||||
* 构造带分销参数的商家详情页分享路径
|
||||
* @returns {string}
|
||||
*/
|
||||
buildSharePath() {
|
||||
return '/pages/merchant/detail?' + this.buildShareQuery()
|
||||
},
|
||||
iconUrl,
|
||||
assetUrl,
|
||||
/**
|
||||
|
||||
@@ -766,23 +766,32 @@ export default {
|
||||
.mine__vip-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
margin-right: 16rpx;
|
||||
}
|
||||
|
||||
.mine__vip-icon {
|
||||
width: 52rpx;
|
||||
height: 52rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.mine__vip-texts {
|
||||
margin-left: 18rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.mine__vip-title {
|
||||
color: #4a2600; /* 深褐黑金字 */
|
||||
font-size: 30rpx;
|
||||
font-weight: 800;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.mine__vip-sub {
|
||||
@@ -790,9 +799,14 @@ export default {
|
||||
color: #7a4600; /* 高清晰深暖金副标题 */
|
||||
font-size: 22rpx;
|
||||
font-weight: 600;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.mine__vip-btn {
|
||||
flex-shrink: 0;
|
||||
white-space: nowrap;
|
||||
padding: 10rpx 26rpx;
|
||||
border-radius: $fc-radius-pill;
|
||||
background: #ffffff;
|
||||
|
||||
在新工单中引用
屏蔽一个用户