11
No files matched your search
@@ -10,8 +10,8 @@ SHOPRO_VERSION=v2.4.1
|
||||
SHOPRO_API_PATH=/app-api
|
||||
|
||||
# 后端接口 - 测试环境(通过 process.env.NODE_ENV == development)
|
||||
SHOPRO_DEV_BASE_URL = https://api.htysapp.com
|
||||
# SHOPRO_DEV_BASE_URL=https://baac9a029ea84f36ba28c023fed4fc5f.hd.takin.cc
|
||||
# SHOPRO_DEV_BASE_URL = https://api.htysapp.com
|
||||
SHOPRO_DEV_BASE_URL=https://baac9a029ea84f36ba28c023fed4fc5f.hd.takin.cc
|
||||
|
||||
# 后端接口 - 正式环境(通过 process.env.NODE_ENV != development)
|
||||
SHOPRO_BASE_URL=https://api.htysapp.com
|
||||
|
||||
@@ -1,456 +1,356 @@
|
||||
<!DOCTYPE html>
|
||||
<html style="font-size:50px">
|
||||
|
||||
<head>
|
||||
<html style="font-size: 50px">
|
||||
<head>
|
||||
<title>荷塘月色 - app下载</title>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width,user-scalable=no">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no">
|
||||
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
||||
<meta http-equiv="pragma" content="no-cache">
|
||||
<meta http-equiv="cache-control" content="no-cache">
|
||||
<meta http-equiv="expires" content="0">
|
||||
<meta name="description" content="高端私密真实可靠的聊天交友APP。平台通过人工审核和会员制度,保证用户质量。快来邂逅附近的女神吧!">
|
||||
<meta name="keywords" content="荷塘月色,聊天,交友">
|
||||
<meta charset="UTF-8" />
|
||||
<meta
|
||||
name="viewport"
|
||||
content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no"
|
||||
/>
|
||||
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
|
||||
<meta http-equiv="pragma" content="no-cache" />
|
||||
<meta http-equiv="cache-control" content="no-cache" />
|
||||
<meta http-equiv="expires" content="0" />
|
||||
<meta
|
||||
name="description"
|
||||
content="高端私密真实可靠的聊天交友APP。平台通过人工审核和会员制度,保证用户质量。快来邂逅附近的女神吧!"
|
||||
/>
|
||||
<meta name="keywords" content="荷塘月色,聊天,交友" />
|
||||
<script src="./device-fingerprint.js"></script>
|
||||
<script src="https://code.jquery.com/jquery-2.1.1.min.js"></script>
|
||||
<!-- <script src="https://cdn.jsdelivr.net/npm/vconsole@3.4.0/dist/vconsole.min.js"></script> -->
|
||||
<!-- <script src="https://res.sharetrace.com/sharetrace.min.js"></script> -->
|
||||
|
||||
<style type="text/css">
|
||||
a,
|
||||
abbr,
|
||||
acronym,
|
||||
address,
|
||||
applet,
|
||||
article,
|
||||
aside,
|
||||
audio,
|
||||
b,
|
||||
big,
|
||||
blockquote,
|
||||
body,
|
||||
canvas,
|
||||
caption,
|
||||
center,
|
||||
cite,
|
||||
code,
|
||||
dd,
|
||||
del,
|
||||
details,
|
||||
dfn,
|
||||
div,
|
||||
dl,
|
||||
dt,
|
||||
em,
|
||||
embed,
|
||||
fieldset,
|
||||
figcaption,
|
||||
figure,
|
||||
footer,
|
||||
form,
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6,
|
||||
header,
|
||||
html,
|
||||
i,
|
||||
iframe,
|
||||
img,
|
||||
input,
|
||||
ins,
|
||||
kbd,
|
||||
label,
|
||||
legend,
|
||||
li,
|
||||
mark,
|
||||
menu,
|
||||
nav,
|
||||
object,
|
||||
ol,
|
||||
output,
|
||||
p,
|
||||
pre,
|
||||
q,
|
||||
ruby,
|
||||
s,
|
||||
samp,
|
||||
section,
|
||||
small,
|
||||
span,
|
||||
strike,
|
||||
strong,
|
||||
sub,
|
||||
summary,
|
||||
sup,
|
||||
table,
|
||||
tbody,
|
||||
td,
|
||||
tfoot,
|
||||
th,
|
||||
thead,
|
||||
time,
|
||||
tr,
|
||||
tt,
|
||||
u,
|
||||
ul,
|
||||
var,
|
||||
video {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
font-size: 100%;
|
||||
font-weight: 400;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
a,
|
||||
abbr,
|
||||
acronym,
|
||||
address,
|
||||
applet,
|
||||
article,
|
||||
aside,
|
||||
audio,
|
||||
b,
|
||||
big,
|
||||
blockquote,
|
||||
body,
|
||||
canvas,
|
||||
caption,
|
||||
center,
|
||||
cite,
|
||||
code,
|
||||
dd,
|
||||
del,
|
||||
details,
|
||||
dfn,
|
||||
div,
|
||||
dl,
|
||||
dt,
|
||||
em,
|
||||
embed,
|
||||
fieldset,
|
||||
figcaption,
|
||||
figure,
|
||||
footer,
|
||||
form,
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6,
|
||||
header,
|
||||
html,
|
||||
i,
|
||||
iframe,
|
||||
img,
|
||||
input,
|
||||
ins,
|
||||
kbd,
|
||||
label,
|
||||
legend,
|
||||
li,
|
||||
mark,
|
||||
menu,
|
||||
nav,
|
||||
object,
|
||||
ol,
|
||||
output,
|
||||
p,
|
||||
pre,
|
||||
q,
|
||||
ruby,
|
||||
s,
|
||||
samp,
|
||||
section,
|
||||
small,
|
||||
span,
|
||||
strike,
|
||||
strong,
|
||||
sub,
|
||||
summary,
|
||||
sup,
|
||||
table,
|
||||
tbody,
|
||||
td,
|
||||
tfoot,
|
||||
th,
|
||||
thead,
|
||||
time,
|
||||
tr,
|
||||
tt,
|
||||
u,
|
||||
ul,
|
||||
var,
|
||||
video {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
font-size: 100%;
|
||||
font-weight: 400;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
</style>
|
||||
<style type="text/css">
|
||||
#txt-title{
|
||||
color: white;
|
||||
}
|
||||
@media (min-width: 765px) {
|
||||
.pc {
|
||||
display: block
|
||||
}
|
||||
#txt-title {
|
||||
color: white;
|
||||
}
|
||||
|
||||
.msite {
|
||||
display: none
|
||||
}
|
||||
|
||||
.msite-footer {
|
||||
display: none !important;
|
||||
}
|
||||
@keyframes myfirst {
|
||||
0% {
|
||||
background: url(bg-image.png) 0 no-repeat;
|
||||
background-size: auto 100%;
|
||||
}
|
||||
|
||||
@media (max-width: 764px) {
|
||||
.pc {
|
||||
display: none;
|
||||
font-size: 14px
|
||||
}
|
||||
|
||||
.msite {
|
||||
display: block;
|
||||
font-size: .24rem
|
||||
}
|
||||
|
||||
.msite-footer {
|
||||
display: block;
|
||||
font-size: .24rem
|
||||
}
|
||||
50% {
|
||||
background: url(bg-image.png) 100% no-repeat;
|
||||
background-size: auto 100%;
|
||||
}
|
||||
|
||||
@keyframes myfirst {
|
||||
0% {
|
||||
background: url(bg-image.png) 0 no-repeat;
|
||||
background-size: auto 100%
|
||||
}
|
||||
to {
|
||||
background: url(bg-image.png) 0 no-repeat;
|
||||
background-size: auto 100%;
|
||||
}
|
||||
}
|
||||
|
||||
50% {
|
||||
background: url(bg-image.png) 100% no-repeat;
|
||||
background-size: auto 100%
|
||||
}
|
||||
|
||||
to {
|
||||
background: url(bg-image.png) 0 no-repeat;
|
||||
background-size: auto 100%
|
||||
}
|
||||
@-webkit-keyframes myfirst {
|
||||
0% {
|
||||
background: url(bg-image.png) 0 no-repeat;
|
||||
background-size: auto 100%;
|
||||
}
|
||||
|
||||
@-webkit-keyframes myfirst {
|
||||
0% {
|
||||
background: url(bg-image.png) 0 no-repeat;
|
||||
background-size: auto 100%
|
||||
}
|
||||
|
||||
50% {
|
||||
background: url(bg-image.png) 100% no-repeat;
|
||||
background-size: auto 100%
|
||||
}
|
||||
|
||||
to {
|
||||
background: url(bg-image.png) 0 no-repeat;
|
||||
background-size: auto 100%
|
||||
}
|
||||
50% {
|
||||
background: url(bg-image.png) 100% no-repeat;
|
||||
background-size: auto 100%;
|
||||
}
|
||||
|
||||
.msite {
|
||||
display: block;
|
||||
font-size: .24rem;
|
||||
to {
|
||||
background: url(bg-image.png) 0 no-repeat;
|
||||
background-size: auto 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.container {
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
top: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
background: url(./bg-image.png) 100% no-repeat;
|
||||
background-size: auto 100%;
|
||||
text-align: center;
|
||||
-webkit-animation: myfirst 50s linear infinite;
|
||||
animation: myfirst 50s linear infinite;
|
||||
}
|
||||
.msite {
|
||||
display: block;
|
||||
font-size: 0.24rem;
|
||||
}
|
||||
|
||||
.top-box {
|
||||
text-align: center;
|
||||
padding-top: 1.7rem;
|
||||
}
|
||||
.container {
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
top: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
background: url(./bg-image.png) 100% no-repeat;
|
||||
background-size: auto 100%;
|
||||
text-align: center;
|
||||
-webkit-animation: myfirst 50s linear infinite;
|
||||
animation: myfirst 50s linear infinite;
|
||||
}
|
||||
|
||||
.logo-ico {
|
||||
width: 1.4rem;
|
||||
height: auto;
|
||||
border-radius: 10px;
|
||||
}
|
||||
.top-box {
|
||||
text-align: center;
|
||||
padding-top: 1.7rem;
|
||||
}
|
||||
|
||||
.sub-title {
|
||||
font-size: .64rem;
|
||||
font-weight: 700;
|
||||
line-height: .5rem;
|
||||
letter-spacing: 3px;
|
||||
margin-top: .2rem;
|
||||
color: white;
|
||||
}
|
||||
.logo-ico {
|
||||
width: 1.4rem;
|
||||
height: auto;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.top-box p {
|
||||
font-size: .36rem;
|
||||
font-family: PingFang-SC-Bold, PingFang-SC;
|
||||
font-weight: 700;
|
||||
color: #fff;
|
||||
line-height: .5rem;
|
||||
letter-spacing: 3px;
|
||||
margin-top: .2rem;
|
||||
}
|
||||
.sub-title {
|
||||
font-size: 0.64rem;
|
||||
font-weight: 700;
|
||||
line-height: 0.5rem;
|
||||
letter-spacing: 3px;
|
||||
margin-top: 0.2rem;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.top-box .sagn-ico {
|
||||
width: 5.89rem;
|
||||
height: auto;
|
||||
margin-top: .49rem;
|
||||
}
|
||||
.top-box p {
|
||||
font-size: 0.36rem;
|
||||
font-family: PingFang-SC-Bold, PingFang-SC;
|
||||
font-weight: 700;
|
||||
color: #fff;
|
||||
line-height: 0.5rem;
|
||||
letter-spacing: 3px;
|
||||
margin-top: 0.2rem;
|
||||
}
|
||||
|
||||
.download-btn {
|
||||
position: relative;
|
||||
bottom: -52px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
.top-box .sagn-ico {
|
||||
width: 5.89rem;
|
||||
height: auto;
|
||||
margin-top: 0.49rem;
|
||||
}
|
||||
|
||||
.download-btn-real {
|
||||
margin: 0 auto;
|
||||
width: 5.6rem;
|
||||
height: .9rem;
|
||||
background: linear-gradient(-89deg, #EC6978, #F2AC66);
|
||||
box-shadow: 0.02rem 0.02rem 0.3rem 0 rgb(255 155 116 / 46%);
|
||||
line-height: .9rem;
|
||||
border-radius: .45rem;
|
||||
border: 1px solid #faa36a;
|
||||
text-align: center;
|
||||
font-size: .32rem;
|
||||
font-family: PingFang-SC-Bold, PingFang-SC;
|
||||
font-weight: 700;
|
||||
color: #fff;
|
||||
letter-spacing: 4px;
|
||||
text-shadow: 1px 1px 1px rgb(213 86 29 / 55%);
|
||||
}
|
||||
.download-btn {
|
||||
position: relative;
|
||||
bottom: -52px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.footer {
|
||||
width: 100%;
|
||||
color: #fff;
|
||||
text-align: center;
|
||||
position: fixed;
|
||||
bottom: .21rem;
|
||||
line-height: .4rem;
|
||||
}
|
||||
.download-btn-real {
|
||||
margin: 0 auto;
|
||||
width: 5.6rem;
|
||||
height: 0.9rem;
|
||||
background: linear-gradient(-89deg, #ec6978, #f2ac66);
|
||||
box-shadow: 0.02rem 0.02rem 0.3rem 0 rgb(255 155 116 / 46%);
|
||||
line-height: 0.9rem;
|
||||
border-radius: 0.45rem;
|
||||
border: 1px solid #faa36a;
|
||||
text-align: center;
|
||||
font-size: 0.32rem;
|
||||
font-family: PingFang-SC-Bold, PingFang-SC;
|
||||
font-weight: 700;
|
||||
color: #fff;
|
||||
letter-spacing: 4px;
|
||||
text-shadow: 1px 1px 1px rgb(213 86 29 / 55%);
|
||||
}
|
||||
|
||||
.footer-container p {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.footer {
|
||||
width: 100%;
|
||||
color: #fff;
|
||||
text-align: center;
|
||||
position: fixed;
|
||||
bottom: 0.21rem;
|
||||
line-height: 0.4rem;
|
||||
}
|
||||
|
||||
.footer-container p {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<body>
|
||||
<div class="msite container">
|
||||
<div class="top-box">
|
||||
<img class="logo-ico" src="./logo.png" alt="logo">
|
||||
<p id='txt-title'>荷塘月色</p>
|
||||
<div class="sub-title">同城交友平台</div>
|
||||
</div>
|
||||
<div class="download-btn" id="downloadButton" style="visibility: visible;" onclick="download()">
|
||||
<div class="download-btn-real">下载安装</div>
|
||||
</div>
|
||||
<div id="footer" class="msite footer msite-footer">
|
||||
<div class="footer-container">
|
||||
<p>更新时间: 2025年12月12日</p>
|
||||
</div>
|
||||
<div class="top-box">
|
||||
<img class="logo-ico" src="./logo.png" alt="logo" />
|
||||
<p id="txt-title">荷塘月色</p>
|
||||
<div class="sub-title">同城交友平台</div>
|
||||
</div>
|
||||
<div class="download-btn" onclick="download()">
|
||||
<div class="download-btn-real">下载安装</div>
|
||||
</div>
|
||||
<div class="footer">
|
||||
<div class="footer-container">
|
||||
<p>更新时间: 2025年12月12日</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</body>
|
||||
|
||||
<script type="text/javascript">
|
||||
// var vConsole = new VConsole();
|
||||
var app = ''
|
||||
var param = ''
|
||||
var device = ''
|
||||
var isInitSuccess = false;
|
||||
<script type="text/javascript">
|
||||
const IOS_DOWNLOAD_URL =
|
||||
'https://apps.apple.com/cn/app/%E8%8D%B7%E5%A1%98%E5%8A%A9%E6%89%8B/id6764264313';
|
||||
const ANDROID_DOWNLOAD_URL = 'http://download.htysapp.com/荷塘月色.apk';
|
||||
|
||||
function isWeiXin() {
|
||||
var ua = window.navigator.userAgent.toLowerCase();
|
||||
if (ua.indexOf('micromessenger') !== -1) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
return /MicroMessenger/i.test(navigator.userAgent);
|
||||
}
|
||||
|
||||
function judgeClient() {
|
||||
client = '';
|
||||
if (/(iPhone|iPad|iPod|iOS)/i.test(navigator.userAgent)) {
|
||||
//判断iPhone|iPad|iPod|iOS
|
||||
client = 'ios';
|
||||
} else if (/(Android)/i.test(navigator.userAgent)) {
|
||||
//判断Android
|
||||
client = 'android';
|
||||
} else {
|
||||
client = 'pc';
|
||||
}
|
||||
return client;
|
||||
}
|
||||
|
||||
// 点击下载安装包
|
||||
function _download() {
|
||||
client = judgeClient()
|
||||
if (client == 'android') {
|
||||
const ua = navigator.userAgent
|
||||
var src = 'http://download.htysapp.com/荷塘月色.apk';
|
||||
var form = document.createElement('form');
|
||||
form.action = src;
|
||||
document.getElementsByTagName('body')[0].appendChild(form);
|
||||
form.submit()
|
||||
return
|
||||
}
|
||||
if (client == 'ios') {
|
||||
// window.open("https://apps.apple.com/cn/app/id6444849074", "_self")
|
||||
alert("暂不支持")
|
||||
}
|
||||
const userAgent = navigator.userAgent;
|
||||
// iPadOS 13+ 使用桌面模式时 UA 类似 macOS,需要结合触摸点数判断。
|
||||
const isIOS =
|
||||
/iPhone|iPad|iPod/i.test(userAgent) ||
|
||||
(navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 1);
|
||||
|
||||
if (isIOS) {
|
||||
return 'ios';
|
||||
}
|
||||
if (/Android/i.test(userAgent)) {
|
||||
return 'android';
|
||||
}
|
||||
return 'pc';
|
||||
}
|
||||
|
||||
// 根据当前设备跳转到对应的应用下载地址。
|
||||
function download() {
|
||||
client = judgeClient()
|
||||
if (isWeiXin() && client != 'ios') {
|
||||
alert('请点击右上角按钮选择从浏览器打开此页面。');
|
||||
return;
|
||||
}
|
||||
if (isInitSuccess && client == 'ios') {
|
||||
// ShareTrace.download();
|
||||
alert("暂不支持")
|
||||
} else {
|
||||
//如果初始化失败建议自己指定下载地址,虽然不能获取安装参数但是不会影响用户的下载
|
||||
_download()
|
||||
}
|
||||
}
|
||||
const client = judgeClient();
|
||||
|
||||
//获取url中的参数
|
||||
function getUrlParam(name) {
|
||||
var reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)");
|
||||
//构造一个含有目标参数的正则表达式对象
|
||||
var r = window.location.search.substr(1).match(reg);
|
||||
//匹配目标参数
|
||||
if (r != null)
|
||||
return unescape(r[2]);
|
||||
return null;
|
||||
//返回参数值
|
||||
}
|
||||
if (client === 'ios') {
|
||||
window.location.href = IOS_DOWNLOAD_URL;
|
||||
return;
|
||||
}
|
||||
|
||||
function initShareSdk() {
|
||||
key = '1e778edaea34f45b'
|
||||
// 微信内置浏览器通常会拦截 Android APK 下载。
|
||||
if (client === 'android' && isWeiXin()) {
|
||||
alert('请点击右上角按钮选择从浏览器打开此页面。');
|
||||
return;
|
||||
}
|
||||
|
||||
client = judgeClient()
|
||||
if (client == 'ios') {
|
||||
key = '193c5fe0456ed92a'
|
||||
}
|
||||
ShareTrace.init({
|
||||
appkey: key,
|
||||
param: param,
|
||||
success: function () {
|
||||
console.log("init success...")
|
||||
isInitSuccess = true;
|
||||
if (!app || client == 'ios') {
|
||||
download();
|
||||
// window.open("https://a.app.qq.com/o/simple.jsp?pkgname=com.xxdd.imsearch", "_self")
|
||||
}
|
||||
},
|
||||
error: function (msg) {
|
||||
console.log(msg)
|
||||
}
|
||||
});
|
||||
}
|
||||
if (client === 'android') {
|
||||
window.location.href = ANDROID_DOWNLOAD_URL;
|
||||
return;
|
||||
}
|
||||
|
||||
function generateRandomNumber() {
|
||||
const options = [2, 3, 5, 6];
|
||||
|
||||
const randomIndex = Math.floor(Math.random() * options.length);
|
||||
const randomNumber = options[randomIndex];
|
||||
|
||||
return randomNumber;
|
||||
alert('请使用手机浏览器下载安装。');
|
||||
}
|
||||
|
||||
function getApiUrl(app) {
|
||||
if (app == 'yyq') {
|
||||
return 'https://api.newroad.top/api/v1/publicShare/setKey'
|
||||
}
|
||||
return 'https://api.xiaoquanapp.top/api/v1/publicShare/setKey'
|
||||
if (app === 'yyq') {
|
||||
return 'https://api.newroad.top/api/v1/publicShare/setKey';
|
||||
}
|
||||
return 'https://api.xiaoquanapp.top/api/v1/publicShare/setKey';
|
||||
}
|
||||
|
||||
async function sendUid(uid) {
|
||||
const deviceFingerprint = DeviceFingerprint.init({});
|
||||
// 等待依赖加载完成
|
||||
await new Promise((resolve) => setTimeout(resolve, 500));
|
||||
const fingerprints = await deviceFingerprint.getWebGLFingerprint();
|
||||
console.log(fingerprints);
|
||||
$.ajax({
|
||||
url: getApiUrl(app),
|
||||
type: 'POST',
|
||||
data: {
|
||||
key: fingerprints,
|
||||
value: uid
|
||||
}
|
||||
// 记录设备指纹与分享用户的对应关系,用于渠道归因。
|
||||
async function sendUid(app, uid) {
|
||||
const deviceFingerprint = DeviceFingerprint.init({});
|
||||
// 等待指纹依赖完成初始化。
|
||||
await new Promise((resolve) => setTimeout(resolve, 500));
|
||||
const fingerprints = await deviceFingerprint.getWebGLFingerprint();
|
||||
$.ajax({
|
||||
url: getApiUrl(app),
|
||||
type: 'POST',
|
||||
data: {
|
||||
key: fingerprints,
|
||||
value: uid,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
(function () {
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const app = urlParams.get('app') || '';
|
||||
const uid = urlParams.get('uid');
|
||||
|
||||
if (uid) {
|
||||
sendUid(app, uid).catch(function (error) {
|
||||
console.error('设备指纹上报失败:', error);
|
||||
});
|
||||
}
|
||||
|
||||
(async function ($) {
|
||||
var tmp = getUrlParam('app')
|
||||
if (tmp != null) {
|
||||
app = tmp;
|
||||
}
|
||||
var d = getUrlParam('device')
|
||||
if (d != null) {
|
||||
device = d;
|
||||
}
|
||||
var uid = getUrlParam('uid')
|
||||
if (uid != null) {
|
||||
param = 'uid=' + uid;
|
||||
}
|
||||
|
||||
document.title = '荷塘月色-高端约会'
|
||||
document.getElementById('txt-title').innerHTML = '荷塘月色'
|
||||
initShareSdk();
|
||||
sendUid(uid);
|
||||
}
|
||||
)(jQuery);
|
||||
</script>
|
||||
<script>
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
<script>
|
||||
var _hmt = _hmt || [];
|
||||
(function () {
|
||||
var hm = document.createElement("script");
|
||||
hm.src = "https://hm.baidu.com/hm.js?8841fecf6f4a06974ef8797421c60dc0";
|
||||
var s = document.getElementsByTagName("script")[0];
|
||||
s.parentNode.insertBefore(hm, s);
|
||||
}
|
||||
)();
|
||||
</script>
|
||||
|
||||
</html>
|
||||
var hm = document.createElement('script');
|
||||
hm.src = 'https://hm.baidu.com/hm.js?8841fecf6f4a06974ef8797421c60dc0';
|
||||
var s = document.getElementsByTagName('script')[0];
|
||||
s.parentNode.insertBefore(hm, s);
|
||||
})();
|
||||
</script>
|
||||
</html>
|
||||
@@ -58,7 +58,7 @@
|
||||
"lodash-es": "^4.17.21",
|
||||
"luch-request": "^3.0.8",
|
||||
"pinia": "^2.0.33",
|
||||
"pinia-plugin-persist-uni": "^1.2.0",
|
||||
"pinia-plugin-persistedstate": "3.2.3",
|
||||
"pinyin-pro": "^3.23.1",
|
||||
"quill": "^1.3.7",
|
||||
"quill-image-resize-mp": "^3.0.1",
|
||||
|
||||
@@ -77,9 +77,9 @@ importers:
|
||||
pinia:
|
||||
specifier: ^2.0.33
|
||||
version: 2.3.1(typescript@4.9.5)(vue@3.5.30(typescript@4.9.5))
|
||||
pinia-plugin-persist-uni:
|
||||
specifier: ^1.2.0
|
||||
version: 1.3.2(pinia@2.3.1(typescript@4.9.5)(vue@3.5.30(typescript@4.9.5)))(vue@3.5.30(typescript@4.9.5))
|
||||
pinia-plugin-persistedstate:
|
||||
specifier: 3.2.3
|
||||
version: 3.2.3(pinia@2.3.1(typescript@4.9.5)(vue@3.5.30(typescript@4.9.5)))
|
||||
pinyin-pro:
|
||||
specifier: ^3.23.1
|
||||
version: 3.28.0
|
||||
@@ -3266,15 +3266,10 @@ packages:
|
||||
resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
pinia-plugin-persist-uni@1.3.2:
|
||||
resolution: {integrity: sha512-Z47MWlAI3dnm9xENwodYyT6B3TMDuwFK7MHHymwQyrTchuhG2Yu26w7bYlIap7df0fCwNCmAo0n7n1RK3Ute+w==}
|
||||
pinia-plugin-persistedstate@3.2.3:
|
||||
resolution: {integrity: sha512-Cm819WBj/s5K5DGw55EwbXDtx+EZzM0YR5AZbq9XE3u0xvXwvX2JnWoFpWIcdzISBHqy9H1UiSIUmXyXqWsQRQ==}
|
||||
peerDependencies:
|
||||
'@vue/composition-api': ^1.0.0
|
||||
pinia: '>2.0.0'
|
||||
vue: ^2.0.0 || >=3.0.0
|
||||
peerDependenciesMeta:
|
||||
'@vue/composition-api':
|
||||
optional: true
|
||||
pinia: ^2.0.0
|
||||
|
||||
pinia@2.3.1:
|
||||
resolution: {integrity: sha512-khUlZSwt9xXCaTbbxFYBKDc/bWAGWJjOgvxETwkTN7KRm66EeT1ZdZj6i2ceh9sP2Pzqsbc704r2yngBrxBVug==}
|
||||
@@ -3968,17 +3963,6 @@ packages:
|
||||
terser:
|
||||
optional: true
|
||||
|
||||
vue-demi@0.12.5:
|
||||
resolution: {integrity: sha512-BREuTgTYlUr0zw0EZn3hnhC3I6gPWv+Kwh4MCih6QcAeaTlaIX0DwOVN0wHej7hSvDPecz4jygy/idsgKfW58Q==}
|
||||
engines: {node: '>=12'}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
'@vue/composition-api': ^1.0.0-rc.1
|
||||
vue: ^3.0.0-0 || ^2.6.0
|
||||
peerDependenciesMeta:
|
||||
'@vue/composition-api':
|
||||
optional: true
|
||||
|
||||
vue-demi@0.14.10:
|
||||
resolution: {integrity: sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==}
|
||||
engines: {node: '>=12'}
|
||||
@@ -8435,11 +8419,9 @@ snapshots:
|
||||
|
||||
pify@2.3.0: {}
|
||||
|
||||
pinia-plugin-persist-uni@1.3.2(pinia@2.3.1(typescript@4.9.5)(vue@3.5.30(typescript@4.9.5)))(vue@3.5.30(typescript@4.9.5)):
|
||||
pinia-plugin-persistedstate@3.2.3(pinia@2.3.1(typescript@4.9.5)(vue@3.5.30(typescript@4.9.5))):
|
||||
dependencies:
|
||||
pinia: 2.3.1(typescript@4.9.5)(vue@3.5.30(typescript@4.9.5))
|
||||
vue: 3.5.30(typescript@4.9.5)
|
||||
vue-demi: 0.12.5(vue@3.5.30(typescript@4.9.5))
|
||||
|
||||
pinia@2.3.1(typescript@4.9.5)(vue@3.5.30(typescript@4.9.5)):
|
||||
dependencies:
|
||||
@@ -9207,10 +9189,6 @@ snapshots:
|
||||
sass: 1.78.0
|
||||
terser: 5.46.1
|
||||
|
||||
vue-demi@0.12.5(vue@3.5.30(typescript@4.9.5)):
|
||||
dependencies:
|
||||
vue: 3.5.30(typescript@4.9.5)
|
||||
|
||||
vue-demi@0.14.10(vue@3.5.30(typescript@4.9.5)):
|
||||
dependencies:
|
||||
vue: 3.5.30(typescript@4.9.5)
|
||||
|
||||
@@ -536,7 +536,6 @@ export default {
|
||||
);
|
||||
// #endif
|
||||
// #ifndef MP-WEIXIN
|
||||
this.$mountStore();
|
||||
this.closeSplashscreen(1000); // 延迟1s,避免用户看到页面跳转
|
||||
let loginInfo = uni.getStorageSync('loginInfo');
|
||||
if (!loginInfo || !loginInfo.refreshToken) {
|
||||
|
||||
@@ -546,4 +546,4 @@ export default defineStore('chatStore', {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -5,27 +5,27 @@ import { TERMINAL_TYPE } from '../common/enums.js'
|
||||
export default defineStore('friendStore', {
|
||||
state: () => {
|
||||
return {
|
||||
friendMap: new Map(),
|
||||
friendMap: {},
|
||||
timer: null
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
setFriends(friends) {
|
||||
this.friendMap.clear();
|
||||
friends.forEach(f => this.friendMap.set(f.id, f))
|
||||
this.friendMap = {};
|
||||
friends.forEach(f => this.friendMap[f.id] = f)
|
||||
},
|
||||
updateFriend(friend) {
|
||||
let f = this.findFriend(friend.id);
|
||||
friend.online = f.online;
|
||||
friend.onlineWeb = f.onlineWeb;
|
||||
friend.onlineApp = f.onlineApp;
|
||||
this.friendMap.set(friend.id, friend)
|
||||
this.friendMap[friend.id] = friend
|
||||
},
|
||||
removeFriend(id) {
|
||||
this.friendMap.get(id).deleted = true;
|
||||
this.friendMap[id].deleted = true;
|
||||
},
|
||||
addFriend(friend) {
|
||||
this.friendMap.set(friend.id, friend)
|
||||
this.friendMap[friend.id] = friend
|
||||
},
|
||||
setOnlineStatus(onlineTerminals) {
|
||||
this.friends.forEach((f) => {
|
||||
@@ -59,10 +59,10 @@ export default defineStore('friendStore', {
|
||||
}, 30000)
|
||||
},
|
||||
setDnd(id, isDnd) {
|
||||
this.friendMap.get(id).isDnd = isDnd;
|
||||
this.friendMap[id].isDnd = isDnd;
|
||||
},
|
||||
clear() {
|
||||
this.friendMap.clear();
|
||||
this.friendMap = {};
|
||||
clearTimeout(this.timer);
|
||||
this.timer = null;
|
||||
},
|
||||
@@ -83,14 +83,20 @@ export default defineStore('friendStore', {
|
||||
},
|
||||
getters: {
|
||||
friends: (state) => {
|
||||
return Array.from(state.friendMap.values());
|
||||
return Object.values(state.friendMap);
|
||||
},
|
||||
findFriend: (state) => (userId) => {
|
||||
return state.friendMap.get(userId);
|
||||
return state.friendMap[userId];
|
||||
},
|
||||
isFriend: (state) => (userId) => {
|
||||
let f = state.findFriend(userId)
|
||||
return f && !f.deleted
|
||||
}
|
||||
},
|
||||
persist: {
|
||||
paths: ['friendMap'],
|
||||
afterRestore: ({ store }) => {
|
||||
store.timer = null;
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
@@ -4,28 +4,28 @@ import http from '@/common/request';
|
||||
export default defineStore('groupStore', {
|
||||
state: () => {
|
||||
return {
|
||||
groupMap: new Map()
|
||||
groupMap: {}
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
setGroups(groups) {
|
||||
this.groupMap.clear();
|
||||
groups.forEach(g => this.groupMap.set(g.id, g))
|
||||
this.groupMap = {};
|
||||
groups.forEach(g => this.groupMap[g.id] = g)
|
||||
},
|
||||
addGroup(group) {
|
||||
this.groupMap.set(group.id, group);
|
||||
this.groupMap[group.id] = group;
|
||||
},
|
||||
removeGroup(id) {
|
||||
this.groupMap.get(id).quit = true;
|
||||
this.groupMap[id].quit = true;
|
||||
},
|
||||
updateGroup(group) {
|
||||
this.groupMap.set(group.id, group);
|
||||
this.groupMap[group.id] = group;
|
||||
},
|
||||
setDnd(id, isDnd) {
|
||||
this.groupMap.get(id).isDnd = isDnd;
|
||||
this.groupMap[id].isDnd = isDnd;
|
||||
},
|
||||
clear() {
|
||||
this.groupMap.clear();
|
||||
this.groupMap = {};
|
||||
},
|
||||
loadGroup() {
|
||||
return new Promise((resolve, reject) => {
|
||||
@@ -43,14 +43,15 @@ export default defineStore('groupStore', {
|
||||
},
|
||||
getters: {
|
||||
groups: (state) => {
|
||||
return Array.from(state.groupMap.values());
|
||||
return Object.values(state.groupMap);
|
||||
},
|
||||
findGroup: (state) => (id) => {
|
||||
return state.groupMap.get(id);
|
||||
return state.groupMap[id];
|
||||
},
|
||||
isGroup: (state) => (id) => {
|
||||
let group = state.findGroup(id);
|
||||
return group && !group.quit
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
persist: true
|
||||
})
|
||||
@@ -30,4 +30,5 @@ export default defineStore('userStore', {
|
||||
});
|
||||
},
|
||||
},
|
||||
persist: true,
|
||||
});
|
||||
@@ -34,37 +34,11 @@ const emoTextList = [
|
||||
'享受',
|
||||
'鼻涕',
|
||||
'快乐',
|
||||
'雀跃',
|
||||
'微笑',
|
||||
'贪婪',
|
||||
'红心',
|
||||
'粉心',
|
||||
'星星',
|
||||
'大火',
|
||||
'眼睛',
|
||||
'音符',
|
||||
'叹号',
|
||||
'问号',
|
||||
'绿叶',
|
||||
'燃烧',
|
||||
'喇叭',
|
||||
'警告',
|
||||
'信封',
|
||||
'房子',
|
||||
'礼物',
|
||||
'点赞',
|
||||
'举手',
|
||||
'拍手',
|
||||
'点头',
|
||||
'摇头',
|
||||
'偷瞄',
|
||||
'庆祝',
|
||||
'疾跑',
|
||||
'打滚',
|
||||
'惊吓',
|
||||
'起跳',
|
||||
'雀跃', '微笑',
|
||||
'贪婪', '红心', '粉心', '星星', '大火', '眼睛', '音符', "叹号", "问号", "绿叶",
|
||||
"燃烧", "喇叭", "警告", "信封", "房子", "礼物", "点赞", "举手", "拍手", "点头",
|
||||
"摇头", "偷瞄", "庆祝", "疾跑", "打滚", "惊吓", "起跳"
|
||||
];
|
||||
|
||||
const regex = /\#[\u4E00-\u9FA5]{1,3}\;/gi;
|
||||
|
||||
let containEmoji = (content) => {
|
||||
@@ -88,7 +62,7 @@ let transform = (content, extClass) => {
|
||||
let textToPath = (emoText) => {
|
||||
let word = emoText.replace(/\#|\;/gi, '');
|
||||
let idx = emoTextList.indexOf(word);
|
||||
return EMO_URL + idx + '.png';
|
||||
return EMO_URL + idx + '.gif';
|
||||
};
|
||||
|
||||
export default {
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import CHAT_ENV from '@/chat-env.js';
|
||||
|
||||
// 请求队列
|
||||
let requestList = [];
|
||||
const TOKEN_REFRESH_THRESHOLD_SECONDS = 5 * 60;
|
||||
|
||||
// 等待同一次 token 刷新结果的请求队列
|
||||
let refreshWaitList = [];
|
||||
// 是否正在刷新中
|
||||
let isRefreshToken = false;
|
||||
|
||||
@@ -20,36 +22,41 @@ const request = (options) => {
|
||||
timeout: options.timeout || 3000,
|
||||
async success(res) {
|
||||
if (res.data.code == 200) {
|
||||
const currentLoginInfo = uni.getStorageSync('loginInfo');
|
||||
if (isAccessTokenExpiringSoon(currentLoginInfo?.accessToken)) {
|
||||
try {
|
||||
await refreshAccessToken(currentLoginInfo);
|
||||
} catch (error) {
|
||||
console.log('刷新token失败', error);
|
||||
getApp().$vm.exit();
|
||||
return reject(error);
|
||||
}
|
||||
}
|
||||
return resolve(res.data.data);
|
||||
} else if (res.data.code == 400) {
|
||||
console.log('code 400');
|
||||
getApp().$vm.exit();
|
||||
} else if (res.data.code == 401) {
|
||||
console.log('token失效,尝试重新获取');
|
||||
if (isRefreshToken) {
|
||||
// 正在刷新token,把其他请求存起来
|
||||
requestList.push(() => {
|
||||
resolve(request(options));
|
||||
});
|
||||
return;
|
||||
const currentLoginInfo = uni.getStorageSync('loginInfo');
|
||||
|
||||
// 当前请求使用的是旧 token,直接用已刷新的 token 重试。
|
||||
if (
|
||||
currentLoginInfo?.accessToken &&
|
||||
currentLoginInfo.accessToken !== header.accessToken
|
||||
) {
|
||||
return resolve(request(options));
|
||||
}
|
||||
isRefreshToken = true;
|
||||
// 发送请求, 进行刷新token操作, 获取新的token
|
||||
const res = await reqRefreshToken(loginInfo);
|
||||
if (!res || res.data.code != 200) {
|
||||
requestList = [];
|
||||
isRefreshToken = false;
|
||||
console.log('刷新token失败');
|
||||
|
||||
try {
|
||||
await refreshAccessToken(currentLoginInfo);
|
||||
// 重新发送刚才的请求
|
||||
return resolve(request(options));
|
||||
} catch (error) {
|
||||
console.log('刷新token失败', error);
|
||||
getApp().$vm.exit();
|
||||
return;
|
||||
return reject(error);
|
||||
}
|
||||
let newInfo = res.data.data;
|
||||
uni.setStorageSync('loginInfo', newInfo);
|
||||
requestList.forEach((cb) => cb());
|
||||
requestList = [];
|
||||
isRefreshToken = false;
|
||||
// 重新发送刚才的请求
|
||||
return resolve(request(options));
|
||||
} else {
|
||||
uni.showToast({
|
||||
icon: 'none',
|
||||
@@ -71,6 +78,64 @@ const request = (options) => {
|
||||
});
|
||||
};
|
||||
|
||||
const isAccessTokenExpiringSoon = (accessToken) => {
|
||||
if (!accessToken) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
const payload = accessToken.split('.')[1];
|
||||
const base64 = payload.replace(/-/g, '+').replace(/_/g, '/');
|
||||
const paddedBase64 = base64.padEnd(Math.ceil(base64.length / 4) * 4, '=');
|
||||
const bytes = new Uint8Array(uni.base64ToArrayBuffer(paddedBase64));
|
||||
let decodedPayload = '';
|
||||
bytes.forEach((byte) => {
|
||||
decodedPayload += String.fromCharCode(byte);
|
||||
});
|
||||
const expMatch = decodedPayload.match(/"exp"\s*:\s*(\d+)/);
|
||||
if (!expMatch) {
|
||||
return false;
|
||||
}
|
||||
const remainingSeconds = Number(expMatch[1]) - Math.floor(Date.now() / 1000);
|
||||
console.log("remainingSeconds:", remainingSeconds);
|
||||
|
||||
return remainingSeconds <= TOKEN_REFRESH_THRESHOLD_SECONDS;
|
||||
} catch (error) {
|
||||
console.warn('解析 accessToken 过期时间失败', error);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
const refreshAccessToken = (loginInfo) => {
|
||||
if (!loginInfo?.refreshToken) {
|
||||
return Promise.reject(new Error('缺少 refreshToken'));
|
||||
}
|
||||
if (isRefreshToken) {
|
||||
return new Promise((resolve, reject) => {
|
||||
refreshWaitList.push({ resolve, reject });
|
||||
});
|
||||
}
|
||||
isRefreshToken = true;
|
||||
return reqRefreshToken(loginInfo)
|
||||
.then((res) => {
|
||||
if (!res || res.data.code != 200) {
|
||||
throw res?.data || new Error('刷新token失败');
|
||||
}
|
||||
const newInfo = res.data.data;
|
||||
uni.setStorageSync('loginInfo', newInfo);
|
||||
refreshWaitList.forEach(({ resolve }) => resolve(newInfo));
|
||||
return newInfo;
|
||||
})
|
||||
.catch((error) => {
|
||||
refreshWaitList.forEach(({ reject }) => reject(error));
|
||||
throw error;
|
||||
})
|
||||
.finally(() => {
|
||||
refreshWaitList = [];
|
||||
isRefreshToken = false;
|
||||
});
|
||||
};
|
||||
|
||||
const reqRefreshToken = (loginInfo) => {
|
||||
return new Promise(function (resolve, reject) {
|
||||
uni.request({
|
||||
|
||||
@@ -1,13 +1,6 @@
|
||||
<template>
|
||||
<view class="head-image none-pointer-events" @click="showUserInfo($event)" :title="name">
|
||||
<image
|
||||
class="avatar-image"
|
||||
v-if="url"
|
||||
:src="url"
|
||||
:style="avatarImageStyle"
|
||||
lazy-load="true"
|
||||
mode="aspectFill"
|
||||
/>
|
||||
<image class="avatar-image" v-if="url" :src="url" :style="avatarImageStyle" lazy-load="true" mode="aspectFill" />
|
||||
<view class="avatar-text" v-if="!url" :style="avatarTextStyle">
|
||||
{{ name?.substring(0, 1).toUpperCase() }}
|
||||
</view>
|
||||
@@ -16,117 +9,117 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'head-image',
|
||||
data() {
|
||||
return {
|
||||
colors: ['#5daa31', '#c7515a', '#e03697', '#85029b', '#c9b455', '#326eb6'],
|
||||
};
|
||||
export default {
|
||||
name: 'head-image',
|
||||
data() {
|
||||
return {
|
||||
colors: ['#5daa31', '#c7515a', '#e03697', '#85029b', '#c9b455', '#326eb6'],
|
||||
};
|
||||
},
|
||||
props: {
|
||||
id: {
|
||||
type: Number,
|
||||
},
|
||||
props: {
|
||||
id: {
|
||||
type: Number,
|
||||
},
|
||||
size: {
|
||||
type: [Number, String],
|
||||
default: 'default',
|
||||
},
|
||||
url: {
|
||||
type: String,
|
||||
},
|
||||
name: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
radius: {
|
||||
type: String,
|
||||
default: '50%',
|
||||
},
|
||||
online: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
size: {
|
||||
type: [Number, String],
|
||||
default: 'default',
|
||||
},
|
||||
methods: {
|
||||
showUserInfo(e) {
|
||||
if (this.id && this.id > 0) {
|
||||
uni.navigateTo({
|
||||
url: '/pages/message/user-info?id=' + this.id,
|
||||
});
|
||||
}
|
||||
},
|
||||
url: {
|
||||
type: String,
|
||||
},
|
||||
computed: {
|
||||
_size() {
|
||||
if (typeof this.size === 'number') {
|
||||
return this.size;
|
||||
} else if (typeof this.size === 'string') {
|
||||
return {
|
||||
default: 96,
|
||||
small: 84,
|
||||
smaller: 72,
|
||||
mini: 60,
|
||||
minier: 48,
|
||||
lage: 108,
|
||||
lager: 120,
|
||||
}[this.size];
|
||||
}
|
||||
},
|
||||
avatarImageStyle() {
|
||||
return `width:${this._size}rpx;
|
||||
name: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
radius: {
|
||||
type: String,
|
||||
default: '50%',
|
||||
},
|
||||
online: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
showUserInfo(e) {
|
||||
if (this.id && this.id > 0) {
|
||||
uni.navigateTo({
|
||||
url: '/pages/message/user-info?id=' + this.id,
|
||||
});
|
||||
}
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
_size() {
|
||||
if (typeof this.size === 'number') {
|
||||
return this.size;
|
||||
} else if (typeof this.size === 'string') {
|
||||
return {
|
||||
default: 96,
|
||||
small: 84,
|
||||
smaller: 72,
|
||||
mini: 60,
|
||||
minier: 48,
|
||||
lage: 108,
|
||||
lager: 120,
|
||||
}[this.size];
|
||||
}
|
||||
},
|
||||
avatarImageStyle() {
|
||||
return `width:${this._size}rpx;
|
||||
height:${this._size}rpx;`;
|
||||
},
|
||||
avatarTextStyle() {
|
||||
return `width: ${this._size}rpx;
|
||||
},
|
||||
avatarTextStyle() {
|
||||
return `width: ${this._size}rpx;
|
||||
height:${this._size}rpx;
|
||||
background: linear-gradient(145deg,#ffffff20 25%,#00000060),${this.textColor};
|
||||
font-size:${this._size * 0.45}rpx;
|
||||
border-radius: ${this.radius};
|
||||
`;
|
||||
},
|
||||
textColor() {
|
||||
if (!this.name) {
|
||||
return '#fff';
|
||||
}
|
||||
let hash = 0;
|
||||
for (var i = 0; i < this.name.length; i++) {
|
||||
hash += this.name.charCodeAt(i);
|
||||
}
|
||||
return this.colors[hash % this.colors.length];
|
||||
},
|
||||
},
|
||||
};
|
||||
textColor() {
|
||||
if (!this.name) {
|
||||
return '#fff';
|
||||
}
|
||||
let hash = 0;
|
||||
for (var i = 0; i < this.name.length; i++) {
|
||||
hash += this.name.charCodeAt(i);
|
||||
}
|
||||
return this.colors[hash % this.colors.length];
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.head-image {
|
||||
.head-image {
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
|
||||
.avatar-image {
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
|
||||
.avatar-image {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
border-radius: 50%;
|
||||
vertical-align: bottom;
|
||||
}
|
||||
|
||||
.avatar-text {
|
||||
color: white;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.online {
|
||||
position: absolute;
|
||||
right: -10%;
|
||||
bottom: 0;
|
||||
width: 24rpx;
|
||||
height: 24rpx;
|
||||
background: limegreen;
|
||||
border-radius: 50%;
|
||||
border: 6rpx solid white;
|
||||
}
|
||||
overflow: hidden;
|
||||
border-radius: 50%;
|
||||
vertical-align: bottom;
|
||||
}
|
||||
|
||||
.avatar-text {
|
||||
color: white;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.online {
|
||||
position: absolute;
|
||||
right: -10%;
|
||||
bottom: 0;
|
||||
width: 24rpx;
|
||||
height: 24rpx;
|
||||
background: limegreen;
|
||||
border-radius: 50%;
|
||||
border: 6rpx solid white;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -23,8 +23,8 @@ import { initH5HostShell } from '@/sheep/platform/h5-shell';
|
||||
window.Quill = Quill;
|
||||
window.ImageResize = { default: ImageResize };
|
||||
// 调试器
|
||||
import VConsole from 'vconsole';
|
||||
new VConsole();
|
||||
// import VConsole from 'vconsole';
|
||||
// new VConsole();
|
||||
initH5HostShell();
|
||||
patchH5Navigation();
|
||||
// #endif
|
||||
@@ -48,7 +48,7 @@ import switchBar from '@/components/bar/switch-bar';
|
||||
export function createApp() {
|
||||
const app = createSSRApp(App);
|
||||
|
||||
setupPinia(app);
|
||||
const pinia = setupPinia(app);
|
||||
|
||||
// 聊天common模块挂载为全局属性
|
||||
app.config.globalProperties.$http = request;
|
||||
@@ -67,14 +67,16 @@ export function createApp() {
|
||||
app.component('btn-bar', btnBar);
|
||||
app.component('switch-bar', switchBar);
|
||||
|
||||
// $mountStore挂载为全局属性,等app初始化时再挂载chat-store到全局属性,方便页面中直接this使用
|
||||
app.config.globalProperties.$mountStore = () => {
|
||||
app.config.globalProperties.chatStore = useChatStore();
|
||||
app.config.globalProperties.friendStore = useFriendStore();
|
||||
app.config.globalProperties.groupStore = useGroupStore();
|
||||
app.config.globalProperties.configStore = useConfigStore();
|
||||
app.config.globalProperties.userStore = useUserStore();
|
||||
// 在页面首次渲染前挂载聊天仓库,避免刷新页面时全局 store 尚未可用。
|
||||
const mountStores = () => {
|
||||
app.config.globalProperties.chatStore = useChatStore(pinia);
|
||||
app.config.globalProperties.friendStore = useFriendStore(pinia);
|
||||
app.config.globalProperties.groupStore = useGroupStore(pinia);
|
||||
app.config.globalProperties.configStore = useConfigStore(pinia);
|
||||
app.config.globalProperties.userStore = useUserStore(pinia);
|
||||
};
|
||||
app.config.globalProperties.$mountStore = mountStores;
|
||||
mountStores();
|
||||
|
||||
return {
|
||||
app,
|
||||
|
||||
@@ -6,13 +6,9 @@
|
||||
<view>
|
||||
<view class="commont-from-wrap">
|
||||
<!-- 评价商品 -->
|
||||
<s-goods-item
|
||||
:img="item.picUrl"
|
||||
:title="item.spuName"
|
||||
:skuText="item.properties.map((property) => property.valueName).join(' ')"
|
||||
:price="item.payPrice"
|
||||
:num="item.count"
|
||||
/>
|
||||
<s-goods-item :img="item.picUrl" :title="item.spuName"
|
||||
:skuText="item.properties.map((property) => property.valueName).join(' ')" :price="item.payPrice"
|
||||
:num="item.count" />
|
||||
</view>
|
||||
|
||||
<view class="form-item">
|
||||
@@ -27,23 +23,12 @@
|
||||
</view>
|
||||
<!-- 评价 -->
|
||||
<view class="area-box">
|
||||
<uni-easyinput
|
||||
:inputBorder="false"
|
||||
type="textarea"
|
||||
maxlength="120"
|
||||
autoHeight
|
||||
v-model="state.commentList[index].content"
|
||||
placeholder="宝贝满足你的期待吗?说说你的使用心得,分享给想买的他们吧~"
|
||||
/>
|
||||
<uni-easyinput :inputBorder="false" type="textarea" maxlength="120" autoHeight
|
||||
v-model="state.commentList[index].content" placeholder="宝贝满足你的期待吗?说说你的使用心得,分享给想买的他们吧~" />
|
||||
<view class="img-box">
|
||||
<s-uploader
|
||||
v-model:url="state.commentList[index].images"
|
||||
fileMediatype="image"
|
||||
limit="9"
|
||||
mode="grid"
|
||||
<s-uploader v-model:url="state.commentList[index].images" fileMediatype="image" limit="9" mode="grid"
|
||||
:imageStyles="{ width: '168rpx', height: '168rpx' }"
|
||||
@success="(payload) => uploadSuccess(payload, index)"
|
||||
/>
|
||||
@success="(payload) => uploadSuccess(payload, index)" />
|
||||
</view>
|
||||
</view>
|
||||
<view class="checkbox-container">
|
||||
@@ -69,122 +54,122 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import sheep from '@/sheep';
|
||||
import { onLoad } from '@dcloudio/uni-app';
|
||||
import { reactive } from 'vue';
|
||||
import OrderApi from '@/sheep/api/trade/order';
|
||||
import sheep from '@/sheep';
|
||||
import { onLoad } from '@dcloudio/uni-app';
|
||||
import { reactive } from 'vue';
|
||||
import OrderApi from '@/sheep/api/trade/order';
|
||||
|
||||
const state = reactive({
|
||||
orderInfo: {},
|
||||
commentList: [],
|
||||
id: null,
|
||||
});
|
||||
const state = reactive({
|
||||
orderInfo: {},
|
||||
commentList: [],
|
||||
id: null,
|
||||
});
|
||||
|
||||
/**
|
||||
* 切换是否匿名
|
||||
*
|
||||
* @param commentIndex 当前评论下标
|
||||
* @param event 复选框事件
|
||||
*/
|
||||
function toggleAnonymous(commentIndex, event) {
|
||||
state.commentList[commentIndex].anonymous = event.detail.value[0] === 'anonymousChecked';
|
||||
/**
|
||||
* 切换是否匿名
|
||||
*
|
||||
* @param commentIndex 当前评论下标
|
||||
* @param event 复选框事件
|
||||
*/
|
||||
function toggleAnonymous(commentIndex, event) {
|
||||
state.commentList[commentIndex].anonymous = event.detail.value[0] === 'anonymousChecked';
|
||||
}
|
||||
|
||||
/**
|
||||
* 发布评论
|
||||
*
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async function onSubmit() {
|
||||
// 顺序提交评论
|
||||
for (const comment of state.commentList) {
|
||||
await OrderApi.createOrderItemComment(comment);
|
||||
}
|
||||
// 都评论好,返回
|
||||
sheep.$router.back();
|
||||
}
|
||||
|
||||
/**
|
||||
* 发布评论
|
||||
*
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async function onSubmit() {
|
||||
// 顺序提交评论
|
||||
for (const comment of state.commentList) {
|
||||
await OrderApi.createOrderItemComment(comment);
|
||||
}
|
||||
// 都评论好,返回
|
||||
sheep.$router.back();
|
||||
/**
|
||||
* 图片添加到表单
|
||||
*
|
||||
* @param payload 上传成功后的回调数据
|
||||
* @param commentIndex 当前评论的下标
|
||||
*/
|
||||
function uploadSuccess(payload, commentIndex) {
|
||||
state.commentList[commentIndex].picUrls = payload.tempFilePaths;
|
||||
}
|
||||
|
||||
onLoad(async (options) => {
|
||||
if (!options.id) {
|
||||
sheep.$helper.toast(`缺少订单信息,请检查`);
|
||||
return;
|
||||
}
|
||||
state.id = options.id;
|
||||
|
||||
/**
|
||||
* 图片添加到表单
|
||||
*
|
||||
* @param payload 上传成功后的回调数据
|
||||
* @param commentIndex 当前评论的下标
|
||||
*/
|
||||
function uploadSuccess(payload, commentIndex) {
|
||||
state.commentList[commentIndex].picUrls = payload.tempFilePaths;
|
||||
const { code, data } = await OrderApi.getOrderDetail(state.id);
|
||||
if (code !== 0) {
|
||||
sheep.$helper.toast('无待评价订单');
|
||||
return;
|
||||
}
|
||||
|
||||
onLoad(async (options) => {
|
||||
if (!options.id) {
|
||||
sheep.$helper.toast(`缺少订单信息,请检查`);
|
||||
return;
|
||||
}
|
||||
state.id = options.id;
|
||||
|
||||
const { code, data } = await OrderApi.getOrderDetail(state.id);
|
||||
if (code !== 0) {
|
||||
sheep.$helper.toast('无待评价订单');
|
||||
return;
|
||||
}
|
||||
// 处理评论
|
||||
data.items.forEach((item) => {
|
||||
state.commentList.push({
|
||||
anonymous: false,
|
||||
orderItemId: item.id,
|
||||
descriptionScores: 5,
|
||||
benefitScores: 5,
|
||||
content: '',
|
||||
picUrls: [],
|
||||
});
|
||||
// 处理评论
|
||||
data.items.forEach((item) => {
|
||||
state.commentList.push({
|
||||
anonymous: false,
|
||||
orderItemId: item.id,
|
||||
descriptionScores: 5,
|
||||
benefitScores: 5,
|
||||
content: '',
|
||||
picUrls: [],
|
||||
});
|
||||
state.orderInfo = data;
|
||||
});
|
||||
state.orderInfo = data;
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
// 评价商品
|
||||
.goods-card {
|
||||
margin: 10rpx 0;
|
||||
padding: 20rpx;
|
||||
background: #fff;
|
||||
// 评价商品
|
||||
.goods-card {
|
||||
margin: 10rpx 0;
|
||||
padding: 20rpx;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
// 评论,选择图片
|
||||
.form-item {
|
||||
background: #fff;
|
||||
|
||||
.star-box {
|
||||
height: 100rpx;
|
||||
padding: 0 25rpx;
|
||||
}
|
||||
|
||||
// 评论,选择图片
|
||||
.form-item {
|
||||
background: #fff;
|
||||
|
||||
.star-box {
|
||||
height: 100rpx;
|
||||
padding: 0 25rpx;
|
||||
}
|
||||
|
||||
.star-title {
|
||||
font-weight: 600;
|
||||
}
|
||||
.star-title {
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
|
||||
.area-box {
|
||||
width: 690rpx;
|
||||
min-height: 306rpx;
|
||||
background: rgba(249, 250, 251, 1);
|
||||
border-radius: 20rpx;
|
||||
padding: 28rpx;
|
||||
margin: auto;
|
||||
.area-box {
|
||||
width: 690rpx;
|
||||
min-height: 306rpx;
|
||||
background: rgba(249, 250, 251, 1);
|
||||
border-radius: 20rpx;
|
||||
padding: 28rpx;
|
||||
margin: auto;
|
||||
|
||||
.img-box {
|
||||
margin-top: 20rpx;
|
||||
}
|
||||
.img-box {
|
||||
margin-top: 20rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.checkbox-container {
|
||||
padding: 10rpx;
|
||||
}
|
||||
.checkbox-container {
|
||||
padding: 10rpx;
|
||||
}
|
||||
|
||||
.post-btn {
|
||||
width: 690rpx;
|
||||
line-height: 80rpx;
|
||||
border-radius: 40rpx;
|
||||
color: rgba(#fff, 0.9);
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
.post-btn {
|
||||
width: 690rpx;
|
||||
line-height: 80rpx;
|
||||
border-radius: 40rpx;
|
||||
color: rgba(#fff, 0.9);
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
</style>
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<view class="relative h-screen w-screen overflow-hidden">
|
||||
<view class="login-page fixed inset-0 w-screen overflow-hidden" :style="pageStyle">
|
||||
<!-- <image class="absolute inset-0 h-full w-full" src="@/static/login-bg.png" mode="aspectFill" /> -->
|
||||
<view class="relative z-10 flex flex-col items-center px-40rpx">
|
||||
<view class="mt-300rpx mb-200rpx text-center">
|
||||
@@ -11,16 +11,19 @@
|
||||
<view class="relative flex w-full items-center">
|
||||
<!-- #ifdef MP-WEIXIN -->
|
||||
<input class="h-100rpx w-full rounded-50rpx bg-color-light px-40rpx text-32rpx text-white" type="number"
|
||||
placeholder="请输入账号" placeholder-style="color: rgba(255, 255, 255, 0.6)" v-model="phone" maxlength="11" />
|
||||
placeholder="请输入账号" placeholder-style="color: rgba(255, 255, 255, 0.6)" v-model="phone" maxlength="11"
|
||||
:adjust-position="false" />
|
||||
<!-- #endif -->
|
||||
<!-- #ifndef MP-WEIXIN -->
|
||||
<input class="h-100rpx w-full rounded-50rpx bg-color-light px-40rpx text-32rpx text-white" type="number"
|
||||
placeholder="请输入手机号" placeholder-style="color: rgba(255, 255, 255, 0.6)" v-model="phone" maxlength="11" />
|
||||
placeholder="请输入手机号" placeholder-style="color: rgba(255, 255, 255, 0.6)" v-model="phone" maxlength="11"
|
||||
:adjust-position="false" />
|
||||
<!-- #endif -->
|
||||
</view>
|
||||
<view class="relative flex w-full items-center">
|
||||
<input class="h-100rpx w-full rounded-50rpx bg-color-light px-40rpx text-32rpx text-white" type="number"
|
||||
placeholder="验证码" placeholder-style="color: rgba(255, 255, 255, 0.6)" v-model="smsCode" maxlength="4" />
|
||||
placeholder="验证码" placeholder-style="color: rgba(255, 255, 255, 0.6)" v-model="smsCode" maxlength="4"
|
||||
:adjust-position="false" />
|
||||
<view class="absolute right-20rpx text-12px flex-row-center z-200">
|
||||
<wd-button @click.stop.prevent="handleGetCode" :disabled="isTiming" size="medium">
|
||||
<text>获取验证码</text><text v-if="isTiming"> ({{ countDown }}s)</text>
|
||||
@@ -75,7 +78,7 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue';
|
||||
import { computed, ref } from 'vue';
|
||||
import { onShow } from '@dcloudio/uni-app';
|
||||
|
||||
import AuthUtil from '@/sheep/api/member/auth';
|
||||
@@ -91,8 +94,20 @@ const isTiming = ref(false);
|
||||
const countDown = ref(60);
|
||||
const isAgree = ref(false);
|
||||
const nodes = ref(''); // 同意协议的文本
|
||||
const loginPageHeight = ref('100vh');
|
||||
const pageStyle = computed(() => ({ height: loginPageHeight.value }));
|
||||
|
||||
// h5端进入输入态时锁定页面高度,避免键盘把整页顶起
|
||||
const syncLoginPageHeight = () => {
|
||||
// #ifdef H5
|
||||
if (typeof window !== 'undefined') {
|
||||
loginPageHeight.value = `${window.innerHeight}px`;
|
||||
}
|
||||
// #endif
|
||||
};
|
||||
|
||||
onShow(() => {
|
||||
syncLoginPageHeight();
|
||||
// #ifndef MP-WEIXIN
|
||||
if (!phone.value) {
|
||||
const lastLoginPhone = uni.getStorageSync('last_login_phone');
|
||||
@@ -187,3 +202,12 @@ async function alert(type, title) {
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.login-page {
|
||||
// #ifdef H5
|
||||
height: 100vh;
|
||||
min-height: 100vh;
|
||||
// #endif
|
||||
}
|
||||
</style>
|
||||
@@ -3,12 +3,14 @@
|
||||
<wd-config-provider theme="dark">
|
||||
<wd-navbar fixed safeAreaInsetTop :bordered="false">
|
||||
<template #left>
|
||||
<navigator open-type="navigateBack" hover-class="navigator-hover">
|
||||
<uni-icons type="back" size="30" color="#D29E52"></uni-icons>
|
||||
</navigator>
|
||||
<view class="flex items-center" :style="navUserInfoStyle">
|
||||
<image class="size-70rpx bg-#333 rounded-full mr-1" :src="avatar" mode="aspectFill" />
|
||||
<text class="truncate text-sm">{{ title }}</text>
|
||||
<view class="flex items-center">
|
||||
<navigator open-type="navigateBack" hover-class="navigator-hover">
|
||||
<uni-icons type="back" size="30" color="#D29E52"></uni-icons>
|
||||
</navigator>
|
||||
<view class="flex items-center" :style="navUserInfoStyle">
|
||||
<image class="size-70rpx bg-#333 rounded-full mr-1" :src="avatar" mode="aspectFill" />
|
||||
<text class="truncate text-sm">{{ title }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
<template #right>
|
||||
@@ -182,6 +184,9 @@
|
||||
import CHAT_ENV from '@/chat-env.js';
|
||||
import RecentApi from '@/sheep/api/member/recent';
|
||||
import sheep from '@/sheep';
|
||||
// #ifdef H5
|
||||
import { getH5StatusBarHeight } from '@/sheep/platform/h5-shell';
|
||||
// #endif
|
||||
|
||||
import WarningCar from "./components/warning-car.vue"
|
||||
import UserInfoCar from "./components/user-info-car.vue"
|
||||
@@ -208,6 +213,7 @@ export default {
|
||||
screenHeight: 1000, // 屏幕高度
|
||||
windowHeight: 1000, // 窗口高度
|
||||
initHeight: 1000, // h5初始高度
|
||||
h5StatusBarHeight: 0, // h5套壳状态栏高度
|
||||
atUserIds: [],
|
||||
showMinIdx: 0, // 下标小于showMinIdx的消息不显示,否则可能很卡
|
||||
reqQueue: [], // 请求队列
|
||||
@@ -933,14 +939,15 @@ export default {
|
||||
});
|
||||
},
|
||||
async ensureChatStoreReady() {
|
||||
if (!this.userStore.userInfo?.id) {
|
||||
await this.userStore.loadUser();
|
||||
this.$mountStore?.();
|
||||
if (!this.userStore?.userInfo?.id) {
|
||||
await this.userStore?.loadUser?.();
|
||||
}
|
||||
if (!this.chatStore.curChats?.length) {
|
||||
await this.chatStore.loadChat();
|
||||
if (!this.chatStore?.curChats?.length) {
|
||||
await this.chatStore?.loadChat?.();
|
||||
}
|
||||
if (
|
||||
this.chatStore.refreshChats &&
|
||||
this.chatStore?.refreshChats &&
|
||||
this.chatStore.curChats?.length !== this.chatStore.chats.length
|
||||
) {
|
||||
this.chatStore.refreshChats();
|
||||
@@ -1024,7 +1031,11 @@ export default {
|
||||
let sysInfo = uni.getSystemInfoSync();
|
||||
let h = this.windowHeight;
|
||||
// 减去标题栏高度
|
||||
h -= 50;
|
||||
h -= 44;
|
||||
// #ifdef H5
|
||||
// h5套壳场景下,页面内容还需要减去宿主状态栏高度
|
||||
h -= this.h5StatusBarHeight;
|
||||
// #endif
|
||||
// 减去键盘高度
|
||||
if (this.isShowKeyBoard || this.chatTabBox != 'none') {
|
||||
// ios app的键盘高度不准,需要减去屏幕和窗口差
|
||||
@@ -1059,6 +1070,21 @@ export default {
|
||||
}
|
||||
// #endif
|
||||
},
|
||||
syncH5StatusBarHeight() {
|
||||
// #ifdef H5
|
||||
this.h5StatusBarHeight = getH5StatusBarHeight();
|
||||
// #endif
|
||||
},
|
||||
handleH5ShellStatusBarChange(event) {
|
||||
// #ifdef H5
|
||||
const nextHeight = Number.parseFloat(event?.detail?.height);
|
||||
this.h5StatusBarHeight = Number.isFinite(nextHeight)
|
||||
? Math.max(nextHeight, 0)
|
||||
: getH5StatusBarHeight();
|
||||
this.windowHeight = uni.getSystemInfoSync().windowHeight;
|
||||
this.reCalChatMainHeight();
|
||||
// #endif
|
||||
},
|
||||
listenKeyBoard() {
|
||||
// #ifdef H5
|
||||
if (navigator.platform == 'Win32') {
|
||||
@@ -1414,10 +1440,17 @@ export default {
|
||||
|
||||
this.windowHeight = uni.getSystemInfoSync().windowHeight;
|
||||
this.screenHeight = uni.getSystemInfoSync().screenHeight;
|
||||
// #ifdef H5
|
||||
this.syncH5StatusBarHeight();
|
||||
// #endif
|
||||
this.reCalChatMainHeight();
|
||||
this.$nextTick(() => {
|
||||
// 上面获取的windowHeight可能不准,重新计算一次聊天窗口高度
|
||||
this.windowHeight = uni.getSystemInfoSync().windowHeight;
|
||||
// #ifdef H5
|
||||
this.syncH5StatusBarHeight();
|
||||
window.addEventListener('app-shell-status-bar-change', this.handleH5ShellStatusBarChange);
|
||||
// #endif
|
||||
this.reCalChatMainHeight();
|
||||
this.scrollToBottom();
|
||||
// #ifdef H5
|
||||
@@ -1437,6 +1470,9 @@ export default {
|
||||
},
|
||||
onUnload() {
|
||||
this.unListenKeyboard();
|
||||
// #ifdef H5
|
||||
window.removeEventListener('app-shell-status-bar-change', this.handleH5ShellStatusBarChange);
|
||||
// #endif
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -1474,6 +1510,9 @@ export default {
|
||||
// #ifndef APP-PLUS
|
||||
top: $im-nav-bar-height;
|
||||
// #endif
|
||||
// #ifdef H5
|
||||
top: calc($im-nav-bar-height + var(--status-bar-height, 0px));
|
||||
// #endif
|
||||
// #ifdef APP-PLUS
|
||||
top: calc($im-nav-bar-height + var(--status-bar-height));
|
||||
// #endif
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
<wd-textarea v-model="params.description" :maxlength="1000" clearable show-word-limit />
|
||||
<wd-tabs v-model="activeTab">
|
||||
<wd-tab title="发图文">
|
||||
<view class="px-4 py-8">
|
||||
<view class="px-4 py-8 flex-row-center">
|
||||
<s-uploader v-model:url="params.coverPic" fileMediatype="image" limit="6" mode="grid" :imageStyles="{
|
||||
width: '230rpx',
|
||||
height: '230rpx',
|
||||
@@ -24,6 +24,19 @@
|
||||
}" />
|
||||
</view>
|
||||
</wd-tab>
|
||||
<wd-tab title="发视频">
|
||||
<view class="px-4 py-8 flex-row-center">
|
||||
<s-uploader v-model:url="params.coverVideo" fileMediatype="video" limit="6" mode="grid" :imageStyles="{
|
||||
width: '230rpx',
|
||||
height: '230rpx',
|
||||
border: { width: '4rpx', color: '#D29E52', radius: '40rpx' },
|
||||
}">
|
||||
<view class="flex-row-center size-full">
|
||||
<image src="/static/video-icon.png" class="size-100rpx"></image>
|
||||
</view>
|
||||
</s-uploader>
|
||||
</view>
|
||||
</wd-tab>
|
||||
<wd-tab v-if="false" title="发视频">
|
||||
<view class="px-4 py-8">
|
||||
<wd-upload v-model:file-list="params.coverVideo" :action="baseUrl + apiPath + '/infra/file/upload'"
|
||||
@@ -31,16 +44,6 @@
|
||||
</view>
|
||||
</wd-tab>
|
||||
</wd-tabs>
|
||||
|
||||
<!-- <wd-col-picker
|
||||
label="选择地址"
|
||||
title="选择地址"
|
||||
align-right
|
||||
v-model="addressValue"
|
||||
:columns="area"
|
||||
:column-change="columnChange"
|
||||
@confirm="areaPickerConfirm"
|
||||
/> -->
|
||||
</scroll-view>
|
||||
<wd-toast />
|
||||
</wd-config-provider>
|
||||
@@ -72,10 +75,8 @@ const params = reactive({
|
||||
issueCity: '',
|
||||
issueDistrict: '',
|
||||
});
|
||||
const coverPic = computed(() => params.coverPic?.join(',') || []);
|
||||
const coverVideo = computed(() => {
|
||||
return params.coverVideo.map((item: any) => JSON.parse(item.response).data);
|
||||
});
|
||||
const coverPic = computed(() => params.coverPic?.join(',') || '');
|
||||
const coverVideo = computed(() => params.coverVideo?.join(',') || '');
|
||||
const area = ref<any[]>([
|
||||
colPickerData.map((item) => {
|
||||
return {
|
||||
@@ -123,8 +124,10 @@ const header = {
|
||||
// 发布朋友圈
|
||||
const publishMoments = async () => {
|
||||
if (!params.description) return uni.showToast({ title: '请填写内容', icon: 'error' });
|
||||
if (!params.coverPic || !params.coverPic.length)
|
||||
if (activeTab.value === 0 && (!params.coverPic || !params.coverPic.length))
|
||||
return uni.showToast({ title: '请上传图片', icon: 'error' });
|
||||
if (activeTab.value === 1 && (!params.coverVideo || !params.coverVideo.length))
|
||||
return uni.showToast({ title: '请上传视频', icon: 'error' });
|
||||
|
||||
// 获取经纬度位置信息
|
||||
uni.getLocation({
|
||||
@@ -134,8 +137,8 @@ const publishMoments = async () => {
|
||||
|
||||
const { code, msg } = await RecentApi.createMoment({
|
||||
...params,
|
||||
coverPic: coverPic.value,
|
||||
coverVideo: coverVideo.value.join(','),
|
||||
coverPic: activeTab.value === 0 ? coverPic.value : '',
|
||||
coverVideo: activeTab.value === 1 ? coverVideo.value : '',
|
||||
});
|
||||
if (code !== 0)
|
||||
return toast.error({
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
<view class="text-base mx-4 my-2">图片</view>
|
||||
<view class="px-4">
|
||||
<s-uploader v-model:url="params.pic" fileMediatype="image" limit="1" mode="grid" :imageStyles="{
|
||||
width: '230rpx',
|
||||
height: '230rpx',
|
||||
border: { width: '6rpx', color: '#D29E52' },
|
||||
}" />
|
||||
</view>
|
||||
|
||||
@@ -108,6 +108,7 @@ import sheep from '@/sheep';
|
||||
// @ts-ignore
|
||||
import PayOrderApi from '@/sheep/api/pay/order';
|
||||
import { jumpToWechatMiniProgram, wechatNavBack } from '@/sheep/util/wechat-tool';
|
||||
import appConfig from '@/sheep/config';
|
||||
|
||||
const messageBox = useMessage();
|
||||
|
||||
@@ -199,16 +200,24 @@ const handleRecharge = (item: any) => {
|
||||
msg: `确认使用${payMethodItem.title}${fen2yuanSimple(item.payPrice)}元获取${fen2yuanSimple(item.bonusPrice + item.payPrice)}砖石?`,
|
||||
})
|
||||
.then(async () => {
|
||||
// #ifdef MP-WEIXIN
|
||||
if (payMethod.value === 'wechat') {
|
||||
disposeWechatPay(item.id);
|
||||
if (appConfig.buildTarget === 'H5') {
|
||||
if (payMethod.value === 'wechat') {
|
||||
disposeWechatPay(item.id);
|
||||
} else {
|
||||
disposeAlipayPay(item.id);
|
||||
}
|
||||
} else {
|
||||
disposeAlipayPay(item.id);
|
||||
// #ifdef MP-WEIXIN
|
||||
if (payMethod.value === 'wechat') {
|
||||
disposeWechatPay(item.id);
|
||||
} else {
|
||||
disposeAlipayPay(item.id);
|
||||
}
|
||||
// #endif
|
||||
// #ifndef MP-WEIXIN
|
||||
jumpToWechatPage(item.id);
|
||||
// #endif
|
||||
}
|
||||
// #endif
|
||||
// #ifndef MP-WEIXIN
|
||||
jumpToWechatPage(item.id);
|
||||
// #endif
|
||||
})
|
||||
.catch(() => { });
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<view class="p-4 pb-0">
|
||||
<view class="p-x-4">
|
||||
<view class="flex items-center justify-between text-color-theme mb-1">
|
||||
<text>我的相册</text>
|
||||
<navigator class="flex-row-center" url="/pages/profile/upload-album" open-type="navigate"
|
||||
|
||||
@@ -143,4 +143,4 @@ const goSelectAddress = () => {
|
||||
max-width: 70% !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
@@ -19,7 +19,6 @@
|
||||
<wd-input v-model="searchText" placeholder="搜索好友" prefix-icon="search" clearable :maxlength="10"
|
||||
:focus="true" />
|
||||
</view>
|
||||
|
||||
<view v-if="!pageReady || loading" class="chat-loading">
|
||||
<loading :size="50" :mask="false">
|
||||
<view>{{ pageReady ? '消息接收中...' : '正在初始化...' }}</view>
|
||||
@@ -33,7 +32,6 @@
|
||||
<view class="chat-tip" v-else-if="visibleChats.length == 0">
|
||||
<wd-status-tip image="message" tip="点击私聊,然后开始精彩的对话吧" />
|
||||
</view>
|
||||
|
||||
<scroll-view class="scroll-bar" v-else scroll-with-animation="true" scroll-y="true">
|
||||
<view v-for="(chat, index) in visibleChats" :key="index">
|
||||
<!-- <long-press-menu v-if="isShowChat(chat)" :items="menu.items" @select="onSelectMenu($event, index)"> -->
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
</template>
|
||||
</wd-navbar>
|
||||
|
||||
<scroll-view class="h-[calc(100vh-144rpx)]" scroll-y>
|
||||
<scroll-view style="height: calc(100dvh - 44px - 50px - var(--status-bar-height, 0px))" scroll-y>
|
||||
<!-- 个人信息 -->
|
||||
<navigator url="/pages/profile/userinfo" open-type="navigate" hover-class="navigator-hover">
|
||||
<view class="flex items-center p-6">
|
||||
@@ -101,12 +101,15 @@
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="mt-4 mx-1">
|
||||
<wd-cell-group>
|
||||
<wd-cell title="修改资料" title-width="110px" icon="user" icon-size="24" is-link to="/pages/profile/userinfo" />
|
||||
</wd-cell-group>
|
||||
</view>
|
||||
<!-- 用户相册 -->
|
||||
<user-ablum :ablumList="ablumList" />
|
||||
|
||||
<!-- 菜单 -->
|
||||
<view class="mt-4 mx-1">
|
||||
<view class="mx-1">
|
||||
<wd-cell-group>
|
||||
<wd-cell v-for="(item, index) in MenuCellList" :key="index" :title="item.title" title-width="110px"
|
||||
:value="item.value" icon-size="24" :icon="item.icon" is-link :to="item.to" @click="item.onClick">
|
||||
@@ -173,6 +176,7 @@ import { formatDate } from '@/sheep/helper/utils';
|
||||
// @ts-ignore
|
||||
import { fen2yuanSimple } from '@/sheep/hooks/useGoods';
|
||||
import { useMessage } from '@/uni_modules/wot-design-uni';
|
||||
import { saveImageToPhotosAlbum } from "@/sheep/util/file-tool"
|
||||
|
||||
const { ablumList } = useAblum();
|
||||
|
||||
@@ -253,10 +257,8 @@ onShow(async () => {
|
||||
sheep.$store('user').updateUserData();
|
||||
const { data: totalData } = await RecentApi.recentTotal();
|
||||
recentTotal.value = totalData ?? {};
|
||||
|
||||
const { data: issueTotalData } = await RecentApi.recentIssueTotal();
|
||||
recentTotal.value.selfRecentTotal = issueTotalData?.selfRecentTotal || 0;
|
||||
|
||||
getMemberSetting()
|
||||
});
|
||||
|
||||
@@ -287,16 +289,19 @@ const subOfficial = () => {
|
||||
confirmButtonText: "保存图片",
|
||||
})
|
||||
.then(async () => {
|
||||
uni.saveImageToPhotosAlbum({
|
||||
filePath: "http://download.htysapp.com/gzh.jpg",
|
||||
success: () => {
|
||||
let imagePath = "http://download.htysapp.com/gzh.jpg";
|
||||
// #ifdef H5
|
||||
imagePath = "/static/gzh/gzh.jpg";
|
||||
// #endif
|
||||
saveImageToPhotosAlbum(imagePath,
|
||||
() => {
|
||||
uni.showToast({ title: '保存成功', icon: 'success' });
|
||||
},
|
||||
fail: (err) => {
|
||||
(err) => {
|
||||
uni.showToast({ title: '保存失败', icon: 'none' });
|
||||
console.error('|保存海报|,意外的错误:', err);
|
||||
},
|
||||
});
|
||||
console.error('|保存图片|,意外的错误:', err);
|
||||
}
|
||||
)
|
||||
})
|
||||
};
|
||||
|
||||
@@ -312,7 +317,7 @@ const MenuCellList = ref([
|
||||
// { title: '我的收益', value: '', icon: 'wallet', to: '/pages/wechat-tabbar/index' },
|
||||
{ title: '访客记录', value: '', icon: 'browse', to: '/pages/profile/view-record' },
|
||||
// { title: '认证中心', value: '', icon: 'discount-filled', to: '/pages/profile/verify-status' },
|
||||
{ title: '联系方式', value: '', icon: 'user', to: '/pages/profile/set-wechat' },
|
||||
{ title: '联系方式', value: '', icon: 'chat', to: '/pages/profile/set-wechat' },
|
||||
// { title: '我的动态', value: '', icon: 'star', to: '/pages/profile/my-moment' },
|
||||
{ title: '我的评价', value: '', icon: 'view-module', to: '', onClick: lookMyEvaluation },
|
||||
{ title: '问题反馈', value: '', icon: 'backtop-rectangle', to: '', onClick: goFeedback },
|
||||
@@ -339,4 +344,4 @@ const onTapMenu = (item: any) => {
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scope></style>
|
||||
<style lang="scss" scope></style>
|
||||
@@ -11,7 +11,6 @@
|
||||
</template>
|
||||
</wd-navbar>
|
||||
<scroll-view style="height: calc(100dvh - 44px - var(--status-bar-height, 0px));" scroll-y>
|
||||
<view style="background: red; height: var(--status-bar-height);"></view>
|
||||
<!-- 用户信息 -->
|
||||
<view class="mx-20rpx mt-30rpx bg-color-dark rounded-12rpx flex justify-between items-center px-6 py-4">
|
||||
<view class="flex items-center">
|
||||
@@ -120,6 +119,7 @@ import TradeConfigApi from '@/sheep/api/trade/config.js';
|
||||
import { useMessage } from '@/uni_modules/wot-design-uni';
|
||||
import useBindWechat from "@/sheep/hooks/use-bind-wechat"
|
||||
import { jumpToWechatMiniProgram } from '@/sheep/util/wechat-tool';
|
||||
import { saveImageToPhotosAlbum } from "@/sheep/util/file-tool"
|
||||
|
||||
const message = useMessage();
|
||||
const jumpWechatMessage = useMessage('jump-wechat');
|
||||
@@ -290,16 +290,15 @@ const savePoster = () => {
|
||||
uni.showToast({ title: '海报不存在', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
uni.saveImageToPhotosAlbum({
|
||||
filePath: state.posterPic,
|
||||
success: () => {
|
||||
saveImageToPhotosAlbum(state.posterPic,
|
||||
() => {
|
||||
uni.showToast({ title: '保存成功', icon: 'success' });
|
||||
},
|
||||
fail: (err) => {
|
||||
(err) => {
|
||||
uni.showToast({ title: '保存失败', icon: 'none' });
|
||||
console.error('|保存海报|,意外的错误:', err);
|
||||
},
|
||||
});
|
||||
}
|
||||
)
|
||||
};
|
||||
|
||||
const doLogin = () => {
|
||||
|
||||
@@ -7,22 +7,24 @@
|
||||
</navigator>
|
||||
</template>
|
||||
</wd-navbar>
|
||||
<view class="mx-20rpx mt-30rpx bg-color-dark rounded-12rpx px-6 py-4">
|
||||
<image class="mt-30rpx" :src="state.posterPic" mode="widthFix" />
|
||||
<view class="mt-30rpx flex justify-between items-center text-xs">
|
||||
<view
|
||||
class="flex-1 text-center px-2 py-8rpx border-1px border-solid rounded-full border-color-theme active_cl_bg-color-theme"
|
||||
@tap="copyLink">
|
||||
复制链接
|
||||
</view>
|
||||
<view class="w-30rpx" />
|
||||
<view
|
||||
class="flex-1 text-center px-2 py-8rpx border-1px border-solid rounded-full border-color-theme-dark bg-color-theme-dark active_cl_bg-color-theme"
|
||||
@tap="savePoster">
|
||||
保存海报
|
||||
<scroll-view style="height: calc(100dvh - 44px - var(--status-bar-height, 0px));" scroll-y>
|
||||
<view class="mx-20rpx mt-30rpx bg-color-dark rounded-12rpx px-6 py-4">
|
||||
<image class="mt-30rpx" :src="state.posterPic" mode="widthFix" />
|
||||
<view class="mt-30rpx flex justify-between items-center text-xs">
|
||||
<view
|
||||
class="flex-1 text-center px-2 py-8rpx border-1px border-solid rounded-full border-color-theme active_cl_bg-color-theme"
|
||||
@tap="copyLink">
|
||||
复制链接
|
||||
</view>
|
||||
<view class="w-30rpx" />
|
||||
<view
|
||||
class="flex-1 text-center px-2 py-8rpx border-1px border-solid rounded-full border-color-theme-dark bg-color-theme-dark active_cl_bg-color-theme"
|
||||
@tap="savePoster">
|
||||
保存海报
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</wd-config-provider>
|
||||
</template>
|
||||
|
||||
@@ -32,6 +34,7 @@ import { onLoad, onShow } from '@dcloudio/uni-app';
|
||||
|
||||
import sheep from '@/sheep';
|
||||
import BrokerageApi from '@/sheep/api/trade/brokerage';
|
||||
import { saveImageToPhotosAlbum } from "@/sheep/util/file-tool"
|
||||
|
||||
const state = reactive({
|
||||
posterPic: ''
|
||||
@@ -80,16 +83,15 @@ const savePoster = () => {
|
||||
uni.showToast({ title: '海报不存在', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
uni.saveImageToPhotosAlbum({
|
||||
filePath: state.posterPic,
|
||||
success: () => {
|
||||
saveImageToPhotosAlbum(state.posterPic,
|
||||
() => {
|
||||
uni.showToast({ title: '保存成功', icon: 'success' });
|
||||
},
|
||||
fail: (err) => {
|
||||
(err) => {
|
||||
uni.showToast({ title: '保存失败', icon: 'none' });
|
||||
console.error('|保存海报|,意外的错误:', err);
|
||||
},
|
||||
});
|
||||
}
|
||||
)
|
||||
};
|
||||
|
||||
</script>
|
||||
@@ -2,21 +2,12 @@
|
||||
<template>
|
||||
<su-popup :show="show" round="10" @close="onClosePoster" type="center" class="popup-box">
|
||||
<view class="ss-flex-col ss-col-center ss-row-center">
|
||||
<image
|
||||
v-if="!!painterImageUrl"
|
||||
class="poster-img"
|
||||
:src="painterImageUrl"
|
||||
:style="{
|
||||
height: poster.css.height+ 'px',
|
||||
width: poster.css.width + 'px',
|
||||
}"
|
||||
:show-menu-by-longpress="true"
|
||||
/>
|
||||
<image v-if="!!painterImageUrl" class="poster-img" :src="painterImageUrl" :style="{
|
||||
height: poster.css.height + 'px',
|
||||
width: poster.css.width + 'px',
|
||||
}" :show-menu-by-longpress="true" />
|
||||
</view>
|
||||
<view
|
||||
class="poster-btn-box ss-m-t-20 ss-flex ss-row-between ss-col-center"
|
||||
v-if="!!painterImageUrl"
|
||||
>
|
||||
<view class="poster-btn-box ss-m-t-20 ss-flex ss-row-between ss-col-center" v-if="!!painterImageUrl">
|
||||
<button class="cancel-btn ss-reset-button" @tap="onClosePoster">取消</button>
|
||||
<button class="save-btn ss-reset-button ui-BG-Main" @tap="onSavePoster">
|
||||
{{
|
||||
@@ -27,142 +18,135 @@
|
||||
</button>
|
||||
</view>
|
||||
<!-- 海报画板:默认隐藏只用来生成海报。生成方式为主动调用 -->
|
||||
<l-painter
|
||||
isCanvasToTempFilePath
|
||||
pathType="url"
|
||||
@success="setPainterImageUrl"
|
||||
hidden
|
||||
ref="painterRef"
|
||||
/>
|
||||
<l-painter isCanvasToTempFilePath pathType="url" @success="setPainterImageUrl" hidden ref="painterRef" />
|
||||
</su-popup>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
/**
|
||||
* 海报生成和展示
|
||||
* 提示:小程序码默认跳转首页,由首页进行 spm 参数解析后跳转到对应的分享页面
|
||||
* @description 用于生成分享海报,如:分享商品海报。
|
||||
* @tutorial https://ext.dcloud.net.cn/plugin?id=2389
|
||||
* @property {Boolean} show 弹出层控制
|
||||
* @property {Object} shareInfo 分享信息
|
||||
*/
|
||||
import { reactive, ref, unref } from 'vue';
|
||||
import sheep from '@/sheep';
|
||||
import { getPosterData } from '@/sheep/components/s-share-modal/canvas-poster/poster';
|
||||
/**
|
||||
* 海报生成和展示
|
||||
* 提示:小程序码默认跳转首页,由首页进行 spm 参数解析后跳转到对应的分享页面
|
||||
* @description 用于生成分享海报,如:分享商品海报。
|
||||
* @tutorial https://ext.dcloud.net.cn/plugin?id=2389
|
||||
* @property {Boolean} show 弹出层控制
|
||||
* @property {Object} shareInfo 分享信息
|
||||
*/
|
||||
import { reactive, ref, unref } from 'vue';
|
||||
import sheep from '@/sheep';
|
||||
import { getPosterData } from '@/sheep/components/s-share-modal/canvas-poster/poster';
|
||||
|
||||
const props = defineProps({
|
||||
show: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
const props = defineProps({
|
||||
show: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
shareInfo: {
|
||||
type: Object,
|
||||
default: () => {
|
||||
},
|
||||
shareInfo: {
|
||||
type: Object,
|
||||
default: () => {
|
||||
},
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
const poster = reactive({
|
||||
css: {
|
||||
// 根节点若无尺寸,自动获取父级节点
|
||||
width: sheep.$platform.device.windowWidth * 0.9,
|
||||
height: 600,
|
||||
},
|
||||
views: [],
|
||||
});
|
||||
const poster = reactive({
|
||||
css: {
|
||||
// 根节点若无尺寸,自动获取父级节点
|
||||
width: sheep.$platform.device.windowWidth * 0.9,
|
||||
height: 600,
|
||||
},
|
||||
views: [],
|
||||
});
|
||||
|
||||
const emits = defineEmits(['success', 'close']);
|
||||
const emits = defineEmits(['success', 'close']);
|
||||
|
||||
const onClosePoster = () => {
|
||||
emits('close');
|
||||
};
|
||||
const onClosePoster = () => {
|
||||
emits('close');
|
||||
};
|
||||
|
||||
const painterRef = ref(); // 海报画板
|
||||
const painterImageUrl = ref(); // 海报 url
|
||||
// 渲染海报
|
||||
const renderPoster = async () => {
|
||||
await painterRef.value.render(unref(poster));
|
||||
};
|
||||
// 获得生成的图片
|
||||
const setPainterImageUrl = (path) => {
|
||||
painterImageUrl.value = path;
|
||||
};
|
||||
// 保存海报图片
|
||||
const onSavePoster = () => {
|
||||
if (['WechatOfficialAccount', 'H5'].includes(sheep.$platform.name)) {
|
||||
sheep.$helper.toast('请长按图片保存');
|
||||
return;
|
||||
}
|
||||
|
||||
// 非H5 保存到相册
|
||||
uni.saveImageToPhotosAlbum({
|
||||
filePath: painterImageUrl.value,
|
||||
success: (res) => {
|
||||
onClosePoster();
|
||||
sheep.$helper.toast('保存成功');
|
||||
},
|
||||
fail: (err) => {
|
||||
sheep.$helper.toast('保存失败');
|
||||
console.log('图片保存失败:', err);
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
// 获得海报数据
|
||||
async function getPoster() {
|
||||
painterImageUrl.value = undefined
|
||||
poster.views = await getPosterData({
|
||||
width: poster.css.width,
|
||||
shareInfo: props.shareInfo,
|
||||
});
|
||||
await renderPoster();
|
||||
const painterRef = ref(); // 海报画板
|
||||
const painterImageUrl = ref(); // 海报 url
|
||||
// 渲染海报
|
||||
const renderPoster = async () => {
|
||||
await painterRef.value.render(unref(poster));
|
||||
};
|
||||
// 获得生成的图片
|
||||
const setPainterImageUrl = (path) => {
|
||||
painterImageUrl.value = path;
|
||||
};
|
||||
// 保存海报图片
|
||||
const onSavePoster = () => {
|
||||
if (['WechatOfficialAccount', 'H5'].includes(sheep.$platform.name)) {
|
||||
sheep.$helper.toast('请长按图片保存');
|
||||
return;
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
getPoster,
|
||||
// 非H5 保存到相册
|
||||
uni.saveImageToPhotosAlbum({
|
||||
filePath: painterImageUrl.value,
|
||||
success: (res) => {
|
||||
onClosePoster();
|
||||
sheep.$helper.toast('保存成功');
|
||||
},
|
||||
fail: (err) => {
|
||||
sheep.$helper.toast('保存失败');
|
||||
console.log('图片保存失败:', err);
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
// 获得海报数据
|
||||
async function getPoster() {
|
||||
painterImageUrl.value = undefined
|
||||
poster.views = await getPosterData({
|
||||
width: poster.css.width,
|
||||
shareInfo: props.shareInfo,
|
||||
});
|
||||
await renderPoster();
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
getPoster,
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.popup-box {
|
||||
position: relative;
|
||||
.popup-box {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.poster-title {
|
||||
color: #999;
|
||||
}
|
||||
|
||||
// 分享海报
|
||||
.poster-btn-box {
|
||||
width: 600rpx;
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
bottom: -80rpx;
|
||||
|
||||
.cancel-btn {
|
||||
width: 240rpx;
|
||||
height: 70rpx;
|
||||
line-height: 70rpx;
|
||||
background: $white;
|
||||
border-radius: 35rpx;
|
||||
font-size: 28rpx;
|
||||
font-weight: 500;
|
||||
color: $dark-9;
|
||||
}
|
||||
|
||||
.poster-title {
|
||||
color: #999;
|
||||
}
|
||||
|
||||
// 分享海报
|
||||
.poster-btn-box {
|
||||
width: 600rpx;
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
bottom: -80rpx;
|
||||
|
||||
.cancel-btn {
|
||||
width: 240rpx;
|
||||
height: 70rpx;
|
||||
line-height: 70rpx;
|
||||
background: $white;
|
||||
border-radius: 35rpx;
|
||||
font-size: 28rpx;
|
||||
font-weight: 500;
|
||||
color: $dark-9;
|
||||
}
|
||||
|
||||
.save-btn {
|
||||
width: 240rpx;
|
||||
height: 70rpx;
|
||||
line-height: 70rpx;
|
||||
border-radius: 35rpx;
|
||||
font-size: 28rpx;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
|
||||
.poster-img {
|
||||
border-radius: 20rpx;
|
||||
.save-btn {
|
||||
width: 240rpx;
|
||||
height: 70rpx;
|
||||
line-height: 70rpx;
|
||||
border-radius: 35rpx;
|
||||
font-size: 28rpx;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
|
||||
.poster-img {
|
||||
border-radius: 20rpx;
|
||||
}
|
||||
</style>
|
||||
@@ -1,7 +1,6 @@
|
||||
'use strict';
|
||||
import FileApi from '@/sheep/api/infra/file';
|
||||
|
||||
const ERR_MSG_OK = 'chooseAndUploadFile:ok';
|
||||
const ERR_MSG_FAIL = 'chooseAndUploadFile:fail';
|
||||
|
||||
function chooseImage(opts) {
|
||||
@@ -30,8 +29,55 @@ function chooseImage(opts) {
|
||||
}
|
||||
|
||||
function chooseVideo(opts) {
|
||||
const { camera, compressed, maxDuration, sourceType = ['album', 'camera'], extension } = opts;
|
||||
const {
|
||||
count,
|
||||
camera,
|
||||
compressed,
|
||||
maxDuration,
|
||||
sourceType = ['album', 'camera'],
|
||||
extension,
|
||||
} = opts;
|
||||
return new Promise((resolve, reject) => {
|
||||
// 微信小程序的 chooseMedia 会直接返回视频缩略图。
|
||||
// #ifdef MP-WEIXIN
|
||||
uni.chooseMedia({
|
||||
count,
|
||||
mediaType: ['video'],
|
||||
sourceType,
|
||||
sizeType: compressed ? ['compressed'] : ['original'],
|
||||
maxDuration,
|
||||
camera,
|
||||
success(res) {
|
||||
resolve(
|
||||
normalizeChooseAndUploadFileRes(
|
||||
{
|
||||
errMsg: 'chooseVideo:ok',
|
||||
tempFiles: res.tempFiles.map((item) => ({
|
||||
name: item.name || '',
|
||||
path: item.tempFilePath,
|
||||
thumbTempFilePath: item.thumbTempFilePath || '',
|
||||
size: item.size,
|
||||
type: item.type || '',
|
||||
width: item.width,
|
||||
height: item.height,
|
||||
duration: item.duration,
|
||||
fileType: 'video',
|
||||
cloudPath: '',
|
||||
})),
|
||||
},
|
||||
'video',
|
||||
),
|
||||
);
|
||||
},
|
||||
fail(res) {
|
||||
reject({
|
||||
errMsg: res.errMsg.replace('chooseMedia:fail', ERR_MSG_FAIL),
|
||||
});
|
||||
},
|
||||
});
|
||||
// #endif
|
||||
|
||||
// #ifndef MP-WEIXIN
|
||||
uni.chooseVideo({
|
||||
camera,
|
||||
compressed,
|
||||
@@ -39,7 +85,7 @@ function chooseVideo(opts) {
|
||||
sourceType,
|
||||
extension,
|
||||
success(res) {
|
||||
const { tempFilePath, duration, size, height, width } = res;
|
||||
const { tempFilePath, thumbTempFilePath, duration, size, height, width } = res;
|
||||
resolve(
|
||||
normalizeChooseAndUploadFileRes(
|
||||
{
|
||||
@@ -49,6 +95,7 @@ function chooseVideo(opts) {
|
||||
{
|
||||
name: (res.tempFile && res.tempFile.name) || '',
|
||||
path: tempFilePath,
|
||||
thumbTempFilePath: thumbTempFilePath || '',
|
||||
size,
|
||||
type: (res.tempFile && res.tempFile.type) || '',
|
||||
width,
|
||||
@@ -69,6 +116,7 @@ function chooseVideo(opts) {
|
||||
});
|
||||
},
|
||||
});
|
||||
// #endif
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -7,15 +7,9 @@
|
||||
<view v-if="list.length > 0" class="uni-file-picker__lists is-text-box" :style="borderStyle">
|
||||
<!-- ,'is-list-card':showType === 'list-card' -->
|
||||
|
||||
<view
|
||||
class="uni-file-picker__lists-box"
|
||||
v-for="(item, index) in list"
|
||||
:key="index"
|
||||
:class="{
|
||||
'files-border': index !== 0 && styles.dividline,
|
||||
}"
|
||||
:style="index !== 0 && styles.dividline && borderLineStyle"
|
||||
>
|
||||
<view class="uni-file-picker__lists-box" v-for="(item, index) in list" :key="index" :class="{
|
||||
'files-border': index !== 0 && styles.dividline,
|
||||
}" :style="index !== 0 && styles.dividline && borderLineStyle">
|
||||
<view class="uni-file-picker__item">
|
||||
<!-- :class="{'is-text-image':showType === 'list'}" -->
|
||||
<!-- <view class="files__image is-text-image">
|
||||
@@ -27,22 +21,11 @@
|
||||
<view class="icon-del rotate"></view>
|
||||
</view>
|
||||
</view>
|
||||
<view
|
||||
v-if="(item.progress && item.progress !== 100) || item.progress === 0"
|
||||
class="file-picker__progress"
|
||||
>
|
||||
<progress
|
||||
class="file-picker__progress-item"
|
||||
:percent="item.progress === -1 ? 0 : item.progress"
|
||||
stroke-width="4"
|
||||
:backgroundColor="item.errMsg ? '#ff5a5f' : '#EBEBEB'"
|
||||
/>
|
||||
<view v-if="(item.progress && item.progress !== 100) || item.progress === 0" class="file-picker__progress">
|
||||
<progress class="file-picker__progress-item" :percent="item.progress === -1 ? 0 : item.progress"
|
||||
stroke-width="4" :backgroundColor="item.errMsg ? '#ff5a5f' : '#EBEBEB'" />
|
||||
</view>
|
||||
<view
|
||||
v-if="item.status === 'error'"
|
||||
class="file-picker__mask"
|
||||
@click.stop="uploadFiles(item, index)"
|
||||
>
|
||||
<view v-if="item.status === 'error'" class="file-picker__mask" @click.stop="uploadFiles(item, index)">
|
||||
点击重试
|
||||
</view>
|
||||
</view>
|
||||
@@ -51,285 +34,285 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'uploadFile',
|
||||
emits: ['uploadFiles', 'choose', 'delFile'],
|
||||
props: {
|
||||
filesList: {
|
||||
type: Array,
|
||||
default() {
|
||||
return [];
|
||||
},
|
||||
},
|
||||
delIcon: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
limit: {
|
||||
type: [Number, String],
|
||||
default: 9,
|
||||
},
|
||||
showType: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
listStyles: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {
|
||||
// 是否显示边框
|
||||
border: true,
|
||||
// 是否显示分隔线
|
||||
dividline: true,
|
||||
// 线条样式
|
||||
borderStyle: {},
|
||||
};
|
||||
},
|
||||
},
|
||||
readonly: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
export default {
|
||||
name: 'uploadFile',
|
||||
emits: ['uploadFiles', 'choose', 'delFile'],
|
||||
props: {
|
||||
filesList: {
|
||||
type: Array,
|
||||
default() {
|
||||
return [];
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
list() {
|
||||
let files = [];
|
||||
this.filesList.forEach((v) => {
|
||||
files.push(v);
|
||||
});
|
||||
return files;
|
||||
},
|
||||
styles() {
|
||||
let styles = {
|
||||
delIcon: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
limit: {
|
||||
type: [Number, String],
|
||||
default: 9,
|
||||
},
|
||||
showType: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
listStyles: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {
|
||||
// 是否显示边框
|
||||
border: true,
|
||||
// 是否显示分隔线
|
||||
dividline: true,
|
||||
'border-style': {},
|
||||
// 线条样式
|
||||
borderStyle: {},
|
||||
};
|
||||
return Object.assign(styles, this.listStyles);
|
||||
},
|
||||
borderStyle() {
|
||||
let { borderStyle, border } = this.styles;
|
||||
let obj = {};
|
||||
if (!border) {
|
||||
obj.border = 'none';
|
||||
} else {
|
||||
let width = (borderStyle && borderStyle.width) || 1;
|
||||
width = this.value2px(width);
|
||||
let radius = (borderStyle && borderStyle.radius) || 5;
|
||||
radius = this.value2px(radius);
|
||||
obj = {
|
||||
'border-width': width,
|
||||
'border-style': (borderStyle && borderStyle.style) || 'solid',
|
||||
'border-color': (borderStyle && borderStyle.color) || '#eee',
|
||||
'border-radius': radius,
|
||||
};
|
||||
}
|
||||
let classles = '';
|
||||
for (let i in obj) {
|
||||
classles += `${i}:${obj[i]};`;
|
||||
}
|
||||
return classles;
|
||||
},
|
||||
borderLineStyle() {
|
||||
let obj = {};
|
||||
let { borderStyle } = this.styles;
|
||||
if (borderStyle && borderStyle.color) {
|
||||
obj['border-color'] = borderStyle.color;
|
||||
}
|
||||
if (borderStyle && borderStyle.width) {
|
||||
let width = (borderStyle && borderStyle.width) || 1;
|
||||
let style = (borderStyle && borderStyle.style) || 0;
|
||||
if (typeof width === 'number') {
|
||||
width += 'px';
|
||||
} else {
|
||||
width = width.indexOf('px') ? width : width + 'px';
|
||||
}
|
||||
obj['border-width'] = width;
|
||||
|
||||
if (typeof style === 'number') {
|
||||
style += 'px';
|
||||
} else {
|
||||
style = style.indexOf('px') ? style : style + 'px';
|
||||
}
|
||||
obj['border-top-style'] = style;
|
||||
}
|
||||
let classles = '';
|
||||
for (let i in obj) {
|
||||
classles += `${i}:${obj[i]};`;
|
||||
}
|
||||
return classles;
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
uploadFiles(item, index) {
|
||||
this.$emit('uploadFiles', {
|
||||
item,
|
||||
index,
|
||||
});
|
||||
},
|
||||
choose() {
|
||||
this.$emit('choose');
|
||||
},
|
||||
delFile(index) {
|
||||
this.$emit('delFile', index);
|
||||
},
|
||||
value2px(value) {
|
||||
if (typeof value === 'number') {
|
||||
value += 'px';
|
||||
} else {
|
||||
value = value.indexOf('px') !== -1 ? value : value + 'px';
|
||||
}
|
||||
return value;
|
||||
},
|
||||
readonly: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
list() {
|
||||
let files = [];
|
||||
this.filesList.forEach((v) => {
|
||||
files.push(v);
|
||||
});
|
||||
return files;
|
||||
},
|
||||
styles() {
|
||||
let styles = {
|
||||
border: true,
|
||||
dividline: true,
|
||||
'border-style': {},
|
||||
};
|
||||
return Object.assign(styles, this.listStyles);
|
||||
},
|
||||
borderStyle() {
|
||||
let { borderStyle, border } = this.styles;
|
||||
let obj = {};
|
||||
if (!border) {
|
||||
obj.border = 'none';
|
||||
} else {
|
||||
let width = (borderStyle && borderStyle.width) || 1;
|
||||
width = this.value2px(width);
|
||||
let radius = (borderStyle && borderStyle.radius) || 5;
|
||||
radius = this.value2px(radius);
|
||||
obj = {
|
||||
'border-width': width,
|
||||
'border-style': (borderStyle && borderStyle.style) || 'solid',
|
||||
'border-color': (borderStyle && borderStyle.color) || '#eee',
|
||||
'border-radius': radius,
|
||||
};
|
||||
}
|
||||
let classles = '';
|
||||
for (let i in obj) {
|
||||
classles += `${i}:${obj[i]};`;
|
||||
}
|
||||
return classles;
|
||||
},
|
||||
borderLineStyle() {
|
||||
let obj = {};
|
||||
let { borderStyle } = this.styles;
|
||||
if (borderStyle && borderStyle.color) {
|
||||
obj['border-color'] = borderStyle.color;
|
||||
}
|
||||
if (borderStyle && borderStyle.width) {
|
||||
let width = (borderStyle && borderStyle.width) || 1;
|
||||
let style = (borderStyle && borderStyle.style) || 0;
|
||||
if (typeof width === 'number') {
|
||||
width += 'px';
|
||||
} else {
|
||||
width = width.indexOf('px') ? width : width + 'px';
|
||||
}
|
||||
obj['border-width'] = width;
|
||||
|
||||
if (typeof style === 'number') {
|
||||
style += 'px';
|
||||
} else {
|
||||
style = style.indexOf('px') ? style : style + 'px';
|
||||
}
|
||||
obj['border-top-style'] = style;
|
||||
}
|
||||
let classles = '';
|
||||
for (let i in obj) {
|
||||
classles += `${i}:${obj[i]};`;
|
||||
}
|
||||
return classles;
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
uploadFiles(item, index) {
|
||||
this.$emit('uploadFiles', {
|
||||
item,
|
||||
index,
|
||||
});
|
||||
},
|
||||
choose() {
|
||||
this.$emit('choose');
|
||||
},
|
||||
delFile(index) {
|
||||
this.$emit('delFile', index);
|
||||
},
|
||||
value2px(value) {
|
||||
if (typeof value === 'number') {
|
||||
value += 'px';
|
||||
} else {
|
||||
value = value.indexOf('px') !== -1 ? value : value + 'px';
|
||||
}
|
||||
return value;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.uni-file-picker__files {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.files-button {
|
||||
// border: 1px red solid;
|
||||
}
|
||||
|
||||
.uni-file-picker__lists {
|
||||
position: relative;
|
||||
margin-top: 5px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.file-picker__mask {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
color: #fff;
|
||||
font-size: 14px;
|
||||
background-color: rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
|
||||
.uni-file-picker__lists-box {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.uni-file-picker__item {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
align-items: center;
|
||||
padding: 8px 10px;
|
||||
padding-right: 5px;
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
.files-border {
|
||||
border-top: 1px #eee solid;
|
||||
}
|
||||
|
||||
.files__name {
|
||||
flex: 1;
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
margin-right: 25px;
|
||||
/* #ifndef APP-NVUE */
|
||||
word-break: break-all;
|
||||
word-wrap: break-word;
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
.icon-files {
|
||||
/* #ifndef APP-NVUE */
|
||||
position: static;
|
||||
background-color: initial;
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
// .icon-files .icon-del {
|
||||
// background-color: #333;
|
||||
// width: 12px;
|
||||
// height: 1px;
|
||||
// }
|
||||
|
||||
.is-list-card {
|
||||
border: 1px #eee solid;
|
||||
margin-bottom: 5px;
|
||||
border-radius: 5px;
|
||||
box-shadow: 0 0 2px 0px rgba(0, 0, 0, 0.1);
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.files__image {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.header-image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.is-text-box {
|
||||
border: 1px #eee solid;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.is-text-image {
|
||||
width: 25px;
|
||||
height: 25px;
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
.rotate {
|
||||
position: absolute;
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
|
||||
.icon-del-box {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
margin: auto 0;
|
||||
/* #endif */
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
bottom: 0;
|
||||
right: 5px;
|
||||
height: 26px;
|
||||
width: 26px;
|
||||
// border-radius: 50%;
|
||||
// background-color: rgba(0, 0, 0, 0.5);
|
||||
z-index: 2;
|
||||
transform: rotate(-45deg);
|
||||
}
|
||||
|
||||
.icon-del {
|
||||
width: 15px;
|
||||
height: 1px;
|
||||
background-color: #333;
|
||||
// border-radius: 1px;
|
||||
}
|
||||
|
||||
/* #ifdef H5 */
|
||||
@media all and (min-width: 768px) {
|
||||
.uni-file-picker__files {
|
||||
max-width: 375px;
|
||||
}
|
||||
}
|
||||
|
||||
.uni-file-picker__files {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.files-button {
|
||||
// border: 1px red solid;
|
||||
}
|
||||
|
||||
.uni-file-picker__lists {
|
||||
position: relative;
|
||||
margin-top: 5px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.file-picker__mask {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
color: #fff;
|
||||
font-size: 14px;
|
||||
background-color: rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
|
||||
.uni-file-picker__lists-box {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.uni-file-picker__item {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
align-items: center;
|
||||
padding: 8px 10px;
|
||||
padding-right: 5px;
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
.files-border {
|
||||
border-top: 1px #eee solid;
|
||||
}
|
||||
|
||||
.files__name {
|
||||
flex: 1;
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
margin-right: 25px;
|
||||
/* #ifndef APP-NVUE */
|
||||
word-break: break-all;
|
||||
word-wrap: break-word;
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
.icon-files {
|
||||
/* #ifndef APP-NVUE */
|
||||
position: static;
|
||||
background-color: initial;
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
// .icon-files .icon-del {
|
||||
// background-color: #333;
|
||||
// width: 12px;
|
||||
// height: 1px;
|
||||
// }
|
||||
|
||||
.is-list-card {
|
||||
border: 1px #eee solid;
|
||||
margin-bottom: 5px;
|
||||
border-radius: 5px;
|
||||
box-shadow: 0 0 2px 0px rgba(0, 0, 0, 0.1);
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.files__image {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.header-image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.is-text-box {
|
||||
border: 1px #eee solid;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.is-text-image {
|
||||
width: 25px;
|
||||
height: 25px;
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
.rotate {
|
||||
position: absolute;
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
|
||||
.icon-del-box {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
margin: auto 0;
|
||||
/* #endif */
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
bottom: 0;
|
||||
right: 5px;
|
||||
height: 26px;
|
||||
width: 26px;
|
||||
// border-radius: 50%;
|
||||
// background-color: rgba(0, 0, 0, 0.5);
|
||||
z-index: 2;
|
||||
transform: rotate(-45deg);
|
||||
}
|
||||
|
||||
.icon-del {
|
||||
width: 15px;
|
||||
height: 1px;
|
||||
background-color: #333;
|
||||
// border-radius: 1px;
|
||||
}
|
||||
|
||||
/* #ifdef H5 */
|
||||
@media all and (min-width: 768px) {
|
||||
.uni-file-picker__files {
|
||||
max-width: 375px;
|
||||
}
|
||||
}
|
||||
|
||||
/* #endif */
|
||||
</style>
|
||||
@@ -1,13 +1,28 @@
|
||||
<template>
|
||||
<view class="uni-file-picker__container">
|
||||
<view class="file-picker__box" v-for="(url, index) in list" :key="index" :style="boxStyle">
|
||||
<view class="file-picker__box" v-for="(item, index) in list" :key="index" :style="boxStyle">
|
||||
<view class="file-picker__box-content" :style="borderStyle">
|
||||
<image
|
||||
v-if="fileMediatype === 'image' || getVideoThumb(item)"
|
||||
class="file-image"
|
||||
:src="getImageUrl(url)"
|
||||
:src="getImageUrl(fileMediatype === 'video' ? getVideoThumb(item) : getFileUrl(item))"
|
||||
mode="aspectFill"
|
||||
@click.stop="previewImage(url, index)"
|
||||
@click.stop="previewFile(item, index)"
|
||||
></image>
|
||||
<!-- #ifndef APP-PLUS -->
|
||||
<video v-else class="file-video" :src="getFileUrl(item)" object-fit="cover" :controls="false"
|
||||
:autoplay="false" :show-center-play-btn="false" :show-fullscreen-btn="false" :show-play-btn="false"
|
||||
:show-loading="false" :show-progress="false" :enable-progress-gesture="false"
|
||||
:initial-time="0.1" :muted="true"
|
||||
@click.stop="previewFile(item, index)"></video>
|
||||
<!-- #endif -->
|
||||
<!-- #ifdef APP-PLUS -->
|
||||
<dom-video v-else class="file-video" :src="getFileUrl(item)" object-fit="cover" :controls="false"
|
||||
:autoplay="false"></dom-video>
|
||||
<!-- #endif -->
|
||||
<view v-if="fileMediatype === 'video'" class="video-play" @click.stop="previewFile(item, index)">
|
||||
<view class="video-play__triangle"></view>
|
||||
</view>
|
||||
<view v-if="delIcon && !readonly" class="icon-del-box" @click.stop="delFile(index)">
|
||||
<view class="icon-del"></view>
|
||||
<view class="icon-del rotate"></view>
|
||||
@@ -26,12 +41,23 @@
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<!-- #ifndef MP-WEIXIN -->
|
||||
<wd-video-preview v-if="fileMediatype === 'video'" ref="videoPreview"></wd-video-preview>
|
||||
<!-- #endif -->
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import sheep from '@/sheep';
|
||||
// #ifdef APP-PLUS
|
||||
import domVideo from '@/sheep/ui/su-video/components/dom-video.vue';
|
||||
// #endif
|
||||
export default {
|
||||
name: 'uploadImage',
|
||||
components: {
|
||||
// #ifdef APP-PLUS
|
||||
domVideo,
|
||||
// #endif
|
||||
},
|
||||
emits: ['uploadFiles', 'choose', 'delFile'],
|
||||
props: {
|
||||
filesList: {
|
||||
@@ -70,6 +96,10 @@
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
fileMediatype: {
|
||||
type: String,
|
||||
default: 'image',
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
list() {
|
||||
@@ -148,8 +178,17 @@
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
getFileUrl(file) {
|
||||
if (typeof file === 'string') return file;
|
||||
return file.url || file.path || '';
|
||||
},
|
||||
getVideoThumb(file) {
|
||||
if (!file || typeof file === 'string') return '';
|
||||
return file.thumb || file.thumbTempFilePath || (file.video && file.video.thumb) || '';
|
||||
},
|
||||
getImageUrl(url) {
|
||||
if ('blob:http:' === url.substr(0, 10)) {
|
||||
if (!url) return '';
|
||||
if (/^(blob:|data:|wxfile:|file:|https?:|_doc\/|_downloads\/|\/(tmp|var|private|storage)\/)/.test(url)) {
|
||||
return url;
|
||||
} else {
|
||||
return sheep.$url.cdn(url);
|
||||
@@ -171,7 +210,7 @@
|
||||
}
|
||||
if (this.disablePreview) return;
|
||||
this.list.forEach((i) => {
|
||||
urls.push(this.getImageUrl(i));
|
||||
urls.push(this.getImageUrl(this.getFileUrl(i)));
|
||||
});
|
||||
|
||||
uni.previewImage({
|
||||
@@ -179,6 +218,28 @@
|
||||
current: index,
|
||||
});
|
||||
},
|
||||
previewFile(file, index) {
|
||||
if (this.fileMediatype === 'image') {
|
||||
this.previewImage(this.getFileUrl(file), index);
|
||||
return;
|
||||
}
|
||||
|
||||
const videoUrl = this.getFileUrl(file);
|
||||
if (!videoUrl) return;
|
||||
// #ifdef MP-WEIXIN
|
||||
uni.previewMedia({
|
||||
current: 0,
|
||||
sources: [{ url: videoUrl, type: 'video', poster: this.getVideoThumb(file) }],
|
||||
});
|
||||
// #endif
|
||||
// #ifndef MP-WEIXIN
|
||||
this.$refs.videoPreview?.open({
|
||||
url: videoUrl,
|
||||
poster: this.getVideoThumb(file),
|
||||
title: file.name || '视频',
|
||||
});
|
||||
// #endif
|
||||
},
|
||||
value2px(value) {
|
||||
if (typeof value === 'number') {
|
||||
value += 'px';
|
||||
@@ -260,6 +321,36 @@
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.file-video {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.video-play {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
z-index: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 56rpx;
|
||||
height: 56rpx;
|
||||
border: 4rpx solid #fff;
|
||||
border-radius: 50%;
|
||||
background-color: rgba(0, 0, 0, 0.35);
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
|
||||
.video-play__triangle {
|
||||
width: 0;
|
||||
height: 0;
|
||||
margin-left: 4rpx;
|
||||
border-top: 12rpx solid transparent;
|
||||
border-bottom: 12rpx solid transparent;
|
||||
border-left: 18rpx solid #fff;
|
||||
}
|
||||
|
||||
.is-add {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
|
||||
@@ -94,7 +94,12 @@ export const get_file_data = async (files, type = 'image') => {
|
||||
size: files.size, //单位是字节
|
||||
image: {},
|
||||
path: files.path,
|
||||
video: {},
|
||||
video: {
|
||||
width: files.width,
|
||||
height: files.height,
|
||||
duration: files.duration,
|
||||
thumb: files.thumbTempFilePath || files.thumb || '',
|
||||
},
|
||||
};
|
||||
if (type === 'image') {
|
||||
const imageinfo = await get_file_info(files.path);
|
||||
|
||||
@@ -18,13 +18,13 @@
|
||||
<text class="py-4rpx px-2 rounded-full text-22rpx bg-color-theme-dark whitespace-nowrap">
|
||||
{{
|
||||
member.appRefreshDate
|
||||
? formatDate(member.appRefreshDate, 'MM-DD HH:mm前') + ' 在线'
|
||||
? formatRelativeTime(member.appRefreshDate) + ' 在线'
|
||||
: '离线'
|
||||
}}
|
||||
</text>
|
||||
</view>
|
||||
<view v-for="(item, index) in data.memberTagList" :key="index">
|
||||
<view v-if="['1'].includes(item.tagBizType)"
|
||||
<view v-if="tagVisibleTypeList.includes(item.tagBizType)"
|
||||
class="mt-2 inline-flex px-1 py-6rpx text-18rpx c-black rounded-4rpx bg-color-theme-dark mr-2">
|
||||
<wd-icon name="discount-filled" size="10px" color="#000" />
|
||||
<text>{{ item.tagTitle }}</text>
|
||||
@@ -61,7 +61,8 @@ import { computed } from 'vue';
|
||||
// @ts-ignore
|
||||
import { getGender } from '@/sheep/util/index';
|
||||
// @ts-ignore
|
||||
import { formatDate } from '@/sheep/util/index';
|
||||
import sheep from '@/sheep';
|
||||
import { formatDate, formatRelativeTime } from '@/sheep/util/index';
|
||||
|
||||
const props = withDefaults(defineProps<{ data: any; backdropFilter?: boolean }>(), {
|
||||
backdropFilter: false,
|
||||
@@ -70,6 +71,18 @@ const emit = defineEmits(['click']);
|
||||
|
||||
const member = computed(() => props.data.member || {});
|
||||
|
||||
const userInfo = computed(() => sheep.$store('user').userInfo);
|
||||
|
||||
// 只有男性用户才显示VIP标签
|
||||
const tagVisibleTypeList = computed(() => {
|
||||
const list = ['1'];
|
||||
const sex = userInfo.value?.sex ?? null;
|
||||
if (sex == 1) {
|
||||
list.push('4');
|
||||
}
|
||||
return list;
|
||||
});
|
||||
|
||||
const filterStyle = computed(() => {
|
||||
return props.backdropFilter ? { backdropFilter: 'blur(10px)' } : {};
|
||||
});
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
const H5_STATUS_BAR_STORAGE_KEY = '__friendship_h5_status_bar_height__';
|
||||
const H5_STATUS_BAR_QUERY_KEYS = ['statusBarHeight', 'safeTop', 'safeAreaTop', 'appStatusBarHeight'];
|
||||
const H5_MIN_STATUS_BAR_HEIGHT = 22;
|
||||
const H5_MAX_STATUS_BAR_HEIGHT = 200;
|
||||
let currentStatusBarHeight = H5_MIN_STATUS_BAR_HEIGHT;
|
||||
let statusBarStyleObserver = null;
|
||||
|
||||
// 将 URL 参数、宿主全局变量和 postMessage 消息中的高度统一转换为数字。
|
||||
function normalizeStatusBarHeight(value) {
|
||||
if (value === undefined || value === null || value === '') {
|
||||
return null;
|
||||
@@ -11,7 +14,13 @@ function normalizeStatusBarHeight(value) {
|
||||
if (!Number.isFinite(height)) {
|
||||
return null;
|
||||
}
|
||||
return Math.max(0, Math.min(height, 200));
|
||||
return Math.max(0, Math.min(height, H5_MAX_STATUS_BAR_HEIGHT));
|
||||
}
|
||||
|
||||
// 宿主未提供高度或提供的高度过小时,统一使用 H5 最小状态栏高度。
|
||||
function resolveStatusBarHeight(value) {
|
||||
const height = normalizeStatusBarHeight(value);
|
||||
return height === null ? H5_MIN_STATUS_BAR_HEIGHT : Math.max(height, H5_MIN_STATUS_BAR_HEIGHT);
|
||||
}
|
||||
|
||||
function parseMessageData(data) {
|
||||
@@ -28,6 +37,7 @@ function parseMessageData(data) {
|
||||
return data;
|
||||
}
|
||||
|
||||
// URL 参数优先级最高,同时兼容普通查询参数和 hash 查询参数。
|
||||
function readStatusBarHeightFromQuery() {
|
||||
const searchParamsList = [];
|
||||
searchParamsList.push(new URLSearchParams(window.location.search));
|
||||
@@ -48,6 +58,7 @@ function readStatusBarHeightFromQuery() {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 宿主可以在 H5 应用初始化前将状态栏高度注入 window。
|
||||
function readStatusBarHeightFromWindow() {
|
||||
const candidates = [
|
||||
window.__APP_STATUS_BAR_HEIGHT__,
|
||||
@@ -64,76 +75,65 @@ function readStatusBarHeightFromWindow() {
|
||||
return null;
|
||||
}
|
||||
|
||||
function readStatusBarHeightFromStorage() {
|
||||
try {
|
||||
return normalizeStatusBarHeight(window.localStorage.getItem(H5_STATUS_BAR_STORAGE_KEY));
|
||||
} catch (error) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function readNativeStatusBarHeight() {
|
||||
if (typeof window === 'undefined') {
|
||||
// 读取页面当前实际使用的 --status-bar-height。
|
||||
export function getH5StatusBarHeight() {
|
||||
if (typeof window === 'undefined' || typeof document === 'undefined') {
|
||||
return 0;
|
||||
}
|
||||
const rootStyle = window.getComputedStyle(document.documentElement);
|
||||
return normalizeStatusBarHeight(rootStyle.getPropertyValue('--native-status-bar-height')) ?? 0;
|
||||
}
|
||||
|
||||
function ensureMinShellStatusBarHeight(height) {
|
||||
if (height === null || height <= 0) {
|
||||
return 0;
|
||||
}
|
||||
return Math.max(height, H5_MIN_STATUS_BAR_HEIGHT);
|
||||
const statusBarHeight = window
|
||||
.getComputedStyle(document.documentElement)
|
||||
.getPropertyValue('--status-bar-height')
|
||||
.replace(/px$/i, '')
|
||||
.trim();
|
||||
return resolveStatusBarHeight(statusBarHeight);
|
||||
}
|
||||
|
||||
// 依次从 URL、宿主全局变量中获取初始高度,均未提供时使用默认最小高度。
|
||||
export function getH5HostStatusBarHeight() {
|
||||
if (typeof window === 'undefined') {
|
||||
return 0;
|
||||
}
|
||||
return ensureMinShellStatusBarHeight(
|
||||
readStatusBarHeightFromQuery() ??
|
||||
readStatusBarHeightFromWindow() ??
|
||||
readStatusBarHeightFromStorage() ??
|
||||
0,
|
||||
);
|
||||
return resolveStatusBarHeight(readStatusBarHeightFromQuery() ?? readStatusBarHeightFromWindow());
|
||||
}
|
||||
|
||||
// 只在 html 上设置一个状态栏变量,body 和其他子元素会自动继承。
|
||||
export function applyH5HostStatusBarHeight(height) {
|
||||
if (typeof window === 'undefined') {
|
||||
if (typeof window === 'undefined' || typeof document === 'undefined') {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const safeTop = ensureMinShellStatusBarHeight(normalizeStatusBarHeight(height));
|
||||
const nativeSafeTop = readNativeStatusBarHeight();
|
||||
const finalSafeTop = Math.max(nativeSafeTop, safeTop);
|
||||
const safeTopValue = `${safeTop}px`;
|
||||
const finalSafeTopValue = `${finalSafeTop}px`;
|
||||
const rootStyle = document.documentElement.style;
|
||||
const bodyStyle = document.body?.style;
|
||||
|
||||
rootStyle.setProperty('--app-shell-status-bar-height', safeTopValue);
|
||||
rootStyle.setProperty('--status-bar-height', finalSafeTopValue);
|
||||
bodyStyle?.setProperty('--app-shell-status-bar-height', safeTopValue);
|
||||
bodyStyle?.setProperty('--status-bar-height', finalSafeTopValue);
|
||||
|
||||
window.__APP_STATUS_BAR_HEIGHT__ = safeTop;
|
||||
|
||||
try {
|
||||
window.localStorage.setItem(H5_STATUS_BAR_STORAGE_KEY, String(safeTop));
|
||||
} catch (error) {
|
||||
// ignore
|
||||
}
|
||||
|
||||
const statusBarHeight = resolveStatusBarHeight(height);
|
||||
currentStatusBarHeight = statusBarHeight;
|
||||
document.documentElement.style.setProperty('--status-bar-height', `${statusBarHeight}px`, 'important');
|
||||
// 通知使用数值高度参与 JavaScript 布局计算的页面重新计算。
|
||||
window.dispatchEvent(
|
||||
new CustomEvent('app-shell-status-bar-change', {
|
||||
detail: { height: finalSafeTop, appHeight: safeTop, nativeHeight: nativeSafeTop },
|
||||
detail: { height: statusBarHeight },
|
||||
}),
|
||||
);
|
||||
|
||||
return finalSafeTop;
|
||||
return statusBarHeight;
|
||||
}
|
||||
|
||||
// uni-app 重写 html 样式后,如果状态栏高度小于 22px,则立即恢复当前有效高度。
|
||||
function observeStatusBarHeight() {
|
||||
if (statusBarStyleObserver || typeof MutationObserver === 'undefined') {
|
||||
return;
|
||||
}
|
||||
statusBarStyleObserver = new MutationObserver(() => {
|
||||
const statusBarHeight = normalizeStatusBarHeight(
|
||||
document.documentElement.style.getPropertyValue('--status-bar-height'),
|
||||
);
|
||||
if (statusBarHeight === null || statusBarHeight < H5_MIN_STATUS_BAR_HEIGHT) {
|
||||
applyH5HostStatusBarHeight(currentStatusBarHeight);
|
||||
}
|
||||
});
|
||||
statusBarStyleObserver.observe(document.documentElement, {
|
||||
attributes: true,
|
||||
attributeFilter: ['style'],
|
||||
});
|
||||
}
|
||||
|
||||
// 解析宿主运行期间通过 postMessage 发送的状态栏高度。
|
||||
function resolveMessageStatusBarHeight(message) {
|
||||
const data = parseMessageData(message?.data);
|
||||
if (!data) {
|
||||
@@ -149,6 +149,7 @@ function resolveMessageStatusBarHeight(message) {
|
||||
);
|
||||
}
|
||||
|
||||
// 初始化状态栏高度,并支持宿主通过全局方法或 postMessage 动态更新。
|
||||
export function initH5HostShell() {
|
||||
if (typeof window === 'undefined') {
|
||||
return 0;
|
||||
@@ -160,6 +161,7 @@ export function initH5HostShell() {
|
||||
}
|
||||
|
||||
window.__FRIENDSHIP_H5_SHELL_READY__ = true;
|
||||
observeStatusBarHeight();
|
||||
window.setAppShellStatusBarHeight = (nextHeight) => applyH5HostStatusBarHeight(nextHeight);
|
||||
window.addEventListener('message', (message) => {
|
||||
const nextHeight = resolveMessageStatusBarHeight(message);
|
||||
@@ -167,9 +169,6 @@ export function initH5HostShell() {
|
||||
applyH5HostStatusBarHeight(nextHeight);
|
||||
}
|
||||
});
|
||||
window.addEventListener('load', () => {
|
||||
applyH5HostStatusBarHeight(getH5HostStatusBarHeight());
|
||||
});
|
||||
|
||||
return safeTop;
|
||||
}
|
||||
@@ -36,32 +36,26 @@ export function patchH5Navigation() {
|
||||
}
|
||||
window.__UNI_H5_NAV_PATCHED__ = true;
|
||||
syncCurrentH5Route();
|
||||
|
||||
const rawNavigateTo = uni.navigateTo?.bind(uni);
|
||||
const rawRedirectTo = uni.redirectTo?.bind(uni);
|
||||
const rawSwitchTab = uni.switchTab?.bind(uni);
|
||||
const rawReLaunch = uni.reLaunch?.bind(uni);
|
||||
|
||||
uni.navigateTo = (options = {}) => {
|
||||
saveH5RouteSnapshot(options.url);
|
||||
return rawNavigateTo(options);
|
||||
};
|
||||
|
||||
uni.redirectTo = (options = {}) => {
|
||||
saveH5RouteSnapshot(options.url);
|
||||
return rawRedirectTo(options);
|
||||
};
|
||||
|
||||
uni.switchTab = (options = {}) => {
|
||||
saveH5RouteSnapshot(options.url);
|
||||
return rawSwitchTab(options);
|
||||
};
|
||||
|
||||
uni.reLaunch = (options = {}) => {
|
||||
saveH5RouteSnapshot(options.url);
|
||||
return rawReLaunch(options);
|
||||
};
|
||||
|
||||
uni.navigateBack = (options = {}) => {
|
||||
const fallbackRoute =
|
||||
sessionStorage.getItem(H5_PREV_ROUTE_KEY) || H5_DEFAULT_ROUTE;
|
||||
@@ -71,18 +65,15 @@ export function patchH5Navigation() {
|
||||
options.success && options.success();
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
if (fallbackRoute && fallbackRoute !== getCurrentH5Route()) {
|
||||
window.location.assign(fallbackRoute);
|
||||
options.success && options.success();
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
window.location.assign(H5_DEFAULT_ROUTE);
|
||||
options.success && options.success();
|
||||
return Promise.resolve();
|
||||
};
|
||||
|
||||
window.addEventListener('pageshow', syncCurrentH5Route);
|
||||
window.addEventListener('popstate', syncCurrentH5Route);
|
||||
}
|
||||
|
||||
@@ -115,12 +115,7 @@ const app = defineStore({
|
||||
},
|
||||
},
|
||||
persist: {
|
||||
enabled: true,
|
||||
strategies: [
|
||||
{
|
||||
key: 'app-store',
|
||||
},
|
||||
],
|
||||
key: 'app-store',
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -109,12 +109,7 @@ const cart = defineStore({
|
||||
},
|
||||
},
|
||||
persist: {
|
||||
enabled: true,
|
||||
strategies: [
|
||||
{
|
||||
key: 'cart-store',
|
||||
},
|
||||
],
|
||||
key: 'cart-store',
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -1,5 +1,18 @@
|
||||
import { createPinia } from 'pinia';
|
||||
import piniaPersist from 'pinia-plugin-persist-uni';
|
||||
import { createPersistedState } from 'pinia-plugin-persistedstate';
|
||||
|
||||
export const uniStorage = {
|
||||
getItem(key) {
|
||||
const value = uni.getStorageSync(key);
|
||||
if (value === undefined || value === null || value === '') {
|
||||
return null;
|
||||
}
|
||||
return typeof value === 'string' ? value : JSON.stringify(value);
|
||||
},
|
||||
setItem(key, value) {
|
||||
uni.setStorageSync(key, value);
|
||||
},
|
||||
};
|
||||
|
||||
// 自动注入所有pinia模块
|
||||
const files = import.meta.glob('./*.js', { eager: true });
|
||||
@@ -10,9 +23,9 @@ Object.keys(files).forEach((key) => {
|
||||
|
||||
export const setupPinia = (app) => {
|
||||
const pinia = createPinia();
|
||||
pinia.use(piniaPersist);
|
||||
|
||||
pinia.use(createPersistedState({ storage: uniStorage }));
|
||||
app.use(pinia);
|
||||
return pinia;
|
||||
};
|
||||
|
||||
export default (name) => {
|
||||
|
||||
@@ -16,13 +16,8 @@ const modal = defineStore({
|
||||
}
|
||||
}),
|
||||
persist: {
|
||||
enabled: true,
|
||||
strategies: [
|
||||
{
|
||||
key: 'modal-store',
|
||||
paths: ['lastTimer', 'advHistory'],
|
||||
},
|
||||
],
|
||||
key: 'modal-store',
|
||||
paths: ['lastTimer', 'advHistory'],
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -20,12 +20,7 @@ const sys = defineStore({
|
||||
},
|
||||
},
|
||||
persist: {
|
||||
enabled: true,
|
||||
strategies: [
|
||||
{
|
||||
key: 'sys-store',
|
||||
},
|
||||
],
|
||||
key: 'sys-store',
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -163,12 +163,7 @@ const user = defineStore({
|
||||
},
|
||||
},
|
||||
persist: {
|
||||
enabled: true,
|
||||
strategies: [
|
||||
{
|
||||
key: 'user-store',
|
||||
},
|
||||
],
|
||||
key: 'user-store',
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
/**
|
||||
* 保存图片到相册
|
||||
* @param {string} filePath 图片地址
|
||||
* @param {Function} successCallback 保存成功回调
|
||||
* @param {Function} errorCallback 保存失败回调
|
||||
*/
|
||||
export const saveImageToPhotosAlbum = (
|
||||
filePath: string,
|
||||
successCallback?: (res: any) => void,
|
||||
errorCallback?: (error: any) => void,
|
||||
) => {
|
||||
if (!filePath) {
|
||||
const error = new Error('图片地址不能为空');
|
||||
errorCallback?.(error);
|
||||
return;
|
||||
}
|
||||
|
||||
// #ifdef H5
|
||||
const saveImageForH5 = async () => {
|
||||
let objectUrl = '';
|
||||
try {
|
||||
const response = await fetch(filePath);
|
||||
if (!response.ok) {
|
||||
throw new Error(`图片下载失败: ${response.status}`);
|
||||
}
|
||||
|
||||
const blob = await response.blob();
|
||||
objectUrl = URL.createObjectURL(blob);
|
||||
const link = document.createElement('a');
|
||||
const fileName = filePath.split('/').pop()?.split('?')[0] || `image-${Date.now()}.jpg`;
|
||||
link.href = objectUrl;
|
||||
link.download = decodeURIComponent(fileName);
|
||||
link.style.display = 'none';
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
successCallback?.({ errMsg: 'saveImageToPhotosAlbum:ok' });
|
||||
} catch (error) {
|
||||
errorCallback?.(error);
|
||||
} finally {
|
||||
if (objectUrl) setTimeout(() => URL.revokeObjectURL(objectUrl), 1000);
|
||||
}
|
||||
};
|
||||
|
||||
saveImageForH5();
|
||||
// #endif
|
||||
|
||||
// #ifndef H5
|
||||
uni.saveImageToPhotosAlbum({
|
||||
filePath,
|
||||
success: (res) => successCallback?.(res),
|
||||
fail: (error) => errorCallback?.(error),
|
||||
});
|
||||
// #endif
|
||||
};
|
||||
@@ -163,3 +163,24 @@ export const getGender = (genderType) => {
|
||||
return '';
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 将时间转换为相对当前时间的文字描述
|
||||
* @param {dayjs.ConfigType} date 需要转换的时间
|
||||
* @returns {string} 几分钟前、几小时前、几天前或近期
|
||||
*/
|
||||
export const formatRelativeTime = (date) => {
|
||||
if (!date) return '';
|
||||
const targetTime = dayjs(date);
|
||||
if (!targetTime.isValid()) return '';
|
||||
const now = dayjs();
|
||||
const minutes = now.diff(targetTime, 'minute');
|
||||
// 未来时间和不足5分钟都按近期处理。
|
||||
if (minutes < 5) return '近期';
|
||||
if (minutes < 60) return `${minutes}分钟前`;
|
||||
const hours = now.diff(targetTime, 'hour');
|
||||
if (hours < 24) return `${hours}小时前`;
|
||||
const days = now.diff(targetTime, 'day');
|
||||
if (days < 7) return `${days}天前`;
|
||||
return '近期';
|
||||
};
|
||||
|
After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 5.1 KiB |
|
Before Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 3.2 KiB |
|
Before Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 8.5 KiB |
|
Before Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 9.0 KiB |
|
Before Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 9.0 KiB |