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 lat = lat_id ? $("#" + lat_id).val() : '';
|
||||||
var lng = lng_id ? $("#" + lng_id).val() : '';
|
var lng = lng_id ? $("#" + lng_id).val() : '';
|
||||||
var zoom = zoom_id ? $("#" + zoom_id).val() : '';
|
var zoom = zoom_id ? $("#" + zoom_id).val() : '';
|
||||||
var url = "/addons/address/index/select";
|
var address = input_id ? $("#" + input_id).val() : ($(that).data("address") || '');
|
||||||
url += (lat && lng) ? '?lat=' + lat + '&lng=' + lng + (input_id ? "&address=" + $("#" + input_id).val() : "") + (zoom ? "&zoom=" + zoom : "") : '';
|
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, '位置选择', {
|
Fast.api.open(url, '位置选择', {
|
||||||
callback: function (res) {
|
callback: function (res) {
|
||||||
input_id && $("#" + input_id).val(res.address).trigger("change");
|
input_id && $("#" + input_id).val(res.address).trigger("change");
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
body {
|
body {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "PingFang SC", "Microsoft YaHei", sans-serif;
|
||||||
}
|
}
|
||||||
|
|
||||||
#container {
|
#container {
|
||||||
@@ -19,72 +20,173 @@
|
|||||||
bottom: 0;
|
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 {
|
.search {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
width: 400px;
|
width: 440px;
|
||||||
top: 0;
|
max-width: 92%;
|
||||||
|
top: 12px;
|
||||||
left: 50%;
|
left: 50%;
|
||||||
padding: 5px;
|
transform: translateX(-50%);
|
||||||
margin-left: -200px;
|
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 {
|
.autocomplete-search {
|
||||||
text-align: left;
|
text-align: left;
|
||||||
cursor: default;
|
cursor: default;
|
||||||
background: #fff;
|
background: #fff;
|
||||||
border-radius: 2px;
|
border-radius: 8px;
|
||||||
-webkit-box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175);
|
box-shadow: 0 8px 24px rgba(15, 23, 42, 0.16);
|
||||||
-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;
|
|
||||||
position: absolute;
|
position: absolute;
|
||||||
display: none;
|
display: none;
|
||||||
z-index: 1036;
|
z-index: 1036;
|
||||||
max-height: 254px;
|
max-height: 280px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
margin-top: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.autocomplete-search .autocomplete-suggestion {
|
.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 {
|
.autocomplete-search .autocomplete-suggestion:last-child {
|
||||||
background: #f0f0f0;
|
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>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="search">
|
<div class="search">
|
||||||
<div class="input-group">
|
<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">
|
<span class="input-group-btn">
|
||||||
<button type="button" name="search" id="search-btn" class="btn btn-success">
|
<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>
|
</button>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</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>
|
<div id="container"></div>
|
||||||
|
|
||||||
<script charset="utf-8" src="//map.qq.com/api/js?v=2.exp&libraries=place&key={$config.tencentkey|default=''}"></script>
|
<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">
|
<script type="text/javascript">
|
||||||
$(function () {
|
$(function () {
|
||||||
var map, marker, geocoder, infoWin, searchService, keyword, address, fromtype, totype;
|
var map, marker, geocoder, infoWin, xhr;
|
||||||
address = "{$address|htmlentities}";
|
var initialAddress = "{$address|htmlentities}";
|
||||||
|
var hasUrlCoord = location.search.indexOf('lat=') !== -1 && location.search.indexOf('lng=') !== -1;
|
||||||
var lng = Number("{$lng}");
|
var lng = Number("{$lng}");
|
||||||
var lat = Number("{$lat}");
|
var lat = Number("{$lat}");
|
||||||
fromtype = "GCJ02";
|
var fromtype = "GCJ02";
|
||||||
totype = "{$config.coordtype|default='DEFAULT'}"
|
var totype = "{$config.coordtype|default='DEFAULT'}";
|
||||||
totype = totype === 'DEFAULT' ? "GCJ02" : totype;
|
totype = totype === 'DEFAULT' ? "GCJ02" : totype;
|
||||||
|
|
||||||
if (lng && lat && fromtype !== totype) {
|
if (lng && lat && fromtype !== totype) {
|
||||||
var result = gcoord.transform([lng, lat], gcoord[totype], gcoord[fromtype]);
|
var converted = gcoord.transform([lng, lat], gcoord[totype], gcoord[fromtype]);
|
||||||
lng = result[0] || lng;
|
lng = converted[0] || lng;
|
||||||
lat = result[1] || lat;
|
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 init = function () {
|
||||||
var center = new qq.maps.LatLng(lat, lng);
|
var center = new qq.maps.LatLng(lat, lng);
|
||||||
map = new qq.maps.Map(document.getElementById('container'), {
|
map = new qq.maps.Map(document.getElementById('container'), {
|
||||||
center: center,
|
center: center,
|
||||||
zoom: parseInt("{$config.zoom}")
|
zoom: parseInt("{$config.zoom}") || 12
|
||||||
});
|
});
|
||||||
|
|
||||||
//实例化信息窗口
|
|
||||||
infoWin = new qq.maps.InfoWindow({
|
infoWin = new qq.maps.InfoWindow({
|
||||||
map: map
|
map: map
|
||||||
});
|
});
|
||||||
|
|
||||||
geocoder = {
|
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://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({
|
$.ajax({
|
||||||
url: "https://apis.map.qq.com/ws/geocoder/v1/?location=" + latLng.lat + "," + latLng.lng + "&key={$config.tencentkey|default=''}&output=jsonp",
|
url: "https://apis.map.qq.com/ws/geocoder/v1/?location=" + pos.lat + "," + pos.lng + "&key={$config.tencentkey|default=''}&output=jsonp",
|
||||||
dataType: "jsonp",
|
dataType: "jsonp",
|
||||||
type: 'GET',
|
type: 'GET',
|
||||||
cache: true,
|
cache: true,
|
||||||
crossDomain: true,
|
crossDomain: true,
|
||||||
success: function (ret) {
|
success: function (ret) {
|
||||||
console.log("getAddress:", ret)
|
if (ret && ret.status === 0 && ret.result) {
|
||||||
if (ret.status === 0) {
|
var component = ret.result.address_component || {};
|
||||||
var component = ret.result.address_component;
|
var resolvedAddr = '';
|
||||||
if (ret.result.formatted_addresses && ret.result.formatted_addresses.recommend) {
|
if (ret.result.formatted_addresses && ret.result.formatted_addresses.recommend) {
|
||||||
var recommend = ret.result.formatted_addresses.recommend;
|
var recommend = ret.result.formatted_addresses.recommend;
|
||||||
var standard_address = ret.result.formatted_addresses.standard_address;
|
resolvedAddr = (component.province !== component.city ? (component.province + component.city) : component.province)
|
||||||
var address = component.province !== component.city ? component.province + component.city : component.province;
|
+ (recommend.indexOf(component.district) === 0 ? '' : component.district) + recommend;
|
||||||
|
|
||||||
address = address + (recommend.indexOf(component.district) === 0 ? '' : component.district) + recommend;
|
|
||||||
} else {
|
} else {
|
||||||
address = ret.result.address;
|
resolvedAddr = ret.result.address || '';
|
||||||
}
|
}
|
||||||
showMarker(ret.result.location, address);
|
selectedState.address = resolvedAddr || fallbackTitle || '';
|
||||||
showInfoWin(ret.result.location, address);
|
selectedState.province = component.province || '';
|
||||||
|
selectedState.city = component.city || '';
|
||||||
|
selectedState.district = component.district || '';
|
||||||
|
updateSelectedDisplay(pos, selectedState.address);
|
||||||
|
} else {
|
||||||
|
fallbackToNominatim();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
error: function (e) {
|
error: function () {
|
||||||
console.log(e, 'error')
|
fallbackToNominatim();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
//初始化marker
|
// 初始选点渲染
|
||||||
showMarker(center);
|
selectPoint({lat: lat, lng: lng}, initialAddress, false);
|
||||||
if (address) {
|
|
||||||
showInfoWin(center, address);
|
// 若打开时未传自定义坐标但带有区域地址,自动定位到该区域
|
||||||
} else {
|
if (!hasUrlCoord && initialAddress) {
|
||||||
geocoder.getAddress(center);
|
$('#place').val(initialAddress);
|
||||||
|
searchAndLocate(initialAddress);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 搜索框自动联想(腾讯 Suggestion + Nominatim 双通道自动降级)
|
||||||
var place = $("#place");
|
var place = $("#place");
|
||||||
place.autoComplete({
|
place.autoComplete({
|
||||||
minChars: 1,
|
minChars: 1,
|
||||||
@@ -167,121 +471,196 @@
|
|||||||
menuClass: 'autocomplete-search',
|
menuClass: 'autocomplete-search',
|
||||||
source: function (term, response) {
|
source: function (term, response) {
|
||||||
try {
|
try {
|
||||||
xhr.abort();
|
xhr && xhr.abort();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
}
|
}
|
||||||
|
var searchByNominatim = function () {
|
||||||
|
xhr = $.ajax({
|
||||||
|
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({
|
xhr = $.ajax({
|
||||||
url: "https://apis.map.qq.com/ws/place/v1/suggestion?keyword=" + term + "&key={$config.tencentkey|default=''}&output=jsonp",
|
url: "https://apis.map.qq.com/ws/place/v1/suggestion?keyword=" + encodeURIComponent(term) + "&key={$config.tencentkey|default=''}&output=jsonp",
|
||||||
dataType: "jsonp",
|
dataType: "jsonp",
|
||||||
type: 'GET',
|
type: 'GET',
|
||||||
cache: true,
|
cache: true,
|
||||||
success: function (ret) {
|
success: function (ret) {
|
||||||
if (ret.status === 0) {
|
if (ret && ret.status === 0 && $.isArray(ret.data)) {
|
||||||
if(ret.data.length === 0){
|
if (ret.data.length === 0) {
|
||||||
$(".autocomplete-suggestions.autocomplete-search").html('');
|
$(".autocomplete-suggestions.autocomplete-search").html('');
|
||||||
}
|
}
|
||||||
response(ret.data);
|
response(ret.data);
|
||||||
} else {
|
} else {
|
||||||
console.log(ret);
|
searchByNominatim();
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
error: function () {
|
||||||
|
searchByNominatim();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
renderItem: function (item, search) {
|
renderItem: function (item, search) {
|
||||||
search = search.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
|
var safeSearch = search.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
|
||||||
var regexp = new RegExp("(" + search.replace(/[\,|\u3000|\uff0c]/, ' ').split(' ').join('|') + ")", "gi");
|
var regexp = new RegExp("(" + safeSearch.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 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) {
|
onSelect: function (e, term, sel) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
var item = $(sel).data("item");
|
var item = $(sel).data("item");
|
||||||
//调用获取位置方法
|
if (item && item.location) {
|
||||||
geocoder.getAddress(item.location);
|
if (item.province) selectedState.province = item.province;
|
||||||
|
if (item.city) selectedState.city = item.city;
|
||||||
var position = new qq.maps.LatLng(item.location.lat, item.location.lng);
|
if (item.district) selectedState.district = item.district;
|
||||||
map.setCenter(position);
|
if (item.rawParts) selectedState.rawParts = item.rawParts;
|
||||||
|
selectPoint(item.location, item.address || item.title, true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
//地图点击
|
// 点击地图任意地点立即选点
|
||||||
qq.maps.event.addListener(map, 'click', function (event) {
|
qq.maps.event.addListener(map, 'click', function (event) {
|
||||||
try {
|
if (event && event.latLng) {
|
||||||
//调用获取位置方法
|
selectPoint(event.latLng, '', false);
|
||||||
geocoder.getAddress(event.latLng);
|
|
||||||
} catch (e) {
|
|
||||||
console.log(e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
);
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
//显示info窗口
|
/**
|
||||||
|
* 显示地图信息气泡
|
||||||
|
* @param {{lat: number, lng: number}} latLng 坐标
|
||||||
|
* @param {string} title 显示文本
|
||||||
|
*/
|
||||||
var showInfoWin = function (latLng, title) {
|
var showInfoWin = function (latLng, title) {
|
||||||
var position = new qq.maps.LatLng(latLng.lat, latLng.lng);
|
var position = new qq.maps.LatLng(latLng.lat, latLng.lng);
|
||||||
infoWin.open();
|
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);
|
infoWin.setPosition(position);
|
||||||
};
|
};
|
||||||
|
|
||||||
//实例化marker和监听拖拽结束事件
|
/**
|
||||||
|
* 放置可拖拽红标并监听拖拽结束选点
|
||||||
|
* @param {{lat: number, lng: number}} latLng 坐标
|
||||||
|
* @param {string} title 提示文字
|
||||||
|
*/
|
||||||
var showMarker = function (latLng, title) {
|
var showMarker = function (latLng, title) {
|
||||||
console.log("showMarker", latLng, title)
|
|
||||||
var position = new qq.maps.LatLng(latLng.lat, latLng.lng);
|
var position = new qq.maps.LatLng(latLng.lat, latLng.lng);
|
||||||
marker && marker.setMap(null);
|
marker && marker.setMap(null);
|
||||||
marker = new qq.maps.Marker({
|
marker = new qq.maps.Marker({
|
||||||
map: map,
|
map: map,
|
||||||
position: position,
|
position: position,
|
||||||
draggable: true,
|
draggable: true,
|
||||||
title: title || '拖动图标选择位置'
|
title: title || '点击地图或拖动图标选择位置'
|
||||||
});
|
});
|
||||||
|
|
||||||
//监听拖拽结束
|
|
||||||
qq.maps.event.addListener(marker, 'dragend', function (event) {
|
qq.maps.event.addListener(marker, 'dragend', function (event) {
|
||||||
//调用获取位置方法
|
if (event && event.latLng) {
|
||||||
geocoder.getAddress(event.latLng);
|
selectPoint(event.latLng, '', false);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 关闭弹窗并向父窗口回传选点数据
|
||||||
|
* @param {Object} data 选点数据
|
||||||
|
*/
|
||||||
var close = function (data) {
|
var close = function (data) {
|
||||||
var index = parent.Layer.getFrameIndex(window.name);
|
var index = parent.Layer.getFrameIndex(window.name);
|
||||||
var callback = parent.$("#layui-layer" + index).data("callback");
|
var callback = parent.$("#layui-layer" + index).data("callback");
|
||||||
//再执行关闭
|
|
||||||
parent.Layer.close(index);
|
parent.Layer.close(index);
|
||||||
//再调用回传函数
|
|
||||||
if (typeof callback === 'function') {
|
if (typeof callback === 'function') {
|
||||||
callback.call(undefined, data);
|
callback.call(undefined, data);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
//点击确定后执行回调赋值
|
// 点击「确定选点」回传坐标与解析出的省市区信息
|
||||||
$(document).on('click', '.confirm', function () {
|
$(document).on('click', '.confirm', function () {
|
||||||
var zoom = map.getZoom();
|
var zoom = map ? map.getZoom() : 12;
|
||||||
var data = {lat: infoWin.position.lat.toFixed(5), lng: infoWin.position.lng.toFixed(5), zoom: zoom, address: infoWin.content};
|
var outLat = Number(selectedState.lat);
|
||||||
|
var outLng = Number(selectedState.lng);
|
||||||
if (fromtype !== totype) {
|
if (fromtype !== totype) {
|
||||||
var result = gcoord.transform([data.lng, data.lat], gcoord[fromtype], gcoord[totype]);
|
var result = gcoord.transform([outLng, outLat], gcoord[fromtype], gcoord[totype]);
|
||||||
data.lng = (result[0] || data.lng).toFixed(5);
|
outLng = Number(result[0] || outLng);
|
||||||
data.lat = (result[1] || data.lat).toFixed(5);
|
outLat = Number(result[1] || outLat);
|
||||||
console.log(data, result, fromtype, totype);
|
|
||||||
}
|
}
|
||||||
|
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);
|
close(data);
|
||||||
});
|
});
|
||||||
|
|
||||||
//点击搜索按钮
|
// 点击搜索按钮或回车搜索
|
||||||
$(document).on('click', '#search-btn', function () {
|
$(document).on('click', '#search-btn', function () {
|
||||||
if ($("#place").val() === '')
|
var kw = $.trim($("#place").val());
|
||||||
return;
|
if (!kw) {
|
||||||
var first = $(".autocomplete-search > .autocomplete-suggestion:first");
|
|
||||||
if (!first.length) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var item = first.data("item");
|
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);
|
||||||
|
});
|
||||||
|
|
||||||
//调用获取位置方法
|
$('#place').on('keydown', function (e) {
|
||||||
geocoder.getAddress(item.location);
|
if (e.keyCode === 13) {
|
||||||
|
e.preventDefault();
|
||||||
var position = new qq.maps.LatLng(item.location.lat, item.location.lng);
|
$('#search-btn').trigger('click');
|
||||||
map.setCenter(position);
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
init();
|
init();
|
||||||
|
|||||||
@@ -80,40 +80,70 @@ class Order extends Backend
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 订单补单
|
* 订单补单(含权益发放与分销结算)
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
*/
|
*/
|
||||||
public function supplement()
|
public function supplement()
|
||||||
{
|
{
|
||||||
$orderNo = $this->request->post('orderNo');
|
$orderNo = trim((string)$this->request->post('orderNo', ''));
|
||||||
if (trim((string)$orderNo) === '') {
|
if ($orderNo === '') {
|
||||||
$this->error('订单号不能为空');
|
$this->error('订单号不能为空');
|
||||||
}
|
}
|
||||||
$order = Db::name('fc_order')->where('order_no', $orderNo)->find();
|
$order = Db::name('fc_order')->where('order_no', $orderNo)->find();
|
||||||
if (!$order) {
|
if (!$order) {
|
||||||
$this->error('订单不存在');
|
$this->error('订单不存在');
|
||||||
}
|
}
|
||||||
if ($order['status'] === 'paid') {
|
if ((string)$order['status'] === 'paid') {
|
||||||
$this->success('订单已支付');
|
$this->success('订单已支付');
|
||||||
}
|
}
|
||||||
Db::name('fc_order')->where('id', $order['id'])->update([
|
if ((string)$order['status'] !== 'pending') {
|
||||||
'status' => 'paid',
|
$this->error('订单状态不支持补单(当前:' . $order['status'] . ')');
|
||||||
'paytime' => time(),
|
}
|
||||||
'updatetime' => time(),
|
|
||||||
]);
|
$now = time();
|
||||||
Db::name('fc_finance_flow')->insert([
|
try {
|
||||||
'user_id' => (int)$order['user_id'],
|
Db::transaction(function () use ($order, $now) {
|
||||||
'flow_no' => 'SK' . date('YmdHis') . $order['id'],
|
$locked = Db::name('fc_order')->where('id', (int)$order['id'])->lock(true)->find();
|
||||||
'type' => 'income',
|
if (!$locked || (string)$locked['status'] !== 'pending') {
|
||||||
'scene' => '订单补单',
|
return;
|
||||||
'amount' => $order['amount'],
|
}
|
||||||
'balance' => 0,
|
|
||||||
'related_id' => $order['id'],
|
$affected = Db::name('fc_order')
|
||||||
'remark' => '订单补单',
|
->where('id', (int)$order['id'])
|
||||||
'createtime' => time(),
|
->where('status', 'pending')
|
||||||
]);
|
->update([
|
||||||
|
'status' => 'paid',
|
||||||
|
'paytime' => $now,
|
||||||
|
'updatetime' => $now,
|
||||||
|
]);
|
||||||
|
if (!$affected) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$locked['status'] = 'paid';
|
||||||
|
$locked['paytime'] = $now;
|
||||||
|
|
||||||
|
Db::name('fc_finance_flow')->insert([
|
||||||
|
'user_id' => (int)$locked['user_id'],
|
||||||
|
'flow_no' => 'SK' . date('YmdHis') . $locked['id'],
|
||||||
|
'type' => 'income',
|
||||||
|
'scene' => '订单补单',
|
||||||
|
'amount' => $locked['amount'],
|
||||||
|
'balance' => 0,
|
||||||
|
'related_id' => $locked['id'],
|
||||||
|
'remark' => '订单补单',
|
||||||
|
'createtime' => $now,
|
||||||
|
]);
|
||||||
|
|
||||||
|
// 统一发放订单权益(会员开通 / 余额充值入账 / 付费查看解锁 / 发帖激活)并触发分销佣金结算
|
||||||
|
\app\common\library\OrderFulfill::handle($locked);
|
||||||
|
});
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
\think\Log::record('后台订单补单失败:订单 ' . $orderNo . ',' . $e->getMessage(), 'error');
|
||||||
|
$this->error('补单失败:' . $e->getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
$this->success('补单成功');
|
$this->success('补单成功');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -110,7 +110,7 @@
|
|||||||
|
|
||||||
<input type="hidden" id="area-options" value="{$areaOptionsJson|htmlentities}">
|
<input type="hidden" id="area-options" value="{$areaOptionsJson|htmlentities}">
|
||||||
|
|
||||||
<div class="fc-area-actions">
|
<div class="fc-area-actions normal-footer">
|
||||||
<button type="submit" class="btn btn-primary btn-embossed disabled">保存</button>
|
<button type="submit" class="btn btn-success btn-embossed fc-area-submit-btn"><i class="fa fa-check"></i> 保存</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@@ -110,7 +110,7 @@
|
|||||||
|
|
||||||
<input type="hidden" id="area-options" data-current-id="{$row.id|htmlentities}" value="{$areaOptionsJson|htmlentities}">
|
<input type="hidden" id="area-options" data-current-id="{$row.id|htmlentities}" value="{$areaOptionsJson|htmlentities}">
|
||||||
|
|
||||||
<div class="fc-area-actions">
|
<div class="fc-area-actions normal-footer">
|
||||||
<button type="submit" class="btn btn-primary btn-embossed disabled">保存</button>
|
<button type="submit" class="btn btn-success btn-embossed fc-area-submit-btn"><i class="fa fa-check"></i> 保存</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@@ -221,7 +221,7 @@ class Distribution extends V1
|
|||||||
/**
|
/**
|
||||||
* D4 分销海报生成要素
|
* D4 分销海报生成要素
|
||||||
* GET /api/v1/distribution/poster
|
* GET /api/v1/distribution/poster
|
||||||
* 获取用于前端合成或分享专属推广海报的元数据
|
* 获取用于前端合成或分享专属推广海报的元数据(含官方小程序码 URL,支持传入 postId 生成详情页小程序码)
|
||||||
*
|
*
|
||||||
* @return void 输出海报配置与素材信息的JSON
|
* @return void 输出海报配置与素材信息的JSON
|
||||||
*/
|
*/
|
||||||
@@ -234,6 +234,16 @@ class Distribution extends V1
|
|||||||
$siteName = (string)$this->cfg('app.name', '同城分类信息');
|
$siteName = (string)$this->cfg('app.name', '同城分类信息');
|
||||||
$slogan = (string)$this->cfg('dist.slogan', '同城便民生活 · 随时随地发布与求助');
|
$slogan = (string)$this->cfg('dist.slogan', '同城便民生活 · 随时随地发布与求助');
|
||||||
$code = (string)$ext['invite_code'];
|
$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([
|
$this->ok([
|
||||||
'inviteCode' => $code,
|
'inviteCode' => $code,
|
||||||
@@ -241,7 +251,8 @@ class Distribution extends V1
|
|||||||
'slogan' => $slogan,
|
'slogan' => $slogan,
|
||||||
'nickname' => (string)(isset($user['nickname']) && $user['nickname'] !== '' ? $user['nickname'] : '同城合伙人'),
|
'nickname' => (string)(isset($user['nickname']) && $user['nickname'] !== '' ? $user['nickname'] : '同城合伙人'),
|
||||||
'avatar' => (string)(isset($user['avatar']) ? $user['avatar'] : ''),
|
'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'] : '',
|
'memberLevel' => $isMember ? (string)$ext['member_level'] : '',
|
||||||
'memberExpireAt' => $ext['member_expiretime'] ? $this->iso($ext['member_expiretime']) : null,
|
'memberExpireAt' => $ext['member_expiretime'] ? $this->iso($ext['member_expiretime']) : null,
|
||||||
'balance' => (float)$user['money'],
|
'balance' => (float)$user['money'],
|
||||||
|
'inviteCode' => isset($ext['invite_code']) ? (string)$ext['invite_code'] : '',
|
||||||
'realnameStatus' => (int)$ext['realname_status'],
|
'realnameStatus' => (int)$ext['realname_status'],
|
||||||
'realname' => isset($ext['realname']) ? (string)$ext['realname'] : '',
|
'realname' => isset($ext['realname']) ? (string)$ext['realname'] : '',
|
||||||
'isMerchant' => (bool)$isMerchant,
|
'isMerchant' => (bool)$isMerchant,
|
||||||
|
|||||||
@@ -185,9 +185,12 @@ class DistributionService
|
|||||||
'createtime' => $now,
|
'createtime' => $now,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// 发送分销佣金入账站内信通知
|
// 发送分销佣金入账站内信通知(区分待结算周期与实时结算)
|
||||||
$levelText = ($level === 1) ? '直接下线' : '间接下线';
|
$levelText = ($level === 1) ? '直接下线' : '间接下线';
|
||||||
$noticeContent = '您的 ' . $levelText . ' 产生了一笔消费(¥' . $amount . '),为您带来分销佣金 ¥' . $commissionAmount . ',已计入可提现资产。';
|
$settleTip = ($settleDays > 0)
|
||||||
|
? '已计入待结算佣金(预计 T+' . $settleDays . ' 自动结算转入可提现资产)。'
|
||||||
|
: '已计入可提现资产。';
|
||||||
|
$noticeContent = '您的 ' . $levelText . ' 产生了一笔消费(¥' . $amount . '),为您带来分销佣金 ¥' . $commissionAmount . ',' . $settleTip;
|
||||||
Db::name('fc_message')->insert([
|
Db::name('fc_message')->insert([
|
||||||
'user_id' => $parentId,
|
'user_id' => $parentId,
|
||||||
'type' => 'interact',
|
'type' => 'interact',
|
||||||
|
|||||||
@@ -207,6 +207,60 @@ class WxMini
|
|||||||
return isset($token['access_token']) ? (string)$token['access_token'] : '';
|
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 统一转数组 */
|
/** SDK 返回 Collection / array / object 统一转数组 */
|
||||||
|
|||||||
@@ -258,16 +258,17 @@
|
|||||||
height: 40px;
|
height: 40px;
|
||||||
border: none;
|
border: none;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
background: linear-gradient(135deg, #00e070, #04874a);
|
background: linear-gradient(135deg, #00d26a, #00a854);
|
||||||
color: #04301e;
|
color: #ffffff;
|
||||||
font-weight: 700;
|
font-weight: 600;
|
||||||
box-shadow: 0 4px 12px rgba(0, 224, 112, 0.2);
|
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:hover,
|
||||||
.fc-area-map-btn:focus {
|
.fc-area-map-btn:focus {
|
||||||
color: #04301e;
|
color: #ffffff;
|
||||||
filter: brightness(0.98);
|
filter: brightness(0.96);
|
||||||
}
|
}
|
||||||
|
|
||||||
.fc-area-actions {
|
.fc-area-actions {
|
||||||
@@ -276,11 +277,28 @@
|
|||||||
padding: 4px 0 8px;
|
padding: 4px 0 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.fc-area-actions .btn {
|
.fc-area-actions .btn,
|
||||||
min-width: 120px;
|
.fc-area-actions .fc-area-submit-btn {
|
||||||
|
min-width: 128px;
|
||||||
height: 40px;
|
height: 40px;
|
||||||
|
border: none;
|
||||||
border-radius: 8px;
|
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) {
|
@media (max-width: 767px) {
|
||||||
|
|||||||
@@ -12,8 +12,19 @@ define([], function () {
|
|||||||
var lat = lat_id ? $("#" + lat_id).val() : '';
|
var lat = lat_id ? $("#" + lat_id).val() : '';
|
||||||
var lng = lng_id ? $("#" + lng_id).val() : '';
|
var lng = lng_id ? $("#" + lng_id).val() : '';
|
||||||
var zoom = zoom_id ? $("#" + zoom_id).val() : '';
|
var zoom = zoom_id ? $("#" + zoom_id).val() : '';
|
||||||
var url = "/addons/address/index/select";
|
var address = input_id ? $("#" + input_id).val() : ($(that).data("address") || '');
|
||||||
url += (lat && lng) ? '?lat=' + lat + '&lng=' + lng + (input_id ? "&address=" + $("#" + input_id).val() : "") + (zoom ? "&zoom=" + zoom : "") : '';
|
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, '位置选择', {
|
Fast.api.open(url, '位置选择', {
|
||||||
callback: function (res) {
|
callback: function (res) {
|
||||||
input_id && $("#" + input_id).val(res.address).trigger("change");
|
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 refreshPreview = function () {
|
||||||
var parts = [
|
var parts = [
|
||||||
@@ -332,6 +375,7 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'backend/format'], fu
|
|||||||
return item !== '';
|
return item !== '';
|
||||||
});
|
});
|
||||||
$('#area-full-name').text(parts.length ? parts.join(' / ') : '未选择');
|
$('#area-full-name').text(parts.length ? parts.join(' / ') : '未选择');
|
||||||
|
$('.fc-area-map-btn').data('address', parts.join(''));
|
||||||
syncWrapState();
|
syncWrapState();
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -354,7 +398,7 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'backend/format'], fu
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 根据所选省市区自动解析中心经纬度(腾讯地图Place API JSONP)
|
* 根据所选省市区自动解析中心经纬度(腾讯Place接口 + Nominatim自动降级)
|
||||||
*/
|
*/
|
||||||
var autoResolveCoordinates = function () {
|
var autoResolveCoordinates = function () {
|
||||||
var p = String($province.val() || '').trim();
|
var p = String($province.val() || '').trim();
|
||||||
@@ -375,13 +419,37 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'backend/format'], fu
|
|||||||
clearTimeout(geocodeTimer);
|
clearTimeout(geocodeTimer);
|
||||||
}
|
}
|
||||||
geocodeTimer = setTimeout(function () {
|
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';
|
var tencentKey = 'WM7BZ-ZAYLL-MZEPZ-MRZBT-MBYJE-ZAFIT';
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: 'https://apis.map.qq.com/ws/place/v1/suggestion',
|
url: 'https://apis.map.qq.com/ws/place/v1/suggestion',
|
||||||
dataType: 'jsonp',
|
dataType: 'jsonp',
|
||||||
data: {
|
data: {
|
||||||
keyword: queryAddress,
|
keyword: (p === c ? p : (p + c)) + d,
|
||||||
region: c,
|
region: c,
|
||||||
key: tencentKey,
|
key: tencentKey,
|
||||||
output: 'jsonp'
|
output: 'jsonp'
|
||||||
@@ -394,8 +462,13 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'backend/format'], fu
|
|||||||
$lat.val(Number(loc.lat).toFixed(6));
|
$lat.val(Number(loc.lat).toFixed(6));
|
||||||
$lng.val(Number(loc.lng).toFixed(6));
|
$lng.val(Number(loc.lng).toFixed(6));
|
||||||
refreshCoordTip(true);
|
refreshCoordTip(true);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
resolveByNominatim();
|
||||||
|
},
|
||||||
|
error: function () {
|
||||||
|
resolveByNominatim();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}, 180);
|
}, 180);
|
||||||
@@ -847,6 +920,66 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'backend/format'], fu
|
|||||||
closeAllDropdowns();
|
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)
|
ic = sceneStr.slice(3)
|
||||||
} else if (sceneStr.indexOf('uid_') === 0) {
|
} else if (sceneStr.indexOf('uid_') === 0) {
|
||||||
inviterId = sceneStr.slice(4)
|
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) {
|
} else if (sceneStr.indexOf('uid=') === 0) {
|
||||||
inviterId = sceneStr.slice(4)
|
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)) {
|
} else if (/^[A-Za-z0-9]{4,16}$/.test(sceneStr)) {
|
||||||
ic = sceneStr
|
ic = sceneStr
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,8 +11,8 @@ export const distributionApi = {
|
|||||||
team: (params = {}) => get('/distribution/team', params, { silent: true }),
|
team: (params = {}) => get('/distribution/team', params, { silent: true }),
|
||||||
/** 分销收益明细账单(佣金流水) */
|
/** 分销收益明细账单(佣金流水) */
|
||||||
commissions: (params = {}) => get('/distribution/commissions', params, { silent: true }),
|
commissions: (params = {}) => get('/distribution/commissions', params, { silent: true }),
|
||||||
/** 专属推广海报要素 */
|
/** 专属推广海报要素(支持传入 postId 获取详情页小程序码) */
|
||||||
poster: () => get('/distribution/poster', {}, { silent: true }),
|
poster: (params = {}) => get('/distribution/poster', params, { silent: true }),
|
||||||
/** 申请提现(提现至微信零钱) */
|
/** 申请提现(提现至微信零钱) */
|
||||||
withdraw: (payload) => post('/distribution/withdraw', payload, { silent: true }),
|
withdraw: (payload) => post('/distribution/withdraw', payload, { silent: true }),
|
||||||
/** 提现记录 */
|
/** 提现记录 */
|
||||||
|
|||||||
@@ -471,19 +471,16 @@
|
|||||||
<text class="detail__poster-tip">点击大图可全屏预览,支持长按直接转发</text>
|
<text class="detail__poster-tip">点击大图可全屏预览,支持长按直接转发</text>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 底部三大快捷分享操作 -->
|
<!-- 底部快捷分享操作 -->
|
||||||
<view class="detail__poster-actions">
|
<view class="detail__poster-actions">
|
||||||
<view class="detail__poster-btn detail__poster-btn--primary" @click="savePoster">
|
<view class="detail__poster-btn detail__poster-btn--primary" @click="savePoster">
|
||||||
<image class="fc-icon fc-icon--sm" :src="iconUrl('image-white')" mode="aspectFit" />
|
<image class="fc-icon fc-icon--sm" :src="iconUrl('image-white')" mode="aspectFit" />
|
||||||
<text class="detail__poster-btn-text">保存到手机相册</text>
|
<text class="detail__poster-btn-text">保存到手机相册</text>
|
||||||
</view>
|
</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" />
|
<image class="fc-icon fc-icon--sm" :src="iconUrl('bar-share')" mode="aspectFit" />
|
||||||
<text class="detail__poster-btn-text">发给微信好友</text>
|
<text class="detail__poster-btn-text">发给微信好友</text>
|
||||||
</button>
|
</button>
|
||||||
<view class="detail__poster-btn detail__poster-btn--ghost" @click="copyPosterText">
|
|
||||||
<text>复制文案</text>
|
|
||||||
</view>
|
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
@@ -503,6 +500,7 @@ import postApi from '@/common/api/post.js'
|
|||||||
import favoriteApi from '@/common/api/favorite.js'
|
import favoriteApi from '@/common/api/favorite.js'
|
||||||
import reportApi from '@/common/api/report.js'
|
import reportApi from '@/common/api/report.js'
|
||||||
import memberApi from '@/common/api/member.js'
|
import memberApi from '@/common/api/member.js'
|
||||||
|
import distributionApi from '@/common/api/distribution.js'
|
||||||
import { REPORT_REASONS } from '@/common/constants/report.js'
|
import { REPORT_REASONS } from '@/common/constants/report.js'
|
||||||
import { iconUrl, categoryIconUrl, loadUiConfig, uiState } from '@/common/store/ui.js'
|
import { iconUrl, categoryIconUrl, loadUiConfig, uiState } from '@/common/store/ui.js'
|
||||||
import { fromNow, assetUrl } from '@/common/utils/format.js'
|
import { fromNow, assetUrl } from '@/common/utils/format.js'
|
||||||
@@ -691,8 +689,29 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
/**
|
||||||
|
* 页面加载:提取帖子ID与分销裂变参数
|
||||||
|
* @param {Object} query 路由入参
|
||||||
|
*/
|
||||||
onLoad(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 = ''
|
this.posterImagePath = ''
|
||||||
loadUiConfig()
|
loadUiConfig()
|
||||||
this.fetch()
|
this.fetch()
|
||||||
@@ -701,19 +720,21 @@ export default {
|
|||||||
if (this.loaded) this.refreshAccess()
|
if (this.loaded) this.refreshAccess()
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* 转发给微信好友(携带分销邀请人与精美封面图)
|
* 转发给微信好友(携带分销邀请码/邀请人与精美封面图)
|
||||||
*/
|
*/
|
||||||
onShareAppMessage() {
|
onShareAppMessage() {
|
||||||
let inviterId = 0
|
let inviterId = 0
|
||||||
|
let inviteCode = ''
|
||||||
try {
|
try {
|
||||||
const user = this.currentUser
|
const user = this.currentUser
|
||||||
if (user && user.id) inviterId = user.id
|
if (user && user.id) inviterId = user.id
|
||||||
|
if (user && user.inviteCode) inviteCode = user.inviteCode
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
|
|
||||||
const shareTitle = this.post.title
|
const shareTitle = this.post.title
|
||||||
? `【${this.post.categoryName || '同城信息'}】${this.post.title}`
|
? `【${this.post.categoryName || '同城信息'}】${this.post.title}`
|
||||||
: (uiState.shareTitle || uiState.siteName || '同城便民信息')
|
: (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) : ''))
|
const shareImg = this.posterImagePath || (this.imageUrls && this.imageUrls.length ? this.imageUrls[0] : (this.post.cover ? assetUrl(this.post.cover) : ''))
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -723,13 +744,15 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* 转发到微信朋友圈
|
* 转发到微信朋友圈(携带分销邀请码/邀请人)
|
||||||
*/
|
*/
|
||||||
onShareTimeline() {
|
onShareTimeline() {
|
||||||
let inviterId = 0
|
let inviterId = 0
|
||||||
|
let inviteCode = ''
|
||||||
try {
|
try {
|
||||||
const user = this.currentUser
|
const user = this.currentUser
|
||||||
if (user && user.id) inviterId = user.id
|
if (user && user.id) inviterId = user.id
|
||||||
|
if (user && user.inviteCode) inviteCode = user.inviteCode
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
|
|
||||||
const shareTitle = this.post.title
|
const shareTitle = this.post.title
|
||||||
@@ -739,7 +762,7 @@ export default {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
title: shareTitle,
|
title: shareTitle,
|
||||||
query: `id=${this.id}${inviterId ? '&inviterId=' + inviterId : ''}`,
|
query: `id=${this.id}${inviteCode ? '&ic=' + encodeURIComponent(inviteCode) : ''}${inviterId ? '&inviterId=' + inviterId : ''}`,
|
||||||
imageUrl: shareImg || undefined,
|
imageUrl: shareImg || undefined,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -1693,8 +1716,22 @@ export default {
|
|||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
const qrUrl = `https://fenleixinxi.gzyakj.com.cn/pages/detail/detail?id=${this.id}${inviterId ? '&inviterId=' + inviterId : ''}`
|
const qrUrl = `https://fenleixinxi.gzyakj.com.cn/pages/detail/detail?id=${this.id}${inviterId ? '&inviterId=' + inviterId : ''}`
|
||||||
|
|
||||||
// 绘制高识别度合规二维码(124 x 124)
|
// 优先拉取并绘制微信官方小程序码,未发布或受限时无缝回退至自绘二维码(124 x 124)
|
||||||
this.drawRealQRCode(ctx, qrUrl, 56, 830, 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, {
|
this.drawSafeText(ctx, '微信长按识别小程序', 198, 842, {
|
||||||
@@ -1743,6 +1780,29 @@ export default {
|
|||||||
}, 300)
|
}, 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>
|
<text>保存到相册</text>
|
||||||
</view>
|
</view>
|
||||||
<!-- #ifdef MP-WEIXIN -->
|
<!-- #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" />
|
<image class="dist__btn-icon" :src="iconUrl('share')" mode="aspectFit" />
|
||||||
<text>发给好友</text>
|
<text>发给好友</text>
|
||||||
</button>
|
</button>
|
||||||
<!-- #endif -->
|
<!-- #endif -->
|
||||||
<view class="dist__poster-act-btn dist__poster-act-btn--ghost" @click="copyPosterText">
|
|
||||||
<text>复制文案</text>
|
|
||||||
</view>
|
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
@@ -528,7 +525,7 @@
|
|||||||
<canvas
|
<canvas
|
||||||
canvas-id="distPosterCanvas"
|
canvas-id="distPosterCanvas"
|
||||||
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>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
@@ -1134,7 +1131,7 @@ export default {
|
|||||||
}, 6000)
|
}, 6000)
|
||||||
|
|
||||||
const W = 600
|
const W = 600
|
||||||
const H = 880
|
const H = 740
|
||||||
const ctx = uni.createCanvasContext('distPosterCanvas', this)
|
const ctx = uni.createCanvasContext('distPosterCanvas', this)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -1158,7 +1155,7 @@ export default {
|
|||||||
ctx.fillRect(0, 0, W, H)
|
ctx.fillRect(0, 0, W, H)
|
||||||
|
|
||||||
// 2. 纯白立体高光主卡片(圆角 24px,细边框 #D1FAE5)
|
// 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)
|
// 3. 顶部 Header 栏(y: 44 ~ 76)
|
||||||
// 左侧绿色品牌胶囊
|
// 左侧绿色品牌胶囊
|
||||||
@@ -1291,7 +1288,16 @@ export default {
|
|||||||
const qrX = 48
|
const qrX = 48
|
||||||
const qrY = 546
|
const qrY = 546
|
||||||
const qrSize = 150
|
const qrSize = 150
|
||||||
this.drawRealQRCode(ctx, qrUrl, qrX, qrY, qrSize)
|
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 极为充裕,排版整洁绝不重叠)
|
// 二维码右侧文字说明(从 x = 216 开始,宽度 344px 极为充裕,排版整洁绝不重叠)
|
||||||
const textX = 218
|
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 {
|
.dist__poster-img {
|
||||||
width: 500rpx;
|
width: 500rpx;
|
||||||
height: 733rpx;
|
height: 616rpx;
|
||||||
border-radius: 20rpx;
|
border-radius: 20rpx;
|
||||||
box-shadow: 0 16rpx 40rpx rgba(4, 48, 30, 0.16);
|
box-shadow: 0 16rpx 40rpx rgba(4, 48, 30, 0.16);
|
||||||
background: #ffffff;
|
background: #ffffff;
|
||||||
|
|||||||
@@ -356,12 +356,7 @@ export default {
|
|||||||
*/
|
*/
|
||||||
vipTitle() {
|
vipTitle() {
|
||||||
if (this.isMember) return '会员权益已生效'
|
if (this.isMember) return '会员权益已生效'
|
||||||
const b = this.benefits || {}
|
return '开通会员 · 享免费发帖看号'
|
||||||
if (b.postMode === 'free') {
|
|
||||||
return '开通会员,尊享 VIP 专属特权'
|
|
||||||
}
|
|
||||||
const discount = b.postDiscount
|
|
||||||
return discount ? `开通会员,享专属发帖与查看特权` : '开通会员,享专属特权'
|
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* 顶栏显示的城市或区域文案(优先当前选择区县,其次城市,缺省回退到同城服务)
|
* 顶栏显示的城市或区域文案(优先当前选择区县,其次城市,缺省回退到同城服务)
|
||||||
@@ -472,16 +467,44 @@ export default {
|
|||||||
onUnload() {
|
onUnload() {
|
||||||
uni.$off('refreshUnreadCount')
|
uni.$off('refreshUnreadCount')
|
||||||
},
|
},
|
||||||
|
/**
|
||||||
|
* 微信好友转发(携带当前登录用户的分销邀请码/ID)
|
||||||
|
*/
|
||||||
onShareAppMessage() {
|
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 {
|
return {
|
||||||
title: uiState.shareTitle || uiState.siteName || '同城分类信息',
|
title: uiState.shareTitle || uiState.siteName || '同城分类信息',
|
||||||
path: '/pages/index/index',
|
path: '/pages/index/index' + queryStr,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
/**
|
||||||
|
* 微信朋友圈转发(携带当前登录用户的分销邀请码/ID)
|
||||||
|
*/
|
||||||
onShareTimeline() {
|
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 {
|
return {
|
||||||
title: uiState.shareTitle || uiState.siteName || '同城分类信息',
|
title: uiState.shareTitle || uiState.siteName || '同城分类信息',
|
||||||
query: '',
|
query: queryStr,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
@@ -1208,35 +1231,52 @@ export default {
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
gap: 16rpx;
|
||||||
box-shadow: $fc-shadow;
|
box-shadow: $fc-shadow;
|
||||||
}
|
}
|
||||||
.home__vip-left {
|
.home__vip-left {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
.home__vip-left .fc-icon {
|
||||||
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
.home__vip-texts {
|
.home__vip-texts {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
margin-left: 12rpx;
|
margin-left: 14rpx;
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
}
|
}
|
||||||
.home__vip-title {
|
.home__vip-title {
|
||||||
color: #ffffff;
|
color: #ffffff;
|
||||||
font-size: $fc-font-lg;
|
font-size: 30rpx;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
line-height: $fc-leading-tight;
|
line-height: 1.3;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
.home__vip-sub {
|
.home__vip-sub {
|
||||||
color: rgba(255, 255, 255, 0.9);
|
color: rgba(255, 255, 255, 0.92);
|
||||||
font-size: $fc-font-xs;
|
font-size: 22rpx;
|
||||||
margin-top: 8rpx;
|
margin-top: 6rpx;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
.home__vip-btn {
|
.home__vip-btn {
|
||||||
|
flex-shrink: 0;
|
||||||
|
white-space: nowrap;
|
||||||
padding: 12rpx 28rpx;
|
padding: 12rpx 28rpx;
|
||||||
border-radius: $fc-radius-pill;
|
border-radius: $fc-radius-pill;
|
||||||
background: #ffffff;
|
background: #ffffff;
|
||||||
color: $fc-primary-text;
|
color: $fc-primary-text;
|
||||||
font-size: $fc-font-sm;
|
font-size: 24rpx;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
|
line-height: 1.2;
|
||||||
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.08);
|
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 { merchantApi } from '@/common/api/merchant.js'
|
||||||
import { iconUrl, loadUiConfig } from '@/common/store/ui.js'
|
import { iconUrl, loadUiConfig } from '@/common/store/ui.js'
|
||||||
import { assetUrl } from '@/common/utils/format.js'
|
import { assetUrl } from '@/common/utils/format.js'
|
||||||
|
import { useUserStore } from '@/common/store/index.js'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: { FcNavbar, FcPostCard, FcEmpty },
|
components: { FcNavbar, FcPostCard, FcEmpty },
|
||||||
@@ -232,7 +233,13 @@ export default {
|
|||||||
* @param {Object} query 路由入参
|
* @param {Object} query 路由入参
|
||||||
*/
|
*/
|
||||||
onLoad(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)
|
this.postId = Number(query.postId || 0)
|
||||||
loadUiConfig()
|
loadUiConfig()
|
||||||
this.fetchLocation()
|
this.fetchLocation()
|
||||||
@@ -260,11 +267,44 @@ export default {
|
|||||||
onShareAppMessage() {
|
onShareAppMessage() {
|
||||||
return {
|
return {
|
||||||
title: `【认证商家】${this.merchant.name || '同城实体门店'}`,
|
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,
|
imageUrl: this.merchant.logo ? assetUrl(this.merchant.logo) : undefined,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
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,
|
iconUrl,
|
||||||
assetUrl,
|
assetUrl,
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -766,23 +766,32 @@ export default {
|
|||||||
.mine__vip-left {
|
.mine__vip-left {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
margin-right: 16rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mine__vip-icon {
|
.mine__vip-icon {
|
||||||
width: 52rpx;
|
width: 52rpx;
|
||||||
height: 52rpx;
|
height: 52rpx;
|
||||||
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mine__vip-texts {
|
.mine__vip-texts {
|
||||||
margin-left: 18rpx;
|
margin-left: 18rpx;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mine__vip-title {
|
.mine__vip-title {
|
||||||
color: #4a2600; /* 深褐黑金字 */
|
color: #4a2600; /* 深褐黑金字 */
|
||||||
font-size: 30rpx;
|
font-size: 30rpx;
|
||||||
font-weight: 800;
|
font-weight: 800;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mine__vip-sub {
|
.mine__vip-sub {
|
||||||
@@ -790,9 +799,14 @@ export default {
|
|||||||
color: #7a4600; /* 高清晰深暖金副标题 */
|
color: #7a4600; /* 高清晰深暖金副标题 */
|
||||||
font-size: 22rpx;
|
font-size: 22rpx;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mine__vip-btn {
|
.mine__vip-btn {
|
||||||
|
flex-shrink: 0;
|
||||||
|
white-space: nowrap;
|
||||||
padding: 10rpx 26rpx;
|
padding: 10rpx 26rpx;
|
||||||
border-radius: $fc-radius-pill;
|
border-radius: $fc-radius-pill;
|
||||||
background: #ffffff;
|
background: #ffffff;
|
||||||
|
|||||||
在新工单中引用
屏蔽一个用户