init
10
.gitignore
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
# OSX
|
||||
#
|
||||
.DS_Store
|
||||
|
||||
#Intellij idea
|
||||
.idea/
|
||||
/idea/
|
||||
.vscode/
|
||||
/unpackage/
|
||||
.hbuilderx/launch.json
|
269
App.vue
Normal file
@ -0,0 +1,269 @@
|
||||
<script>
|
||||
/**
|
||||
* vuex管理登录状态,具体可以参考官方登录模板示例
|
||||
*/
|
||||
import {
|
||||
mapMutations
|
||||
} from "vuex";
|
||||
import APPUpdate from "@/plugins/APPUpdate";
|
||||
import {
|
||||
getClipboardData
|
||||
} from "@/js_sdk/h5-copy/h5-copy.js";
|
||||
import config from "@/config/config";
|
||||
// 区域code
|
||||
import provinceList from "./json/area_province.js";
|
||||
import cityList from "./json/area_city.js";
|
||||
import districtList from "./json/area_district.js";
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
config,
|
||||
};
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* 监听返回
|
||||
*/
|
||||
onBackPress(e) {
|
||||
if (e.from == "backbutton") {
|
||||
let routes = getCurrentPages();
|
||||
let curRoute = routes[routes.length - 1].options;
|
||||
routes.forEach((item) => {
|
||||
if (
|
||||
item.route == "pages/tabbar/cart/cartList" ||
|
||||
item.route.indexOf("pages/product/goods") != -1
|
||||
) {
|
||||
uni.redirectTo({
|
||||
url: item.route,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
if (curRoute.addId) {
|
||||
uni.reLaunch({
|
||||
url: "/pages/tabbar/cart/cartList",
|
||||
});
|
||||
} else {
|
||||
uni.navigateBack();
|
||||
}
|
||||
return true; //阻止默认返回行为
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
...mapMutations(["login"])
|
||||
},
|
||||
onLaunch: function() {
|
||||
// #ifdef APP-PLUS
|
||||
this.checkArguments(); // 检测启动参数
|
||||
APPUpdate();
|
||||
this.hanleTabCenter()
|
||||
// 重点是以下: 一定要监听后台恢复 !一定要
|
||||
plus.globalEvent.addEventListener("newintent", (e) => {
|
||||
this.checkArguments(); // 检测启动参数
|
||||
});
|
||||
// #endif
|
||||
|
||||
// #ifdef MP-WEIXIN
|
||||
this.applyUpdateWeChat();
|
||||
// #endif
|
||||
},
|
||||
|
||||
onShow() {
|
||||
// #ifndef H5
|
||||
this.getClipboard();
|
||||
// #endif
|
||||
},
|
||||
methods: {
|
||||
//
|
||||
hanleTabCenter() {
|
||||
// 点击中间的➕
|
||||
uni.onTabBarMidButtonTap(() => {
|
||||
console.log('center')
|
||||
// 未登录不能发布视频
|
||||
let myUserInfo = null
|
||||
if (myUserInfo == null) {
|
||||
uni.showToast({
|
||||
duration: 3000,
|
||||
title: "请登录~",
|
||||
icon: "none",
|
||||
});
|
||||
uni.navigateTo({
|
||||
// url: "../loginRegist/loginRegist",
|
||||
url: '/pages/passport/login',
|
||||
animationType: "slide-in-bottom",
|
||||
success() {
|
||||
this.loginWords = "请登录";
|
||||
},
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
uni.chooseVideo({
|
||||
sourceType: ["album"],
|
||||
compressed: false,
|
||||
success(e) {
|
||||
console.log(JSON.stringify(e))
|
||||
uni.navigateTo({
|
||||
url: "/pages/publish/publish?fileObjectEvent=" + JSON.stringify(e),
|
||||
});
|
||||
},
|
||||
});
|
||||
});
|
||||
},
|
||||
/**
|
||||
* 微信小程序版本提交更新版本 解决缓存问题
|
||||
*/
|
||||
applyUpdateWeChat() {
|
||||
const updateManager = uni.getUpdateManager();
|
||||
|
||||
updateManager.onCheckForUpdate(function(res) {
|
||||
// 请求完新版本信息的回调
|
||||
});
|
||||
|
||||
updateManager.onUpdateReady(function(res) {
|
||||
uni.showModal({
|
||||
title: "更新提示",
|
||||
content: "发现新版本,是否重启应用?",
|
||||
success(res) {
|
||||
if (res.confirm) {
|
||||
// 新的版本已经下载好,调用 applyUpdate 应用新版本并重启
|
||||
updateManager.applyUpdate();
|
||||
}
|
||||
},
|
||||
});
|
||||
});
|
||||
updateManager.onUpdateFailed(function(res) {
|
||||
// 新的版本下载失败
|
||||
});
|
||||
},
|
||||
|
||||
// TODO 开屏广告 后续优化添加
|
||||
launch() {
|
||||
try {
|
||||
// 获取本地存储中launchFlag标识 开屏广告
|
||||
const value = uni.getStorageSync("launchFlag");
|
||||
if (!value) {
|
||||
// this.$u.route("/pages/index/agreement");
|
||||
} else {
|
||||
//app启动时打开启动广告页
|
||||
var w = plus.webview.open(
|
||||
"/hybrid/html/advertise/advertise.html",
|
||||
"本地地址", {
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
zindex: 999,
|
||||
},
|
||||
"fade-in",
|
||||
500
|
||||
);
|
||||
//设置定时器,4s后关闭启动广告页
|
||||
setTimeout(function() {
|
||||
plus.webview.close(w);
|
||||
APPUpdate();
|
||||
}, 3000);
|
||||
}
|
||||
} catch (e) {
|
||||
// error
|
||||
uni.setStorage({
|
||||
key: "launchFlag",
|
||||
data: true,
|
||||
success: function() {
|
||||
console.log("error时存储launchFlag");
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取粘贴板数据
|
||||
*/
|
||||
async getClipboard() {
|
||||
let res = await getClipboardData();
|
||||
/**
|
||||
* 解析粘贴板数据
|
||||
*/
|
||||
if (res.indexOf(config.shareLink) != -1) {
|
||||
uni.showModal({
|
||||
title: "提示",
|
||||
content: "检测到一个分享链接是否跳转?",
|
||||
confirmText: "跳转",
|
||||
success: function(callback) {
|
||||
if (callback.confirm) {
|
||||
const path = res.split(config.shareLink)[1];
|
||||
if (path.indexOf("tabbar") != -1) {
|
||||
uni.switchTab({
|
||||
url: path,
|
||||
});
|
||||
} else {
|
||||
uni.navigateTo({
|
||||
url: path,
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* h5中打开app获取跳转app的链接并跳转
|
||||
*/
|
||||
checkArguments() {
|
||||
// #ifdef APP-PLUS
|
||||
setTimeout(() => {
|
||||
const args = plus.runtime.arguments;
|
||||
if (args) {
|
||||
const argsStr = decodeURIComponent(args);
|
||||
const path = argsStr.split("//")[1];
|
||||
if (path.indexOf("tabbar") != -1) {
|
||||
uni.switchTab({
|
||||
url: `/${path}`,
|
||||
});
|
||||
} else {
|
||||
uni.navigateTo({
|
||||
url: `/${path}`,
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
// #endif
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
@import "uview-ui/index.scss";
|
||||
|
||||
// -------适配底部安全区 苹果x系列刘海屏
|
||||
|
||||
// #ifdef MP-WEIXIN
|
||||
.mp-iphonex-bottom {
|
||||
padding-bottom: constant(safe-area-inset-bottom);
|
||||
padding-bottom: env(safe-area-inset-bottom);
|
||||
box-sizing: content-box;
|
||||
height: auto !important;
|
||||
padding-top: 10rpx;
|
||||
}
|
||||
|
||||
// #endif
|
||||
|
||||
body {
|
||||
background-color: $bg-color;
|
||||
}
|
||||
|
||||
/************************ */
|
||||
.w200 {
|
||||
width: 200rpx !important;
|
||||
}
|
||||
|
||||
.flex1 {
|
||||
flex: 1; //必须父级设置flex
|
||||
}
|
||||
.activate-line {
|
||||
background-color: #ffffff;
|
||||
transition-duration: 300ms;
|
||||
}
|
||||
</style>
|
BIN
CustomStartPage/start-page.9.png
Normal file
After Width: | Height: | Size: 19 KiB |
BIN
CustomStoryboard/1@2x.png
Normal file
After Width: | Height: | Size: 15 KiB |
BIN
CustomStoryboard/1@3x.png
Normal file
After Width: | Height: | Size: 15 KiB |
BIN
CustomStoryboard/2@2x.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
CustomStoryboard/2@3x.png
Normal file
After Width: | Height: | Size: 2.3 KiB |
71
CustomStoryboard/LaunchScreen.storyboard
Normal file
@ -0,0 +1,71 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="17701" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
|
||||
<device id="ipad12_9" orientation="portrait" layout="fullscreen" appearance="light"/>
|
||||
<dependencies>
|
||||
<deployment version="4096" identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="17703"/>
|
||||
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
||||
<capability name="System colors in document resources" minToolsVersion="11.0"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
<!--View Controller-->
|
||||
<scene sceneID="EHf-IW-A2E">
|
||||
<objects>
|
||||
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
|
||||
<view key="view" tag="1" contentMode="scaleAspectFit" id="Ze5-6b-2t3">
|
||||
<rect key="frame" x="0.0" y="0.0" width="1024" height="1366"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="LILI商城 享你所购" textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="9" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="aap-f2-ctd">
|
||||
<rect key="frame" x="417.5" y="1291" width="189" height="65"/>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" relation="greaterThanOrEqual" constant="189" id="IEH-DH-MSh"/>
|
||||
<constraint firstAttribute="height" constant="65" id="Owl-Zu-11N"/>
|
||||
<constraint firstAttribute="height" relation="greaterThanOrEqual" constant="65" id="pa9-7L-Yn2"/>
|
||||
<constraint firstAttribute="width" constant="189" id="xdI-XG-GYE"/>
|
||||
</constraints>
|
||||
<fontDescription key="fontDescription" type="boldSystem" pointSize="23"/>
|
||||
<color key="textColor" white="0.0" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<color key="highlightedColor" systemColor="secondarySystemGroupedBackgroundColor"/>
|
||||
</label>
|
||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="1.png" highlightedImage="1.png" translatesAutoresizingMaskIntoConstraints="NO" id="tDb-oz-ZqQ" userLabel="logo.png">
|
||||
<rect key="frame" x="437" y="1126" width="150" height="150"/>
|
||||
<color key="tintColor" systemColor="systemGray6Color"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="150" id="2Pc-RJ-7fL"/>
|
||||
<constraint firstAttribute="width" secondItem="tDb-oz-ZqQ" secondAttribute="height" multiplier="1:1" id="K9U-1h-yR6"/>
|
||||
<constraint firstAttribute="height" constant="150" id="Ocb-HD-CX3"/>
|
||||
</constraints>
|
||||
</imageView>
|
||||
</subviews>
|
||||
<viewLayoutGuide key="safeArea" id="IW3-oA-Ytg"/>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<color key="tintColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<constraints>
|
||||
<constraint firstItem="IW3-oA-Ytg" firstAttribute="bottom" secondItem="aap-f2-ctd" secondAttribute="bottom" constant="10" id="AaD-Ch-YkJ"/>
|
||||
<constraint firstItem="aap-f2-ctd" firstAttribute="top" secondItem="tDb-oz-ZqQ" secondAttribute="bottom" constant="15" id="TSj-WP-wlO"/>
|
||||
<constraint firstItem="tDb-oz-ZqQ" firstAttribute="centerX" secondItem="IW3-oA-Ytg" secondAttribute="centerX" id="XFM-cQ-LjU"/>
|
||||
<constraint firstItem="tDb-oz-ZqQ" firstAttribute="centerX" secondItem="aap-f2-ctd" secondAttribute="centerX" id="gTF-LF-cb1"/>
|
||||
</constraints>
|
||||
<variation key="heightClass=regular-widthClass=compact">
|
||||
<color key="tintColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
</variation>
|
||||
</view>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="50.390625" y="373.35285505124449"/>
|
||||
</scene>
|
||||
</scenes>
|
||||
<resources>
|
||||
<image name="1.png" width="86" height="57"/>
|
||||
<systemColor name="secondarySystemGroupedBackgroundColor">
|
||||
<color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
</systemColor>
|
||||
<systemColor name="systemGray6Color">
|
||||
<color red="0.94901960784313721" green="0.94901960784313721" blue="0.96862745098039216" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
</systemColor>
|
||||
</resources>
|
||||
</document>
|
661
LICENSE
Normal file
@ -0,0 +1,661 @@
|
||||
GNU AFFERO GENERAL PUBLIC LICENSE
|
||||
Version 3, 19 November 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The GNU Affero General Public License is a free, copyleft license for
|
||||
software and other kinds of works, specifically designed to ensure
|
||||
cooperation with the community in the case of network server software.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
our General Public Licenses are intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains free
|
||||
software for all its users.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
them if you wish), that you receive source code or can get it if you
|
||||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
Developers that use our General Public Licenses protect your rights
|
||||
with two steps: (1) assert copyright on the software, and (2) offer
|
||||
you this License which gives you legal permission to copy, distribute
|
||||
and/or modify the software.
|
||||
|
||||
A secondary benefit of defending all users' freedom is that
|
||||
improvements made in alternate versions of the program, if they
|
||||
receive widespread use, become available for other developers to
|
||||
incorporate. Many developers of free software are heartened and
|
||||
encouraged by the resulting cooperation. However, in the case of
|
||||
software used on network servers, this result may fail to come about.
|
||||
The GNU General Public License permits making a modified version and
|
||||
letting the public access it on a server without ever releasing its
|
||||
source code to the public.
|
||||
|
||||
The GNU Affero General Public License is designed specifically to
|
||||
ensure that, in such cases, the modified source code becomes available
|
||||
to the community. It requires the operator of a network server to
|
||||
provide the source code of the modified version running there to the
|
||||
users of that server. Therefore, public use of a modified version, on
|
||||
a publicly accessible server, gives the public access to the source
|
||||
code of the modified version.
|
||||
|
||||
An older license, called the Affero General Public License and
|
||||
published by Affero, was designed to accomplish similar goals. This is
|
||||
a different license, not a version of the Affero GPL, but Affero has
|
||||
released a new version of the Affero GPL which permits relicensing under
|
||||
this license.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
TERMS AND CONDITIONS
|
||||
|
||||
0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU Affero General Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
|
||||
"The Program" refers to any copyrightable work licensed under this
|
||||
License. Each licensee is addressed as "you". "Licensees" and
|
||||
"recipients" may be individuals or organizations.
|
||||
|
||||
To "modify" a work means to copy from or adapt all or part of the work
|
||||
in a fashion requiring copyright permission, other than the making of an
|
||||
exact copy. The resulting work is called a "modified version" of the
|
||||
earlier work or a work "based on" the earlier work.
|
||||
|
||||
A "covered work" means either the unmodified Program or a work based
|
||||
on the Program.
|
||||
|
||||
To "propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification), making available to the
|
||||
public, and in some countries other activities as well.
|
||||
|
||||
To "convey" a work means any kind of propagation that enables other
|
||||
parties to make or receive copies. Mere interaction with a user through
|
||||
a computer network, with no transfer of a copy, is not conveying.
|
||||
|
||||
An interactive user interface displays "Appropriate Legal Notices"
|
||||
to the extent that it includes a convenient and prominently visible
|
||||
feature that (1) displays an appropriate copyright notice, and (2)
|
||||
tells the user that there is no warranty for the work (except to the
|
||||
extent that warranties are provided), that licensees may convey the
|
||||
work under this License, and how to view a copy of this License. If
|
||||
the interface presents a list of user commands or options, such as a
|
||||
menu, a prominent item in the list meets this criterion.
|
||||
|
||||
1. Source Code.
|
||||
|
||||
The "source code" for a work means the preferred form of the work
|
||||
for making modifications to it. "Object code" means any non-source
|
||||
form of a work.
|
||||
|
||||
A "Standard Interface" means an interface that either is an official
|
||||
standard defined by a recognized standards body, or, in the case of
|
||||
interfaces specified for a particular programming language, one that
|
||||
is widely used among developers working in that language.
|
||||
|
||||
The "System Libraries" of an executable work include anything, other
|
||||
than the work as a whole, that (a) is included in the normal form of
|
||||
packaging a Major Component, but which is not part of that Major
|
||||
Component, and (b) serves only to enable use of the work with that
|
||||
Major Component, or to implement a Standard Interface for which an
|
||||
implementation is available to the public in source code form. A
|
||||
"Major Component", in this context, means a major essential component
|
||||
(kernel, window system, and so on) of the specific operating system
|
||||
(if any) on which the executable work runs, or a compiler used to
|
||||
produce the work, or an object code interpreter used to run it.
|
||||
|
||||
The "Corresponding Source" for a work in object code form means all
|
||||
the source code needed to generate, install, and (for an executable
|
||||
work) run the object code and to modify the work, including scripts to
|
||||
control those activities. However, it does not include the work's
|
||||
System Libraries, or general-purpose tools or generally available free
|
||||
programs which are used unmodified in performing those activities but
|
||||
which are not part of the work. For example, Corresponding Source
|
||||
includes interface definition files associated with source files for
|
||||
the work, and the source code for shared libraries and dynamically
|
||||
linked subprograms that the work is specifically designed to require,
|
||||
such as by intimate data communication or control flow between those
|
||||
subprograms and other parts of the work.
|
||||
|
||||
The Corresponding Source need not include anything that users
|
||||
can regenerate automatically from other parts of the Corresponding
|
||||
Source.
|
||||
|
||||
The Corresponding Source for a work in source code form is that
|
||||
same work.
|
||||
|
||||
2. Basic Permissions.
|
||||
|
||||
All rights granted under this License are granted for the term of
|
||||
copyright on the Program, and are irrevocable provided the stated
|
||||
conditions are met. This License explicitly affirms your unlimited
|
||||
permission to run the unmodified Program. The output from running a
|
||||
covered work is covered by this License only if the output, given its
|
||||
content, constitutes a covered work. This License acknowledges your
|
||||
rights of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
You may make, run and propagate covered works that you do not
|
||||
convey, without conditions so long as your license otherwise remains
|
||||
in force. You may convey covered works to others for the sole purpose
|
||||
of having them make modifications exclusively for you, or provide you
|
||||
with facilities for running those works, provided that you comply with
|
||||
the terms of this License in conveying all material for which you do
|
||||
not control copyright. Those thus making or running the covered works
|
||||
for you must do so exclusively on your behalf, under your direction
|
||||
and control, on terms that prohibit them from making any copies of
|
||||
your copyrighted material outside their relationship with you.
|
||||
|
||||
Conveying under any other circumstances is permitted solely under
|
||||
the conditions stated below. Sublicensing is not allowed; section 10
|
||||
makes it unnecessary.
|
||||
|
||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
|
||||
No covered work shall be deemed part of an effective technological
|
||||
measure under any applicable law fulfilling obligations under article
|
||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||
similar laws prohibiting or restricting circumvention of such
|
||||
measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to forbid
|
||||
circumvention of technological measures to the extent such circumvention
|
||||
is effected by exercising rights under this License with respect to
|
||||
the covered work, and you disclaim any intention to limit operation or
|
||||
modification of the work as a means of enforcing, against the work's
|
||||
users, your or third parties' legal rights to forbid circumvention of
|
||||
technological measures.
|
||||
|
||||
4. Conveying Verbatim Copies.
|
||||
|
||||
You may convey verbatim copies of the Program's source code as you
|
||||
receive it, in any medium, provided that you conspicuously and
|
||||
appropriately publish on each copy an appropriate copyright notice;
|
||||
keep intact all notices stating that this License and any
|
||||
non-permissive terms added in accord with section 7 apply to the code;
|
||||
keep intact all notices of the absence of any warranty; and give all
|
||||
recipients a copy of this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey,
|
||||
and you may offer support or warranty protection for a fee.
|
||||
|
||||
5. Conveying Modified Source Versions.
|
||||
|
||||
You may convey a work based on the Program, or the modifications to
|
||||
produce it from the Program, in the form of source code under the
|
||||
terms of section 4, provided that you also meet all of these conditions:
|
||||
|
||||
a) The work must carry prominent notices stating that you modified
|
||||
it, and giving a relevant date.
|
||||
|
||||
b) The work must carry prominent notices stating that it is
|
||||
released under this License and any conditions added under section
|
||||
7. This requirement modifies the requirement in section 4 to
|
||||
"keep intact all notices".
|
||||
|
||||
c) You must license the entire work, as a whole, under this
|
||||
License to anyone who comes into possession of a copy. This
|
||||
License will therefore apply, along with any applicable section 7
|
||||
additional terms, to the whole of the work, and all its parts,
|
||||
regardless of how they are packaged. This License gives no
|
||||
permission to license the work in any other way, but it does not
|
||||
invalidate such permission if you have separately received it.
|
||||
|
||||
d) If the work has interactive user interfaces, each must display
|
||||
Appropriate Legal Notices; however, if the Program has interactive
|
||||
interfaces that do not display Appropriate Legal Notices, your
|
||||
work need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent
|
||||
works, which are not by their nature extensions of the covered work,
|
||||
and which are not combined with it such as to form a larger program,
|
||||
in or on a volume of a storage or distribution medium, is called an
|
||||
"aggregate" if the compilation and its resulting copyright are not
|
||||
used to limit the access or legal rights of the compilation's users
|
||||
beyond what the individual works permit. Inclusion of a covered work
|
||||
in an aggregate does not cause this License to apply to the other
|
||||
parts of the aggregate.
|
||||
|
||||
6. Conveying Non-Source Forms.
|
||||
|
||||
You may convey a covered work in object code form under the terms
|
||||
of sections 4 and 5, provided that you also convey the
|
||||
machine-readable Corresponding Source under the terms of this License,
|
||||
in one of these ways:
|
||||
|
||||
a) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by the
|
||||
Corresponding Source fixed on a durable physical medium
|
||||
customarily used for software interchange.
|
||||
|
||||
b) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by a
|
||||
written offer, valid for at least three years and valid for as
|
||||
long as you offer spare parts or customer support for that product
|
||||
model, to give anyone who possesses the object code either (1) a
|
||||
copy of the Corresponding Source for all the software in the
|
||||
product that is covered by this License, on a durable physical
|
||||
medium customarily used for software interchange, for a price no
|
||||
more than your reasonable cost of physically performing this
|
||||
conveying of source, or (2) access to copy the
|
||||
Corresponding Source from a network server at no charge.
|
||||
|
||||
c) Convey individual copies of the object code with a copy of the
|
||||
written offer to provide the Corresponding Source. This
|
||||
alternative is allowed only occasionally and noncommercially, and
|
||||
only if you received the object code with such an offer, in accord
|
||||
with subsection 6b.
|
||||
|
||||
d) Convey the object code by offering access from a designated
|
||||
place (gratis or for a charge), and offer equivalent access to the
|
||||
Corresponding Source in the same way through the same place at no
|
||||
further charge. You need not require recipients to copy the
|
||||
Corresponding Source along with the object code. If the place to
|
||||
copy the object code is a network server, the Corresponding Source
|
||||
may be on a different server (operated by you or a third party)
|
||||
that supports equivalent copying facilities, provided you maintain
|
||||
clear directions next to the object code saying where to find the
|
||||
Corresponding Source. Regardless of what server hosts the
|
||||
Corresponding Source, you remain obligated to ensure that it is
|
||||
available for as long as needed to satisfy these requirements.
|
||||
|
||||
e) Convey the object code using peer-to-peer transmission, provided
|
||||
you inform other peers where the object code and Corresponding
|
||||
Source of the work are being offered to the general public at no
|
||||
charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded
|
||||
from the Corresponding Source as a System Library, need not be
|
||||
included in conveying the object code work.
|
||||
|
||||
A "User Product" is either (1) a "consumer product", which means any
|
||||
tangible personal property which is normally used for personal, family,
|
||||
or household purposes, or (2) anything designed or sold for incorporation
|
||||
into a dwelling. In determining whether a product is a consumer product,
|
||||
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||
product received by a particular user, "normally used" refers to a
|
||||
typical or common use of that class of product, regardless of the status
|
||||
of the particular user or of the way in which the particular user
|
||||
actually uses, or expects or is expected to use, the product. A product
|
||||
is a consumer product regardless of whether the product has substantial
|
||||
commercial, industrial or non-consumer uses, unless such uses represent
|
||||
the only significant mode of use of the product.
|
||||
|
||||
"Installation Information" for a User Product means any methods,
|
||||
procedures, authorization keys, or other information required to install
|
||||
and execute modified versions of a covered work in that User Product from
|
||||
a modified version of its Corresponding Source. The information must
|
||||
suffice to ensure that the continued functioning of the modified object
|
||||
code is in no case prevented or interfered with solely because
|
||||
modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or
|
||||
specifically for use in, a User Product, and the conveying occurs as
|
||||
part of a transaction in which the right of possession and use of the
|
||||
User Product is transferred to the recipient in perpetuity or for a
|
||||
fixed term (regardless of how the transaction is characterized), the
|
||||
Corresponding Source conveyed under this section must be accompanied
|
||||
by the Installation Information. But this requirement does not apply
|
||||
if neither you nor any third party retains the ability to install
|
||||
modified object code on the User Product (for example, the work has
|
||||
been installed in ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a
|
||||
requirement to continue to provide support service, warranty, or updates
|
||||
for a work that has been modified or installed by the recipient, or for
|
||||
the User Product in which it has been modified or installed. Access to a
|
||||
network may be denied when the modification itself materially and
|
||||
adversely affects the operation of the network or violates the rules and
|
||||
protocols for communication across the network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided,
|
||||
in accord with this section must be in a format that is publicly
|
||||
documented (and with an implementation available to the public in
|
||||
source code form), and must require no special password or key for
|
||||
unpacking, reading or copying.
|
||||
|
||||
7. Additional Terms.
|
||||
|
||||
"Additional permissions" are terms that supplement the terms of this
|
||||
License by making exceptions from one or more of its conditions.
|
||||
Additional permissions that are applicable to the entire Program shall
|
||||
be treated as though they were included in this License, to the extent
|
||||
that they are valid under applicable law. If additional permissions
|
||||
apply only to part of the Program, that part may be used separately
|
||||
under those permissions, but the entire Program remains governed by
|
||||
this License without regard to the additional permissions.
|
||||
|
||||
When you convey a copy of a covered work, you may at your option
|
||||
remove any additional permissions from that copy, or from any part of
|
||||
it. (Additional permissions may be written to require their own
|
||||
removal in certain cases when you modify the work.) You may place
|
||||
additional permissions on material, added by you to a covered work,
|
||||
for which you have or can give appropriate copyright permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you
|
||||
add to a covered work, you may (if authorized by the copyright holders of
|
||||
that material) supplement the terms of this License with terms:
|
||||
|
||||
a) Disclaiming warranty or limiting liability differently from the
|
||||
terms of sections 15 and 16 of this License; or
|
||||
|
||||
b) Requiring preservation of specified reasonable legal notices or
|
||||
author attributions in that material or in the Appropriate Legal
|
||||
Notices displayed by works containing it; or
|
||||
|
||||
c) Prohibiting misrepresentation of the origin of that material, or
|
||||
requiring that modified versions of such material be marked in
|
||||
reasonable ways as different from the original version; or
|
||||
|
||||
d) Limiting the use for publicity purposes of names of licensors or
|
||||
authors of the material; or
|
||||
|
||||
e) Declining to grant rights under trademark law for use of some
|
||||
trade names, trademarks, or service marks; or
|
||||
|
||||
f) Requiring indemnification of licensors and authors of that
|
||||
material by anyone who conveys the material (or modified versions of
|
||||
it) with contractual assumptions of liability to the recipient, for
|
||||
any liability that these contractual assumptions directly impose on
|
||||
those licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered "further
|
||||
restrictions" within the meaning of section 10. If the Program as you
|
||||
received it, or any part of it, contains a notice stating that it is
|
||||
governed by this License along with a term that is a further
|
||||
restriction, you may remove that term. If a license document contains
|
||||
a further restriction but permits relicensing or conveying under this
|
||||
License, you may add to a covered work material governed by the terms
|
||||
of that license document, provided that the further restriction does
|
||||
not survive such relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you
|
||||
must place, in the relevant source files, a statement of the
|
||||
additional terms that apply to those files, or a notice indicating
|
||||
where to find the applicable terms.
|
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the
|
||||
form of a separately written license, or stated as exceptions;
|
||||
the above requirements apply either way.
|
||||
|
||||
8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly
|
||||
provided under this License. Any attempt otherwise to propagate or
|
||||
modify it is void, and will automatically terminate your rights under
|
||||
this License (including any patent licenses granted under the third
|
||||
paragraph of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your
|
||||
license from a particular copyright holder is reinstated (a)
|
||||
provisionally, unless and until the copyright holder explicitly and
|
||||
finally terminates your license, and (b) permanently, if the copyright
|
||||
holder fails to notify you of the violation by some reasonable means
|
||||
prior to 60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is
|
||||
reinstated permanently if the copyright holder notifies you of the
|
||||
violation by some reasonable means, this is the first time you have
|
||||
received notice of violation of this License (for any work) from that
|
||||
copyright holder, and you cure the violation prior to 30 days after
|
||||
your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the
|
||||
licenses of parties who have received copies or rights from you under
|
||||
this License. If your rights have been terminated and not permanently
|
||||
reinstated, you do not qualify to receive new licenses for the same
|
||||
material under section 10.
|
||||
|
||||
9. Acceptance Not Required for Having Copies.
|
||||
|
||||
You are not required to accept this License in order to receive or
|
||||
run a copy of the Program. Ancillary propagation of a covered work
|
||||
occurring solely as a consequence of using peer-to-peer transmission
|
||||
to receive a copy likewise does not require acceptance. However,
|
||||
nothing other than this License grants you permission to propagate or
|
||||
modify any covered work. These actions infringe copyright if you do
|
||||
not accept this License. Therefore, by modifying or propagating a
|
||||
covered work, you indicate your acceptance of this License to do so.
|
||||
|
||||
10. Automatic Licensing of Downstream Recipients.
|
||||
|
||||
Each time you convey a covered work, the recipient automatically
|
||||
receives a license from the original licensors, to run, modify and
|
||||
propagate that work, subject to this License. You are not responsible
|
||||
for enforcing compliance by third parties with this License.
|
||||
|
||||
An "entity transaction" is a transaction transferring control of an
|
||||
organization, or substantially all assets of one, or subdividing an
|
||||
organization, or merging organizations. If propagation of a covered
|
||||
work results from an entity transaction, each party to that
|
||||
transaction who receives a copy of the work also receives whatever
|
||||
licenses to the work the party's predecessor in interest had or could
|
||||
give under the previous paragraph, plus a right to possession of the
|
||||
Corresponding Source of the work from the predecessor in interest, if
|
||||
the predecessor has it or can get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the
|
||||
rights granted or affirmed under this License. For example, you may
|
||||
not impose a license fee, royalty, or other charge for exercise of
|
||||
rights granted under this License, and you may not initiate litigation
|
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||
any patent claim is infringed by making, using, selling, offering for
|
||||
sale, or importing the Program or any portion of it.
|
||||
|
||||
11. Patents.
|
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this
|
||||
License of the Program or a work on which the Program is based. The
|
||||
work thus licensed is called the contributor's "contributor version".
|
||||
|
||||
A contributor's "essential patent claims" are all patent claims
|
||||
owned or controlled by the contributor, whether already acquired or
|
||||
hereafter acquired, that would be infringed by some manner, permitted
|
||||
by this License, of making, using, or selling its contributor version,
|
||||
but do not include claims that would be infringed only as a
|
||||
consequence of further modification of the contributor version. For
|
||||
purposes of this definition, "control" includes the right to grant
|
||||
patent sublicenses in a manner consistent with the requirements of
|
||||
this License.
|
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||
patent license under the contributor's essential patent claims, to
|
||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||
propagate the contents of its contributor version.
|
||||
|
||||
In the following three paragraphs, a "patent license" is any express
|
||||
agreement or commitment, however denominated, not to enforce a patent
|
||||
(such as an express permission to practice a patent or covenant not to
|
||||
sue for patent infringement). To "grant" such a patent license to a
|
||||
party means to make such an agreement or commitment not to enforce a
|
||||
patent against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license,
|
||||
and the Corresponding Source of the work is not available for anyone
|
||||
to copy, free of charge and under the terms of this License, through a
|
||||
publicly available network server or other readily accessible means,
|
||||
then you must either (1) cause the Corresponding Source to be so
|
||||
available, or (2) arrange to deprive yourself of the benefit of the
|
||||
patent license for this particular work, or (3) arrange, in a manner
|
||||
consistent with the requirements of this License, to extend the patent
|
||||
license to downstream recipients. "Knowingly relying" means you have
|
||||
actual knowledge that, but for the patent license, your conveying the
|
||||
covered work in a country, or your recipient's use of the covered work
|
||||
in a country, would infringe one or more identifiable patents in that
|
||||
country that you have reason to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or
|
||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||
covered work, and grant a patent license to some of the parties
|
||||
receiving the covered work authorizing them to use, propagate, modify
|
||||
or convey a specific copy of the covered work, then the patent license
|
||||
you grant is automatically extended to all recipients of the covered
|
||||
work and works based on it.
|
||||
|
||||
A patent license is "discriminatory" if it does not include within
|
||||
the scope of its coverage, prohibits the exercise of, or is
|
||||
conditioned on the non-exercise of one or more of the rights that are
|
||||
specifically granted under this License. You may not convey a covered
|
||||
work if you are a party to an arrangement with a third party that is
|
||||
in the business of distributing software, under which you make payment
|
||||
to the third party based on the extent of your activity of conveying
|
||||
the work, and under which the third party grants, to any of the
|
||||
parties who would receive the covered work from you, a discriminatory
|
||||
patent license (a) in connection with copies of the covered work
|
||||
conveyed by you (or copies made from those copies), or (b) primarily
|
||||
for and in connection with specific products or compilations that
|
||||
contain the covered work, unless you entered into that arrangement,
|
||||
or that patent license was granted, prior to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting
|
||||
any implied license or other defenses to infringement that may
|
||||
otherwise be available to you under applicable patent law.
|
||||
|
||||
12. No Surrender of Others' Freedom.
|
||||
|
||||
If conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot convey a
|
||||
covered work so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you may
|
||||
not convey it at all. For example, if you agree to terms that obligate you
|
||||
to collect a royalty for further conveying from those to whom you convey
|
||||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.
|
||||
|
||||
13. Remote Network Interaction; Use with the GNU General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, if you modify the
|
||||
Program, your modified version must prominently offer all users
|
||||
interacting with it remotely through a computer network (if your version
|
||||
supports such interaction) an opportunity to receive the Corresponding
|
||||
Source of your version by providing access to the Corresponding Source
|
||||
from a network server at no charge, through some standard or customary
|
||||
means of facilitating copying of software. This Corresponding Source
|
||||
shall include the Corresponding Source for any work covered by version 3
|
||||
of the GNU General Public License that is incorporated pursuant to the
|
||||
following paragraph.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the work with which it is combined will remain governed by version
|
||||
3 of the GNU General Public License.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU Affero General Public License from time to time. Such new versions
|
||||
will be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Program specifies that a certain numbered version of the GNU Affero General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU Affero General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future
|
||||
versions of the GNU Affero General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
Later license versions may give you additional or different
|
||||
permissions. However, no additional obligations are imposed on any
|
||||
author or copyright holder as a result of your choosing to follow a
|
||||
later version.
|
||||
|
||||
15. Disclaimer of Warranty.
|
||||
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. Limitation of Liability.
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGES.
|
||||
|
||||
17. Interpretation of Sections 15 and 16.
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided
|
||||
above cannot be given local legal effect according to their terms,
|
||||
reviewing courts shall apply local law that most closely approximates
|
||||
an absolute waiver of all civil liability in connection with the
|
||||
Program, unless a warranty or assumption of liability accompanies a
|
||||
copy of the Program in return for a fee.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
state the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If your software can interact with users remotely through a computer
|
||||
network, you should also make sure that it provides a way for users to
|
||||
get its source. For example, if your program is a web application, its
|
||||
interface could display a "Source" link that leads users to an archive
|
||||
of the code. There are many ways you could offer source, and different
|
||||
solutions will be better for different programs; see section 13 for the
|
||||
specific requirements.
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU AGPL, see
|
||||
<http://www.gnu.org/licenses/>.
|
1
MP_verify_qSyvBPhDsPdxvOhC.txt
Normal file
@ -0,0 +1 @@
|
||||
qSyvBPhDsPdxvOhC
|
218
README.md
Normal file
@ -0,0 +1,218 @@
|
||||
## 🔥 Lilishop B2B2C商城系统
|
||||
|
||||
##### 🌹 开源不易,如有帮助请点Star
|
||||
|
||||
|
||||
|
||||
#### 欢迎交流需求,交流业务,交流技术(基础问题自行解决,进群先看文档后提问)
|
||||
|
||||
##### 交流 qq 1群 961316482(已满)
|
||||
|
||||
##### 交流 qq 2群 875294241
|
||||
|
||||
|
||||
|
||||
##### 商城 公众号/小程序体验,扫描二维码
|
||||
|
||||

|
||||
|
||||
[](https://gitee.com/beijing_hongye_huicheng/lilishop/stargazers)
|
||||

|
||||
|
||||
|
||||
### 🔥 商城介绍
|
||||
**官网**:https://pickmall.cn
|
||||
|
||||
Lilishop 商城系统 基于SpringBoot 研发,B2B2C多用户商城系统,前端使用 Vue、uniapp开发 **系统全端全部代码开源**
|
||||
|
||||
业务兼容O2O商城/B2B商城/B2B2C商城/F2B2C商城/S2B2C商城。支持小程序商城、H5商城、APP商城、 PC商城。
|
||||
|
||||
|
||||
商城前后端分离、支持分布式部署。
|
||||
|
||||
商城包含 会员模块、**第三方登录模块**、**第三方支付模块**、**楼层装修模块**、订单模块、分销模块、文章模块、系统设置模块、流量分析模块
|
||||
|
||||
商城包含各种中间件、搜索引擎、多级缓存、分布式事务、分布式任务调度等,支持Docker,支持k8s。是一款高性能,支持高并发的商城系统。
|
||||
|
||||
##### 商城 API/消费者 聚合版
|
||||
api不需要单独部署,只需启动一个jar包就可以正常运转 如有需要,可以点击跳转https://gitee.com/beijing_hongye_huicheng/lilishop-simplify
|
||||
|
||||
### ☃️ 商城 开发/使用/常见问题 帮助文档
|
||||
|
||||
https://docs.pickmall.cn
|
||||
|
||||
### 💧 开源商城项目地址(gitee)
|
||||
|
||||
**API(商城所有API)**:https://gitee.com/beijing_hongye_huicheng/lilishop.git
|
||||
|
||||
**UI(商城管理端/商家端/买家PC端)**: https://gitee.com/beijing_hongye_huicheng/lilishop-ui.git
|
||||
|
||||
**uniapp(商城移动端,支持小程序/APP/H5)**:https://gitee.com/beijing_hongye_huicheng/lilishop-uniapp.git
|
||||
|
||||
**docker一键部署(商城部署脚本)**:https://gitee.com/beijing_hongye_huicheng/docker.git
|
||||
|
||||
### 💧 开源商城项目地址(github)
|
||||
|
||||
**API(商城所有API)**:https://github.com/hongyehuicheng/lilishop.git
|
||||
|
||||
**UI(商城管理端/商家端/买家PC端)**: https://github.com/hongyehuicheng/lilishop-ui.git
|
||||
|
||||
**uniapp(商城移动端,支持小程序/APP/H5)**:https://github.com/hongyehuicheng/lilishop-uniapp.git
|
||||
|
||||
**docker一键部署(商城部署脚本)**:https://github.com/hongyehuicheng/docker.git
|
||||
|
||||
|
||||
☃️ UI 项目下3个文件夹 buyer:买家PC端,seller:商家端,manager:后台管理端
|
||||
|
||||
|
||||
### 💧 演示地址(手机验证码为 ‘111111’)
|
||||
|
||||
**商城管理端**:https://admin-b2b2c.pickmall.cn 账号:admin/123456
|
||||
|
||||
**商城店铺后台**:https://store-b2b2c.pickmall.cn 账号:13011111111/111111
|
||||
|
||||
**商城PC页面**:https://pc-b2b2c.pickmall.cn
|
||||
|
||||
**商城移动端(请使浏览器手机模式,或者用手机浏览器打开)**:https://m-b2b2c.pickmall.cn
|
||||
|
||||
**小程序/公众号**:扫描二维码
|
||||
|
||||

|
||||
|
||||
### 🚙 3行命令搭建本地商城(注:只能本机访问,如需调整,请自行操作镜像)
|
||||
|
||||
温馨提示:由于服务中间件较多,如果笔记本环境启动内存没有32g可能无法启动成功(macbookpro 2020 16g内存启动无法成功),台式机在16g内存、AMD 3700x 的ubuntu系统成功运行。
|
||||
|
||||
|
||||
|
||||
##### docker环境安装 [点击跳转](https://docs.pickmall.cn/deploy/%E8%BF%90%E8%A1%8C%E7%8E%AF%E5%A2%83%E5%87%86%E5%A4%87.html)
|
||||
|
||||
|
||||
|
||||
##### 下载docker-compose脚本
|
||||
`git clone https://gitee.com/beijing_hongye_huicheng/docker.git `
|
||||
|
||||
##### 部署商城所需中间件
|
||||
`docker-compose up -d`
|
||||
|
||||
##### 部署商城应用
|
||||
`docker-compose -f docker-compose-application.yml up -d`
|
||||
|
||||
|
||||
|
||||
PS:商城数据库单独部署 https://gitee.com/beijing_hongye_huicheng/docker/tree/master/init/mysql 这里有与tag版本一致的sql,根据tag获取sql,如果使用master代码,则需要在lilishop项目根目录的DB目录中,获取对应的升级sql。
|
||||
|
||||
##### 商城 API/UI 地址
|
||||
|
||||
| API | 地址 |
|
||||
| -------------- | --------------- |
|
||||
| 商城买家API | http://127.0.0.1:8888 |
|
||||
| 商城商家API | http://127.0.0.1:8889 |
|
||||
| 商城管理端API | http://127.0.0.1:8887 |
|
||||
| 商城基础API | http://127.0.0.1:8890 |
|
||||
|
||||
| 前端演示 | 地址 |
|
||||
| -------------- | --------------- |
|
||||
| 商城PC端 | http://127.0.0.1:10000 |
|
||||
| 商城WAP端 | http://127.0.0.1:10001 |
|
||||
| 商城卖家端 | http://127.0.0.1:10002 |
|
||||
| 商城管理端 | http://127.0.0.1:10003 |
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
### ⚾️ 功能列表
|
||||
|
||||
|
||||
|
||||
#### 🥎 商城平台功能
|
||||
|
||||

|
||||
|
||||
|
||||
|
||||
#### 🥎 商城卖家功能
|
||||
|
||||

|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
### 🧩 商城前端功能展示
|
||||
|
||||
|
||||
|
||||
#### ⚽️ 商城移动端
|
||||
|
||||
<img src="https://pickmall.cn/assets/imgs/other/app.gif" alt="移动端功能展示" style="zoom:50%;" />
|
||||
|
||||
|
||||
|
||||
#### ⚽️ 商城管理端
|
||||
|
||||

|
||||
|
||||
|
||||
### 商城技术选型
|
||||
|
||||
#### 🥅 架构图
|
||||
|
||||

|
||||
|
||||
##### 🕹 后台技术选型
|
||||
|
||||
| 说明 | 框架 | 说明 | |
|
||||
| -------------- | --------------- | -------------- | ------------- |
|
||||
| 基础框架 | Spring Boot | MVC框架 | Spring MVC |
|
||||
| 持久框架 | Mybatis-Plus | 程序构建 | Maven |
|
||||
| 关系型数据库 | MySQL | 消息中间件AMQP | RocketMQ |
|
||||
| 缓存 | Redis +MongoDB | 搜索引擎 | Elasticsearch |
|
||||
| 安全框架 | Spring Security | 数据库连接池 | Druid |
|
||||
| 数据库分库分表 | sharding | 定时任务 | xxl-job |
|
||||
| 负载均衡 | Nginx | 静态资源 | 阿里云OSS |
|
||||
| 短信 | 阿里云短信 | 认证 | JWT |
|
||||
| 日志处理 | Log4j | 接口规范 | RESTful |
|
||||
|
||||
##### 🖥 前端-运营后台、店铺后台
|
||||
|
||||
| 说明 | 框架 | 说明 | 框架 |
|
||||
| ---------- | ---------- | ---------- | ------- |
|
||||
| 构建工具 | webpack | JS版本 | ES6 |
|
||||
| 基础JS框架 | Vue.js | 视频播放器 | Dplayer |
|
||||
| 路由管理 | Vue Router | 状态管理 | Vuex |
|
||||
| 基础UI库 | iView | UI界面基于 | iView |
|
||||
| 网络请求 | axios | | |
|
||||
|
||||
##### 📱前端-移动端
|
||||
|
||||
| 说明 | 架构 | 说明 | 架构 |
|
||||
| --------- | ------- | -------- | ------- |
|
||||
| 基础UI库 | uViewui | 基础框架 | uni-app |
|
||||
| CSS预处理 | scss | 地图引擎 | amap |
|
||||
|
||||
### 🌟 版本升级
|
||||
|
||||
```
|
||||
商城后续会持续版本升级,修复bug,完善功能,覆盖更多业务场景 o2o/b2b/s2b2b2c/跨境电商
|
||||
|
||||
后续会考虑推出微服务商城系统/商城中台等
|
||||
```
|
||||
|
||||
|
||||
### ⚠️ 开源须知
|
||||
1.仅允许用于个人学习研究使用.
|
||||
|
||||
2.禁止将本开源的代码和资源进行任何形式任何名义的出售.
|
||||
|
||||
3.软件受国家计算机软件著作权保护(登记号:2021SR0805085)。
|
||||
|
||||
4.限制商用,如果需要商业使用请联系我们。QQ3409056806.
|
||||
|
||||
|
||||
### 🐧 交流群
|
||||
|
||||
##### 官方qq 1群 961316482(已满)
|
||||
##### 官方qq 2群 875294241
|
||||
|
115
api/address.js
Normal file
@ -0,0 +1,115 @@
|
||||
/**
|
||||
* 收货地址相关API
|
||||
*/
|
||||
|
||||
import { http, Method } from "@/utils/request.js";
|
||||
|
||||
import api from "@/config/api.js";
|
||||
|
||||
/**
|
||||
* 获取收货地址列表
|
||||
* @returns {AxiosPromise}
|
||||
*/
|
||||
export function getAddressList(pageNumber, pageSize) {
|
||||
return http.request({
|
||||
url: "/member/address",
|
||||
method: Method.GET,
|
||||
needToken: true,
|
||||
params: { pageNumber, pageSize },
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取物流公司
|
||||
*/
|
||||
export function getLogistics() {
|
||||
return http.request({
|
||||
url: "/logistics",
|
||||
method: Method.GET,
|
||||
needToken: true,
|
||||
params: { pageNumber: 1, pageSize: 200, disabled: "OPEN" },
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过cityCode获取地区代码
|
||||
*/
|
||||
export function getAddressCode(cityCode, townName) {
|
||||
return http.request({
|
||||
url: api.common + "/common/region/region",
|
||||
method: Method.GET,
|
||||
needToken: true,
|
||||
params: { cityCode, townName },
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加收货地址
|
||||
* @param params 地址参数
|
||||
* @returns {AxiosPromise}
|
||||
*/
|
||||
export function addAddress(data) {
|
||||
return http.request({
|
||||
url: "/member/address",
|
||||
method: Method.POST,
|
||||
needToken: true,
|
||||
header: { "content-type": "application/x-www-form-urlencoded" },
|
||||
data: data,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 编辑地址
|
||||
* @param id 地址ID
|
||||
* @param params 地址参数
|
||||
* @returns {AxiosPromise}
|
||||
*/
|
||||
export function editAddress(params) {
|
||||
return http.request({
|
||||
url: `/member/address`,
|
||||
method: Method.PUT,
|
||||
needToken: true,
|
||||
header: { "content-type": "application/x-www-form-urlencoded" },
|
||||
data: params,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除收货地址
|
||||
* @param id
|
||||
*/
|
||||
export function deleteAddress(id) {
|
||||
return http.request({
|
||||
url: `/member/address/delById/${id}`,
|
||||
method: Method.DELETE,
|
||||
needToken: true,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 根据ID获取会员收件地址
|
||||
* @param id
|
||||
*/
|
||||
export function getAddressDetail(id) {
|
||||
return http.request({
|
||||
url: `/member/address/get/${id}`,
|
||||
method: Method.GET,
|
||||
loading: false,
|
||||
needToken: true,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
export function getAddressDefault() {
|
||||
return http.request({
|
||||
url: `/member/address/get/default`,
|
||||
method: Method.GET,
|
||||
loading: false,
|
||||
needToken: true,
|
||||
});
|
||||
}
|
220
api/after-sale.js
Normal file
@ -0,0 +1,220 @@
|
||||
/**
|
||||
* 申请售后相关API
|
||||
*/
|
||||
|
||||
import { http, Method } from "@/utils/request.js";
|
||||
|
||||
/**
|
||||
* 获取售后列表
|
||||
* @param params
|
||||
* @returns {AxiosPromise}
|
||||
*/
|
||||
export function getAfterSale(params) {
|
||||
return http.request({
|
||||
url: "after-sales/refunds",
|
||||
method: Method.GET,
|
||||
needToken: true,
|
||||
loading: false,
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/******************* 以下为新方法 ***********************/
|
||||
/**
|
||||
* 申请取消订单
|
||||
* @param params
|
||||
*/
|
||||
export function applyCancelOrder(params) {
|
||||
return http.request({
|
||||
url: "after-sales/apply/cancel/order",
|
||||
method: Method.POST,
|
||||
needToken: true,
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* 获取商家售后收件地址
|
||||
*/
|
||||
export function getStoreAfterSaleAddress(sn) {
|
||||
return http.request({
|
||||
url: `/order/afterSale/getStoreAfterSaleAddress/${sn}`,
|
||||
method: Method.GET,
|
||||
needToken: true,
|
||||
});
|
||||
}
|
||||
/**
|
||||
* 取消售后
|
||||
*/
|
||||
export function cancelAfterSale(afterSaleSn) {
|
||||
return http.request({
|
||||
url: `/order/afterSale/cancel/${afterSaleSn}`,
|
||||
method: Method.POST,
|
||||
needToken: true,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 获取售后服务记录相关数据
|
||||
* @param params 参数
|
||||
*/
|
||||
export function getAfterSaleList(params) {
|
||||
return http.request({
|
||||
url: `/order/afterSale/page`,
|
||||
method: Method.GET,
|
||||
needToken: true,
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 查看售后服务详情
|
||||
* @param sn 售后服务单编号
|
||||
*/
|
||||
export function getServiceDetail(sn) {
|
||||
return http.request({
|
||||
url: `/order/afterSale/get/${sn}`,
|
||||
method: Method.GET,
|
||||
needToken: true,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 添加投诉
|
||||
*/
|
||||
export function addComplain(params) {
|
||||
return http.request({
|
||||
url: `/order/complain`,
|
||||
method: Method.POST,
|
||||
needToken: true,
|
||||
header: {
|
||||
"Content-Type": "application/x-www-form-urlencoded",
|
||||
},
|
||||
data: params,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 取消投诉
|
||||
*/
|
||||
export function clearComplain(id, params) {
|
||||
return http.request({
|
||||
url: `/order/complain/status/${id}`,
|
||||
method: Method.PUT,
|
||||
needToken: true,
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 取消投诉
|
||||
*/
|
||||
export function getAfterSaleLog(sn) {
|
||||
return http.request({
|
||||
url: `/order/afterSale/get/getAfterSaleLog/${sn}`,
|
||||
method: Method.GET,
|
||||
needToken: true,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 投诉列表
|
||||
*/
|
||||
export function getComplain(params) {
|
||||
return http.request({
|
||||
url: `/order/complain`,
|
||||
method: Method.GET,
|
||||
needToken: true,
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取申请原因
|
||||
*/
|
||||
export function getAfterSaleReason(serviceType) {
|
||||
return http.request({
|
||||
url: `/order/afterSale/get/afterSaleReason/${serviceType}`,
|
||||
method: Method.GET,
|
||||
needToken: true,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取取消原因
|
||||
*/
|
||||
export function getClearReason() {
|
||||
return http.request({
|
||||
url: `/order/afterSale/get/afterSaleReason/CANCEL`,
|
||||
method: Method.GET,
|
||||
needToken: true,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取投诉原因
|
||||
*/
|
||||
export function getComplainReason() {
|
||||
return http.request({
|
||||
url: `/order/afterSale/get/afterSaleReason/COMPLAIN`,
|
||||
method: Method.GET,
|
||||
needToken: true,
|
||||
});
|
||||
}
|
||||
/**
|
||||
* 获取投诉详情
|
||||
*/
|
||||
export function getComplainDetail(id) {
|
||||
return http.request({
|
||||
url: `/order/complain/${id}`,
|
||||
method: Method.GET,
|
||||
needToken: true,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取申请售后页面信息
|
||||
*/
|
||||
export function getAfterSaleInfo(sn) {
|
||||
return http.request({
|
||||
url: `/order/afterSale/applyAfterSaleInfo/${sn}`,
|
||||
method: Method.GET,
|
||||
needToken: true,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 申请退货服务
|
||||
* @param params
|
||||
*/
|
||||
export function applyReturn(orderItemSn, params) {
|
||||
return http.request({
|
||||
url: `/order/afterSale/save/${orderItemSn}`,
|
||||
method: Method.POST,
|
||||
header: {
|
||||
"Content-Type": "application/x-www-form-urlencoded",
|
||||
},
|
||||
data: params,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 填充物流信息
|
||||
* @param afterSaleSn 售后服务单号
|
||||
* @param params 参数信息
|
||||
*/
|
||||
export function fillShipInfo(afterSaleSn, params) {
|
||||
return http.request({
|
||||
url: `/order/afterSale/delivery/${afterSaleSn}`,
|
||||
method: Method.POST,
|
||||
header: {
|
||||
"Content-Type": "application/x-www-form-urlencoded",
|
||||
},
|
||||
data: params,
|
||||
});
|
||||
}
|
30
api/article.js
Normal file
@ -0,0 +1,30 @@
|
||||
/**
|
||||
* 文章相关API
|
||||
*/
|
||||
|
||||
import {http,Method} from '@/utils/request.js';
|
||||
import api from '@/config/api.js';
|
||||
|
||||
/**
|
||||
* 获取某个分类的文章列表
|
||||
* @param category_type
|
||||
*/
|
||||
export function getArticleCategory(category_type) {
|
||||
return http.request({
|
||||
url: `${api.base}/pages/article-categories`,
|
||||
method: Method.GET,
|
||||
params: {category_type},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取文章详情
|
||||
* @param type
|
||||
*/
|
||||
export function getArticleDetail(type) {
|
||||
return http.request({
|
||||
url: `/other/article/get/${type}`,
|
||||
method: Method.GET,
|
||||
});
|
||||
}
|
||||
|
32
api/common.js
Normal file
@ -0,0 +1,32 @@
|
||||
/**
|
||||
* 公共API
|
||||
*/
|
||||
import { http, Method } from "@/utils/request.js";
|
||||
import api from "@/config/api.js";
|
||||
|
||||
/**
|
||||
* 获取地区数据
|
||||
* @param id
|
||||
*/
|
||||
export function getRegionsById(id = 0) {
|
||||
return http.request({
|
||||
url: `${api.common}/common/region/item/${id}`,
|
||||
method: Method.GET,
|
||||
message: false,
|
||||
});
|
||||
}
|
||||
|
||||
// 获取IM接口前缀
|
||||
export function getIMDetail() {
|
||||
return http.request({
|
||||
url: `${api.common}/IM`,
|
||||
method: Method.GET,
|
||||
message: false,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 文件上传地址
|
||||
* @type {string}
|
||||
*/
|
||||
export const upload = api.common + "/common/upload/file";
|
61
api/connect.js
Normal file
@ -0,0 +1,61 @@
|
||||
/**
|
||||
* 信任登录相关API
|
||||
*/
|
||||
|
||||
import {
|
||||
http,
|
||||
Method
|
||||
} from '@/utils/request.js';
|
||||
const request = http.request;
|
||||
|
||||
|
||||
/**
|
||||
* web 第三方登录
|
||||
* @param {Object} code
|
||||
*/
|
||||
export function webConnect(code) {
|
||||
return http.request({
|
||||
url: `passport/connect/connect/login/web/${code}`,
|
||||
method: Method.GET,
|
||||
needToken: true,
|
||||
header: {
|
||||
"clientType": "H5"
|
||||
}
|
||||
});
|
||||
}
|
||||
export function openIdLogin(params, clientType) {
|
||||
return http.request({
|
||||
url: `passport/connect/connect/app/login`,
|
||||
method: Method.GET,
|
||||
needToken: true,
|
||||
data: params,
|
||||
header: {
|
||||
"clientType": clientType
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 第三方登录成功 回调接口
|
||||
*/
|
||||
export function loginCallback(state) {
|
||||
return http.request({
|
||||
url: `passport/connect/connect/result?state=${state}`,
|
||||
method: Method.GET,
|
||||
needToken: false
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 小程序自动登录
|
||||
* @param params
|
||||
*/
|
||||
export function mpAutoLogin(params) {
|
||||
return http.request({
|
||||
url: 'passport/connect/miniProgram/auto-login',
|
||||
method: Method.GET,
|
||||
params
|
||||
});
|
||||
}
|
220
api/goods.js
Normal file
@ -0,0 +1,220 @@
|
||||
/**
|
||||
* 商品相关API
|
||||
*/
|
||||
|
||||
import { http, Method } from "@/utils/request.js";
|
||||
|
||||
/**
|
||||
* 从ES中获取相关商品品牌名称,分类名称及属性
|
||||
*/
|
||||
export function getGoodsMessage(goodsId) {
|
||||
return http.request({
|
||||
url: `/goods/goods/get/${goodsId}`,
|
||||
method: Method.GET,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 从ES中获取相关商品品牌名称,分类名称及属性
|
||||
*/
|
||||
export function getGoodsRelated(params) {
|
||||
return http.request({
|
||||
url: `/goods/goods/es/related`,
|
||||
method: Method.GET,
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取商品详情
|
||||
* @param skuId 商品ID
|
||||
* @returns {AxiosPromise}
|
||||
*/
|
||||
export function getGoods(skuId, goodsId) {
|
||||
return http.request({
|
||||
url: `/goods/goods/sku/${goodsId}/${skuId}`,
|
||||
method: Method.GET,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取商品分销
|
||||
* @param distributionId 商品分销ID
|
||||
*/
|
||||
export function getGoodsDistribution(distributionId) {
|
||||
return http.request({
|
||||
url: `/distribution/distribution/bindingDistribution/${distributionId}`,
|
||||
method: Method.GET,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取商品列表
|
||||
* @param params
|
||||
* @returns {AxiosPromise}
|
||||
*/
|
||||
export function getGoodsList(params) {
|
||||
return http.request({
|
||||
url: "/goods/goods/es",
|
||||
method: Method.GET,
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取上新商品列表
|
||||
* @param params
|
||||
* @returns {AxiosPromise}
|
||||
*/
|
||||
export function getGoodsListUplog(params) {
|
||||
return http.request({
|
||||
url: "goods/search/uplog",
|
||||
method: Method.GET,
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 获取标签商品
|
||||
* @param storeId 卖家id
|
||||
* @param mark 标签 hot:热卖 new:新品 recommend:推荐
|
||||
* @param num 获取个数
|
||||
*/
|
||||
export function getTagGoods(storeId, mark = "hot", num = 5) {
|
||||
return http.request({
|
||||
url: `goods/tags/${mark}/goods`,
|
||||
method: Method.GET,
|
||||
loading: false,
|
||||
params: {
|
||||
storeId,
|
||||
mark,
|
||||
num,
|
||||
},
|
||||
});
|
||||
}
|
||||
/**
|
||||
* 获取标签商品
|
||||
*/
|
||||
export function getPlateformTagGoods(tag_id) {
|
||||
return http.request({
|
||||
url: `goods/tags/byid/${tag_id}`,
|
||||
method: Method.GET,
|
||||
loading: false,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取首页商品分类 左侧列表
|
||||
* @param parent_id
|
||||
*/
|
||||
export function getCategoryList(id) {
|
||||
return http.request({
|
||||
url: `/goods/category/get/${id}`,
|
||||
method: Method.GET,
|
||||
loading: false,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 获取当前会员的分销商信息 可根据分销商信息查询待提现金额以及冻结金额等信息
|
||||
*/
|
||||
export function distribution() {
|
||||
return http.request({
|
||||
url: `/distribution/distribution`,
|
||||
method: Method.GET,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 申请分销商
|
||||
*/
|
||||
export function applyDistribution(params) {
|
||||
return http.request({
|
||||
url: `/distribution/distribution`,
|
||||
method: Method.POST,
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 分销商提现
|
||||
*/
|
||||
export function cash(params) {
|
||||
return http.request({
|
||||
url: `/distribution/cash`,
|
||||
method: Method.POST,
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 分销商提现历史
|
||||
*/
|
||||
export function cashLog(params) {
|
||||
return http.request({
|
||||
url: `/distribution/cash`,
|
||||
method: Method.GET,
|
||||
params
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取分销商分页订单列表
|
||||
*/
|
||||
export function distributionOrderList(params) {
|
||||
return http.request({
|
||||
url: `/distribution/distribution/distributionOrder`,
|
||||
method: Method.GET,
|
||||
params
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取分销商商品列表
|
||||
*/
|
||||
export function distributionGoods(params) {
|
||||
return http.request({
|
||||
url: `/distribution/goods`,
|
||||
method: Method.GET,
|
||||
params,
|
||||
});
|
||||
}
|
||||
/**
|
||||
* 选择分销商品 分销商品id
|
||||
*/
|
||||
export function checkedDistributionGoods(params) {
|
||||
return http.request({
|
||||
url: `/distribution/goods/checked/${params.id}`,
|
||||
method: Method.GET,
|
||||
params
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 小程序码
|
||||
*/
|
||||
export function getMpCode(params){
|
||||
return http.request({
|
||||
url:`/passport/connect/miniProgram/mp/unlimited`,
|
||||
method:Method.GET,
|
||||
params
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据shortlink 获取页面参数
|
||||
*/
|
||||
export function getMpScene(id){
|
||||
return http.request({
|
||||
url:`/passport/connect/miniProgram/mp/unlimited/scene?id=${id}`,
|
||||
method:Method.GET,
|
||||
|
||||
})
|
||||
}
|
62
api/home.js
Normal file
@ -0,0 +1,62 @@
|
||||
import { http, Method } from "@/utils/request.js";
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 获取广告图
|
||||
*/
|
||||
export function getAdvertisement() {
|
||||
return http.request({
|
||||
url: "/advertisement",
|
||||
method: Method.GET,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 获取首页商品分类
|
||||
* @param parent_id
|
||||
*/
|
||||
export function getCategory(parent_id = 0) {
|
||||
return http.request({
|
||||
url: `goods/categories/${parent_id}/children`,
|
||||
method: Method.GET,
|
||||
loading: false,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取热门关键词
|
||||
* @param num
|
||||
*/
|
||||
export function getHotKeywords(count) {
|
||||
return http.request({
|
||||
url: "/goods/goods/hot-words",
|
||||
method: Method.GET,
|
||||
loading: false,
|
||||
params: { count },
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取楼层数据
|
||||
* @param client_type
|
||||
* @param page_type
|
||||
*/
|
||||
export function getFloorData() {
|
||||
return http.request({
|
||||
url: `/other/pageData/getIndex?clientType=H5`,
|
||||
method: "get",
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取获取首页分类数据
|
||||
*/
|
||||
export function getCategoryIndexData(parentId = 0) {
|
||||
return http.request({
|
||||
url: `/goods/category/get/${parentId}`,
|
||||
method: "get",
|
||||
});
|
||||
}
|
76
api/login.js
Normal file
@ -0,0 +1,76 @@
|
||||
import { http } from "@/utils/request.js";
|
||||
|
||||
import api from "@/config/api.js";
|
||||
|
||||
/**
|
||||
* 通过短信重置密码
|
||||
* @param mobile
|
||||
*/
|
||||
export function resetByMobile(params) {
|
||||
return http.request({
|
||||
url: `/passport/member/resetByMobile`,
|
||||
method: "POST",
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送验证码
|
||||
* @param mobile
|
||||
*/
|
||||
export function sendMobile(mobile,type='LOGIN') {
|
||||
return http.request({
|
||||
url: `${api.common}/common/sms/${type}/${mobile}`,
|
||||
method: "GET",
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 短信登录
|
||||
* @param mobile
|
||||
* @param smsCode
|
||||
*/
|
||||
export function smsLogin(params, clientType) {
|
||||
return http.request({
|
||||
url: `/passport/member/smsLogin`,
|
||||
method: "POST",
|
||||
data: params,
|
||||
header: {
|
||||
"content-type": "application/x-www-form-urlencoded",
|
||||
clientType: clientType,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改密码
|
||||
* @param newPassword
|
||||
* @param password
|
||||
*/
|
||||
|
||||
export function modifyPass(params) {
|
||||
return http.request({
|
||||
url: `/passport/member/modifyPass`,
|
||||
method: "PUT",
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 刷新token
|
||||
*/
|
||||
export function refreshTokenFn(refresh_token) {
|
||||
return http.request({
|
||||
url: `/passport/member/refresh/${refresh_token}`,
|
||||
method: "GET",
|
||||
});
|
||||
}
|
||||
|
||||
// 获取密码状态
|
||||
export function logout () {
|
||||
return http.request({
|
||||
url: '/passport/member/logout',
|
||||
method: "POST",
|
||||
needToken: true,
|
||||
})
|
||||
}
|
421
api/members.js
Normal file
@ -0,0 +1,421 @@
|
||||
import storage from "@/utils/storage";
|
||||
import { http, Method } from "@/utils/request.js";
|
||||
|
||||
/**
|
||||
* 意见反馈
|
||||
*/
|
||||
export function feedBack(params) {
|
||||
return http.request({
|
||||
url: "/other/feedback",
|
||||
method: Method.POST,
|
||||
needToken: true,
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
// 提现
|
||||
export function withdrawalApply(params) {
|
||||
return http.request({
|
||||
url: "/wallet/wallet/withdrawal",
|
||||
method: Method.POST,
|
||||
needToken: true,
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 支付结果查询
|
||||
* @param orderType 交易类型,可用值:TRADE,ORDER,RECHARGE
|
||||
* @param sn 订单编号
|
||||
*/
|
||||
export function payCallback(params) {
|
||||
return http.request({
|
||||
url: `/payment/cashier/result`,
|
||||
method: Method.GET,
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
// 在线充值
|
||||
export function recharge(params) {
|
||||
return http.request({
|
||||
url: "/trade/recharge",
|
||||
method: Method.POST,
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 分页获取预存款充值记录
|
||||
* @param params
|
||||
*/
|
||||
export function getUserRecharge(params) {
|
||||
return http.request({
|
||||
url: "/member/recharge",
|
||||
method: Method.GET,
|
||||
needToken: true,
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 分页获取预存款充值记录
|
||||
* @param params
|
||||
*/
|
||||
export function getWalletLog(params) {
|
||||
return http.request({
|
||||
url: "/wallet/log",
|
||||
method: Method.GET,
|
||||
needToken: true,
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取优惠券列表
|
||||
* @param params
|
||||
*/
|
||||
export function getCoupons(params) {
|
||||
return http.request({
|
||||
url: "/promotion/coupon/getCoupons",
|
||||
method: Method.GET,
|
||||
needToken: true,
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 领取优惠券
|
||||
* @param couponId
|
||||
*/
|
||||
export function receiveCoupons(couponId) {
|
||||
return http.request({
|
||||
url: `/promotion/coupon/receive/${couponId}`,
|
||||
method: Method.GET,
|
||||
needToken: true,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取积分明细数据
|
||||
* @param params
|
||||
* @returns {AxiosPromise}
|
||||
*/
|
||||
export function getPointsData(params) {
|
||||
return http.request({
|
||||
url: "member/memberPointsHistory/getByPage",
|
||||
method: Method.GET,
|
||||
needToken: true,
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取我的评论列表
|
||||
* @param params
|
||||
* @returns {AxiosPromise}
|
||||
*/
|
||||
export function getComments(params) {
|
||||
return http.request({
|
||||
url: "/member/evaluation",
|
||||
method: Method.GET,
|
||||
needToken: true,
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前会员的浏览数量是多少
|
||||
* @param params
|
||||
* @returns {AxiosPromise}
|
||||
*/
|
||||
export function getFootprintNum(params) {
|
||||
return http.request({
|
||||
url: "/member/footprint/getFootprintNum",
|
||||
method: Method.GET,
|
||||
needToken: true,
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 订单评论
|
||||
* @param params
|
||||
*/
|
||||
export function commentsMemberOrder(params) {
|
||||
return http.request({
|
||||
url: "/member/evaluation",
|
||||
method: Method.POST,
|
||||
needToken: true,
|
||||
header: { "content-type": "application/x-www-form-urlencoded" },
|
||||
data: params,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 追加评论
|
||||
* @param params
|
||||
*/
|
||||
export function AppendCommentsOrder(params) {
|
||||
return http.request({
|
||||
url: "members/comments/additional",
|
||||
method: Method.POST,
|
||||
needToken: true,
|
||||
header: { "content-type": "application/x-www-form-urlencoded" },
|
||||
data: params,
|
||||
});
|
||||
}
|
||||
|
||||
// TODO 第一版本暂未实现此功能
|
||||
/**
|
||||
* 商品咨询
|
||||
* @param goods_id
|
||||
* @param ask_content
|
||||
*/
|
||||
export function consultating(goods_id, ask_content, anonymous) {
|
||||
return http.request({
|
||||
url: "members/asks",
|
||||
method: Method.POST,
|
||||
header: { "content-type": "application/x-www-form-urlencoded" },
|
||||
needToken: true,
|
||||
data: {
|
||||
goods_id,
|
||||
ask_content,
|
||||
anonymous,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取商品收藏
|
||||
* @param params
|
||||
* @returns {AxiosPromise}
|
||||
*/
|
||||
export function getGoodsCollection(params, type) {
|
||||
return http.request({
|
||||
url: `/member/collection/${type}`,
|
||||
method: Method.GET,
|
||||
needToken: true,
|
||||
loading: false,
|
||||
message: false,
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 收藏商品
|
||||
* @returns {AxiosPromise}
|
||||
*/
|
||||
export function collectionGoods(type, id) {
|
||||
return http.request({
|
||||
url: `/member/collection/add/${type}/${id}`,
|
||||
method: Method.POST,
|
||||
needToken: true,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除商品收藏
|
||||
* @param ids 收藏ID【集合或单个商品ID】
|
||||
* @returns {AxiosPromise}
|
||||
*/
|
||||
export function deleteGoodsCollection(ids) {
|
||||
if (Array.isArray(ids)) {
|
||||
ids = ids.join(",");
|
||||
}
|
||||
return http.request({
|
||||
url: `/member/collection/delete/GOODS/${ids}`,
|
||||
method: Method.DELETE,
|
||||
needToken: true,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除店铺收藏
|
||||
* @param store_id
|
||||
*/
|
||||
export function deleteStoreCollection(store_id) {
|
||||
return http.request({
|
||||
url: `/member/collection/delete/STORE/${store_id}`,
|
||||
method: Method.DELETE,
|
||||
needToken: true,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取商品是否被收藏
|
||||
* @param good_id
|
||||
*/
|
||||
export function getGoodsIsCollect(type, good_id) {
|
||||
return http.request({
|
||||
url: `/member/collection/isCollection/${type}/${good_id}`,
|
||||
method: Method.GET,
|
||||
needToken: true,
|
||||
loading: false,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 收藏店铺
|
||||
* @param store_id 店铺ID
|
||||
* @returns {AxiosPromise}
|
||||
*/
|
||||
export function collectionStore(store_id) {
|
||||
return http.request({
|
||||
url: "members/collection/store",
|
||||
header: { "content-type": "application/x-www-form-urlencoded" },
|
||||
method: Method.POST,
|
||||
data: { store_id },
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前登录的用户信息
|
||||
* @returns {AxiosPromise}
|
||||
*/
|
||||
export function getUserInfo() {
|
||||
return http.request({
|
||||
url: "/passport/member",
|
||||
method: Method.GET,
|
||||
needToken: true,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前用户的预存款
|
||||
* @returns {AxiosPromise}
|
||||
*/
|
||||
export function getUserWallet() {
|
||||
return http.request({
|
||||
url: "/wallet/wallet",
|
||||
method: Method.GET,
|
||||
needToken: true,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存用户信息
|
||||
* @param params
|
||||
* @returns {AxiosPromise}
|
||||
*/
|
||||
export function saveUserInfo(params) {
|
||||
return http.request({
|
||||
url: "/passport/member/editOwn",
|
||||
method: Method.PUT,
|
||||
header: { "content-type": "application/x-www-form-urlencoded" },
|
||||
needToken: true,
|
||||
data: params,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加发票
|
||||
* @param params
|
||||
*/
|
||||
export function addReceipt(params) {
|
||||
return http.request({
|
||||
url: "/trade/receipt",
|
||||
method: Method.POST,
|
||||
needToken: true,
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取商品评论列表
|
||||
* @param goodsId
|
||||
* @param params
|
||||
*/
|
||||
export function getGoodsComments(goodsId, params) {
|
||||
return http.request({
|
||||
url: `/member/evaluation/${goodsId}/goodsEvaluation`,
|
||||
method: Method.GET,
|
||||
loading: false,
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取商品评论数量统计
|
||||
* @param goodsId
|
||||
*/
|
||||
export function getGoodsCommentsCount(goodsId) {
|
||||
return http.request({
|
||||
url: `/member/evaluation/${goodsId}/evaluationNumber`,
|
||||
method: Method.GET,
|
||||
loading: false,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取未读消息数量信息
|
||||
*/
|
||||
export function getNoReadMessageNum() {
|
||||
return http.request({
|
||||
url: `members/member-nocice-logs/number`,
|
||||
method: Method.GET,
|
||||
needToken: true,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 我的足迹列表
|
||||
* @param pageNumber pageSize
|
||||
*
|
||||
*/
|
||||
export function myTrackList(params) {
|
||||
return http.request({
|
||||
url: `/member/footprint`,
|
||||
method: Method.GET,
|
||||
needToken: true,
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据id删除会员足迹
|
||||
* @param id
|
||||
*/
|
||||
export function deleteHistoryListId(ids) {
|
||||
return http.request({
|
||||
url: `/member/footprint/delByIds/${ids}`,
|
||||
method: Method.DELETE,
|
||||
needToken: true,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前会员优惠券列表
|
||||
* @param
|
||||
*/
|
||||
export function getMemberCoupons(data) {
|
||||
return http.request({
|
||||
url: `/promotion/coupon/getCoupons`,
|
||||
method: Method.GET,
|
||||
needToken: true,
|
||||
params: data,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前会员可使用的优惠券数量
|
||||
*
|
||||
*/
|
||||
export function getCouponsNum() {
|
||||
return http.request({
|
||||
url: `/promotion/coupon/getCouponsNum`,
|
||||
method: Method.GET,
|
||||
needToken: true,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取会员积分VO
|
||||
* @param
|
||||
*/
|
||||
export function getMemberPointSum() {
|
||||
return http.request({
|
||||
url: `member/memberPointsHistory/getMemberPointsHistoryVO`,
|
||||
method: Method.GET,
|
||||
});
|
||||
}
|
94
api/message.js
Normal file
@ -0,0 +1,94 @@
|
||||
/**
|
||||
* 站内消息相关API
|
||||
*/
|
||||
|
||||
import {http,Method} from '@/utils/request.js';
|
||||
const request = http.request
|
||||
|
||||
import api from '@/config/api.js';
|
||||
|
||||
/**
|
||||
* 获取微信消息订阅
|
||||
* @param params
|
||||
* @returns {AxiosPromise}
|
||||
*/
|
||||
export function getWeChatMpMessage() {
|
||||
return http.request({
|
||||
url: 'mini-program/subscribeMessage',
|
||||
method: Method.GET
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取消息列表
|
||||
* @param params
|
||||
* @returns {AxiosPromise}
|
||||
*/
|
||||
export function getMessages(params) {
|
||||
params = params || {};
|
||||
params.pageSize = params.pageSize || 5;
|
||||
return http.request({
|
||||
url: 'members/member-nocice-logs',
|
||||
method: Method.GET,
|
||||
needToken: true,
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 标记消息为已读
|
||||
* @param ids
|
||||
*/
|
||||
export function messageMarkAsRead(ids) {
|
||||
return http.request({
|
||||
url: `members/member-nocice-logs/${ids}/read`,
|
||||
method: Method.PUT,
|
||||
needToken: true,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取物流消息列表
|
||||
* @param params
|
||||
* @returns {AxiosPromise}
|
||||
*/
|
||||
export function getLogisticsMessages(params) {
|
||||
params = params || {};
|
||||
params.pageSize = params.pageSize || 5;
|
||||
return http.request({
|
||||
url: 'trade/logistics/message',
|
||||
method: Method.GET,
|
||||
needToken: true,
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param appType
|
||||
* @returns {AxiosPromise}
|
||||
*
|
||||
*/
|
||||
export function getAppVersion(appType) {
|
||||
return http.request({
|
||||
url: `/appVersion/${appType}`,
|
||||
method: Method.GET,
|
||||
type:"manager"
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param appType
|
||||
* @returns {AxiosPromise}
|
||||
*
|
||||
*/
|
||||
export function getAppVersionList(type,data) {
|
||||
return http.request({
|
||||
url: `/appVersion/appVersion/${type}`,
|
||||
method: Method.GET,
|
||||
type:"manager",
|
||||
data
|
||||
});
|
||||
}
|
115
api/order.js
Normal file
@ -0,0 +1,115 @@
|
||||
/**
|
||||
* 订单相关API
|
||||
*/
|
||||
|
||||
import { http, Method } from "@/utils/request.js";
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 选择发票
|
||||
* @param params
|
||||
*/
|
||||
export function getReceipt(params) {
|
||||
return http.request({
|
||||
url: "/trade/carts/select/receipt",
|
||||
method: Method.GET,
|
||||
needToken: true,
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 选择发票
|
||||
* @param id
|
||||
*/
|
||||
export function getReceiptDetail(id) {
|
||||
return http.request({
|
||||
url: `/trade/receipt/${id}`,
|
||||
method: Method.GET,
|
||||
needToken: true,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 选择配送方式
|
||||
* @param params
|
||||
*/
|
||||
export function selectedShipMethod(params) {
|
||||
return http.request({
|
||||
url: "/trade/carts/shippingMethod",
|
||||
method: Method.GET,
|
||||
needToken: true,
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取订单列表
|
||||
* @param params
|
||||
*/
|
||||
export function getOrderList(params) {
|
||||
return http.request({
|
||||
url: "/order/order",
|
||||
method: Method.GET,
|
||||
needToken: true,
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取订单详情
|
||||
* @param orderSn 订单编号
|
||||
*/
|
||||
export function getOrderDetail(orderSn) {
|
||||
return http.request({
|
||||
url: `/order/order/${orderSn}`,
|
||||
method: Method.GET,
|
||||
needToken: true,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 取消订单
|
||||
* @param orderSn 订单编号
|
||||
* @param reason 取消原因
|
||||
*/
|
||||
export function cancelOrder(orderSn, reason) {
|
||||
return http.request({
|
||||
url: `/order/order/${orderSn}/cancel`,
|
||||
method: Method.POST,
|
||||
needToken: true,
|
||||
header: { "content-type": "application/x-www-form-urlencoded" },
|
||||
data: reason,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 确认收货
|
||||
* @param orderSn 订单编号
|
||||
*/
|
||||
export function confirmReceipt(orderSn) {
|
||||
return http.request({
|
||||
url: `/order/order/${orderSn}/receiving`,
|
||||
method: Method.POST,
|
||||
needToken: true,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 获取当前拼团订单的拼团分享信息
|
||||
* @param {*} parentOrderSn
|
||||
* @param {*} skuId
|
||||
*/
|
||||
export function getPinTuanShare(parentOrderSn,skuId) {
|
||||
return http.request({
|
||||
url: `promotion/pintuan/share`,
|
||||
method: Method.GET,
|
||||
needToken: true,
|
||||
params:{parentOrderSn,skuId}
|
||||
});
|
||||
}
|
||||
|
111
api/passport.js
Normal file
@ -0,0 +1,111 @@
|
||||
/**
|
||||
* Created by Andste on 2018/5/2.
|
||||
* 用户认证相关API
|
||||
*/
|
||||
import storage from '@/utils/storage.js';
|
||||
import {http, Method} from '@/utils/request.js';
|
||||
import { md5 } from '@/utils/md5.js';
|
||||
|
||||
/**
|
||||
* 普通登录
|
||||
* @param username
|
||||
* @param password
|
||||
* @param captcha
|
||||
*/
|
||||
export function login(username, password, captcha) {
|
||||
return http.request({
|
||||
url: 'passport/login',
|
||||
method: Method.POST,
|
||||
params: {
|
||||
username,
|
||||
password: md5(password),
|
||||
captcha,
|
||||
uuid: storage.getUuid(),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 验证账户信息
|
||||
* @param captcha
|
||||
* @param account
|
||||
*/
|
||||
export function validAccount(captcha, account) {
|
||||
return http.request({
|
||||
url: 'passport/find-pwd',
|
||||
method: Method.GET,
|
||||
params: {
|
||||
uuid: storage.getUuid(),
|
||||
captcha,
|
||||
account,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送找回密码短信
|
||||
* @param uuid
|
||||
* @param captcha
|
||||
*/
|
||||
export function sendFindPasswordSms(uuid,captcha) {
|
||||
return http.request({
|
||||
url: 'passport/find-pwd/send',
|
||||
method: Method.POST,
|
||||
header:{'content-type':"application/x-www-form-urlencoded"},
|
||||
data: {
|
||||
uuid:uuid,
|
||||
captcha,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验找回密码验证码
|
||||
* @param uuid
|
||||
* @param sms_code
|
||||
*/
|
||||
export function validFindPasswordSms(uuid, sms_code) {
|
||||
return http.request({
|
||||
url: 'passport/find-pwd/valid',
|
||||
method: Method.GET,
|
||||
params: {
|
||||
uuid,
|
||||
sms_code,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改密码【找回密码用】
|
||||
* @param password
|
||||
* @param uuid
|
||||
*/
|
||||
export function changePassword(password, uuid) {
|
||||
if (!uuid) {
|
||||
uuid = storage.getUuid();
|
||||
}
|
||||
return http.request({
|
||||
url: 'passport/find-pwd/update-password',
|
||||
method: Method.PUT,
|
||||
header:{'content-type':"application/x-www-form-urlencoded"},
|
||||
data: {
|
||||
uuid,
|
||||
password: md5(password),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
// 保存生物认证登录
|
||||
export function setBiolofy(params) {
|
||||
return http.request({
|
||||
url: `passport/login/save/biology`,
|
||||
method: 'POST',
|
||||
params
|
||||
})
|
||||
}
|
28
api/point.js
Normal file
@ -0,0 +1,28 @@
|
||||
|
||||
import {http, Method} from '@/utils/request.js';
|
||||
|
||||
/**
|
||||
* 签到
|
||||
* @param params
|
||||
*/
|
||||
export function sign() {
|
||||
return http.request({
|
||||
url: '/members/sign',
|
||||
method: Method.POST,
|
||||
needToken: true,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 签到时间获取
|
||||
* @param params
|
||||
*/
|
||||
export function signTime(time) {
|
||||
return http.request({
|
||||
url: '/members/sign?time='+time,
|
||||
method: Method.GET,
|
||||
needToken: true,
|
||||
});
|
||||
}
|
188
api/promotions.js
Normal file
@ -0,0 +1,188 @@
|
||||
/**
|
||||
* 促销相关API
|
||||
*/
|
||||
import { http, Method } from "@/utils/request.js";
|
||||
|
||||
/**
|
||||
* 获取当前直播列表
|
||||
*
|
||||
* @param {*}
|
||||
* @returns
|
||||
*/
|
||||
export function getLiveList(params) {
|
||||
return http.request({
|
||||
url: `broadcast/studio`,
|
||||
method: Method.GET,
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前拼团活动的未成团的会员
|
||||
*/
|
||||
export function getPromotionGroupMember(pintuanId) {
|
||||
return http.request({
|
||||
url: `promotion/pintuan/${pintuanId}/members`,
|
||||
method: Method.GET,
|
||||
});
|
||||
}
|
||||
|
||||
/** 获取拼团列表 */
|
||||
export function getAssembleList(params) {
|
||||
return http.request({
|
||||
url: "promotion/pintuan",
|
||||
method: Method.GET,
|
||||
loading: false,
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取积分商城分类
|
||||
*/
|
||||
export function getPointsCategory() {
|
||||
return http.request({
|
||||
url: "/promotion/pointsGoods/category",
|
||||
method: Method.GET,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取积分商城商品
|
||||
* @param params
|
||||
*/
|
||||
export function getPointsGoods(params) {
|
||||
return http.request({
|
||||
url: "/promotion/pointsGoods",
|
||||
method: Method.GET,
|
||||
params,
|
||||
});
|
||||
}
|
||||
/**
|
||||
* 获取积分商城商品详情
|
||||
* @param params
|
||||
*/
|
||||
export function getPointsGoodsDetail(id) {
|
||||
return http.request({
|
||||
url: "/promotion/pointsGoods/"+id,
|
||||
method: Method.GET,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 获取限时抢购时间线 当天限时抢购信息
|
||||
*/
|
||||
export function getSeckillTimeLine() {
|
||||
return http.request({
|
||||
url: "promotion/seckill",
|
||||
method: Method.GET,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取限时抢购商品 获取某个时刻的限时抢购商品信息
|
||||
* @param params
|
||||
*/
|
||||
export function getSeckillTimeGoods(timeline) {
|
||||
return http.request({
|
||||
url: `promotion/seckill/${timeline}`,
|
||||
method: Method.GET,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取全部优惠券
|
||||
* @param params
|
||||
*/
|
||||
export function getAllCoupons(params) {
|
||||
return http.request({
|
||||
url: "/promotion/coupon",
|
||||
method: Method.GET,
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 分页获取砍价商品
|
||||
* @param params
|
||||
*/
|
||||
export function getBargainList(params) {
|
||||
return http.request({
|
||||
url: "/promotion/kanjiaGoods",
|
||||
method: Method.GET,
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 分页获取砍价商品
|
||||
* @param params
|
||||
*/
|
||||
export function getBargainDetail(id) {
|
||||
return http.request({
|
||||
url: `/promotion/kanjiaGoods/${id}`,
|
||||
method: Method.GET,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取砍价活动
|
||||
* @param params
|
||||
*/
|
||||
export function getBargainActivity(params) {
|
||||
return http.request({
|
||||
url: `/promotion/kanjiaGoods/getKanjiaActivity`,
|
||||
method: Method.POST,
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 发起砍价活动
|
||||
* @param params
|
||||
*/
|
||||
export function openBargain(params) {
|
||||
return http.request({
|
||||
url: `/promotion/kanjiaGoods`,
|
||||
method: Method.POST,
|
||||
header: { "content-type": "application/x-www-form-urlencoded" },
|
||||
data: params,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 分页获取砍价活动-帮砍记录
|
||||
*/
|
||||
export function getBargainLog(params) {
|
||||
return http.request({
|
||||
url: `/promotion/kanjiaGoods/getKanjiaActivity/logs`,
|
||||
method: Method.GET,
|
||||
data: params,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 分页获取砍价活动-帮砍记录
|
||||
*/
|
||||
export function helpBargain(kanJiaActivityId) {
|
||||
return http.request({
|
||||
url: `promotion/kanjiaGoods/help/${kanJiaActivityId}`,
|
||||
method: Method.POST,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 分页获取已参与的砍价活动
|
||||
*/
|
||||
export function getMineBargainLog(params) {
|
||||
return http.request({
|
||||
url: `/promotion/kanjiaGoods/kanjiaActivity/mine/`,
|
||||
method: Method.GET,
|
||||
params
|
||||
});
|
||||
}
|
||||
|
||||
|
136
api/safe.js
Normal file
@ -0,0 +1,136 @@
|
||||
/**
|
||||
* 安全相关API
|
||||
*/
|
||||
|
||||
import {http,Method} from '@/utils/request.js';
|
||||
import storage from "@/utils/storage.js"
|
||||
import { md5 } from '@/utils/md5.js'
|
||||
|
||||
/**
|
||||
* 发送绑定手机验证码
|
||||
* @param mobile
|
||||
* @param captcha
|
||||
*/
|
||||
export function sendBindMobileSms(mobile, captcha) {
|
||||
return http.request({
|
||||
url: `members/security/bind/send/${mobile}`,
|
||||
method: Method.POST,
|
||||
needToken: true,
|
||||
header:{'content-type':"application/x-www-form-urlencoded"},
|
||||
data: {
|
||||
uuid: storage.getUuid(),
|
||||
captcha,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 绑定手机号
|
||||
* @param mobile
|
||||
* @param sms_code
|
||||
*/
|
||||
export function bindMobile(mobile, sms_code) {
|
||||
return http.request({
|
||||
url: `members/security/bind/${mobile}`,
|
||||
method: Method.PUT,
|
||||
needToken: true,
|
||||
data: {sms_code},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送手机验证码
|
||||
* 在修改手机号和更改密码时通用
|
||||
* @param captcha
|
||||
*/
|
||||
export function sendMobileSms(captcha) {
|
||||
return http.request({
|
||||
url: 'members/security/send',
|
||||
method: Method.POST,
|
||||
needToken: true,
|
||||
header:{'content-type':"application/x-www-form-urlencoded"},
|
||||
data: {
|
||||
uuid: storage.getUuid(),
|
||||
captcha,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证更换手机号短信
|
||||
* @param sms_code
|
||||
*/
|
||||
export function validChangeMobileSms(sms_code) {
|
||||
return http.request({
|
||||
url: 'members/security/exchange-bind',
|
||||
method: Method.GET,
|
||||
needToken: true,
|
||||
params: {sms_code},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 更换手机号
|
||||
* @param mobile
|
||||
* @param sms_code
|
||||
*/
|
||||
export function changeMobile(mobile, sms_code) {
|
||||
return http.request({
|
||||
url: `members/security/exchange-bind/${mobile}`,
|
||||
method: Method.PUT,
|
||||
header:{'content-type':"application/x-www-form-urlencoded"},
|
||||
needToken: true,
|
||||
data: {sms_code},
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 更改密码
|
||||
* @param captcha
|
||||
* @param password
|
||||
*/
|
||||
export function changePassword(captcha, password) {
|
||||
return http.request({
|
||||
url: 'members/security/password',
|
||||
method: Method.PUT,
|
||||
header:{'content-type':"application/x-www-form-urlencoded"},
|
||||
needToken: true,
|
||||
data: {
|
||||
uuid: storage.getUuid(),
|
||||
captcha,
|
||||
password: md5(password),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前实名认证进度
|
||||
* @param email
|
||||
* @param email_code
|
||||
*/
|
||||
export function contractStep() {
|
||||
return http.request({
|
||||
url: `members/contract/step`,
|
||||
method: Method.GET,
|
||||
needToken: true
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 实名认证
|
||||
* @param email
|
||||
* @param email_code
|
||||
*/
|
||||
export function authentication(params) {
|
||||
return http.request({
|
||||
url: `members/contract/authentication`,
|
||||
method: Method.POST,
|
||||
needToken: true,
|
||||
header:{'content-type':"application/x-www-form-urlencoded"},
|
||||
data: params
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
|
59
api/store.js
Normal file
@ -0,0 +1,59 @@
|
||||
/**
|
||||
* 店铺相关API
|
||||
*/
|
||||
|
||||
import {http, Method} from '@/utils/request.js';
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 获取店铺列表
|
||||
* @param params
|
||||
*/
|
||||
export function getStoreList(params) {
|
||||
return http.request({
|
||||
url: '/store',
|
||||
method: Method.GET,
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取店铺基本信息
|
||||
* @param storeId
|
||||
*/
|
||||
export function getStoreBaseInfo(storeId) {
|
||||
return http.request({
|
||||
url: `/store/store/get/detail/${storeId}`,
|
||||
method: Method.GET,
|
||||
loading: false,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取店铺分类
|
||||
* @param id
|
||||
*/
|
||||
export function getStoreCategory(id) {
|
||||
return http.request({
|
||||
url: `/store/store/label/get/${id}`,
|
||||
method: Method.GET,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 营业执照
|
||||
* @param id
|
||||
*/
|
||||
export function getLicencePhoto(id) {
|
||||
return http.request({
|
||||
url: `/store/store/get/licencePhoto/${id}`,
|
||||
method: Method.GET,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
283
api/trade.js
Normal file
@ -0,0 +1,283 @@
|
||||
/**
|
||||
* 交♂易相关API
|
||||
*/
|
||||
|
||||
import { http, Method } from "@/utils/request.js";
|
||||
|
||||
/**
|
||||
* 获取购物车列表
|
||||
* @param show_type 要显示的类型 all:全部 checked:已选中的
|
||||
*/
|
||||
export function getCarts() {
|
||||
return http.request({
|
||||
url: `/trade/carts/all`,
|
||||
method: Method.GET,
|
||||
needToken: true,
|
||||
loading: false,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取购物车总数
|
||||
* @param show_type 要显示的类型 all:全部 checked:已选中的
|
||||
*/
|
||||
export function getCartNum() {
|
||||
return http.request({
|
||||
url: `/trade/carts/count`,
|
||||
method: Method.GET,
|
||||
needToken: true,
|
||||
loading: false,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取购物车可用优惠券数量
|
||||
* @param way 购物车购买:CART/立即购买:BUY_NOW/拼团购买:PINTUAN / 积分购买:POINT
|
||||
*/
|
||||
export function getCartCouponNum(way) {
|
||||
return http.request({
|
||||
url: `/trade/carts/coupon/num?way=${way}`,
|
||||
method: Method.GET,
|
||||
needToken: true,
|
||||
loading: false,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加货品到购物车
|
||||
* @param skuId 产品ID
|
||||
* @param num 产品的购买数量
|
||||
* @param cartType 购物车类型,默认加入购物车
|
||||
*/
|
||||
export function addToCart(data) {
|
||||
return http.request({
|
||||
url: "/trade/carts",
|
||||
method: Method.POST,
|
||||
needToken: true,
|
||||
header: { "content-type": "application/x-www-form-urlencoded" },
|
||||
data,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 更新购物车商品数量
|
||||
* @param skuId
|
||||
* @param num
|
||||
*/
|
||||
export function updateSkuNum(skuId, num = 1) {
|
||||
return http.request({
|
||||
url: `/trade/carts/sku/num/${skuId}`,
|
||||
method: Method.POST,
|
||||
header: { "content-type": "application/x-www-form-urlencoded" },
|
||||
needToken: true,
|
||||
data: { num },
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新购物车货品选中状态
|
||||
* @param skuId
|
||||
* @param checked
|
||||
*/
|
||||
export function updateSkuChecked(skuId, checked) {
|
||||
return http.request({
|
||||
url: `/trade/carts/sku/checked/${skuId}`,
|
||||
method: Method.POST,
|
||||
needToken: true,
|
||||
header: { "content-type": "application/x-www-form-urlencoded" },
|
||||
data: { checked },
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除多个货品项
|
||||
* @param skuIds
|
||||
*/
|
||||
export function deleteSkuItem(skuIds) {
|
||||
return http.request({
|
||||
url: `/trade/carts/sku/remove?skuIds=${skuIds}`,
|
||||
method: Method.DELETE,
|
||||
needToken: true,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 设置全部货品为选中或不选中
|
||||
* @param checked
|
||||
*/
|
||||
export function checkAll(checked) {
|
||||
return http.request({
|
||||
url: "/trade/carts/sku/checked",
|
||||
method: Method.POST,
|
||||
needToken: true,
|
||||
params: { checked },
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置店铺内全部货品选中状态
|
||||
* @param storeId
|
||||
* @param checked
|
||||
*/
|
||||
export function checkStore(storeId, checked) {
|
||||
return http.request({
|
||||
url: `/trade/carts/store/${storeId}`,
|
||||
method: Method.POST,
|
||||
needToken: true,
|
||||
header: { "content-type": "application/x-www-form-urlencoded" },
|
||||
data: { checked },
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取结算参数
|
||||
*/
|
||||
export function getCheckoutParams(way) {
|
||||
return http.request({
|
||||
url: "/trade/carts/checked?way=" + way,
|
||||
method: Method.GET,
|
||||
needToken: true,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置收货地址ID
|
||||
* @param addressId
|
||||
*/
|
||||
export function setAddressId(addressId,way) {
|
||||
return http.request({
|
||||
url: `/trade/carts/shippingAddress?shippingAddressId=${addressId}&way=${way}`,
|
||||
method: Method.GET,
|
||||
needToken: true,
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 创建交易
|
||||
*/
|
||||
export function createTrade(params) {
|
||||
return http.request({
|
||||
url: "/trade/carts/create/trade",
|
||||
method: Method.POST,
|
||||
needToken: true,
|
||||
message: false,
|
||||
data:params,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 根据交易编号或订单编号查询收银台数据
|
||||
* @param params
|
||||
*/
|
||||
export function getCashierData(params) {
|
||||
return http.request({
|
||||
url: "payment/cashier/tradeDetail",
|
||||
method: Method.GET,
|
||||
needToken: true,
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 发起支付
|
||||
* @param paymentMethod
|
||||
* @param paymentClient
|
||||
* @param params
|
||||
* @returns {*|*}
|
||||
*/
|
||||
export function initiatePay(paymentMethod, paymentClient, params) {
|
||||
return http.request({
|
||||
url: `payment/cashier/pay/${paymentMethod}/${paymentClient}`,
|
||||
method: Method.GET,
|
||||
needToken: true,
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 查询物流
|
||||
* @param orderSn
|
||||
|
||||
*/
|
||||
export function getExpress(orderSn) {
|
||||
return http.request({
|
||||
url: `/order/order/getTraces/${orderSn}`,
|
||||
method: Method.POST,
|
||||
needToken: true,
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取当前会员的对于当前商品可使用的优惠券列表
|
||||
*/
|
||||
export function getMemberCanUse(data) {
|
||||
return http.request({
|
||||
url: `/promotion/coupon/canUse`,
|
||||
method: Method.GET,
|
||||
params: data,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 获取当前会员的优惠券列表
|
||||
*/
|
||||
export function getMemberCouponList(data) {
|
||||
return http.request({
|
||||
url: `/promotion/coupon/getCoupons`,
|
||||
method: Method.GET,
|
||||
params: data,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用优惠券
|
||||
|
||||
*/
|
||||
export function useCoupon(params) {
|
||||
return http.request({
|
||||
url: `/trade/carts/select/coupon`,
|
||||
method: Method.GET,
|
||||
needToken: true,
|
||||
params: params,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 更换参与活动
|
||||
* @param params
|
||||
*/
|
||||
export function changeActivity(params) {
|
||||
return http.request({
|
||||
url: "trade/promotion",
|
||||
method: Method.POST,
|
||||
needToken: true,
|
||||
data: params,
|
||||
|
||||
header: { "content-type": "application/x-www-form-urlencoded" },
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据交易单号查询订单列表
|
||||
* @param trade_sn
|
||||
*/
|
||||
export function reBuy(sn) {
|
||||
return http.request({
|
||||
url: `trade/carts/rebuy/${sn}`,
|
||||
method: Method.POST,
|
||||
needToken: true,
|
||||
});
|
||||
}
|
164
api/vlog.js
Normal file
@ -0,0 +1,164 @@
|
||||
/**
|
||||
* 短视频相关API
|
||||
*/
|
||||
|
||||
import { http, Method } from "@/utils/request.js";
|
||||
|
||||
import api from "@/config/api.js";
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 短视频列表
|
||||
*/
|
||||
export function vlogList(page, pageSize) {
|
||||
return http.request({
|
||||
url: api.vlog + "/vlog/indexList",
|
||||
method: Method.GET,
|
||||
needToken: false,
|
||||
params: { page, pageSize },
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 短视频喜欢
|
||||
*/
|
||||
export function vlogLike({userId,vlogId,vlogerId}) {
|
||||
return http.request({
|
||||
url: api.vlog + "/vlog/like",
|
||||
method: Method.POST,
|
||||
needToken: true,
|
||||
params: {userId,vlogId,vlogerId}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 短视频不喜欢
|
||||
*/
|
||||
export function vlogUnLike({userId,vlogId,vlogerId}) {
|
||||
return http.request({
|
||||
url: api.vlog + "/vlog/unlike",
|
||||
method: Method.POST,
|
||||
needToken: true,
|
||||
params: {userId,vlogId,vlogerId}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 短视频评论
|
||||
*/
|
||||
export function vlogComment(vlogId) {
|
||||
return http.request({
|
||||
url: api.vlog + "/comment/counts",
|
||||
method: Method.GET,
|
||||
needToken: true,
|
||||
params: { vlogId },
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 短视频关注博主
|
||||
*/
|
||||
export function vlogFollow({myId,vlogerId}) {
|
||||
return http.request({
|
||||
url: api.vlog + "/fans/follow",
|
||||
method: Method.POST,
|
||||
needToken: true,
|
||||
params: {myId,vlogerId}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 短视频查询当前点赞数
|
||||
*/
|
||||
export function vlogTotalLikedCounts(vlogId) {
|
||||
return http.request({
|
||||
url: api.vlog + "/vlog/totalLikedCounts",
|
||||
method: Method.POST,
|
||||
needToken: true,
|
||||
params: {vlogId}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 通过cityCode获取地区代码
|
||||
*/
|
||||
export function getAddressCode(cityCode, townName) {
|
||||
return http.request({
|
||||
url: api.common + "/common/region/region",
|
||||
method: Method.GET,
|
||||
needToken: true,
|
||||
params: { cityCode, townName },
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加收货地址
|
||||
* @param params 地址参数
|
||||
* @returns {AxiosPromise}
|
||||
*/
|
||||
export function addAddress(data) {
|
||||
return http.request({
|
||||
url: "/member/address",
|
||||
method: Method.POST,
|
||||
needToken: true,
|
||||
header: { "content-type": "application/x-www-form-urlencoded" },
|
||||
data: data,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 编辑地址
|
||||
* @param id 地址ID
|
||||
* @param params 地址参数
|
||||
* @returns {AxiosPromise}
|
||||
*/
|
||||
export function editAddress(params) {
|
||||
return http.request({
|
||||
url: `/member/address`,
|
||||
method: Method.PUT,
|
||||
needToken: true,
|
||||
header: { "content-type": "application/x-www-form-urlencoded" },
|
||||
data: params,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除收货地址
|
||||
* @param id
|
||||
*/
|
||||
export function deleteAddress(id) {
|
||||
return http.request({
|
||||
url: `/member/address/delById/${id}`,
|
||||
method: Method.DELETE,
|
||||
needToken: true,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 根据ID获取会员收件地址
|
||||
* @param id
|
||||
*/
|
||||
export function getAddressDetail(id) {
|
||||
return http.request({
|
||||
url: `/member/address/get/${id}`,
|
||||
method: Method.GET,
|
||||
loading: false,
|
||||
needToken: true,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
export function getAddressDefault() {
|
||||
return http.request({
|
||||
url: `/member/address/get/default`,
|
||||
method: Method.GET,
|
||||
loading: false,
|
||||
needToken: true,
|
||||
});
|
||||
}
|
86
components/default-page/default-page.vue
Normal file
@ -0,0 +1,86 @@
|
||||
<template>
|
||||
<view class="default-page">
|
||||
<view class="default-wrap">
|
||||
|
||||
<text>{{title}}</text>
|
||||
<view v-if="isBtn" class="btn" @click="toHome">
|
||||
去逛逛
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
// 购物车 cart 消息 msg 订单 order 查询 search
|
||||
type: {
|
||||
type: String,
|
||||
default: 'search'
|
||||
},
|
||||
isBtn:{
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
title:{
|
||||
type: String,
|
||||
default: '没有相关内容'
|
||||
}
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
src:''
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.src ='/static/default/default_'+ this.type + '.png';
|
||||
},
|
||||
methods: {
|
||||
toHome() {
|
||||
uni.switchTab({
|
||||
url: '/pages/home/home'
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.default-page{
|
||||
background: #FFFFFF;
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
.default-wrap{
|
||||
position: absolute;
|
||||
top: calc(50vh - 320rpx);
|
||||
left: calc(50% - 120rpx);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
|
||||
>image{
|
||||
width: 260rpx;
|
||||
height: 240rpx;
|
||||
|
||||
margin-bottom: 24rpx;
|
||||
}
|
||||
text{
|
||||
font-size: 32rpx;
|
||||
color: #828385;
|
||||
}
|
||||
.btn{
|
||||
width: 160rpx;
|
||||
height: 56rpx;
|
||||
border: 2rpx solid #3180F6;
|
||||
border-radius: 6rpx;
|
||||
text-align: center;
|
||||
line-height: 56rpx;
|
||||
margin-top: 32rpx;
|
||||
font-size: 24rpx;
|
||||
color: #3180F6;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
</style>
|
117
components/m-airbtn/index.vue
Normal file
@ -0,0 +1,117 @@
|
||||
<template>
|
||||
<view>
|
||||
<!-- #ifdef H5 -->
|
||||
<view class="wrapper" v-if="!weChat" @click="openApp()">
|
||||
<!-- 左侧图标 -->
|
||||
<image class="img" :src="logo"></image>
|
||||
<view class="open">
|
||||
打开{{config.name}}
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- #endif -->
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import config from "@/config/config";
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
config, // 设置工具类
|
||||
weChat: false, // 是否微信浏览器,该项为true时不显示 当前整个页面
|
||||
logo: require("@/icon.png"), //显示的圆形logo
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
// #ifdef H5
|
||||
// 判断是否是微信浏览器
|
||||
var ua = navigator.userAgent.toLowerCase();
|
||||
var isWeixin = ua.indexOf("micromessenger") != -1;
|
||||
if (isWeixin) {
|
||||
this.weChat = true;
|
||||
} else {
|
||||
this.weChat = false;
|
||||
}
|
||||
// #endif
|
||||
},
|
||||
methods: {
|
||||
|
||||
/**
|
||||
* 跳转到下载app页面
|
||||
*/
|
||||
downloadApp() {
|
||||
setTimeout(function () {
|
||||
window.location.href = config.downloadLink;
|
||||
}, 2000);
|
||||
},
|
||||
|
||||
/**
|
||||
* 打开app 仅在h5生效 使用ifream唤醒app
|
||||
*/
|
||||
openApp() {
|
||||
let src;
|
||||
if (location.href) {
|
||||
src = location.href.split("/pages")[1];
|
||||
}
|
||||
let t = `${config.schemeLink}pages${src}`;
|
||||
|
||||
try {
|
||||
var e = navigator.userAgent.toLowerCase(),
|
||||
n = e.match(/cpu iphone os (.*?) like mac os/);
|
||||
if (
|
||||
((n = null !== n ? n[1].replace(/_/g, ".") : 0), parseInt(n) >= 9)
|
||||
) {
|
||||
window.location.href = t;
|
||||
|
||||
this.downloadApp();
|
||||
} else {
|
||||
var r = document.createElement("iframe");
|
||||
(r.src = t), (r.style.display = "none"), document.body.appendChild(r);
|
||||
|
||||
this.downloadApp();
|
||||
}
|
||||
} catch (e) {
|
||||
window.location.href = t;
|
||||
this.downloadApp();
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.img {
|
||||
margin: 0 4rpx;
|
||||
width: 50rpx;
|
||||
height: 50rpx;
|
||||
border-radius: 50%;
|
||||
border: 5rpx solid #fff;
|
||||
}
|
||||
|
||||
.open {
|
||||
margin: 0 10rpx;
|
||||
text-align: center;
|
||||
font-size: 26rpx;
|
||||
}
|
||||
.wrapper:hover {
|
||||
transform: translateX(0);
|
||||
}
|
||||
.wrapper {
|
||||
transform: translateX(160rpx);
|
||||
transition: 0.35s;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
display: flex;
|
||||
border-top-left-radius: 20px;
|
||||
border-bottom-left-radius: 20px;
|
||||
color: #fff;
|
||||
// width: 220rpx;
|
||||
background: $light-color;
|
||||
position: fixed;
|
||||
top: 25%;
|
||||
right: 0;
|
||||
height: 60rpx;
|
||||
z-index: 9;
|
||||
}
|
||||
</style>
|
3
components/m-buy/README.md
Normal file
@ -0,0 +1,3 @@
|
||||
## 购买组件
|
||||
1.本组件是砍价,积分购买,参团等复用组
|
||||
2.后续会将此组件和商品购买组件合并,文档后续再出
|
515
components/m-buy/goods.vue
Normal file
@ -0,0 +1,515 @@
|
||||
<template>
|
||||
<div class="wrapper">
|
||||
<u-popup
|
||||
class="popup"
|
||||
v-model="buyMask"
|
||||
:height="setup.height"
|
||||
closeable
|
||||
:mode="setup.mode"
|
||||
:border-radius="setup.radius"
|
||||
@close="closeMask()"
|
||||
>
|
||||
<!-- 商品 -->
|
||||
<view class="goods-box bottom">
|
||||
<view class="goods-header">
|
||||
<view class="goods-img">
|
||||
<u-image
|
||||
width="200rpx"
|
||||
border-radius="20"
|
||||
class="uimage"
|
||||
height="200rpx"
|
||||
:src="selectedSpecImg ? selectedSpecImg : goodsDetail.thumbnail"
|
||||
></u-image>
|
||||
</view>
|
||||
<view class="goods-skus">
|
||||
<!-- 有活动商品价格 -->
|
||||
<view class="goods-price" v-if="goodsDetail.promotionPrice && ((isGroup && buyType === 'PINTUAN') || !isGroup)">
|
||||
<span v-if="goodsDetail.promotionPrice && !pointDetail">
|
||||
¥
|
||||
<span class="goods-price-promotionShow goods-price-bigshow">{{
|
||||
formatPrice(goodsDetail.promotionPrice)[0]
|
||||
}}</span>
|
||||
.{{ formatPrice(goodsDetail.promotionPrice)[1] }}
|
||||
</span>
|
||||
<span v-if="pointDetail.points">
|
||||
<span class="goods-price-promotionShow goods-price-bigshow">{{
|
||||
pointDetail.points
|
||||
}}</span>
|
||||
积分
|
||||
</span>
|
||||
<div class="promotion-box">
|
||||
¥
|
||||
<span class="goods-price-bigshow">{{
|
||||
formatPrice(goodsDetail.price)[0]
|
||||
}}</span>
|
||||
.{{ formatPrice(goodsDetail.price)[1] }}
|
||||
</div>
|
||||
</view>
|
||||
<!-- 正常商品的价格 -->
|
||||
<view class="goods-price" v-else>
|
||||
<span>
|
||||
¥
|
||||
<span class="goods-price-bigshow">{{
|
||||
formatPrice(goodsDetail.price)[0]
|
||||
}}</span>
|
||||
.{{ formatPrice(goodsDetail.price)[1] }}
|
||||
</span>
|
||||
</view>
|
||||
<view class="goods-check-skus">
|
||||
已选
|
||||
<span class="goods-check-skus-name">
|
||||
{{ selectName }}
|
||||
<span>,{{ num }}个</span>
|
||||
</span>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<!-- 商品信息 -->
|
||||
<scroll-view class="goods-skus-box" :scroll-y="true">
|
||||
<!-- 规格 -->
|
||||
<view
|
||||
class="goods-skus-view"
|
||||
:key="specIndex"
|
||||
v-for="(spec, specIndex) in formatList"
|
||||
>
|
||||
<view class="skus-view-list">
|
||||
<view class="view-class-title">{{ spec.name }}</view>
|
||||
|
||||
<!-- 正常逻辑 循环出sku -->
|
||||
<view
|
||||
v-if="!parentOrder"
|
||||
:class="{ active: spec_val.value == currentSelceted[specIndex] }"
|
||||
class="skus-view-item"
|
||||
v-for="(spec_val, spec_index) in spec.values"
|
||||
:key="spec_index"
|
||||
@click="handleClickSpec(spec, specIndex, spec_val)"
|
||||
>{{ spec_val.value }}
|
||||
</view>
|
||||
|
||||
<!-- 拼团购买,仅筛选出当前拼团类型商品 -->
|
||||
<view
|
||||
v-if="parentOrder && spec_val.skuId == goodsDetail.id"
|
||||
:class="{ active: spec_val.value == currentSelceted[specIndex] }"
|
||||
class="skus-view-item"
|
||||
v-for="(spec_val, spec_index) in spec.values"
|
||||
:key="spec_index"
|
||||
@click="handleClickSpec(spec, specIndex, spec_val)"
|
||||
>{{ spec_val.value }}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<!-- 数量 -->
|
||||
<view class="goods-skus-number">
|
||||
<view class="view-class-title">数量</view>
|
||||
<u-number-box
|
||||
:bg-color="numberBox.bgColor"
|
||||
:max="200"
|
||||
:color="numberBox.color"
|
||||
:input-width="numberBox.width"
|
||||
:input-height="numberBox.height"
|
||||
:size="numberBox.size"
|
||||
:min="1"
|
||||
v-model="num"
|
||||
>
|
||||
</u-number-box>
|
||||
</view>
|
||||
</scroll-view>
|
||||
<!-- 按钮 -->
|
||||
<view class="btns">
|
||||
<view
|
||||
class="box-btn card"
|
||||
v-if="buyType != 'PINTUAN' && goodsDetail.goodsType != 'VIRTUAL_GOODS'"
|
||||
@click="addToCartOrBuy('cart')"
|
||||
>加入购物车</view
|
||||
>
|
||||
<view class="box-btn buy" @click="addToCartOrBuy('buy')">立即购买</view>
|
||||
</view>
|
||||
</view>
|
||||
</u-popup>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import * as API_trade from "@/api/trade.js";
|
||||
import setup from "./popup";
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
setup,
|
||||
num: 1,
|
||||
// 步进器的大小尺寸单位是 rpx
|
||||
numberBox: {
|
||||
width: "50",
|
||||
height: "50",
|
||||
size: "22",
|
||||
color: "#333",
|
||||
bgColor: "#fff",
|
||||
},
|
||||
selectName: "", //选中商品的昵称
|
||||
selectSkuList: "", //选中商铺sku,
|
||||
selectedSpecImg: "", //选中的图片路径
|
||||
buyType: "", //用于存储促销,拼团等活动类型
|
||||
parentOrder: "", //父级拼团活动的数据 - 如果是团员则有数据
|
||||
formatList: [],
|
||||
currentSelceted: [],
|
||||
skuList: "",
|
||||
isClose: false, //是否可以点击遮罩关闭
|
||||
};
|
||||
},
|
||||
props: {
|
||||
buyMask: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
isGroup: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
goodsDetail: {
|
||||
default: "",
|
||||
type: null,
|
||||
},
|
||||
selectedSku: {
|
||||
default: "",
|
||||
type: null,
|
||||
},
|
||||
goodsSpec: {
|
||||
default: "",
|
||||
type: null,
|
||||
},
|
||||
addr: {
|
||||
default: "",
|
||||
type: null,
|
||||
},
|
||||
pointDetail: {
|
||||
default: "",
|
||||
type: null,
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
buyType: {
|
||||
handler(val) {
|
||||
if (val) {
|
||||
this.buyType = val;
|
||||
}
|
||||
},
|
||||
immediate: true,
|
||||
},
|
||||
selectSkuList: {
|
||||
handler(val, oldval) {
|
||||
this.$emit("changed", val);
|
||||
},
|
||||
deep: true,
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
// 格式化金钱 1999 --> [1999,00]
|
||||
formatPrice(val) {
|
||||
if (typeof val == "undefined") {
|
||||
return val;
|
||||
}
|
||||
return val.toFixed(2).split(".");
|
||||
},
|
||||
|
||||
closeMask() {
|
||||
this.$emit("closeBuy", false);
|
||||
},
|
||||
|
||||
/**点击规格 */
|
||||
handleClickSpec(val, index, specValue) {
|
||||
this.currentSelceted[index] = specValue.value;
|
||||
let selectedSkuId = this.goodsSpec.find((i) => {
|
||||
let matched = true;
|
||||
let specValues = i.specValues.filter((j) => j.specName !== "images");
|
||||
for (let n = 0; n < specValues.length; n++) {
|
||||
if (specValues[n].specValue !== this.currentSelceted[n]) {
|
||||
matched = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (matched) {
|
||||
return i;
|
||||
}
|
||||
});
|
||||
if (selectedSkuId?.skuId) {
|
||||
this.$set(this.currentSelceted, index, specValue.value);
|
||||
this.selectSkuList = {
|
||||
spec: {
|
||||
specName: val.name,
|
||||
specValue: specValue.value,
|
||||
},
|
||||
data: this.goodsDetail,
|
||||
};
|
||||
this.selectName = specValue.value;
|
||||
|
||||
this.$emit("handleClickSku", {
|
||||
skuId: selectedSkuId.skuId,
|
||||
goodsId: this.goodsDetail.goodsId,
|
||||
});
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: "暂无该商品!",
|
||||
duration: 2000,
|
||||
icon: "none",
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 直接购买
|
||||
*/
|
||||
buy(data) {
|
||||
API_trade.addToCart(data).then((res) => {
|
||||
if (res.data.success) {
|
||||
uni.navigateTo({
|
||||
url: `/pages/order/fillorder?way=${
|
||||
data.cartType
|
||||
}&addr=${""}&parentOrder=${encodeURIComponent(
|
||||
JSON.stringify(this.parentOrder)
|
||||
)}`,
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* 添加到购物车或购买
|
||||
*/
|
||||
addToCartOrBuy(val) {
|
||||
if (!this.selectSkuList) {
|
||||
uni.showToast({
|
||||
title: "请选择规格商品",
|
||||
icon: "none",
|
||||
});
|
||||
return;
|
||||
}
|
||||
let data = {
|
||||
skuId: this.goodsDetail.id,
|
||||
num: this.num,
|
||||
};
|
||||
|
||||
if (val == "cart") {
|
||||
API_trade.addToCart(data).then((res) => {
|
||||
if (res.data.code == 200) {
|
||||
uni.showToast({
|
||||
title: "商品已添加到购物车",
|
||||
icon: "none",
|
||||
});
|
||||
|
||||
this.$emit("queryCart");
|
||||
this.closeMask();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// 判断是否拼团商品
|
||||
if (this.buyType) {
|
||||
data.cartType = "PINTUAN";
|
||||
} else if (this.goodsDetail.goodsType == "VIRTUAL_GOODS") {
|
||||
data.cartType = "VIRTUAL";
|
||||
} else {
|
||||
data.cartType = "BUY_NOW";
|
||||
}
|
||||
|
||||
API_trade.addToCart(data).then((res) => {
|
||||
if (res.data.code == 200) {
|
||||
uni.navigateTo({
|
||||
url: `/pages/order/fillorder?way=${data.cartType}&addr=${
|
||||
this.addr.id || ""
|
||||
}&parentOrder=${encodeURIComponent(JSON.stringify(this.parentOrder))}`,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
formatSku(list) {
|
||||
// 格式化数据
|
||||
let arr = [{}];
|
||||
|
||||
if(!Array.isArray(list)){
|
||||
return false
|
||||
}
|
||||
list.forEach((item, index) => {
|
||||
item.specValues.forEach((spec, specIndex) => {
|
||||
let name = spec.specName;
|
||||
let values = {
|
||||
value: spec.specValue,
|
||||
quantity: item.quantity,
|
||||
skuId: item.skuId,
|
||||
};
|
||||
if (name === "images") {
|
||||
return;
|
||||
}
|
||||
|
||||
arr.forEach((arrItem, arrIndex) => {
|
||||
if (
|
||||
arrItem.name == name &&
|
||||
arrItem.values &&
|
||||
!arrItem.values.find((i) => {
|
||||
return i.value === values.value;
|
||||
})
|
||||
) {
|
||||
arrItem.values.push(values);
|
||||
}
|
||||
|
||||
let keys = arr.map((key) => {
|
||||
return key.name;
|
||||
});
|
||||
if (!keys.includes(name)) {
|
||||
arr.push({
|
||||
name: name,
|
||||
values: [values],
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
arr.shift();
|
||||
this.formatList = arr;
|
||||
|
||||
list.forEach((item) => {
|
||||
// 默认选中
|
||||
if (item.skuId === this.goodsDetail.id) {
|
||||
item.specValues
|
||||
.filter((i) => i.specName !== "images")
|
||||
.forEach((value, _index) => {
|
||||
this.currentSelceted[_index] = value.specValue;
|
||||
|
||||
this.selectName = value.specValue;
|
||||
|
||||
this.selectSkuList = {
|
||||
spec: value,
|
||||
data: this.goodsDetail,
|
||||
};
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
this.skuList = list;
|
||||
// console.log(" this.skuList", this.skuList)
|
||||
},
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.formatSku(this.goodsSpec);
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
@import "./popup.scss";
|
||||
|
||||
.buy {
|
||||
background-image: linear-gradient(135deg, #ffba0d, #ffc30d 69%, #ffcf0d);
|
||||
box-shadow: 0 2px 6px 0 rgba(255, 65, 66, 0.2);
|
||||
}
|
||||
|
||||
.card {
|
||||
background-image: linear-gradient(135deg, #f2140c, #f2270c 70%, #f24d0c);
|
||||
box-shadow: 0 2px 6px 0 rgba(255, 65, 66, 0.2);
|
||||
}
|
||||
|
||||
/deep/.u-icon-plus,
|
||||
.u-icon-minus,
|
||||
.u-icon-disabled {
|
||||
height: 30rpx !important;
|
||||
background: #fff !important;
|
||||
}
|
||||
|
||||
.goods-skus-number {
|
||||
justify-content: space-between;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
/deep/ .uni-scroll-view {
|
||||
overflow: hidden !important;
|
||||
}
|
||||
|
||||
.active {
|
||||
background: $price-light-color !important;
|
||||
border: 2rpx solid $price-color;
|
||||
font-weight: bold;
|
||||
color: $price-color !important;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.goods-skus-box {
|
||||
overflow-y: auto;
|
||||
height: 610rpx;
|
||||
// #ifdef MP-WEIXIN
|
||||
height: 570rpx;
|
||||
// #endif
|
||||
margin-bottom: 10rpx;
|
||||
}
|
||||
|
||||
.goods-skus-view {
|
||||
overflow: hidden;
|
||||
|
||||
.skus-view-list {
|
||||
> .skus-view-item {
|
||||
flex: 1;
|
||||
padding: 0 36rpx;
|
||||
|
||||
overflow: hidden;
|
||||
height: 60rpx;
|
||||
line-height: 60rpx;
|
||||
float: left;
|
||||
text-align: center;
|
||||
margin-left: 24rpx;
|
||||
margin-bottom: 20rpx;
|
||||
font-size: 22rpx;
|
||||
color: #262626;
|
||||
background: #f2f2f2;
|
||||
border-radius: 30rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.goods-header {
|
||||
height: 200rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 36rpx;
|
||||
}
|
||||
|
||||
.goods-box {
|
||||
padding: 50rpx 36rpx 0 36rpx;
|
||||
}
|
||||
|
||||
.goods-skus {
|
||||
padding: 0 20rpx;
|
||||
}
|
||||
|
||||
.goods-price {
|
||||
color: $price-color;
|
||||
line-height: 80rpx;
|
||||
display: flex;
|
||||
}
|
||||
.promotion-box {
|
||||
line-height: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
text-decoration: line-through;
|
||||
color: #999;
|
||||
margin-left: 10rpx;
|
||||
/deep/ span {
|
||||
font-size: 30rpx;
|
||||
}
|
||||
}
|
||||
.promotion {
|
||||
font-size: 30rpx;
|
||||
}
|
||||
.goods-price-promotionShow {
|
||||
font-size: 48rpx;
|
||||
}
|
||||
.goods-check-skus {
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
> .goods-check-skus-name {
|
||||
margin-left: 4rpx;
|
||||
}
|
||||
> span {
|
||||
color: #333;
|
||||
}
|
||||
}
|
||||
</style>
|
8
components/m-buy/popup.js
Normal file
@ -0,0 +1,8 @@
|
||||
|
||||
|
||||
export default {
|
||||
height:"1000rpx", //弹出层高度
|
||||
mode:"bottom", //弹出层位置
|
||||
radius:"32", //圆角 rpx,
|
||||
close:false //能否点击遮罩退出
|
||||
}
|
37
components/m-buy/popup.scss
Normal file
@ -0,0 +1,37 @@
|
||||
.view-class-title {
|
||||
font-size: 26rpx;
|
||||
color: #262626;
|
||||
|
||||
font-weight: 700;
|
||||
height: 80rpx;
|
||||
line-height: 80rpx;
|
||||
}
|
||||
.confirmBtn {
|
||||
width: 90%;
|
||||
}
|
||||
.confirmBtn,
|
||||
.box-btn {
|
||||
line-height: 80rpx;
|
||||
height: 80rpx;
|
||||
|
||||
background: $price-color;
|
||||
color: #fff;
|
||||
border-radius: 200px;
|
||||
text-align: center;
|
||||
margin: 5rpx auto;
|
||||
}
|
||||
|
||||
.btns {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
|
||||
margin: 0 auto;
|
||||
}
|
||||
.goods-price-bigshow {
|
||||
font-size: 48rpx;
|
||||
font-weight: bold;
|
||||
}
|
||||
.box-btn {
|
||||
flex: 1;
|
||||
margin: 0 10rpx;
|
||||
}
|
1
components/m-canvas/README.md
Normal file
@ -0,0 +1 @@
|
||||
### 说明 https://ext.dcloud.net.cn/plugin?id=3237
|
211
components/m-canvas/index.vue
Normal file
@ -0,0 +1,211 @@
|
||||
<template>
|
||||
|
||||
<div class="index">
|
||||
<u-modal v-model="show" :show-title="false" :show-confirm-button="false" mask-close-able>
|
||||
<view class="slot-content">
|
||||
<image @click="downLoad()" class="img" :src="imgUrl" />
|
||||
<div class="canvas-hide">
|
||||
<!-- #ifdef MP-WEIXIN -->
|
||||
<canvas id="canvas" type="2d" style="width: 600px; height: 960px" />
|
||||
<!-- #endif -->
|
||||
<!-- #ifndef MP-WEIXIN -->
|
||||
<canvas canvas-id="canvas" id="canvas" style="width: 600px; height: 960px" />
|
||||
<!-- #endif -->
|
||||
</div>
|
||||
</view>
|
||||
</u-modal>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
// 引入绘制插件
|
||||
import DrawPoster from "@/js_sdk/u-draw-poster";
|
||||
|
||||
export default {
|
||||
data: () => ({
|
||||
imgUrl: "", //绘制出来的图片路径
|
||||
show: false, //是否展示模态框
|
||||
dp: {}, //绘制的dp对象,用于存储绘制等一些方法。
|
||||
logo: require("@/pages/passport/static/logo-title.png"), //本地logo地址
|
||||
}),
|
||||
|
||||
props: {
|
||||
/**
|
||||
* 父级传参的数据
|
||||
*/
|
||||
res: {
|
||||
type: null,
|
||||
default: "",
|
||||
},
|
||||
},
|
||||
onUnload() {},
|
||||
|
||||
methods: {
|
||||
/**
|
||||
* 解决微信小程序中图片模糊问题
|
||||
*/
|
||||
// #ifdef MP-WEIXIN
|
||||
st2: (size) => size * 2,
|
||||
// #endif
|
||||
|
||||
// #ifndef MP-WEIXIN
|
||||
st2: (size) => size,
|
||||
// #endif
|
||||
|
||||
/**
|
||||
* 保存图片
|
||||
*/
|
||||
downLoad() {
|
||||
uni.saveImageToPhotosAlbum({
|
||||
filePath: this.imgUrl,
|
||||
success: function () {
|
||||
uni.showToast({
|
||||
title: "保存成功!",
|
||||
icon: "none",
|
||||
});
|
||||
},
|
||||
fail: function () {
|
||||
uni.showToast({
|
||||
title: "保存失败,请稍后重试!",
|
||||
icon: "none",
|
||||
});
|
||||
},
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* 创建canvas
|
||||
*/
|
||||
async init() {
|
||||
this.show = true;
|
||||
this.dp = await DrawPoster.build({
|
||||
selector: "canvas",
|
||||
componentThis: this,
|
||||
loading: true,
|
||||
debugging: true,
|
||||
});
|
||||
let dp = this.dp;
|
||||
// #ifdef MP-WEIXIN
|
||||
// 用于微信小程序中画布错乱问题
|
||||
dp.canvas.width = this.st2(600);
|
||||
dp.canvas.height = this.st2(960);
|
||||
// #endif
|
||||
this.draw(dp);
|
||||
},
|
||||
|
||||
async draw(dp) {
|
||||
const { width, height, background, title } = this.res.container;
|
||||
const { code, img, price } = this.res.bottom;
|
||||
|
||||
// /** 绘制背景 */
|
||||
await dp.draw((ctx) => {
|
||||
ctx.fillStyle = background;
|
||||
ctx.fillRoundRect(
|
||||
this.st2(0),
|
||||
this.st2(0),
|
||||
this.st2(width),
|
||||
this.st2(height),
|
||||
this.st2(12)
|
||||
);
|
||||
ctx.clip();
|
||||
});
|
||||
/** 绘制图片 */
|
||||
dp.draw(async (ctx) => {
|
||||
await Promise.all([
|
||||
// 绘制Logo
|
||||
ctx.drawImage(
|
||||
this.logo,
|
||||
this.st2(175),
|
||||
this.st2(0),
|
||||
this.st2(256),
|
||||
this.st2(144)
|
||||
),
|
||||
// 中间图片
|
||||
ctx.drawImage(
|
||||
img,
|
||||
this.st2(100),
|
||||
this.st2(150),
|
||||
this.st2(400),
|
||||
this.st2(400)
|
||||
),
|
||||
// 二维码
|
||||
ctx.drawImage(
|
||||
code,
|
||||
this.st2(39),
|
||||
this.st2(750),
|
||||
this.st2(150),
|
||||
this.st2(150)
|
||||
),
|
||||
]);
|
||||
});
|
||||
|
||||
/** 绘制中间文字*/
|
||||
await dp.draw((ctx) => {
|
||||
ctx.fillStyle = "#333";
|
||||
ctx.font = `bold ${this.st2(24)}px PingFang SC`;
|
||||
ctx.textAlign = "center";
|
||||
ctx.fillWarpText({
|
||||
text: title,
|
||||
maxWidth: this.st2(500),
|
||||
x: this.st2(300),
|
||||
y: this.st2(600),
|
||||
layer: 1,
|
||||
});
|
||||
|
||||
ctx.fillStyle = "#ff3c2a";
|
||||
ctx.font = `${this.st2(38)}px PingFang SC`;
|
||||
ctx.textAlign = "center";
|
||||
ctx.fillText(price, this.st2(300), this.st2(680));
|
||||
});
|
||||
|
||||
// /** 绘制底部文字 */
|
||||
await dp.draw((ctx) => {
|
||||
ctx.fillStyle = "#666";
|
||||
ctx.font = `${this.st2(24)}px PingFang SC`;
|
||||
ctx.fillText("长按图片,识别二维码", this.st2(200), this.st2(866));
|
||||
ctx.fillStyle = "#666";
|
||||
ctx.font = `${this.st2(24)}px PingFang SC`;
|
||||
ctx.fillText("查看商品详情", this.st2(200), this.st2(900));
|
||||
});
|
||||
|
||||
this.imgUrl = await dp.createImagePath();
|
||||
|
||||
// console.log(posterImgUrl)
|
||||
},
|
||||
},
|
||||
|
||||
async mounted() {
|
||||
this.init();
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
page,
|
||||
.index {
|
||||
height: 100%;
|
||||
}
|
||||
.canvas-hide {
|
||||
/* 1 */
|
||||
position: fixed;
|
||||
right: 100vw;
|
||||
bottom: 100vh;
|
||||
/* 2 */
|
||||
z-index: -9999;
|
||||
/* 3 */
|
||||
opacity: 0;
|
||||
}
|
||||
.index {
|
||||
position: relative;
|
||||
text-align: center;
|
||||
background: rgba($color: grey, $alpha: 0.2);
|
||||
}
|
||||
|
||||
image {
|
||||
display: block;
|
||||
}
|
||||
.img {
|
||||
width: 600rpx;
|
||||
height: 960rpx;
|
||||
}
|
||||
</style>
|
390
components/m-city/m-city.vue
Normal file
@ -0,0 +1,390 @@
|
||||
<template>
|
||||
<view>
|
||||
<view
|
||||
class="mask"
|
||||
:class="{ maskShow: showPicker }"
|
||||
@click="hide"
|
||||
@click.stop.prevent
|
||||
@touchmove.stop.prevent
|
||||
catchtouchmove="true"
|
||||
></view>
|
||||
<view class="cpicker-content" :class="{ cpickerShow: showPicker }">
|
||||
<view
|
||||
class="city-head"
|
||||
@click.stop.prevent
|
||||
@touchmove.stop.prevent
|
||||
catchtouchmove="true"
|
||||
>
|
||||
<view class="city-head-title">{{ headTitle }}</view>
|
||||
<icon
|
||||
type="clear"
|
||||
v-if="clearRightIcon"
|
||||
class="clearRightIcon"
|
||||
size="20"
|
||||
color="#cccccc"
|
||||
@click="hide"
|
||||
></icon>
|
||||
</view>
|
||||
<scroll-view
|
||||
id="nav-bar"
|
||||
class="nav-bar"
|
||||
scroll-x="true"
|
||||
scroll-with-animation="true"
|
||||
:scroll-left="scrollLeft"
|
||||
>
|
||||
<view
|
||||
v-for="(item, index) in tabbars"
|
||||
class="nav-item"
|
||||
:key="index"
|
||||
:id="'tab' + index"
|
||||
@click="changeTab(index)"
|
||||
:class="{ current: index === tabCurrentIndex }"
|
||||
><text class="nav-bar-title">{{ item.localName }}</text></view
|
||||
>
|
||||
</scroll-view>
|
||||
<view class="city_content">
|
||||
<scroll-view
|
||||
class="panel-scroll-box"
|
||||
:scroll-y="enableScroll"
|
||||
:cscrollTop="scrollTop"
|
||||
:current="tabCurrentIndex"
|
||||
:scroll-top="scrollTop"
|
||||
>
|
||||
<block
|
||||
v-for="(item, index) in tabbars[tabCurrentIndex].children"
|
||||
:key="index"
|
||||
>
|
||||
<view
|
||||
class="flex-row-c-c"
|
||||
@click="changCity(tabCurrentIndex, item)"
|
||||
>
|
||||
<text
|
||||
class="city-text"
|
||||
:class="{ color: tabbars[tabCurrentIndex].id == item.id }"
|
||||
>{{ item.name }}</text
|
||||
>
|
||||
<icon
|
||||
type="success_no_circle"
|
||||
v-if="tabbars[tabCurrentIndex].id == item.id"
|
||||
:id="'show' + tabCurrentIndex"
|
||||
class="ischeck"
|
||||
size="14"
|
||||
:color="$lightColor"
|
||||
></icon>
|
||||
</view>
|
||||
</block>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
let windowWidth = 0
|
||||
import { getRegionsById } from "@/api/common.js";
|
||||
export default {
|
||||
name: "UniCityNvue",
|
||||
props: {
|
||||
headTitle: {
|
||||
//标题
|
||||
type: String,
|
||||
default: "区域选择",
|
||||
},
|
||||
pickerSize: {
|
||||
// 使用多少个tab
|
||||
type: [String, String],
|
||||
default: "1",
|
||||
},
|
||||
provinceData: {
|
||||
// 默认的省市区id,如果不使用id的情况下则为[];
|
||||
type: Array,
|
||||
default: function () {
|
||||
return [];
|
||||
},
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
clearRightIcon: true, //是否显示右侧关闭icon
|
||||
scrollLeft: 500, //顶部选项卡左滑距离
|
||||
scrollTop: 0, //默认滚动顶部为0
|
||||
enableScroll: true, //是否启用滚动
|
||||
tabCurrentIndex: 0, //当前选项卡索引
|
||||
tabbars: this.provinceData, //默认的省市区id
|
||||
pickersize: this.pickerSize, //多少个tab 推荐为4级
|
||||
showPicker: false, //显示选取器
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
|
||||
/**
|
||||
* 显示选择器
|
||||
*/
|
||||
show() {
|
||||
this.showPicker = true;
|
||||
if (this.tabbars[0].children.length == 0) {
|
||||
getRegionsById(0).then((res) => {
|
||||
this.tabbars[0].children = res.data.result;
|
||||
});
|
||||
}
|
||||
|
||||
windowWidth = uni.getSystemInfoSync().windowWidth;
|
||||
},
|
||||
|
||||
/**
|
||||
* 关闭选择器
|
||||
*/
|
||||
hide() {
|
||||
this.showPicker = false;
|
||||
},
|
||||
|
||||
/**
|
||||
* tab切换
|
||||
*/
|
||||
changeTab(e) {
|
||||
let index = e;
|
||||
this.setScroll(index);
|
||||
//延迟300ms,等待swiper动画结束再修改tabbar
|
||||
this.tabCurrentIndex = index;
|
||||
setTimeout(() => {
|
||||
this.getScroll("show" + index);
|
||||
}, 10);
|
||||
},
|
||||
|
||||
/**
|
||||
* 获得元素的大小
|
||||
*/
|
||||
getElSize(id) {
|
||||
return new Promise((res, rej) => {
|
||||
let el = uni
|
||||
.createSelectorQuery()
|
||||
.in(this)
|
||||
.select("#" + id);
|
||||
el.fields(
|
||||
{
|
||||
size: true,
|
||||
scrollOffset: true,
|
||||
rect: true,
|
||||
},
|
||||
(data) => {
|
||||
res(data);
|
||||
}
|
||||
).exec();
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* 点击城市后回调
|
||||
*/
|
||||
async changCity(index, item) {
|
||||
if (this.tabbars[index].id != item.id) {
|
||||
this.tabbars[index].localName = item.name;
|
||||
this.tabbars[index].id = item.id;
|
||||
if (index < this.tabbars.length - 1) {
|
||||
this.tabbars.splice(index + 1, this.tabbars.length - index - 1);
|
||||
}
|
||||
if (this.tabbars.length < this.pickersize) {
|
||||
let data = await getRegionsById(item.id);
|
||||
// 当前选项级为最后一级时回调,将选中的数据返回
|
||||
if (data.data.result.length == 0) {
|
||||
this.$emit("funcValue", this.tabbars);
|
||||
this.hide();
|
||||
} else {
|
||||
// 将新的数据填充进下一级
|
||||
var current = {
|
||||
localName: "请选择",
|
||||
id: "",
|
||||
children: data.data.result,
|
||||
};
|
||||
this.tabbars.push(current);
|
||||
this.tabCurrentIndex++;
|
||||
|
||||
// 当前距离重新为最上面
|
||||
this.scrollTop = 0;
|
||||
}
|
||||
} else {
|
||||
this.$emit("funcValue", this.tabbars);
|
||||
this.hide();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取当前tab中滚动的距离
|
||||
*/
|
||||
async setScroll(index) {
|
||||
let width = 0;
|
||||
let nowWidth = 0;
|
||||
for (let i = 0; i <= index; i++) {
|
||||
let result = await this.getElSize("tab" + i);
|
||||
width += result.width;
|
||||
if (i === index) {
|
||||
nowWidth = result.width;
|
||||
}
|
||||
}
|
||||
if (width + nowWidth > windowWidth) {
|
||||
this.scrollLeft = width + nowWidth;
|
||||
} else {
|
||||
this.scrollLeft = 0;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 计算当前的滚动距离
|
||||
*/
|
||||
getScroll(id) {
|
||||
uni
|
||||
.createSelectorQuery()
|
||||
.in(this)
|
||||
.select(".panel-scroll-box")
|
||||
.boundingClientRect((data) => {
|
||||
uni
|
||||
.createSelectorQuery()
|
||||
.in(this)
|
||||
.select("#" + id)
|
||||
.boundingClientRect((res) => {
|
||||
if (res != undefined && res != null && res != "") {
|
||||
this.scrollTop = res.top - data.top;
|
||||
}
|
||||
})
|
||||
.exec();
|
||||
})
|
||||
.exec();
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
.mask {
|
||||
visibility: hidden;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
z-index: 1000;
|
||||
background: rgba(0, 0, 0, 0.6);
|
||||
opacity: 0;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
.maskShow {
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
}
|
||||
.cpicker-content {
|
||||
position: fixed;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
background-color: #ffffff;
|
||||
transition: all 0.3s ease;
|
||||
transform: translateY(100%);
|
||||
z-index: 3000;
|
||||
}
|
||||
.cpickerShow {
|
||||
transform: translateY(0);
|
||||
}
|
||||
.city-head {
|
||||
width: 750rpx;
|
||||
height: 88rpx;
|
||||
flex-direction: column;
|
||||
border-bottom-width: 1px;
|
||||
border-bottom-color: #f4f4f4;
|
||||
border-bottom-style: solid;
|
||||
}
|
||||
.city-head-title {
|
||||
font-size: 15px;
|
||||
line-height: 88rpx;
|
||||
align-items: center;
|
||||
/* #ifndef APP-NVUE */
|
||||
text-align: center;
|
||||
/* #endif */
|
||||
}
|
||||
.clearRightIcon {
|
||||
position: absolute;
|
||||
right: 15px;
|
||||
top: 12px;
|
||||
font-size: 10px;
|
||||
color: #bebebe;
|
||||
}
|
||||
.nav-bar {
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
height: 90rpx;
|
||||
white-space: nowrap;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.06);
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.nav-item {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: inline-flex !important;
|
||||
/* #endif */
|
||||
/* #ifdef APP-NVUE */
|
||||
flex-direction: row !important;
|
||||
/* #endif */
|
||||
width: 170rpx;
|
||||
padding: 7px 0px;
|
||||
line-height: 26px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #303133;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.nav-bar-title {
|
||||
font-size: 12px;
|
||||
}
|
||||
.current {
|
||||
color: $aider-light-color;
|
||||
border-color: $aider-light-color;
|
||||
border-bottom-width: 4rpx;
|
||||
border-bottom-style: solid;
|
||||
}
|
||||
.current:after {
|
||||
width: 50%;
|
||||
}
|
||||
.panel-scroll-box {
|
||||
height: 516rpx;
|
||||
margin-top: 8px;
|
||||
}
|
||||
.flex-row-c-c {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: block;
|
||||
/* #endif */
|
||||
/* #ifdef APP-NVUE */
|
||||
flex-direction: row;
|
||||
/* #endif */
|
||||
padding-left: 25px;
|
||||
}
|
||||
.city-text {
|
||||
/* #ifdef APP-NVUE */
|
||||
flex-direction: row;
|
||||
/* #endif */
|
||||
height: 35px;
|
||||
line-height: 35px;
|
||||
font-size: 13px;
|
||||
}
|
||||
.hide {
|
||||
opacity: 0;
|
||||
}
|
||||
.ischeck {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: inline-flex !important;
|
||||
/* #endif */
|
||||
/* #ifdef APP-NVUE */
|
||||
flex-direction: column;
|
||||
/* #endif */
|
||||
margin-right: 5px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.color {
|
||||
color: $light-color;
|
||||
}
|
||||
icon {
|
||||
margin-left: 40rpx;
|
||||
}
|
||||
</style>
|
10
components/m-goods-recommend/README.md
Normal file
@ -0,0 +1,10 @@
|
||||
## 商品推荐
|
||||
|
||||
|
||||
### OBJECT 参数说明
|
||||
属性|说明|类型|必填
|
||||
---|---|---|---
|
||||
`title`|标题,用于顶部显示的内容|String|否
|
||||
`pageSize`|显示多少条数据,建议是2的倍数,默认为12条|*|否
|
||||
`categoryId`|分类id|Function|否
|
||||
`storeId`|卖家id,搜索店铺商品的时候使用|String|否
|
152
components/m-goods-recommend/index.vue
Normal file
@ -0,0 +1,152 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="goods-recommend">{{title ? `--${title}-- `:''}}</div>
|
||||
<div class="goods-list">
|
||||
<div @click="handleClick(item)" class="goods-item" v-for="(item, item_index) in goodsList" :key="item_index">
|
||||
<div class="goods-img">
|
||||
<u-image :src="item.content.thumbnail" mode="aspectFill" height="350rpx" width="100%">
|
||||
<u-loading slot="loading"></u-loading>
|
||||
</u-image>
|
||||
</div>
|
||||
<div class="goods-desc">
|
||||
<div class="goods-title">
|
||||
{{ item.content.goodsName }}
|
||||
</div>
|
||||
<div class="goods-bottom">
|
||||
<div class="goods-price">¥{{ item.content.price | unitPrice }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getGoodsList } from "@/api/goods.js";
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
goodsList: [],
|
||||
params: {
|
||||
pageNumber: 1,
|
||||
},
|
||||
};
|
||||
},
|
||||
props: {
|
||||
title: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
pageSize: {
|
||||
type: null,
|
||||
default: 12,
|
||||
},
|
||||
categoryId: {
|
||||
type: null,
|
||||
default: "",
|
||||
},
|
||||
storeId: {
|
||||
type: null,
|
||||
default: "",
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.initGoods();
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* 初始化商品
|
||||
*/
|
||||
async initGoods() {
|
||||
let submit = JSON.parse(
|
||||
JSON.stringify(
|
||||
Object.assign(this.params, {
|
||||
pageSize: this.pageSize,
|
||||
categoryId: this.categoryId,
|
||||
storeId: this.storeId,
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
Object.keys(submit).map((key) => {
|
||||
if (!submit[key] || submit[key].length == 0) {
|
||||
delete submit[key];
|
||||
}
|
||||
});
|
||||
let goodsList = await getGoodsList(submit);
|
||||
this.goodsList.push(...goodsList.data.result.content);
|
||||
},
|
||||
handleClick(item) {
|
||||
uni.navigateTo({
|
||||
url: `/pages/product/goods?id=${item.content.id}&goodsId=${item.content.goodsId}`,
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
/**商品代码 */
|
||||
$w_94: 94%;
|
||||
.goods-recommend {
|
||||
background: #f7f7f7;
|
||||
height: 100rpx;
|
||||
line-height: 100rpx;
|
||||
text-align: center;
|
||||
font-size: 30rpx;
|
||||
font-weight: bold;
|
||||
}
|
||||
.goods-list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
background: #f7f7f7;
|
||||
}
|
||||
.goods-item {
|
||||
width: 50%;
|
||||
margin-bottom: 10px;
|
||||
border-radius: 0.4em;
|
||||
overflow: hidden;
|
||||
}
|
||||
.goods-img {
|
||||
position: relative;
|
||||
margin: 0 auto;
|
||||
width: $w_94;
|
||||
height: 350rpx;
|
||||
border-top-left-radius: 20rpx;
|
||||
border-top-right-radius: 20rpx;
|
||||
overflow: hidden;
|
||||
> img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
.goods-desc {
|
||||
border-bottom-left-radius: 20rpx;
|
||||
border-bottom-right-radius: 20rpx;
|
||||
width: $w_94;
|
||||
background: #fff;
|
||||
padding: 8rpx 0 8rpx 8rpx;
|
||||
margin: 0 auto;
|
||||
> .goods-title {
|
||||
font-size: 12px;
|
||||
height: 70rpx;
|
||||
display: -webkit-box;
|
||||
font-weight: 500;
|
||||
-webkit-box-orient: vertical;
|
||||
|
||||
-webkit-line-clamp: 2;
|
||||
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
> .goods-bottom {
|
||||
display: flex;
|
||||
font-weight: bold;
|
||||
|
||||
> .goods-price {
|
||||
line-height: 2;
|
||||
color: $main-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
246
components/m-search-revision/m-search-revision.vue
Normal file
@ -0,0 +1,246 @@
|
||||
<template>
|
||||
<view class="serach">
|
||||
<view class="left-box" @tap="onClickLeft">
|
||||
<u-icon name="arrow-left" size="40" color="#666"></u-icon>
|
||||
</view>
|
||||
<view class="content" :style="{ 'border-radius': radius + 'px' }">
|
||||
<!-- HM修改 增加进入输入状态的点击范围 -->
|
||||
<view class="content-box" :class="{ center: mode === 2 }">
|
||||
<u-icon name="search" size="32" style="padding:0 15rpx;"></u-icon>
|
||||
<!-- HM修改 增加placeholder input confirm-type confirm-->
|
||||
<input style="width:100%; " :placeholder="placeholder" placeholder-class="placeholder-color"
|
||||
@input="inputChange" confirm-type="search" @confirm="triggerConfirm" class="input"
|
||||
:class="{ center: !active && mode === 2 }" :focus="isFocus" v-model="inputVal" @focus="focus" @blur="blur" />
|
||||
<u-icon name="close" v-if="isDelShow" style="padding:0 30rpx;" @click="clear"></u-icon>
|
||||
</view>
|
||||
|
||||
</view>
|
||||
<view class="button active" >
|
||||
<view v-if="isShowSeachGoods !=true" class="button-item">
|
||||
<div @click="out()">取消</div>
|
||||
</view>
|
||||
|
||||
<view v-else class="button-item">
|
||||
<u-icon name="grid-fill" size="50" @click="handelListClass()" v-if="!switchLayout"></u-icon>
|
||||
<u-icon v-else @click="handelListClass()" name="list-dot" size="50"></u-icon>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
mode: {
|
||||
value: Number,
|
||||
default: 1,
|
||||
},
|
||||
//HM修改 定义默认搜索关键词(水印文字)
|
||||
placeholder: {
|
||||
value: String,
|
||||
default: "请输入搜索内容",
|
||||
},
|
||||
value: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
// 默认半径为60
|
||||
radius: {
|
||||
value: String,
|
||||
default: 60,
|
||||
},
|
||||
// 是否获取焦点
|
||||
isFocusVal: {
|
||||
value: Boolean,
|
||||
default: true,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isShowSeachGoods: false, //是否显示查询的商品
|
||||
active: false, //是否选中
|
||||
inputVal: "", //Input中内容
|
||||
isDelShow: false, //是否显示右侧删除icon
|
||||
isFocus: false, //是否获取焦点
|
||||
switchLayout: true, //切换当前商品的布局,默认为两列
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.isFocus = this.isFocusVal;
|
||||
},
|
||||
methods: {
|
||||
//
|
||||
out() {
|
||||
uni.reLaunch({
|
||||
url: "/pages/tabbar/home/index",
|
||||
});
|
||||
},
|
||||
// 切换排列顺序
|
||||
handelListClass() {
|
||||
this.switchLayout = !this.switchLayout;
|
||||
this.$emit("SwitchType");
|
||||
},
|
||||
//HM修改 触发组件confirm事件
|
||||
triggerConfirm() {
|
||||
this.$emit("confirm", false);
|
||||
uni.hideKeyboard();
|
||||
},
|
||||
//HM修改 触发组件input事件
|
||||
inputChange(event) {
|
||||
var keyword = event.detail.value;
|
||||
this.$emit("input", keyword);
|
||||
if (this.inputVal) {
|
||||
this.isDelShow = true;
|
||||
}
|
||||
},
|
||||
focus() {
|
||||
this.active = true;
|
||||
//HM修改 增加获取焦点判断
|
||||
if (this.inputVal) {
|
||||
this.isDelShow = true;
|
||||
}
|
||||
},
|
||||
blur() {
|
||||
this.isFocus = false;
|
||||
if (!this.inputVal) {
|
||||
this.active = false;
|
||||
}
|
||||
},
|
||||
clear() {
|
||||
//HM修改 收起键盘
|
||||
uni.hideKeyboard();
|
||||
this.isFocus = false;
|
||||
this.inputVal = "";
|
||||
this.active = false;
|
||||
//HM修改 清空内容时候触发组件input
|
||||
this.$emit("input", "");
|
||||
//this.$emit('search', '');//HM修改 清空内容时候不进行搜索
|
||||
},
|
||||
|
||||
/**
|
||||
* 回退到上一级
|
||||
*/
|
||||
onClickLeft() {
|
||||
uni.navigateBack();
|
||||
},
|
||||
|
||||
/**
|
||||
* 内容为空时,输入默认关键字
|
||||
*/
|
||||
search() {
|
||||
if (!this.inputVal) {
|
||||
if (this.searchName == "取消") {
|
||||
uni.hideKeyboard();
|
||||
this.isFocus = false;
|
||||
this.active = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
this.$emit("search", this.inputVal ? this.inputVal : this.placeholder);
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
/**
|
||||
* 监听当前是否有值 是否显示清除图标
|
||||
*/
|
||||
inputVal(newVal) {
|
||||
newVal ? (this.isDelShow = true) : (this.isDelShow = false);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.serach {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
//border-bottom: 1px #f5f5f5 solid; //HM修改 去掉边框
|
||||
box-sizing: border-box;
|
||||
font-size: $uni-font-size-base;
|
||||
|
||||
.left-box {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 15%;
|
||||
/* #ifndef APP-NVUE */
|
||||
text-align: center;
|
||||
// display: flex;
|
||||
// /* #endif */
|
||||
}
|
||||
|
||||
.content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
height: 70rpx;
|
||||
color: #999;
|
||||
background: #eee;
|
||||
overflow: hidden;
|
||||
transition: all 0.2s linear;
|
||||
.content-box {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
&.center {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.icon {
|
||||
padding: 0 15rpx;
|
||||
|
||||
&.icon-serach:before {
|
||||
content: "\e61c";
|
||||
}
|
||||
}
|
||||
|
||||
.input {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
line-height: 60rpx;
|
||||
height: 60rpx;
|
||||
transition: all 0.2s linear;
|
||||
|
||||
&.center {
|
||||
width: 200rpx;
|
||||
}
|
||||
|
||||
&.sub {
|
||||
// position: absolute;
|
||||
width: auto;
|
||||
color: grey;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
flex-shrink: 0;
|
||||
width: 0;
|
||||
transition: all 0.2s linear;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
|
||||
&.active {
|
||||
padding-left: 15rpx;
|
||||
width: 100rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.icon {
|
||||
font-family: iconfont;
|
||||
font-size: 32rpx;
|
||||
font-style: normal;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.placeholder-color {
|
||||
color: #999;
|
||||
opacity: 0.4;
|
||||
}
|
||||
</style>
|
179
components/m-share/index.vue
Normal file
@ -0,0 +1,179 @@
|
||||
<template>
|
||||
<!-- 遮罩层 -->
|
||||
<u-popup @close="close" v-model="show" mode="bottom" border-radius="30" height="260rpx">
|
||||
|
||||
<view class="share-title">
|
||||
<span>分享至</span>
|
||||
</view>
|
||||
<view class="share-list">
|
||||
<!-- #ifdef MP-WEIXIN -->
|
||||
<view class="share-item">
|
||||
<button class="share-btn" open-type="share">
|
||||
<u-icon color="#04BE02" size="80" name="weixin-fill"></u-icon>微信好友
|
||||
</button>
|
||||
</view>
|
||||
<!-- #endif -->
|
||||
<!-- #ifdef APP-PLUS -->
|
||||
<view class="share-item" @click="handleShare(item)" v-for="(item, index) in list" :key="index">
|
||||
<u-icon :color="item.color" size="80" :name="item.icon"></u-icon>
|
||||
<view>{{ item.title }}</view>
|
||||
</view>
|
||||
<!-- #endif -->
|
||||
<!-- #ifdef H5 -->
|
||||
<view class="share-item" @click="copyLink()">
|
||||
<u-icon color="#b4aee8" size="80" name="share-fill"></u-icon>
|
||||
<view>{{ '复制链接' }}</view>
|
||||
</view>
|
||||
<!-- #endif -->
|
||||
</view>
|
||||
</u-popup>
|
||||
</template>
|
||||
<script>
|
||||
import { h5Copy } from "@/js_sdk/h5-copy/h5-copy.js";
|
||||
import configs from "@/config/config";
|
||||
import mpShare from "uview-ui/libs/mixin/mpShare.js";
|
||||
|
||||
export default {
|
||||
mixins: [mpShare],
|
||||
data() {
|
||||
return {
|
||||
configs,
|
||||
show: true,
|
||||
list: [
|
||||
{
|
||||
color: "#04BE02",
|
||||
title: "微信好友",
|
||||
icon: "weixin-fill",
|
||||
type: 0,
|
||||
},
|
||||
{
|
||||
color: "#04BE02",
|
||||
title: "朋友圈",
|
||||
icon: "weixin-circle-fill",
|
||||
type: 1,
|
||||
},
|
||||
],
|
||||
};
|
||||
},
|
||||
// 图片缩略图、 商品名称 、 type(goods,shop,pintuan) 拼团商品分享以及店铺分享
|
||||
|
||||
props: ["thumbnail", "goodsName", "type", "goodsId", "link"],
|
||||
methods: {
|
||||
close() {
|
||||
this.$emit("close");
|
||||
},
|
||||
// h5复制链接
|
||||
// #ifdef H5
|
||||
copyLink() {
|
||||
let content;
|
||||
if (this.link) {
|
||||
content = this.configs.shareLink + this.link;
|
||||
} else {
|
||||
content =
|
||||
this.configs.shareLink +
|
||||
getCurrentPages()[getCurrentPages().length - 1].__page__.fullPath;
|
||||
}
|
||||
if (content === null || content === undefined) {
|
||||
content = "";
|
||||
} else content = content + "";
|
||||
const result = h5Copy(content);
|
||||
if (result === false) {
|
||||
uni.showToast({
|
||||
title: "不支持",
|
||||
});
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: "复制成功",
|
||||
icon: "none",
|
||||
});
|
||||
}
|
||||
},
|
||||
// #endif
|
||||
|
||||
shareTitle() {
|
||||
let shareTitle;
|
||||
if (this.type == "goods") {
|
||||
shareTitle = `我发现了一个${this.goodsName}商品快来跟我一起看看吧`;
|
||||
} else if (this.type == "shops") {
|
||||
shareTitle = `我发现了一个${this.goodsName}店铺快来跟我一起看看吧`;
|
||||
} else if (this.type == "pintuan") {
|
||||
shareTitle = `我拼了一个${this.goodsName}快来跟我一起抢购吧!`;
|
||||
} else if (this.type == "kanjia") {
|
||||
shareTitle = `请快来帮我砍一刀${this.goodsName}`;
|
||||
}
|
||||
return shareTitle;
|
||||
},
|
||||
|
||||
// #ifdef APP-PLUS
|
||||
handleShare(val) {
|
||||
console.log("12312312")
|
||||
if (val.type <= 1) {
|
||||
let scene; // "WXSenceTimeline 朋友圈 WXSceneSession 微信好友"
|
||||
val.type == 1
|
||||
? (scene = "WXSenceTimeline")
|
||||
: (scene = "WXSceneSession");
|
||||
uni.share({
|
||||
provider: "weixin",
|
||||
scene: scene,
|
||||
href: configs.shareLink + this.link,
|
||||
imageUrl: this.thumbnail,
|
||||
type: 0,
|
||||
summary: this.goodsName,
|
||||
title: this.shareTitle(),
|
||||
success: function (res) {
|
||||
uni.showToast({
|
||||
title: "分享成功!",
|
||||
duration: 2000,
|
||||
icon: "none",
|
||||
});
|
||||
this.$emit("close");
|
||||
},
|
||||
fail: function (err) {
|
||||
uni.showToast({
|
||||
title: "分享失败!",
|
||||
duration: 2000,
|
||||
icon: "none",
|
||||
});
|
||||
this.$emit("close");
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
// #endif
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
@import "./mp-share.scss";
|
||||
.share-title {
|
||||
position: relative;
|
||||
height: 90rpx;
|
||||
font-size: 32rpx;
|
||||
line-height: 90rpx;
|
||||
text-align: center;
|
||||
> .share-close {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
right: 20rpx;
|
||||
top: 30rpx;
|
||||
}
|
||||
}
|
||||
button:after {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.share-list {
|
||||
padding: 0 32rpx;
|
||||
display: flex;
|
||||
text-align: center;
|
||||
align-items: center;
|
||||
> .share-item {
|
||||
width: 25%;
|
||||
font-size: 24rpx;
|
||||
color: #666;
|
||||
> * {
|
||||
margin: 8rpx 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
8
components/m-share/mp-share.scss
Normal file
@ -0,0 +1,8 @@
|
||||
.share-btn {
|
||||
background: none;
|
||||
font-size: 24rpx;
|
||||
color: #666;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
343
components/popups/popups.vue
Normal file
@ -0,0 +1,343 @@
|
||||
<template>
|
||||
<view class="shadow" :class="!show?'':'shadow-show'" :style="{backgroundColor:show?maskBg:'rgba(0,0,0,0)'}" @tap="tapMask">
|
||||
<view class="popups" :class="[theme]" :style="{top: popupsTop ,left: popupsLeft,flexDirection:direction}">
|
||||
<text :class="dynPlace" :style="{width:'0px',height:'0px'}" v-if="triangle"></text>
|
||||
<view v-for="(item,index) in popData" :key="index" @tap.stop="tapItem(item)" class="itemChild view" :class="[direction=='row'?'solid-right':'solid-bottom',item.disabled?'disabledColor':'']">
|
||||
<u-icon size="35" :name="item.icon" v-if="item.icon"></u-icon><span class="title">{{item.title}}</span>
|
||||
</view>
|
||||
<slot></slot>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
maskBg: {
|
||||
type: String,
|
||||
default: "rgba(0,0,0,0)",
|
||||
},
|
||||
placement: {
|
||||
type: String,
|
||||
default: "default", //default top-start top-end bottom-start bottom-end
|
||||
},
|
||||
direction: {
|
||||
type: String,
|
||||
default: "column", //column row
|
||||
},
|
||||
x: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
y: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
value: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
popData: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
theme: {
|
||||
type: String,
|
||||
default: "light", //light dark
|
||||
},
|
||||
dynamic: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
gap: {
|
||||
type: Number,
|
||||
default: 20,
|
||||
},
|
||||
triangle: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
popupsTop: "0rpx",
|
||||
popupsLeft: "0rpx",
|
||||
show: false,
|
||||
dynPlace: "",
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.popupsPosition();
|
||||
},
|
||||
methods: {
|
||||
tapMask() {
|
||||
this.$emit("input", !this.value);
|
||||
},
|
||||
tapItem(item) {
|
||||
if (item.disabled) return;
|
||||
this.$emit("tapPopup", item);
|
||||
this.$emit("input", !this.value);
|
||||
},
|
||||
getStatusBar() {
|
||||
let promise = new Promise((resolve, reject) => {
|
||||
uni.getSystemInfo({
|
||||
success: function (e) {
|
||||
let customBar;
|
||||
// #ifdef H5
|
||||
|
||||
customBar = e.statusBarHeight + e.windowTop;
|
||||
|
||||
// #endif
|
||||
resolve(customBar);
|
||||
},
|
||||
});
|
||||
});
|
||||
return promise;
|
||||
},
|
||||
async popupsPosition() {
|
||||
let statusBar = await this.getStatusBar();
|
||||
let promise = new Promise((resolve, reject) => {
|
||||
let popupsDom = uni.createSelectorQuery().in(this).select(".popups");
|
||||
popupsDom
|
||||
.fields(
|
||||
{
|
||||
size: true,
|
||||
},
|
||||
(data) => {
|
||||
let width = data.width;
|
||||
let height = data.height;
|
||||
|
||||
|
||||
|
||||
let y = this.dynamic
|
||||
? this.dynamicGetY(this.y, this.gap)
|
||||
: this.transformRpx(this.y);
|
||||
|
||||
let x = this.dynamic
|
||||
? this.dynamicGetX(this.x, this.gap)
|
||||
: this.transformRpx(this.x);
|
||||
|
||||
// #ifdef H5
|
||||
y = this.dynamic
|
||||
? this.y + statusBar
|
||||
: this.transformRpx(this.y + statusBar);
|
||||
// #endif
|
||||
|
||||
this.dynPlace =
|
||||
this.placement == "default"
|
||||
? this.getPlacement(x, y)
|
||||
: this.placement;
|
||||
|
||||
switch (this.dynPlace) {
|
||||
case "top-start":
|
||||
this.popupsTop = `${y + 9}rpx`;
|
||||
this.popupsLeft = `${x - 15}rpx`;
|
||||
break;
|
||||
case "top-end":
|
||||
this.popupsTop = `${y + 9}rpx`;
|
||||
this.popupsLeft = `${x + 15 - width}rpx`;
|
||||
break;
|
||||
case "bottom-start":
|
||||
this.popupsTop = `${y - 18 - height}rpx`;
|
||||
this.popupsLeft = `${x - 15}rpx`;
|
||||
break;
|
||||
case "bottom-end":
|
||||
this.popupsTop = `${y - 9 - height}rpx`;
|
||||
this.popupsLeft = `${x + 15 - width}rpx`;
|
||||
break;
|
||||
}
|
||||
resolve();
|
||||
}
|
||||
)
|
||||
.exec();
|
||||
});
|
||||
return promise;
|
||||
},
|
||||
getPlacement(x, y) {
|
||||
let width = uni.getSystemInfoSync().windowWidth;
|
||||
let height = uni.getSystemInfoSync().windowHeight;
|
||||
if (x > width / 2 && y > height / 2) {
|
||||
return "bottom-end";
|
||||
} else if (x < width / 2 && y < height / 2) {
|
||||
return "top-start";
|
||||
} else if (x > width / 2 && y < height / 2) {
|
||||
return "top-end";
|
||||
} else if (x < width / 2 && y > height / 2) {
|
||||
return "bottom-start";
|
||||
} else if (x > width / 2) {
|
||||
return "top-end";
|
||||
} else {
|
||||
return "top-start";
|
||||
}
|
||||
},
|
||||
dynamicGetY(y, gap) {
|
||||
let height = uni.getSystemInfoSync().windowHeight;
|
||||
y = y < gap ? gap : y;
|
||||
y = height - y < gap ? height - gap : y;
|
||||
|
||||
return y;
|
||||
},
|
||||
dynamicGetX(x, gap) {
|
||||
let width = uni.getSystemInfoSync().windowWidth;
|
||||
x = x < gap ? gap : x;
|
||||
x = width - x < gap ? width - gap : x;
|
||||
return x;
|
||||
},
|
||||
transformRpx(params) {
|
||||
return (params * uni.getSystemInfoSync().screenWidth) / 375;
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
value: {
|
||||
immediate: true,
|
||||
handler: async function (newVal, oldVal) {
|
||||
if (newVal) await this.popupsPosition();
|
||||
this.show = newVal;
|
||||
},
|
||||
},
|
||||
placement: {
|
||||
immediate: true,
|
||||
handler(newVal, oldVal) {
|
||||
this.dynPlace = newVal;
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.title {
|
||||
margin-left: 20rpx;
|
||||
}
|
||||
.shadow {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
height: 100%;
|
||||
z-index: 9999;
|
||||
transition: background 0.3s ease-in-out;
|
||||
visibility: hidden;
|
||||
|
||||
&.shadow-show {
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
|
||||
.popups {
|
||||
position: absolute;
|
||||
padding: 20rpx;
|
||||
border-radius: 5px;
|
||||
display: flex;
|
||||
.view {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 15rpx 10rpx;
|
||||
font-size: 25rpx;
|
||||
}
|
||||
.image {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
width: 40rpx;
|
||||
height: 40rpx;
|
||||
margin-right: 20rpx;
|
||||
}
|
||||
}
|
||||
.dark {
|
||||
background-color: #4c4c4c;
|
||||
color: #fff;
|
||||
.top-start:after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: -18rpx;
|
||||
left: 10rpx;
|
||||
border-width: 0 20rpx 20rpx;
|
||||
border-style: solid;
|
||||
border-color: transparent transparent #4c4c4c;
|
||||
}
|
||||
.top-end:after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: -18rpx;
|
||||
right: 10rpx;
|
||||
border-width: 0 20rpx 20rpx;
|
||||
border-style: solid;
|
||||
border-color: transparent transparent #4c4c4c;
|
||||
}
|
||||
.bottom-start:after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
bottom: -18rpx;
|
||||
left: 10rpx;
|
||||
border-width: 20rpx 20rpx 0;
|
||||
border-style: solid;
|
||||
border-color: #4c4c4c transparent transparent;
|
||||
}
|
||||
.bottom-end:after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
bottom: -18rpx;
|
||||
right: 10rpx;
|
||||
border-width: 20rpx 20rpx 0;
|
||||
border-style: solid;
|
||||
border-color: #4c4c4c transparent transparent;
|
||||
}
|
||||
.disabledColor {
|
||||
color: #c5c8ce;
|
||||
}
|
||||
}
|
||||
.light {
|
||||
color: #515a6e;
|
||||
box-shadow: 0upx 0upx 30upx rgba(0, 0, 0, 0.2);
|
||||
background: #fff;
|
||||
.top-start:after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: -18rpx;
|
||||
left: 10rpx;
|
||||
border-width: 0 20rpx 20rpx;
|
||||
border-style: solid;
|
||||
border-color: transparent transparent #fff;
|
||||
}
|
||||
.top-end:after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: -18rpx;
|
||||
right: 10rpx;
|
||||
border-width: 0 20rpx 20rpx;
|
||||
border-style: solid;
|
||||
border-color: transparent transparent #fff;
|
||||
}
|
||||
.bottom-start:after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
bottom: -18rpx;
|
||||
left: 10rpx;
|
||||
border-width: 20rpx 20rpx 0;
|
||||
border-style: solid;
|
||||
border-color: #fff transparent transparent;
|
||||
}
|
||||
.bottom-end:after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
bottom: -18rpx;
|
||||
right: 10rpx;
|
||||
border-width: 20rpx 20rpx 0;
|
||||
border-style: solid;
|
||||
border-color: #fff transparent transparent;
|
||||
}
|
||||
.disabledColor {
|
||||
color: #c5c8ce;
|
||||
}
|
||||
}
|
||||
.solid-bottom {
|
||||
border-bottom: 1px solid #f3f5f7;
|
||||
}
|
||||
.solid-right {
|
||||
border-right: 1px solid #ccc;
|
||||
}
|
||||
.popups .itemChild:last-child {
|
||||
border: none;
|
||||
}
|
||||
</style>
|
200
components/uni-load-more/uni-load-more.vue
Normal file
@ -0,0 +1,200 @@
|
||||
<template>
|
||||
<view class="uni-load-more">
|
||||
<view class="uni-load-more__img" v-show="status === 'loading' && showIcon">
|
||||
<view class="load1">
|
||||
<view :style="{ background: color }"></view>
|
||||
<view :style="{ background: color }"></view>
|
||||
<view :style="{ background: color }"></view>
|
||||
<view :style="{ background: color }"></view>
|
||||
</view>
|
||||
<view class="load2">
|
||||
<view :style="{ background: color }"></view>
|
||||
<view :style="{ background: color }"></view>
|
||||
<view :style="{ background: color }"></view>
|
||||
<view :style="{ background: color }"></view>
|
||||
</view>
|
||||
<view class="load3">
|
||||
<view :style="{ background: color }"></view>
|
||||
<view :style="{ background: color }"></view>
|
||||
<view :style="{ background: color }"></view>
|
||||
<view :style="{ background: color }"></view>
|
||||
</view>
|
||||
</view>
|
||||
<text class="uni-load-more__text" :style="{ color: color }" v-if="showText">
|
||||
{{ status === 'more' ? contentText.contentdown : status === 'loading' ? contentText.contentrefresh : contentText.contentnomore }}
|
||||
</text>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'uni-load-more',
|
||||
props: {
|
||||
status: {
|
||||
//上拉的状态:more-loading前;loading-loading中;noMore-没有更多了
|
||||
type: String,
|
||||
default: 'more'
|
||||
},
|
||||
showIcon: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
showText: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
color: {
|
||||
type: String,
|
||||
default: '#777777'
|
||||
},
|
||||
contentText: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {
|
||||
contentdown: '上拉显示更多',
|
||||
contentrefresh: '正在加载...',
|
||||
contentnomore: '没有更多数据了'
|
||||
};
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {};
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
@charset "UTF-8";
|
||||
|
||||
.uni-load-more {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
height: 80rpx;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.uni-load-more__text {
|
||||
font-size: 28rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.uni-load-more__img {
|
||||
height: 24px;
|
||||
width: 24px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.uni-load-more__img > view {
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.uni-load-more__img > view view {
|
||||
width: 6px;
|
||||
height: 2px;
|
||||
border-top-left-radius: 1px;
|
||||
border-bottom-left-radius: 1px;
|
||||
background: #999;
|
||||
position: absolute;
|
||||
opacity: 0.2;
|
||||
transform-origin: 50%;
|
||||
animation: load 1.56s ease infinite;
|
||||
}
|
||||
|
||||
.uni-load-more__img > view view:nth-child(1) {
|
||||
transform: rotate(90deg);
|
||||
top: 2px;
|
||||
left: 9px;
|
||||
}
|
||||
|
||||
.uni-load-more__img > view view:nth-child(2) {
|
||||
transform: rotate(180deg);
|
||||
top: 11px;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.uni-load-more__img > view view:nth-child(3) {
|
||||
transform: rotate(270deg);
|
||||
bottom: 2px;
|
||||
left: 9px;
|
||||
}
|
||||
|
||||
.uni-load-more__img > view view:nth-child(4) {
|
||||
top: 11px;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.load1,
|
||||
.load2,
|
||||
.load3 {
|
||||
height: 24px;
|
||||
width: 24px;
|
||||
}
|
||||
|
||||
.load2 {
|
||||
transform: rotate(30deg);
|
||||
}
|
||||
|
||||
.load3 {
|
||||
transform: rotate(60deg);
|
||||
}
|
||||
|
||||
.load1 view:nth-child(1) {
|
||||
animation-delay: 0s;
|
||||
}
|
||||
|
||||
.load2 view:nth-child(1) {
|
||||
animation-delay: 0.13s;
|
||||
}
|
||||
|
||||
.load3 view:nth-child(1) {
|
||||
animation-delay: 0.26s;
|
||||
}
|
||||
|
||||
.load1 view:nth-child(2) {
|
||||
animation-delay: 0.39s;
|
||||
}
|
||||
|
||||
.load2 view:nth-child(2) {
|
||||
animation-delay: 0.52s;
|
||||
}
|
||||
|
||||
.load3 view:nth-child(2) {
|
||||
animation-delay: 0.65s;
|
||||
}
|
||||
|
||||
.load1 view:nth-child(3) {
|
||||
animation-delay: 0.78s;
|
||||
}
|
||||
|
||||
.load2 view:nth-child(3) {
|
||||
animation-delay: 0.91s;
|
||||
}
|
||||
|
||||
.load3 view:nth-child(3) {
|
||||
animation-delay: 1.04s;
|
||||
}
|
||||
|
||||
.load1 view:nth-child(4) {
|
||||
animation-delay: 1.17s;
|
||||
}
|
||||
|
||||
.load2 view:nth-child(4) {
|
||||
animation-delay: 1.3s;
|
||||
}
|
||||
|
||||
.load3 view:nth-child(4) {
|
||||
animation-delay: 1.43s;
|
||||
}
|
||||
|
||||
@-webkit-keyframes load {
|
||||
0% {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 0.2;
|
||||
}
|
||||
}
|
||||
</style>
|
184
components/verification/animation.css
Normal file
@ -0,0 +1,184 @@
|
||||
/*
|
||||
Animation 微动画
|
||||
基于ColorUI组建库的动画模块 by 文晓港 2019年3月26日19:52:28
|
||||
*/
|
||||
|
||||
/* css 滤镜 控制黑白底色gif的 */
|
||||
.gif-black{
|
||||
mix-blend-mode: screen;
|
||||
}
|
||||
.gif-white{
|
||||
mix-blend-mode: multiply;
|
||||
}
|
||||
|
||||
|
||||
/* Animation css */
|
||||
[class*=animation-] {
|
||||
animation-duration: .5s;
|
||||
animation-timing-function: ease-out;
|
||||
animation-fill-mode: both
|
||||
}
|
||||
|
||||
.animation-fade {
|
||||
animation-name: fade;
|
||||
animation-duration: .8s;
|
||||
animation-timing-function: linear
|
||||
}
|
||||
|
||||
.animation-scale-up {
|
||||
animation-name: scale-up
|
||||
}
|
||||
|
||||
.animation-scale-down {
|
||||
animation-name: scale-down
|
||||
}
|
||||
|
||||
.animation-slide-top {
|
||||
animation-name: slide-top
|
||||
}
|
||||
|
||||
.animation-slide-bottom {
|
||||
animation-name: slide-bottom
|
||||
}
|
||||
|
||||
.animation-slide-left {
|
||||
animation-name: slide-left
|
||||
}
|
||||
|
||||
.animation-slide-right {
|
||||
animation-name: slide-right
|
||||
}
|
||||
|
||||
.animation-shake {
|
||||
animation-name: shake
|
||||
}
|
||||
|
||||
.animation-reverse {
|
||||
animation-direction: reverse
|
||||
}
|
||||
|
||||
@keyframes fade {
|
||||
0% {
|
||||
opacity: 0
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 1
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes scale-up {
|
||||
0% {
|
||||
opacity: 0;
|
||||
transform: scale(.2)
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 1;
|
||||
transform: scale(1)
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes scale-down {
|
||||
0% {
|
||||
opacity: 0;
|
||||
transform: scale(1.8)
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 1;
|
||||
transform: scale(1)
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes slide-top {
|
||||
0% {
|
||||
opacity: 0;
|
||||
transform: translateY(-100%)
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 1;
|
||||
transform: translateY(0)
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes slide-bottom {
|
||||
0% {
|
||||
opacity: 0;
|
||||
transform: translateY(100%)
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 1;
|
||||
transform: translateY(0)
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes shake {
|
||||
|
||||
0%,
|
||||
100% {
|
||||
transform: translateX(0)
|
||||
}
|
||||
|
||||
10% {
|
||||
transform: translateX(-9px)
|
||||
}
|
||||
|
||||
20% {
|
||||
transform: translateX(8px)
|
||||
}
|
||||
|
||||
30% {
|
||||
transform: translateX(-7px)
|
||||
}
|
||||
|
||||
40% {
|
||||
transform: translateX(6px)
|
||||
}
|
||||
|
||||
50% {
|
||||
transform: translateX(-5px)
|
||||
}
|
||||
|
||||
60% {
|
||||
transform: translateX(4px)
|
||||
}
|
||||
|
||||
70% {
|
||||
transform: translateX(-3px)
|
||||
}
|
||||
|
||||
80% {
|
||||
transform: translateX(2px)
|
||||
}
|
||||
|
||||
90% {
|
||||
transform: translateX(-1px)
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes slide-left {
|
||||
0% {
|
||||
opacity: 0;
|
||||
transform: translateX(-100%)
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 1;
|
||||
transform: translateX(0)
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes slide-right {
|
||||
0% {
|
||||
opacity: 0;
|
||||
transform: translateX(100%)
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 1;
|
||||
transform: translateX(0)
|
||||
}
|
||||
}
|
1226
components/verification/icon.css
Normal file
531
components/verification/verification.vue
Normal file
@ -0,0 +1,531 @@
|
||||
<template>
|
||||
<view>
|
||||
<view
|
||||
v-if="!hid"
|
||||
class="flex-row-center"
|
||||
:style="{ top: scHight }"
|
||||
style="width: 750rpx; position: fixed; z-index: 100; left: 0"
|
||||
>
|
||||
<view
|
||||
class="flex-column-center"
|
||||
style="background-color: #fcfcfc; padding: 30rpx; border-radius: 10rpx"
|
||||
>
|
||||
<movable-area
|
||||
class="flex"
|
||||
style="width: 100%"
|
||||
animation="false"
|
||||
:style="{ height: originalHeight }"
|
||||
>
|
||||
<movable-view
|
||||
scale-value="1"
|
||||
animation="false"
|
||||
damping="5000"
|
||||
:x="moveX"
|
||||
:style="{
|
||||
height: sliderHeight,
|
||||
width: sliderWidth,
|
||||
'z-index': 101,
|
||||
}"
|
||||
direction="horizontal"
|
||||
>
|
||||
<image
|
||||
:src="imgbk"
|
||||
class="image"
|
||||
mode="aspectFit"
|
||||
:style="{
|
||||
height: sliderHeight,
|
||||
width: sliderWidth,
|
||||
'margin-top': imgbKH,
|
||||
}"
|
||||
></image>
|
||||
</movable-view>
|
||||
<image
|
||||
:src="img"
|
||||
mode="aspectFit"
|
||||
:style="{ height: originalHeight, width: originalWidth }"
|
||||
style="border-radius: 10rpx"
|
||||
></image>
|
||||
</movable-area>
|
||||
|
||||
<movable-area
|
||||
class="flex-row-start"
|
||||
style="
|
||||
width: 100%;
|
||||
background-color: #efefef;
|
||||
height: 80rpx;
|
||||
border-radius: 40rpx;
|
||||
margin-top: 30rpx;
|
||||
"
|
||||
>
|
||||
<movable-view
|
||||
scale-value="1"
|
||||
animation="false"
|
||||
damping="50"
|
||||
:x="movePv"
|
||||
class="flex-row-center"
|
||||
style="
|
||||
border-radius: 50%;
|
||||
height: 100rpx;
|
||||
width: 100rpx;
|
||||
background-color: #ffffff;
|
||||
border: 2rpx solid #e3e3e3;
|
||||
margin-top: -13rpx;
|
||||
"
|
||||
direction="horizontal"
|
||||
@change="moveChange"
|
||||
@touchend="end"
|
||||
>
|
||||
<u-icon
|
||||
:color="mainColor"
|
||||
size="40"
|
||||
v-if="endLoad"
|
||||
name="arrow-right"
|
||||
></u-icon>
|
||||
<u-icon :color="mainColor" size="40" v-else name="reload"></u-icon>
|
||||
</movable-view>
|
||||
|
||||
<text style="padding-left: 140rpx" :style="{ color: col }">{{
|
||||
hasImg
|
||||
}}</text>
|
||||
</movable-area>
|
||||
<view class="flex-row-around padding-top" style="width: 100%">
|
||||
<u-icon
|
||||
@click="hide"
|
||||
:color="mainColor"
|
||||
size="40"
|
||||
name="close"
|
||||
></u-icon>
|
||||
|
||||
<text class="cu-tag bg-cyan round" @click="getCode">刷新拼图</text>
|
||||
<text class="my-neirong-sm cuIcon-safe" style="color: #c1c1c1"
|
||||
>Lili-FRAMEWORK</text
|
||||
>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import api from "@/config/api.js";
|
||||
import storage from "@/utils/storage.js";
|
||||
import uuid from "@/utils/uuid.modified.js";
|
||||
const phone = uni.getSystemInfoSync();
|
||||
const l = phone.screenWidth / 750;
|
||||
export default {
|
||||
name: "verification",
|
||||
created() {
|
||||
// 可自行调整
|
||||
this.scHight = phone.screenHeight / 2 - 200 + "px";
|
||||
this.getCode();
|
||||
},
|
||||
props: {
|
||||
height: {
|
||||
type: String,
|
||||
default: "80rpx",
|
||||
},
|
||||
width: {
|
||||
type: String,
|
||||
default: "350rpx",
|
||||
},
|
||||
left: {
|
||||
type: String,
|
||||
default: "180rpx",
|
||||
},
|
||||
top: {
|
||||
type: String,
|
||||
default: "30rpx",
|
||||
},
|
||||
business: {
|
||||
type: String,
|
||||
default: "LOGIN",
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
mainColor: this.$mainColor,
|
||||
flage: false,
|
||||
key: "", //key
|
||||
vsrtx: "点击进行验证", //按钮提示语
|
||||
vsr: false, //
|
||||
hid: true,
|
||||
col: "#838383",
|
||||
movePv: 0,
|
||||
hasImg: "拖动滑块已完成拼图",
|
||||
spcode: "",
|
||||
tl: 0,
|
||||
moveCode: 0,
|
||||
//X轴移动距离
|
||||
moveX: 0,
|
||||
//模版高度
|
||||
originalHeight: "",
|
||||
//模版宽度
|
||||
originalWidth: "",
|
||||
//拼图高度
|
||||
sliderHeight: "",
|
||||
//平涂宽度
|
||||
sliderWidth: "",
|
||||
scHight: 0,
|
||||
//原图
|
||||
img: "",
|
||||
//拼图
|
||||
imgbk: "",
|
||||
endLoad: true,
|
||||
imgbKH: "",
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
show() {
|
||||
this.hid = false;
|
||||
},
|
||||
hide() {
|
||||
if (!this.vsr) {
|
||||
// vsr判断是否验证成功,成功隐藏验证框
|
||||
this.hid = !this.hid;
|
||||
}
|
||||
},
|
||||
error() {
|
||||
this.vsr = false;
|
||||
this.hid = false;
|
||||
this.moveX = 0;
|
||||
this.moveCode = 0;
|
||||
},
|
||||
// 获取验证图片
|
||||
getCode() {
|
||||
this.col = "#b3afae";
|
||||
this.hasImg = "图片加载中...";
|
||||
if (!storage.getUuid()) {
|
||||
storage.setUuid(uuid.v1());
|
||||
}
|
||||
uni.request({
|
||||
url: api.common + "/common/slider/" + this.business,
|
||||
header: {
|
||||
uuid: storage.getUuid(),
|
||||
},
|
||||
success: (res) => {
|
||||
this.col = "#838383";
|
||||
this.hasImg = "拖动滑块以完成拼图";
|
||||
var data = res.data.result;
|
||||
|
||||
// base64的图片
|
||||
this.img = data.backImage;
|
||||
this.imgbk = data.slidingImage;
|
||||
// 根据参数动态适应验证图片的高宽
|
||||
this.imgbKH = data.randomY * 1.8 + "rpx";
|
||||
this.originalHeight = data.originalHeight * 1.8 + "rpx";
|
||||
this.originalWidth = data.originalWidth * 1.8 + "rpx";
|
||||
this.sliderHeight = data.sliderHeight * 1.8 + "rpx";
|
||||
this.sliderWidth = data.sliderWidth * 1.8 + "rpx";
|
||||
// 适应比率,用来适应滑动距离
|
||||
this.tl = 1 / (1.8 * l);
|
||||
// 无用信息
|
||||
this.spcode = data.capcode;
|
||||
// 验证令牌
|
||||
this.key = data.key;
|
||||
this.$store.state.verificationKey = data.key;
|
||||
},
|
||||
});
|
||||
},
|
||||
end(e) {
|
||||
this.endLoad = false;
|
||||
// 验证拼图位置是否正确
|
||||
uni.request({
|
||||
method: "POST",
|
||||
url:
|
||||
api.common +
|
||||
"/common/slider/" +
|
||||
this.business +
|
||||
"?xPos=" +
|
||||
parseInt(this.moveCode * this.tl),
|
||||
header: {
|
||||
uuid: storage.getUuid(),
|
||||
},
|
||||
success: (res) => {
|
||||
this.endLoad = true;
|
||||
res.data.result == false
|
||||
? (res.data.result = false)
|
||||
: (res.data.result = true);
|
||||
|
||||
if (res.data && res.data.result) {
|
||||
//验证成功后把key发送出去,后端会把验证信息存在缓存里
|
||||
this.$emit("send", this.key);
|
||||
this.hide();
|
||||
this.vsr = true;
|
||||
this.vsrtx = "已通过验证";
|
||||
} else {
|
||||
this.getCode(); // 让滑块回到起始位置
|
||||
if (this.movePv == 1) {
|
||||
this.movePv = 0;
|
||||
} else {
|
||||
this.movePv = 1;
|
||||
}
|
||||
}
|
||||
},
|
||||
fail: (res) => {
|
||||
this.$msg("连接服务器失败");
|
||||
},
|
||||
});
|
||||
},
|
||||
// 绑定拼图位置
|
||||
moveChange(e) {
|
||||
this.moveX = e.detail.x;
|
||||
this.moveCode = e.detail.x;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "./animation.css";
|
||||
@import "./icon.css";
|
||||
// @import './main.css';
|
||||
.dh-wt {
|
||||
animation: at 1.1s ease;
|
||||
animation-iteration-count: infinite;
|
||||
animation-direction: alternate;
|
||||
background-color: $main-color;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
@keyframes at {
|
||||
from {
|
||||
width: 27rpx;
|
||||
height: 27rpx;
|
||||
}
|
||||
|
||||
to {
|
||||
width: 45rpx;
|
||||
height: 45rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.ttcl {
|
||||
color: $main-color;
|
||||
}
|
||||
|
||||
.border-index {
|
||||
border: 1rpx solid $main-color;
|
||||
}
|
||||
|
||||
.status_bar {
|
||||
height: var(--status-bar-height);
|
||||
background-color: #f1f1f1;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.status_bar-nobg {
|
||||
height: var(--status-bar-height);
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* 转圈动画 */
|
||||
.turn-load {
|
||||
animation: turnmy 1s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes turnmy {
|
||||
0% {
|
||||
-webkit-transform: rotate(0deg);
|
||||
}
|
||||
|
||||
25% {
|
||||
-webkit-transform: rotate(90deg);
|
||||
}
|
||||
|
||||
50% {
|
||||
-webkit-transform: rotate(180deg);
|
||||
}
|
||||
|
||||
75% {
|
||||
-webkit-transform: rotate(270deg);
|
||||
}
|
||||
|
||||
100% {
|
||||
-webkit-transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
.status_bar-fixed {
|
||||
height: var(--status-bar-height);
|
||||
width: 100%;
|
||||
position: fixed;
|
||||
background-color: #f1f1f1;
|
||||
z-index: 20;
|
||||
}
|
||||
|
||||
.head-dh-my {
|
||||
display: flex;
|
||||
position: fixed;
|
||||
justify-content: space-around;
|
||||
align-items: flex-end;
|
||||
padding-bottom: 10rpx;
|
||||
z-index: 15;
|
||||
background-color: #e3e3e3;
|
||||
width: 750rpx;
|
||||
}
|
||||
|
||||
.padding-left {
|
||||
padding-left: 20rpx;
|
||||
}
|
||||
|
||||
.padding-left-top {
|
||||
padding-left: 20rpx;
|
||||
padding-top: 20rpx;
|
||||
}
|
||||
|
||||
.padding-right {
|
||||
padding-right: 20rpx;
|
||||
}
|
||||
|
||||
.input-my {
|
||||
padding-left: 20rpx;
|
||||
border-radius: 40rpx;
|
||||
height: 50rpx;
|
||||
margin: 10rpx;
|
||||
}
|
||||
|
||||
.tb-tag-absolute {
|
||||
position: absolute;
|
||||
z-index: 5;
|
||||
border-radius: 25rpx;
|
||||
font-size: 16rpx;
|
||||
margin-left: 25rpx;
|
||||
margin-top: -35rpx;
|
||||
}
|
||||
|
||||
.flex-column-center {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.flex-column-between {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.flex-column-start {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.flex-column-around {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-around;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.flex-row-start {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.flex-row-around {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-around;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.flex-row-center {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.flex-row-between {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.my-title {
|
||||
font-size: 35rpx;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.my-neirong {
|
||||
font-size: 26rpx;
|
||||
color: #6d6d6d;
|
||||
}
|
||||
|
||||
.my-neirong-sm {
|
||||
font-size: 23rpx;
|
||||
color: #616161;
|
||||
}
|
||||
|
||||
.my-tag-text {
|
||||
font-size: 22rpx;
|
||||
padding-top: 20rpx;
|
||||
color: #bababa;
|
||||
}
|
||||
|
||||
.padding-top {
|
||||
padding-top: 35rpx;
|
||||
}
|
||||
|
||||
.padding-top-sm {
|
||||
padding-top: 20rpx;
|
||||
}
|
||||
|
||||
.bottom-dh {
|
||||
background-color: #f1f1f1;
|
||||
position: fixed;
|
||||
z-index: 10;
|
||||
bottom: 0;
|
||||
width: 750rpx;
|
||||
height: 110rpx;
|
||||
}
|
||||
|
||||
.tb-text {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.bottom-text {
|
||||
width: 750rpx;
|
||||
position: fixed;
|
||||
text-align: center;
|
||||
font-size: 26rpx;
|
||||
color: #9d9d9d;
|
||||
bottom: 70rpx;
|
||||
}
|
||||
|
||||
.moneycolor {
|
||||
color: #ea5002;
|
||||
}
|
||||
|
||||
.margin-top {
|
||||
margin-top: 20rpx;
|
||||
}
|
||||
|
||||
.margin-top-sm {
|
||||
margin-top: 12rpx;
|
||||
}
|
||||
|
||||
.margin {
|
||||
margin: 20rpx;
|
||||
}
|
||||
|
||||
.margin-left {
|
||||
margin-left: 20rpx;
|
||||
}
|
||||
|
||||
.margin-right {
|
||||
margin-right: 20rpx;
|
||||
}
|
||||
|
||||
.main-color {
|
||||
color: #07d188;
|
||||
}
|
||||
</style>
|
278
components/verify-code/verify-code.vue
Normal file
@ -0,0 +1,278 @@
|
||||
<template>
|
||||
<view class="xt__verify-code">
|
||||
<!-- 输入框 -->
|
||||
<input
|
||||
id="xt__input"
|
||||
:value="code"
|
||||
class="xt__input"
|
||||
:focus="isFocus"
|
||||
:password="isPassword"
|
||||
:type="inputType"
|
||||
:maxlength="size"
|
||||
@input="input"
|
||||
@focus="inputFocus"
|
||||
@blur="inputBlur"
|
||||
/>
|
||||
|
||||
<!-- 光标 -->
|
||||
<view
|
||||
id="xt__cursor"
|
||||
v-if="cursorVisible && type !== 'middle'"
|
||||
class="xt__cursor"
|
||||
:style="{ left: codeCursorLeft[code.length] + 'px', height: cursorHeight + 'px', backgroundColor: cursorColor }"
|
||||
></view>
|
||||
|
||||
<!-- 输入框 - 组 -->
|
||||
<view id="xt__input-ground" class="xt__input-ground">
|
||||
<template v-for="(item, index) in size">
|
||||
<view
|
||||
:key="index"
|
||||
:style="{ borderColor: code.length === index && cursorVisible ? boxActiveColor : boxNormalColor }"
|
||||
:class="['xt__box', `xt__box-${type + ''}`, `xt__box::after`]"
|
||||
>
|
||||
<view :style="{ borderColor: boxActiveColor }" class="xt__middle-line" v-if="type === 'middle' && !code[index]"></view>
|
||||
<text class="xt__code-text">{{ code[index] | codeFormat(isPassword) }}</text>
|
||||
</view>
|
||||
</template>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
<script>
|
||||
/**
|
||||
* @description 输入验证码组件
|
||||
* @property {string} type = [box|middle|bottom] - 显示类型 默认:box -eg:bottom
|
||||
* @property {string} inputType = [text|number] - 输入框类型 默认:number -eg:number
|
||||
* @property {number} size = [4|6] - 支持的验证码数量 默认:6 -eg:6
|
||||
* @property {boolean} isFocus - 是否立即聚焦 默认:true
|
||||
* @property {boolean} isPassword - 是否以密码形式显示 默认false -eg:false
|
||||
* @property {string} cursorColor - 光标颜色 默认:#cccccc
|
||||
* @property {string} boxNormalColor - 光标未聚焦到的框的颜色 默认:#cccccc
|
||||
* @property {string} boxActiveColor - 光标聚焦到的框的颜色 默认:#000000
|
||||
* @event {Function(data)} confirm - 输入完成
|
||||
*/
|
||||
export default {
|
||||
name: 'xt-verify-code',
|
||||
props: {
|
||||
value: {
|
||||
type: String,
|
||||
default: () => ''
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
default: () => 'box'
|
||||
},
|
||||
inputType: {
|
||||
type: String,
|
||||
default: () => 'number'
|
||||
},
|
||||
size: {
|
||||
type: Number,
|
||||
default: () => 6
|
||||
},
|
||||
isFocus: {
|
||||
type: Boolean,
|
||||
default: () => true
|
||||
},
|
||||
isPassword: {
|
||||
type: Boolean,
|
||||
default: () => false
|
||||
},
|
||||
cursorColor: {
|
||||
type: String,
|
||||
default: () => '#cccccc'
|
||||
},
|
||||
boxNormalColor: {
|
||||
type: String,
|
||||
default: () => '#cccccc'
|
||||
},
|
||||
boxActiveColor: {
|
||||
type: String,
|
||||
default: () => '#000000'
|
||||
}
|
||||
},
|
||||
model: {
|
||||
prop: 'value',
|
||||
event: 'input'
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
cursorVisible: false,
|
||||
cursorHeight: 35,
|
||||
code: '', // 输入的验证码
|
||||
codeCursorLeft: [] // 向左移动的距离数组
|
||||
};
|
||||
},
|
||||
created() {
|
||||
this.cursorVisible = this.isFocus;
|
||||
},
|
||||
mounted() {
|
||||
this.init();
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* @description 初始化
|
||||
*/
|
||||
init() {
|
||||
this.getCodeCursorLeft();
|
||||
this.setCursorHeight();
|
||||
},
|
||||
/**
|
||||
* @description 获取元素节点
|
||||
* @param {string} elm - 节点的id、class 相当于 document.querySelect的参数 -eg: #id
|
||||
* @param {string} type = [single|array] - 单个元素获取多个元素 默认是单个元素
|
||||
* @param {Function} callback - 回调函数
|
||||
*/
|
||||
getElement(elm, type = 'single', callback) {
|
||||
uni
|
||||
.createSelectorQuery()
|
||||
.in(this)
|
||||
[type === 'array' ? 'selectAll' : 'select'](elm)
|
||||
.boundingClientRect()
|
||||
.exec(data => {
|
||||
callback(data[0]);
|
||||
});
|
||||
},
|
||||
/**
|
||||
* @description 计算光标的高度
|
||||
*/
|
||||
setCursorHeight() {
|
||||
this.getElement('.xt__box', 'single', boxElm => {
|
||||
this.cursorHeight = boxElm.height * 0.6;
|
||||
});
|
||||
},
|
||||
/**
|
||||
* @description 获取光标在每一个box的left位置
|
||||
*/
|
||||
getCodeCursorLeft() {
|
||||
// 获取父级框的位置信息
|
||||
this.getElement('#xt__input-ground', 'single', parentElm => {
|
||||
const parentLeft = parentElm.left;
|
||||
// 获取各个box信息
|
||||
this.getElement('.xt__box', 'array', elms => {
|
||||
this.codeCursorLeft = [];
|
||||
elms.forEach(elm => {
|
||||
this.codeCursorLeft.push(elm.left - parentLeft + elm.width / 2);
|
||||
});
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
// 输入框输入变化的回调
|
||||
input(e) {
|
||||
const value = e.detail.value;
|
||||
this.cursorVisible = value.length !== this.size;
|
||||
this.$emit('input', value);
|
||||
this.inputSuccess(value);
|
||||
},
|
||||
|
||||
// 输入完成回调
|
||||
inputSuccess(value) {
|
||||
if (value.length === this.size) {
|
||||
this.$emit('confirm', value);
|
||||
}
|
||||
},
|
||||
// 输入聚焦
|
||||
inputFocus() {
|
||||
this.cursorVisible = this.code.length !== this.size;
|
||||
},
|
||||
// 输入失去焦点
|
||||
inputBlur() {
|
||||
this.cursorVisible = false;
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
value(val) {
|
||||
this.code = val;
|
||||
}
|
||||
},
|
||||
filters: {
|
||||
codeFormat(val, isPassword) {
|
||||
let value = '';
|
||||
if (val) {
|
||||
value = isPassword ? '*' : val;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.xt__verify-code {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
|
||||
.xt__input {
|
||||
height: 100%;
|
||||
width: 200%;
|
||||
position: absolute;
|
||||
left: -100%;
|
||||
z-index: 1;
|
||||
}
|
||||
.xt__cursor {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
display: inline-block;
|
||||
width: 2px;
|
||||
animation-name: cursor;
|
||||
animation-duration: 0.8s;
|
||||
animation-iteration-count: infinite;
|
||||
}
|
||||
|
||||
.xt__input-ground {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
.xt__box {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
width: 76rpx;
|
||||
height: 112rpx;
|
||||
&-bottom {
|
||||
border-bottom-width: 2px;
|
||||
border-bottom-style: solid;
|
||||
}
|
||||
|
||||
&-box {
|
||||
border-width: 2px;
|
||||
border-style: solid;
|
||||
}
|
||||
|
||||
&-middle {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.xt__middle-line {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
width: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
border-bottom-width: 2px;
|
||||
border-bottom-style: solid;
|
||||
}
|
||||
|
||||
.xt__code-text {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
font-size:52rpx;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes cursor {
|
||||
0% {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
713
components/vlog/videoComp.vue
Executable file
@ -0,0 +1,713 @@
|
||||
<template>
|
||||
<view style="flex: 1">
|
||||
<!-- <uni-list @change="onchange" :num="playerList.length"> -->
|
||||
<list :pagingEnabled="true" :show-scrollbar="false" @scroll="listScroll" @scrollend="scroll" :scrollable="true">
|
||||
<refresh @pullingdown="onpullingdown" @refresh="onrefresh" :display="refreshing ? 'show' : 'hide'">
|
||||
<text class="refresh-info-txt"></text>
|
||||
<loading-indicator></loading-indicator>
|
||||
</refresh>
|
||||
<cell :recycle="false" v-for="(item, index) in playerList" :key="index" :data-index="index"
|
||||
:style="{ height: screenHeight + 'px' }">
|
||||
<!-- <uni-video :src="item.url" :playStatus="playStatus" :screenHeight="screenHeight" v-if="playerCur === index" @play="onplay"></uni-video> -->
|
||||
<video ref="myVideo" id="myVideo" :object-fit="item.width >= item.height ? 'contain' : 'fill'"
|
||||
:src="item.url" :controls="false" :enable-progress-gesture="false" v-if="playerCur === index" loop
|
||||
autoplay show-loading="true" style="width: 750rpx" :style="{ height: screenHeight + 'px' }"
|
||||
@click="playOrPause" @play="onplay" @error="onerror" @timeupdate="timeupdate"></video>
|
||||
<image :lazy-load="true" :fade-show="false" v-if="!item.play" :src="item.cover"
|
||||
:mode="item.width >= item.height ? 'aspectFit' : 'scaleToFill'"
|
||||
style="width: 750rpx; filter: blur(10px)" :style="{ height: screenHeight + 'px' }"></image>
|
||||
<!--<image :lazy-load="true" :fade-show="false" v-if="!item.play" :src="item.cover" mode="" style="width: 750rpx;position:absolute;left: 0;right: 0;top: 0;bottom: 0; filter: blur(10px);" :style="{height: screenHeight+ 'px'}"></image>-->
|
||||
<view class="publish-info-box">
|
||||
<view class="">
|
||||
<text class="publish-info-vloger-name">@{{ item.vlogerName }}</text>
|
||||
<text class="publish-info-content">{{ item.content }}</text>
|
||||
<view class="publish-info-music-box">
|
||||
<image src="/static/images/icon-fire.png" class="icon-fire"></image>
|
||||
<text class="muisc-words">{{ item.vlogerName }}的原声创作</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="" style="flex-direction: row">
|
||||
<image src="/static/images/cd-play-4.gif" style="width: 150rpx;height: 150rpx;opacity: 0.8;">
|
||||
</image>
|
||||
<image v-if="!isIOS" src="/static/images/icon-cd.png" class="play-cd"
|
||||
style="width: 120rpx; height: 120rpx"></image>
|
||||
<image v-if="isIOS" :src="
|
||||
'https://imooc-news.oss-cn-shanghai.aliyuncs.com/image/cd-play-4.gif?time=' +
|
||||
times
|
||||
" class="play-cd"></image>
|
||||
</view>
|
||||
</view>
|
||||
<!-- 视频展示右侧的操作按钮,头像 - 点赞 - 评论 - 转发 -->
|
||||
<view class="operation-box">
|
||||
<view class="operation-face-box">
|
||||
<image :src="item.vlogerFace" class="user-face" @click="goUserInfoSeeSee(item.vlogerId)">
|
||||
</image>
|
||||
</view>
|
||||
<image v-if="!item.doIFollowVloger && userId != thisVlogerId" src="/static/images/icon-follow.png"
|
||||
@click="followMe(item.vlogerId)" class="follow-me"></image>
|
||||
<view class="like-box">
|
||||
<image v-if="!item.doILikeThisVlog" src="/static/images/icon-unlike.png"
|
||||
@click="likeOrDislikeVlog(1)" class="icon"></image>
|
||||
<image v-if="item.doILikeThisVlog" src="/static/images/icon-like.png"
|
||||
@click="likeOrDislikeVlog(0)" class="icon"></image>
|
||||
<text class="some-counts">
|
||||
{{ getGraceNumber(item.likeCounts) }}
|
||||
</text>
|
||||
</view>
|
||||
<view class="comment-and-share-box">
|
||||
<image src="/static/images/icon-comments.png" @click="commentToggle" class="icon"></image>
|
||||
<!-- <text class="some-counts">{{item.commentsCounts}}</text> -->
|
||||
<text class="some-counts">
|
||||
{{ getGraceNumber(thisVlogTotalComentCounts) }}
|
||||
</text>
|
||||
</view>
|
||||
<view class="comment-and-share-box">
|
||||
<image src="/static/images/icon-share.png" @click="shareToggle" class="icon"></image>
|
||||
<text class="some-counts">分享</text>
|
||||
</view>
|
||||
</view>
|
||||
</cell>
|
||||
</list>
|
||||
<!-- </uni-list> -->
|
||||
|
||||
<view>
|
||||
<!-- 底部窗口popup -->
|
||||
<uni-popup ref="comment" type="comment">
|
||||
<uni-popup-comments :thisVlogerId="thisVlogerId" :thisVlogId="thisVlogId"></uni-popup-comments>
|
||||
</uni-popup>
|
||||
<uni-popup ref="share" background-color="#fff" type="share">
|
||||
<uni-popup-share :thisVlogerId="thisVlogerId" :thisVlogId="thisVlogId" :vlogUrl="thisVlog.url"
|
||||
:isPrivate="thisVlog.isPrivate"></uni-popup-share>
|
||||
</uni-popup>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import storage from "@/utils/storage.js"; //缓存
|
||||
import {
|
||||
graceNumber
|
||||
} from '@/utils/tools.js'
|
||||
import api from "@/config/api.js";
|
||||
import {
|
||||
vlogList,
|
||||
vlogLike,
|
||||
vlogUnLike,
|
||||
vlogComment,
|
||||
vlogFollow,
|
||||
vlogTotalLikedCounts
|
||||
} from "@/api/vlog";
|
||||
export default {
|
||||
props: {
|
||||
screenHeight: {
|
||||
default: 0,
|
||||
},
|
||||
src: {
|
||||
default: false,
|
||||
},
|
||||
playStatus: {
|
||||
default: false,
|
||||
},
|
||||
videoList: {
|
||||
default: [],
|
||||
},
|
||||
refreshList: {
|
||||
default: [],
|
||||
},
|
||||
pagingList: {
|
||||
default: [],
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
thisVlog: {}, // 当前的短视频对象
|
||||
thisVlogId: "", // 当前的短视频主键id
|
||||
thisVlogerId: "", // 当前的短视频博主的主键id
|
||||
userId: "", // 当前用户id
|
||||
|
||||
refreshing: false,
|
||||
showRefreshLoading: "hide",
|
||||
|
||||
playerCur: 0,
|
||||
page: 0,
|
||||
totalPage: 0,
|
||||
playerList: this.videoList,
|
||||
thisVlogTotalComentCounts: 0,
|
||||
|
||||
videoContext: {},
|
||||
|
||||
currentIndex: 0,
|
||||
contentOffsetY: 0,
|
||||
|
||||
times: new Date().getTime(),
|
||||
|
||||
objectFit: "fill",
|
||||
isIOS: uni.getSystemInfoSync().platform == "ios",
|
||||
};
|
||||
},
|
||||
created() {
|
||||
if (!this.isIOS) {
|
||||
this.objectFit = "cover";
|
||||
}
|
||||
|
||||
let myUserInfo = storage.getVlogUserInfo();
|
||||
if (myUserInfo != null) {
|
||||
this.userId = myUserInfo.id;
|
||||
}
|
||||
|
||||
// 查询首页短视频列表
|
||||
this.displayVideoPaging(this.page + 1, true);
|
||||
|
||||
var videoContext = uni.createVideoContext("myVideo");
|
||||
this.videoContext = videoContext;
|
||||
},
|
||||
watch: {
|
||||
refreshList(value) {
|
||||
var me = this;
|
||||
var newList = value;
|
||||
if (newList != null && newList != undefined && newList.length > 0) {
|
||||
me.playerList = newList;
|
||||
}
|
||||
|
||||
// 重置
|
||||
this.playerCur = 0;
|
||||
this.currentIndex = 0;
|
||||
this.contentOffsetY = 0;
|
||||
},
|
||||
|
||||
playStatus: function(val) {
|
||||
var me = this;
|
||||
|
||||
if (!val) {
|
||||
me.videoContext.pause();
|
||||
} else {
|
||||
me.videoContext.play();
|
||||
}
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
// 把超过1000或10000的数字调整,比如1.3k/6.8w
|
||||
getGraceNumber(num) {
|
||||
return graceNumber(num);
|
||||
},
|
||||
async freshCommentCounts() {
|
||||
var me = this;
|
||||
var currentIndex = me.playerCur;
|
||||
console.log('currentIndex',currentIndex)
|
||||
var vlog = me.playerList[currentIndex];
|
||||
var vlogId = vlog.vlogId;
|
||||
var result = await vlogComment(vlogId)
|
||||
if (result.data.status == 200) {
|
||||
me.thisVlogTotalComentCounts = result.data.data;
|
||||
} else {
|
||||
me.thisVlogTotalComentCounts = 0;
|
||||
}
|
||||
},
|
||||
async likeOrDislikeVlog(isLike) {
|
||||
var me = this;
|
||||
var myUserInfo = storage.getVlogUserInfo()
|
||||
var userId = myUserInfo.id;
|
||||
var currentIndex = me.playerCur;
|
||||
var vlog = me.playerList[currentIndex];
|
||||
if (isLike == 1) {
|
||||
// 喜欢/点赞视频
|
||||
if (myUserInfo == null) {
|
||||
uni.showToast({
|
||||
duration: 3000,
|
||||
title: "请登录~",
|
||||
icon: "none",
|
||||
});
|
||||
uni.navigateTo({
|
||||
url: "/pages/passport/login",
|
||||
animationType: "slide-in-bottom",
|
||||
success() {
|
||||
me.loginWords = "请登录";
|
||||
},
|
||||
});
|
||||
return;
|
||||
}
|
||||
var result = await vlogLike({userId,vlogerId:vlog.vlogerId,vlogId:vlog.vlogId})
|
||||
if (result.data.status == 200) {
|
||||
me.reLikePlayList(vlog.vlogId);
|
||||
me.refreshVlogCounts();
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: result.data.msg,
|
||||
icon: "none",
|
||||
duration: 3000,
|
||||
});
|
||||
}
|
||||
|
||||
} else if (isLike == 0) {
|
||||
// 取消喜欢/点赞视频
|
||||
var myUserInfo = storage.getVlogUserInfo()
|
||||
if (myUserInfo == null) {
|
||||
uni.showToast({
|
||||
duration: 3000,
|
||||
title: "请登录~",
|
||||
icon: "none",
|
||||
});
|
||||
uni.navigateTo({
|
||||
url: "../loginRegist/loginRegist",
|
||||
animationType: "slide-in-bottom",
|
||||
success() {
|
||||
me.loginWords = "请登录";
|
||||
},
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
var result = await vlogUnLike({userId,vlogerId:vlog.vlogerId,vlogId:vlog.vlogId})
|
||||
if (result.data.status == 200) {
|
||||
me.reDislikePlayList(vlog.vlogId);
|
||||
me.refreshVlogCounts();
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: result.data.msg,
|
||||
icon: "none",
|
||||
duration: 3000,
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// 喜欢/点赞的list重新设置
|
||||
reLikePlayList(vlogId) {
|
||||
var me = this;
|
||||
var playerList = me.playerList;
|
||||
|
||||
// 关注以后,循环当前playerList,修改对应粉丝关系的doIFollowVloger改为true
|
||||
for (var i = 0; i < playerList.length; i++) {
|
||||
var vlog = playerList[i];
|
||||
if (vlog.vlogId == vlogId) {
|
||||
vlog.doILikeThisVlog = true;
|
||||
playerList.splice(i, 1, vlog);
|
||||
}
|
||||
}
|
||||
me.playerList = playerList;
|
||||
},
|
||||
reDislikePlayList(vlogId) {
|
||||
var me = this;
|
||||
var playerList = me.playerList;
|
||||
|
||||
// 关注以后,循环当前playerList,修改对应粉丝关系的doIFollowVloger改为true
|
||||
for (var i = 0; i < playerList.length; i++) {
|
||||
var vlog = playerList[i];
|
||||
if (vlog.vlogId == vlogId) {
|
||||
vlog.doILikeThisVlog = false;
|
||||
playerList.splice(i, 1, vlog);
|
||||
}
|
||||
}
|
||||
me.playerList = playerList;
|
||||
},
|
||||
|
||||
// 关注后的list重新设置
|
||||
reFollowPlayList(vlogerId) {
|
||||
var me = this;
|
||||
var playerList = me.playerList;
|
||||
|
||||
// 关注以后,循环当前playerList,修改对应粉丝关系的doIFollowVloger改为true
|
||||
for (var i = 0; i < playerList.length; i++) {
|
||||
var vlog = playerList[i];
|
||||
if (vlog.vlogerId == vlogerId) {
|
||||
vlog.doIFollowVloger = true;
|
||||
|
||||
playerList.splice(i, 1, vlog);
|
||||
}
|
||||
}
|
||||
me.playerList = playerList;
|
||||
},
|
||||
// 取关后的list重新设置
|
||||
reCancelPlayList(vlogerId) {
|
||||
var me = this;
|
||||
var playerList = me.playerList;
|
||||
|
||||
// 关注以后,循环当前playerList,修改对应粉丝关系的doIFollowVloger改为true
|
||||
for (var i = 0; i < playerList.length; i++) {
|
||||
var vlog = playerList[i];
|
||||
if (vlog.vlogerId == vlogerId) {
|
||||
vlog.doIFollowVloger = false;
|
||||
playerList.splice(i, 1, vlog);
|
||||
}
|
||||
}
|
||||
me.playerList = playerList;
|
||||
},
|
||||
|
||||
// 关注博主
|
||||
async followMe(vlogerId) {
|
||||
var me = this;
|
||||
var myUserInfo = storage.getVlogUserInfo()
|
||||
var userId = myUserInfo.id;
|
||||
if (myUserInfo == null) {
|
||||
uni.showToast({
|
||||
duration: 3000,
|
||||
title: "请登录~",
|
||||
icon: "none",
|
||||
});
|
||||
|
||||
uni.navigateTo({
|
||||
url: "../loginRegist/loginRegist",
|
||||
animationType: "slide-in-bottom",
|
||||
success() {
|
||||
me.loginWords = "请登录";
|
||||
},
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
var result = await vlogFollow({myId:userId,vlogerId})
|
||||
if (result.data.status == 200) {
|
||||
me.reFollowPlayList(vlogerId);
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: result.data.msg,
|
||||
icon: "none",
|
||||
duration: 3000,
|
||||
});
|
||||
}
|
||||
},
|
||||
// 查看用户详情
|
||||
goUserInfoSeeSee(userId) {
|
||||
uni.setStorageSync("userPageId", userId);
|
||||
const info = storage.getVlogUserInfo()
|
||||
// 是否是当前登录的用户
|
||||
let myUserId = "";
|
||||
if (info) {
|
||||
myUserId = info.id;
|
||||
}
|
||||
if (myUserId == userId) {
|
||||
uni.switchTab({
|
||||
url: "/pages/me/me",
|
||||
});
|
||||
} else {
|
||||
uni.navigateTo({
|
||||
url: "/pages/me/vlogerInfo?userPageId=" + userId,
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
listScroll(e) {
|
||||
// console.log(e)
|
||||
if (e.contentOffset.y > 0) {
|
||||
this.$emit("showLoading");
|
||||
}
|
||||
},
|
||||
|
||||
downloadVlog() {
|
||||
var me = this;
|
||||
var serverUrl = app.globalData.serverUrl;
|
||||
var currentIndex = me.playerCur;
|
||||
var vlog = me.playerList[currentIndex];
|
||||
var pendingLength = vlog.url;
|
||||
},
|
||||
|
||||
copyLink() {
|
||||
var me = me;
|
||||
},
|
||||
|
||||
showQRCode() {
|
||||
var me = me;
|
||||
},
|
||||
|
||||
changeVlogToPrivate() {
|
||||
var me = me;
|
||||
},
|
||||
|
||||
// 下拉刷新的过程中,改变头部tab显示的字样
|
||||
onpullingdown(e) {},
|
||||
onrefresh(e) {
|
||||
this.refreshing = true;
|
||||
setTimeout(() => {
|
||||
this.refreshing = false;
|
||||
this.$emit("hideLoading");
|
||||
this.refreshText = "↓ Pull To Refresh";
|
||||
}, 300);
|
||||
|
||||
// 通过list组件的下拉刷新触发当前所在页面的下拉刷新
|
||||
uni.startPullDownRefresh();
|
||||
},
|
||||
|
||||
onplay: function(e) {
|
||||
if (uni.getSystemInfoSync().platform == "ios") {
|
||||
this.doplay(0.1);
|
||||
}
|
||||
},
|
||||
onerror: function(err) {},
|
||||
timeupdate: function(e) {
|
||||
if (e.detail.currentTime > 0.2) {
|
||||
this.doplay(e.detail.currentTime);
|
||||
}
|
||||
},
|
||||
|
||||
playOrPause() {
|
||||
var me = this;
|
||||
var playStatus = this.playStatus;
|
||||
|
||||
if (!playStatus) {
|
||||
me.videoContext.pause();
|
||||
} else {
|
||||
me.videoContext.play();
|
||||
}
|
||||
this.playStatus = !playStatus;
|
||||
},
|
||||
|
||||
scroll: function(e) {
|
||||
console.log(e)
|
||||
let originalIndex = this.currentIndex;
|
||||
let isNext = false;
|
||||
if (e.contentOffset.y < this.contentOffsetY) {
|
||||
isNext = true;
|
||||
}
|
||||
this.contentOffsetY = Number(e.contentOffset.y);
|
||||
console.log(this.contentOffsetY)
|
||||
var num = this.playerList.length;
|
||||
console.log(num)
|
||||
if(num>0){
|
||||
var ht = Number(e.contentSize.height)
|
||||
console.log(ht)
|
||||
this.currentIndex = Math.round(
|
||||
Math.abs(this.contentOffsetY) / (ht / num)
|
||||
);
|
||||
}else{
|
||||
this.currentIndex=0
|
||||
}
|
||||
console.log(this.currentIndex)
|
||||
this.onchange(this.currentIndex);
|
||||
|
||||
this.times = new Date().getTime();
|
||||
// 判断如果视频列表总长度-当前下标,少于3个,则开始分页查询后续的视频,并且追加到现有list中
|
||||
if (this.playerList.length - this.currentIndex < 3) {
|
||||
// 如果要分页的page和总数totalPage相等,则没有更多
|
||||
if (this.page == this.totalPage) {
|
||||
return;
|
||||
}
|
||||
this.displayVideoPaging(this.page + 1, false);
|
||||
}
|
||||
},
|
||||
|
||||
// 分页查询新的list, 并且追加到现有list中
|
||||
async displayVideoPaging(page, needClearList) {
|
||||
// 查询首页短视频列表
|
||||
let me = this
|
||||
let myUserInfo = storage.getVlogUserInfo()
|
||||
let userId = "";
|
||||
if (myUserInfo != null) {
|
||||
userId = myUserInfo.id;
|
||||
}
|
||||
const result = await vlogList(page,10).catch(err=>{
|
||||
uni.stopPullDownRefresh();
|
||||
})
|
||||
uni.stopPullDownRefresh();
|
||||
console.log(result)
|
||||
if (result.data.status == 200) {
|
||||
let vlogList = result.data.data.rows;
|
||||
let totalPage = result.data.data.total;
|
||||
// me.playerList = vlogList;
|
||||
if (needClearList) {
|
||||
me.playerList = [];
|
||||
}
|
||||
me.playerList = me.playerList.concat(vlogList);
|
||||
me.page = page;
|
||||
me.totalPage = totalPage;
|
||||
|
||||
if (needClearList) {
|
||||
me.setThisVlogInfo();
|
||||
me.freshCommentCounts();
|
||||
}
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: result.data.msg,
|
||||
icon: "none",
|
||||
duration: 3000,
|
||||
});
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
doplay: function(time) {
|
||||
if (time > 0) {
|
||||
this.playerList[this.playerCur].play = true;
|
||||
}
|
||||
},
|
||||
onchange: function(index) {
|
||||
console.log(index)
|
||||
console.log(this.playerCur)
|
||||
if (index != this.playerCur) {
|
||||
this.playerList[this.playerCur].play = false;
|
||||
this.playerCur = index;
|
||||
}
|
||||
|
||||
this.refreshVlogCounts();
|
||||
this.setThisVlogInfo();
|
||||
this.freshCommentCounts();
|
||||
},
|
||||
|
||||
// 设置当前vlog的信息
|
||||
setThisVlogInfo() {
|
||||
var me = this;
|
||||
var serverUrl = app.globalData.serverUrl;
|
||||
var currentIndex = me.playerCur;
|
||||
var vlog = me.playerList[currentIndex];
|
||||
this.thisVlog = vlog;
|
||||
this.thisVlogId = vlog.vlogId;
|
||||
this.thisVlogerId = vlog.vlogerId;
|
||||
},
|
||||
|
||||
async refreshVlogCounts() {
|
||||
// 查询当前点赞数,重新赋值给当前视频
|
||||
var me = this;
|
||||
var currentIndex = me.playerCur;
|
||||
var vlog = me.playerList[currentIndex];
|
||||
var result = await vlogTotalLikedCounts(vlog.vlogId)
|
||||
if (result.data.status == 200) {
|
||||
var counts = result.data.data;
|
||||
me.reChangeVlogLikedCountsInPlayList(vlog.vlogId, counts);
|
||||
}
|
||||
},
|
||||
|
||||
reChangeVlogLikedCountsInPlayList(vlogId, counts) {
|
||||
var me = this;
|
||||
var playerList = me.playerList;
|
||||
|
||||
// 关注以后,循环当前playerList,修改对应粉丝关系的doIFollowVloger改为true
|
||||
for (var i = 0; i < playerList.length; i++) {
|
||||
var vlog = playerList[i];
|
||||
if (vlog.vlogId == vlogId) {
|
||||
vlog.likeCounts = counts;
|
||||
playerList.splice(i, 1, vlog);
|
||||
}
|
||||
}
|
||||
me.playerList = playerList;
|
||||
},
|
||||
|
||||
commentToggle() {
|
||||
this.$refs.comment.open();
|
||||
uni.hideTabBar({
|
||||
animation: true,
|
||||
});
|
||||
},
|
||||
shareToggle() {
|
||||
this.$refs.share.open();
|
||||
uni.hideTabBar({
|
||||
animation: true,
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.icon {
|
||||
width: 80rpx;
|
||||
height: 80rpx;
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.user-face {
|
||||
width: 100rpx;
|
||||
height: 100rpx;
|
||||
border-radius: 100rpx;
|
||||
}
|
||||
|
||||
.play-cd {
|
||||
width: 150rpx;
|
||||
height: 150rpx;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.refresh-info-txt {
|
||||
color: #f1f1f1;
|
||||
text-align: center;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.publish-info-box {
|
||||
position: absolute;
|
||||
bottom: 200rpx;
|
||||
left: 0;
|
||||
right: 0;
|
||||
padding-left: 20rpx;
|
||||
padding-right: 20rpx;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.publish-info-vloger-name {
|
||||
color: #ffffff;
|
||||
font-size: 40rpx;
|
||||
font-weight: 600;
|
||||
padding: 10rpx;
|
||||
}
|
||||
|
||||
.publish-info-music-box {
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.publish-info-content {
|
||||
color: #ffffff;
|
||||
font-size: 28rpx;
|
||||
font-weight: 400;
|
||||
padding: 10rpx;
|
||||
lines: 5;
|
||||
width: 520rpx;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.icon-fire {
|
||||
width: 36rpx;
|
||||
height: 36rpx;
|
||||
}
|
||||
|
||||
.muisc-words {
|
||||
color: #ffffff;
|
||||
font-size: 28rpx;
|
||||
padding: 10rpx;
|
||||
width: 400rpx;
|
||||
}
|
||||
|
||||
.some-counts {
|
||||
font-size: 24rpx;
|
||||
font-weight: 500;
|
||||
text-align: center;
|
||||
color: #ffffff;
|
||||
margin-top: 2rpx;
|
||||
}
|
||||
|
||||
.operation-box {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding-right: 20rpx;
|
||||
}
|
||||
|
||||
.operation-face-box {
|
||||
border-radius: 100rpx;
|
||||
border-color: #ffffff;
|
||||
border-width: 3rpx;
|
||||
}
|
||||
|
||||
.follow-me {
|
||||
width: 40rpx;
|
||||
height: 40rpx;
|
||||
border-radius: 10px;
|
||||
position: relative;
|
||||
top: -20rpx;
|
||||
}
|
||||
|
||||
.like-box {
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
margin-top: 30rpx;
|
||||
}
|
||||
|
||||
.comment-and-share-box {
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
margin-top: 45rpx;
|
||||
}
|
||||
</style>
|
714
components/vlog/videoDetail.vue
Executable file
@ -0,0 +1,714 @@
|
||||
<template>
|
||||
<view style="flex: 1">
|
||||
<list
|
||||
:pagingEnabled="true"
|
||||
:show-scrollbar="false"
|
||||
@scroll="listScroll"
|
||||
@scrollend="scroll"
|
||||
:scrollable="true">
|
||||
<cell
|
||||
:recycle="false"
|
||||
v-for="(item, index) in playerList"
|
||||
:key="index"
|
||||
:data-index="index"
|
||||
:style="{ height: screenHeight + 'px' }">
|
||||
<video
|
||||
ref="videoDetail"
|
||||
id="videoDetail"
|
||||
:object-fit="item.width >= item.height ? 'contain' : 'fill'"
|
||||
:src="item.url"
|
||||
:controls="false"
|
||||
:enable-progress-gesture="false"
|
||||
v-if="playerCur === index"
|
||||
loop
|
||||
autoplay
|
||||
show-loading="true"
|
||||
style="width: 750rpx"
|
||||
:style="{ height: screenHeight + 'px' }"
|
||||
@click="playOrPause"
|
||||
@play="onplay"
|
||||
@error="onerror"
|
||||
@timeupdate="timeupdate"></video>
|
||||
<image
|
||||
:lazy-load="true"
|
||||
:fade-show="false"
|
||||
v-if="!item.play"
|
||||
:src="item.cover"
|
||||
:mode="item.width >= item.height ? 'aspectFit' : 'scaleToFill'"
|
||||
style="width: 750rpx; filter: blur(10px)"
|
||||
:style="{ height: screenHeight + 'px' }"></image>
|
||||
<view class="publish-info-box">
|
||||
<view class="">
|
||||
<text class="publish-info-vloger-name">@{{ item.vlogerName }}</text>
|
||||
<text class="publish-info-content">{{ item.content }}</text>
|
||||
<view class="publish-info-music-box">
|
||||
<image
|
||||
src="/static/images/icon-fire.png"
|
||||
class="icon-fire"></image>
|
||||
<text class="muisc-words">{{ item.vlogerName }}的原声创作</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="" style="flex-direction: row">
|
||||
<image src="/static/images/cd-play-4.gif"
|
||||
style="width: 150rpx;height: 150rpx;opacity: 0.8;"></image>
|
||||
<image
|
||||
v-if="!isIOS"
|
||||
src="/static/images/icon-cd.png"
|
||||
class="play-cd"
|
||||
style="width: 120rpx; height: 120rpx"></image>
|
||||
<image
|
||||
v-if="isIOS"
|
||||
:src="
|
||||
'https://imooc-news.oss-cn-shanghai.aliyuncs.com/image/cd-play-4.gif?time=' +
|
||||
times
|
||||
"
|
||||
class="play-cd"></image>
|
||||
</view>
|
||||
</view>
|
||||
<!-- 视频展示右侧的操作按钮, 头像 - 点赞 - 评论 - 转发 -->
|
||||
<view class="operation-box">
|
||||
<view class="operation-face-box">
|
||||
<image
|
||||
:src="item.vlogerFace"
|
||||
class="user-face"
|
||||
@click="goUserInfoSeeSee(item.vlogerId)"></image>
|
||||
</view>
|
||||
<image
|
||||
v-if="!item.doIFollowVloger && userId != item.vlogerId"
|
||||
src="/static/images/icon-follow.png"
|
||||
@click="followMe(item.vlogerId)"
|
||||
class="follow-me"></image>
|
||||
<view class="like-box">
|
||||
<image
|
||||
v-if="!item.doILikeThisVlog"
|
||||
src="/static/images/icon-unlike.png"
|
||||
@click="likeOrDislikeVlog(1)"
|
||||
class="icon"></image>
|
||||
<image
|
||||
v-if="item.doILikeThisVlog"
|
||||
src="/static/images/icon-like.png"
|
||||
@click="likeOrDislikeVlog(0)"
|
||||
class="icon"></image>
|
||||
<text class="some-counts">{{ item.likeCounts }}</text>
|
||||
</view>
|
||||
<view class="comment-and-share-box">
|
||||
<image
|
||||
src="/static/images/icon-comments.png"
|
||||
@click="commentToggle"
|
||||
class="icon"></image>
|
||||
<text class="some-counts">{{ thisVlogTotalComentCounts }}</text>
|
||||
</view>
|
||||
<view class="comment-and-share-box">
|
||||
<image
|
||||
src="/static/images/icon-share.png"
|
||||
@click="shareToggle"
|
||||
class="icon"></image>
|
||||
<text class="some-counts">分享</text>
|
||||
</view>
|
||||
</view>
|
||||
</cell>
|
||||
</list>
|
||||
<!-- </uni-list> -->
|
||||
|
||||
<view v-if="thisVlog != null && thisVlog != {}">
|
||||
<uni-popup ref="comment" type="comment">
|
||||
<uni-popup-comments
|
||||
:thisVlogerId="thisVlogerId"
|
||||
:thisVlogId="thisVlogId"></uni-popup-comments>
|
||||
</uni-popup>
|
||||
<uni-popup ref="share" background-color="#fff" type="share">
|
||||
<uni-popup-share
|
||||
:thisVlogerId="thisVlogerId"
|
||||
:thisVlogId="thisVlogId"
|
||||
:vlogUrl="thisVlog.url"
|
||||
:isPrivate="thisVlog.isPrivate"></uni-popup-share>
|
||||
</uni-popup>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
let app = getApp();
|
||||
|
||||
|
||||
|
||||
export default {
|
||||
props: {
|
||||
screenHeight: {
|
||||
default: 0,
|
||||
},
|
||||
src: {
|
||||
default: false,
|
||||
},
|
||||
playStatus: {
|
||||
default: false,
|
||||
},
|
||||
vlogId: {
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
thisVlog: {}, // 当前的短视频对象
|
||||
thisVlogId: "", // 当前的短视频主键id
|
||||
thisVlogerId: "", // 当前的短视频博主的主键id
|
||||
userId: "",
|
||||
|
||||
playerCur: 0,
|
||||
page: 0,
|
||||
totalPage: 0,
|
||||
// videoList:[
|
||||
// {
|
||||
// width:750,//视频宽
|
||||
// height:300,//视频高
|
||||
// play:true, // 是否自动播放
|
||||
// cover:'/static/video/c1.jpg', //首帧图
|
||||
// url:'/static/video/v1.mp4', // 视频地址
|
||||
// vlogerName:'我是vlogerName', // 博主名称
|
||||
// vlogerFace: '/static/face/face-cat.png', // 博主头像
|
||||
// content:'我是content', //视频内容
|
||||
// vlogerId:123,//博主id
|
||||
// doIFollowVloger:false, //是否关注博主
|
||||
// doILikeThisVlog:false, //是否喜欢视频
|
||||
|
||||
// }
|
||||
// ],
|
||||
playerList: this.videoList,
|
||||
thisVlogTotalComentCounts: 0,
|
||||
|
||||
videoContext: {},
|
||||
currentIndex: 0,
|
||||
contentOffsetY: 0,
|
||||
times: new Date().getTime(),
|
||||
objectFit: "fill",
|
||||
isIOS: uni.getSystemInfoSync().platform == "ios",
|
||||
};
|
||||
},
|
||||
created() {
|
||||
if (!this.isIOS) {
|
||||
this.objectFit = "cover";
|
||||
}
|
||||
let myUserInfo = getApp().getUserInfoSession();
|
||||
if (!app.isStrEmpty(myUserInfo)) {
|
||||
this.userId = myUserInfo.id;
|
||||
}
|
||||
// 查询首页短视频列表
|
||||
this.displayVideoPaging(this.page + 1, true);
|
||||
let videoContext = uni.createVideoContext("videoDetail");
|
||||
this.videoContext = videoContext;
|
||||
},
|
||||
watch: {
|
||||
refreshList(value) {
|
||||
let me = this;
|
||||
let newList = value;
|
||||
if (newList != null && newList != undefined && newList.length > 0) {
|
||||
me.playerList = newList;
|
||||
}
|
||||
|
||||
// 重置
|
||||
this.playerCur = 0;
|
||||
this.currentIndex = 0;
|
||||
this.contentOffsetY = 0;
|
||||
},
|
||||
playStatus: function (val) {
|
||||
let me = this;
|
||||
if (!val) {
|
||||
me.videoContext.pause();
|
||||
} else {
|
||||
me.videoContext.play();
|
||||
}
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
// 喜欢, 点赞的list重新设置
|
||||
reLikePlayList(vlogId) {
|
||||
let me = this;
|
||||
let playerList = me.playerList;
|
||||
for (let i = 0; i < playerList.length; i++) {
|
||||
let vlog = playerList[i];
|
||||
if (vlog.vlogId == vlogId) {
|
||||
vlog.doILikeThisVlog = true;
|
||||
playerList.splice(i, 1, vlog);
|
||||
}
|
||||
}
|
||||
me.playerList = playerList;
|
||||
},
|
||||
reDislikePlayList(vlogId) {
|
||||
let me = this;
|
||||
let playerList = me.playerList;
|
||||
for (let i = 0; i < playerList.length; i++) {
|
||||
let vlog = playerList[i];
|
||||
if (vlog.vlogId == vlogId) {
|
||||
vlog.doILikeThisVlog = false;
|
||||
playerList.splice(i, 1, vlog);
|
||||
}
|
||||
}
|
||||
me.playerList = playerList;
|
||||
},
|
||||
|
||||
reChangeVlogLikedCountsInPlayList(vlogId, counts) {
|
||||
let me = this;
|
||||
let playerList = me.playerList;
|
||||
for (let i = 0; i < playerList.length; i++) {
|
||||
let vlog = playerList[i];
|
||||
if (vlog.vlogId == vlogId) {
|
||||
vlog.likeCounts = counts;
|
||||
playerList.splice(i, 1, vlog);
|
||||
}
|
||||
}
|
||||
me.playerList = playerList;
|
||||
},
|
||||
|
||||
refreshVlogCounts() {
|
||||
// 查询当前点赞数, 重新赋值给当前视频
|
||||
let me = this;
|
||||
let serverUrl = app.globalData.serverUrl;
|
||||
let currentIndex = me.playerCur;
|
||||
let vlog = me.playerList[currentIndex];
|
||||
uni.request({
|
||||
method: "POST",
|
||||
url: serverUrl + "/vlog/totalLikedCounts?vlogId=" + vlog.vlogId,
|
||||
success(result) {
|
||||
if (result.data.status == 2095) {
|
||||
let counts = result.data.data;
|
||||
me.reChangeVlogLikedCountsInPlayList(vlog.vlogId, counts);
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: result.data.msg,
|
||||
duration: 3000,
|
||||
icon: "none",
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
},
|
||||
likeOrDislikeVlog(isLike) {
|
||||
let me = this;
|
||||
if (isLike == 1) {
|
||||
// 点赞视频
|
||||
let myUserInfo = getApp().getUserInfoSession();
|
||||
if (app.isStrEmpty(myUserInfo)) {
|
||||
uni.showToast({
|
||||
duration: 3000,
|
||||
title: "请登录后再进行操作~",
|
||||
icon: "none",
|
||||
});
|
||||
uni.navigateTo({
|
||||
url: "../loginRegist/loginRegist",
|
||||
animationType: "slide-in-bottom",
|
||||
success() {
|
||||
me.loginWords = "请登录";
|
||||
},
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
let userId = myUserInfo.id;
|
||||
let serverUrl = app.globalData.serverUrl;
|
||||
let currentIndex = me.playerCur;
|
||||
let vlog = me.playerList[currentIndex];
|
||||
uni.request({
|
||||
method: "POST",
|
||||
header: {
|
||||
headerUserId: userId,
|
||||
headerUserToken: app.getUserSessionToken(),
|
||||
},
|
||||
url:
|
||||
serverUrl +
|
||||
"/vlog/like?userId=" +
|
||||
userId +
|
||||
"&vlogerId=" +
|
||||
vlog.vlogerId +
|
||||
"&vlogId=" +
|
||||
vlog.vlogId,
|
||||
success(result) {
|
||||
if (result.data.status == 2093) {
|
||||
me.reLikePlayList(vlog.vlogId);
|
||||
me.refreshVlogCounts();
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: result.data.msg,
|
||||
icon: "none",
|
||||
duration: 3000,
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
} else if (isLike == 0) {
|
||||
let myUserInfo = getApp().getUserInfoSession();
|
||||
if (app.isStrEmpty(myUserInfo)) {
|
||||
uni.showToast({
|
||||
duration: 3000,
|
||||
title: "请登录后再进行操作~",
|
||||
icon: "none",
|
||||
});
|
||||
uni.navigateTo({
|
||||
url: "../loginRegist/loginRegist",
|
||||
animationType: "slide-in-bottom",
|
||||
success() {
|
||||
me.loginWords = "请登录";
|
||||
},
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
let userId = myUserInfo.id;
|
||||
let serverUrl = app.globalData.serverUrl;
|
||||
let currentIndex = me.playerCur;
|
||||
let vlog = me.playerList[currentIndex];
|
||||
uni.request({
|
||||
method: "POST",
|
||||
header: {
|
||||
headerUserId: userId,
|
||||
headerUserToken: app.getUserSessionToken(),
|
||||
},
|
||||
url:
|
||||
serverUrl +
|
||||
"/vlog/unlike?userId=" +
|
||||
userId +
|
||||
"&vlogerId=" +
|
||||
vlog.vlogerId +
|
||||
"&vlogId=" +
|
||||
vlog.vlogId,
|
||||
success(result) {
|
||||
if (result.data.status == 2094) {
|
||||
me.reDislikePlayList(vlog.vlogId);
|
||||
me.refreshVlogCounts();
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: result.data.msg,
|
||||
icon: "none",
|
||||
duration: 3000,
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
// 关注后的list重新设置
|
||||
reFollowPlayList(vlogerId) {
|
||||
let me = this;
|
||||
let playerList = me.playerList;
|
||||
for (let i = 0; i < playerList.length; i++) {
|
||||
let vlog = playerList[i];
|
||||
if (vlog.vlogerId == vlogerId) {
|
||||
vlog.doIFollowVloger = true;
|
||||
playerList.splice(i, 1, vlog);
|
||||
}
|
||||
}
|
||||
me.playerList = playerList;
|
||||
},
|
||||
// 取关后的list重新设置
|
||||
reCancelPlayList(vlogerId) {
|
||||
let me = this;
|
||||
let playerList = me.playerList;
|
||||
for (let i = 0; i < playerList.length; i++) {
|
||||
let vlog = playerList[i];
|
||||
if (vlog.vlogerId == vlogerId) {
|
||||
vlog.doIFollowVloger = false;
|
||||
playerList.splice(i, 1, vlog);
|
||||
}
|
||||
}
|
||||
me.playerList = playerList;
|
||||
},
|
||||
// 关注博主
|
||||
followMe(vlogerId) {
|
||||
let me = this;
|
||||
let myUserInfo = getApp().getUserInfoSession();
|
||||
if (app.isStrEmpty(myUserInfo)) {
|
||||
uni.showToast({
|
||||
duration: 3000,
|
||||
title: "请登录后再进行操作~",
|
||||
icon: "none",
|
||||
});
|
||||
|
||||
uni.navigateTo({
|
||||
url: "../loginRegist/loginRegist",
|
||||
animationType: "slide-in-bottom",
|
||||
success() {
|
||||
me.loginWords = "请登录";
|
||||
},
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
let userId = myUserInfo.id;
|
||||
let serverUrl = app.globalData.serverUrl;
|
||||
uni.request({
|
||||
method: "POST",
|
||||
header: {
|
||||
headerUserId: userId,
|
||||
headerUserToken: app.getUserSessionToken(),
|
||||
},
|
||||
url:
|
||||
serverUrl + "/fans/follow?myId=" + userId + "&vlogerId=" + vlogerId,
|
||||
success(result) {
|
||||
if (result.data.status == 208) {
|
||||
me.reFollowPlayList(vlogerId);
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: result.data.msg,
|
||||
icon: "none",
|
||||
duration: 3000,
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
},
|
||||
|
||||
goUserInfoSeeSee(userId) {
|
||||
// 是否是当前登录的用户
|
||||
let myUserId = "";
|
||||
if (!app.isStrEmpty(app.getUserInfoSession())) {
|
||||
myUserId = app.getUserInfoSession().id;
|
||||
}
|
||||
if (myUserId == userId) {
|
||||
uni.switchTab({
|
||||
url: "/pages/me/me",
|
||||
});
|
||||
} else {
|
||||
uni.navigateTo({
|
||||
url: "/pages/me/vlogerInfo?userPageId=" + userId,
|
||||
});
|
||||
}
|
||||
},
|
||||
downloadVlog() {
|
||||
var me = this;
|
||||
var serverUrl = app.globalData.serverUrl;
|
||||
var currentIndex = me.playerCur;
|
||||
var vlog = me.playerList[currentIndex];
|
||||
var pendingLength = vlog.url;
|
||||
},
|
||||
|
||||
onplay: function (e) {
|
||||
if (uni.getSystemInfoSync().platform == "ios") {
|
||||
this.doplay(0.1);
|
||||
}
|
||||
},
|
||||
timeupdate: function (e) {
|
||||
if (e.detail.currentTime > 0.2) {
|
||||
this.doplay(e.detail.currentTime);
|
||||
}
|
||||
},
|
||||
|
||||
playOrPause() {
|
||||
let me = this;
|
||||
let playStatus = this.playStatus;
|
||||
|
||||
if (!playStatus) {
|
||||
me.videoContext.pause();
|
||||
} else {
|
||||
me.videoContext.play();
|
||||
}
|
||||
this.playStatus = !playStatus;
|
||||
},
|
||||
|
||||
displayVideoPaging(page, needClearList) {
|
||||
let me = this;
|
||||
let vlogId = me.vlogId;
|
||||
|
||||
let myUserInfo = getApp().getUserInfoSession();
|
||||
let userId = "";
|
||||
if (!app.isStrEmpty(myUserInfo)) {
|
||||
userId = myUserInfo.id;
|
||||
}
|
||||
|
||||
let serverUrl = app.globalData.serverUrl;
|
||||
uni.request({
|
||||
method: "GET",
|
||||
url: serverUrl + "/vlog/detail?userId=" + userId + "&vlogId=" + vlogId,
|
||||
success(result) {
|
||||
if (result.data.status == 207) {
|
||||
let vlog = result.data.data;
|
||||
let playerList = [];
|
||||
playerList.push(vlog);
|
||||
me.playerList = playerList;
|
||||
me.freshCommentCounts();
|
||||
me.setThisVlogInfo();
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: result.data.msg,
|
||||
icon: "none",
|
||||
duration: 3000,
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
},
|
||||
|
||||
doplay: function (time) {
|
||||
if (time > 0) {
|
||||
this.playerList[this.playerCur].play = true;
|
||||
}
|
||||
},
|
||||
onchange: function (index) {
|
||||
if (index != this.playerCur) {
|
||||
this.playerList[this.playerCur].play = false;
|
||||
this.playerCur = index;
|
||||
}
|
||||
this.setThisVlogInfo();
|
||||
},
|
||||
|
||||
// 设置当前vlog的信息
|
||||
setThisVlogInfo() {
|
||||
let me = this;
|
||||
let serverUrl = app.globalData.serverUrl;
|
||||
|
||||
if (
|
||||
me.playerList != null &&
|
||||
me.playerList != undefined &&
|
||||
me.playerList.length > 0
|
||||
) {
|
||||
let currentIndex = me.playerCur;
|
||||
let vlog = me.playerList[currentIndex];
|
||||
this.thisVlog = vlog;
|
||||
this.thisVlogId = vlog.vlogId;
|
||||
this.thisVlogerId = vlog.vlogerId;
|
||||
}
|
||||
},
|
||||
freshCommentCounts() {
|
||||
var me = this;
|
||||
var userId = getApp().getUserInfoSession().id;
|
||||
var serverUrl = app.globalData.serverUrl;
|
||||
if (
|
||||
me.playerList == null ||
|
||||
me.playerList == undefined ||
|
||||
me.playerList.length == 0
|
||||
) {
|
||||
return;
|
||||
}
|
||||
var currentIndex = me.playerCur;
|
||||
var vlog = me.playerList[currentIndex];
|
||||
var vlogId = vlog.vlogId;
|
||||
var serverUrl = app.globalData.serverUrl;
|
||||
uni.request({
|
||||
method: "GET",
|
||||
url: serverUrl + "/comment/counts?vlogId=" + vlogId,
|
||||
success(result) {
|
||||
if (result.data.status == 200) {
|
||||
me.thisVlogTotalComentCounts = result.data.data;
|
||||
} else {
|
||||
me.thisVlogTotalComentCounts = 0;
|
||||
}
|
||||
},
|
||||
});
|
||||
},
|
||||
commentToggle() {
|
||||
this.$refs.comment.open();
|
||||
uni.hideTabBar({
|
||||
animation: true,
|
||||
});
|
||||
},
|
||||
shareToggle() {
|
||||
this.$refs.share.open();
|
||||
uni.hideTabBar({
|
||||
animation: true,
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.icon {
|
||||
width: 80rpx;
|
||||
height: 80rpx;
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.user-face {
|
||||
width: 100rpx;
|
||||
height: 100rpx;
|
||||
border-radius: 100rpx;
|
||||
}
|
||||
|
||||
.play-cd {
|
||||
width: 150rpx;
|
||||
height: 150rpx;
|
||||
opacity: 0.8;
|
||||
}
|
||||
.refresh-info-txt {
|
||||
color: #f1f1f1;
|
||||
text-align: center;
|
||||
font-size: 12px;
|
||||
}
|
||||
.publish-info-box {
|
||||
position: absolute;
|
||||
bottom: 200rpx;
|
||||
left: 0;
|
||||
right: 0;
|
||||
padding-left: 20rpx;
|
||||
padding-right: 20rpx;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
.publish-info-vloger-name {
|
||||
color: #ffffff;
|
||||
font-size: 40rpx;
|
||||
font-weight: 600;
|
||||
padding: 10rpx;
|
||||
}
|
||||
.publish-info-music-box {
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
}
|
||||
.publish-info-content {
|
||||
color: #ffffff;
|
||||
font-size: 28rpx;
|
||||
font-weight: 400;
|
||||
padding: 10rpx;
|
||||
lines: 5;
|
||||
width: 520rpx;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.icon-fire {
|
||||
width: 36rpx;
|
||||
height: 36rpx;
|
||||
}
|
||||
.muisc-words {
|
||||
color: #ffffff;
|
||||
font-size: 28rpx;
|
||||
padding: 10rpx;
|
||||
width: 400rpx;
|
||||
}
|
||||
.some-counts {
|
||||
font-size: 24rpx;
|
||||
font-weight: 500;
|
||||
text-align: center;
|
||||
color: #ffffff;
|
||||
margin-top: 2rpx;
|
||||
}
|
||||
.operation-box {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding-right: 20rpx;
|
||||
}
|
||||
.operation-face-box {
|
||||
border-radius: 100rpx;
|
||||
border-color: #ffffff;
|
||||
border-width: 3rpx;
|
||||
}
|
||||
.follow-me {
|
||||
width: 40rpx;
|
||||
height: 40rpx;
|
||||
border-radius: 10px;
|
||||
position: relative;
|
||||
top: -20rpx;
|
||||
}
|
||||
.like-box {
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
margin-top: 30rpx;
|
||||
}
|
||||
.comment-and-share-box {
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
margin-top: 45rpx;
|
||||
}
|
||||
</style>
|
776
components/vlog/videoFollowComp.vue
Executable file
@ -0,0 +1,776 @@
|
||||
<style>
|
||||
.icon {
|
||||
width: 80rpx;
|
||||
height: 80rpx;
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.user-face {
|
||||
width: 100rpx;
|
||||
height: 100rpx;
|
||||
border-radius: 100rpx;
|
||||
}
|
||||
|
||||
.play-cd {
|
||||
width: 150rpx;
|
||||
height: 150rpx;
|
||||
opacity: 0.8;
|
||||
}
|
||||
.refresh-info-txt {
|
||||
color: #F1F1F1;
|
||||
text-align: center;
|
||||
font-size: 12px;
|
||||
}
|
||||
.publish-info-box {
|
||||
position: absolute;
|
||||
bottom: 200rpx;
|
||||
left: 0;
|
||||
right: 0;
|
||||
padding-left: 20rpx;
|
||||
padding-right: 20rpx;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
.publish-info-vloger-name {
|
||||
color: #FFFFFF;
|
||||
font-size: 40rpx;
|
||||
font-weight: 600;
|
||||
padding: 10rpx;
|
||||
}
|
||||
.publish-info-music-box {
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
}
|
||||
.publish-info-content {
|
||||
color: #FFFFFF;
|
||||
font-size: 28rpx;
|
||||
font-weight: 400;
|
||||
padding: 10rpx;
|
||||
lines: 5;
|
||||
width: 520rpx;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.icon-fire {
|
||||
width: 36rpx;
|
||||
height: 36rpx;
|
||||
}
|
||||
.muisc-words {
|
||||
color: #FFFFFF;
|
||||
font-size: 28rpx;
|
||||
padding: 10rpx;
|
||||
width:400rpx;
|
||||
}
|
||||
.some-counts {
|
||||
font-size:24rpx;
|
||||
font-weight: 500;
|
||||
text-align: center;
|
||||
color: #FFFFFF;
|
||||
margin-top: 2rpx;
|
||||
}
|
||||
.operation-box {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding-right: 20rpx;
|
||||
}
|
||||
.operation-face-box {
|
||||
border-radius: 100rpx;
|
||||
border-color: #FFFFFF;
|
||||
border-width: 3rpx;
|
||||
}
|
||||
.follow-me {
|
||||
width: 40rpx;
|
||||
height: 40rpx;
|
||||
border-radius: 10px;
|
||||
position: relative;
|
||||
top: -20rpx;
|
||||
}
|
||||
.like-box {
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
margin-top: 30rpx;
|
||||
}
|
||||
.comment-and-share-box {
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
margin-top: 45rpx;
|
||||
}
|
||||
</style>
|
||||
|
||||
<template>
|
||||
<view style="flex: 1;">
|
||||
<!-- <uni-list @change="onchange" :num="playerList.length"> -->
|
||||
<list :pagingEnabled="true" :show-scrollbar="false" @scroll="listScroll" @scrollend="scroll" :scrollable="true" >
|
||||
<refresh @pullingdown="onpullingdown" @refresh="onrefresh" :display="refreshing ? 'show' : 'hide'">
|
||||
<text class="refresh-info-txt"></text>
|
||||
<loading-indicator></loading-indicator>
|
||||
</refresh>
|
||||
<cell :recycle="false"
|
||||
v-for="(item, index) in playerList"
|
||||
:key="index"
|
||||
:data-index="index"
|
||||
:style="{'height': screenHeight + 'px'}">
|
||||
<!-- <uni-video :src="item.url" :playStatus="playStatus" :screenHeight="screenHeight" v-if="playerCur === index" @play="onplay"></uni-video> -->
|
||||
<video
|
||||
ref="myFollowVideo"
|
||||
id="myFollowVideo"
|
||||
:object-fit="item.width >= item.height ? 'contain' : 'fill'"
|
||||
:src="item.url"
|
||||
:controls="false"
|
||||
:enable-progress-gesture="false"
|
||||
v-if="playerCur === index"
|
||||
loop
|
||||
autoplay
|
||||
show-loading="true"
|
||||
style="width: 750rpx;"
|
||||
:style="{height: screenHeight + 'px'}"
|
||||
@click="playOrPause"
|
||||
@play="onplay"
|
||||
@error="onerror"
|
||||
@timeupdate="timeupdate"></video>
|
||||
<image
|
||||
:lazy-load="true"
|
||||
:fade-show="false"
|
||||
v-if="!item.play"
|
||||
:src="item.cover"
|
||||
:mode="item.width >= item.height ? 'aspectFit' : 'scaleToFill'"
|
||||
style="width: 750rpx; filter: blur(10px);"
|
||||
:style="{height: screenHeight+ 'px'}"></image>
|
||||
<!--<image :lazy-load="true" :fade-show="false" v-if="!item.play" :src="item.cover" mode="" style="width: 750rpx;position:absolute;left: 0;right: 0;top: 0;bottom: 0; filter: blur(10px);" :style="{height: screenHeight+ 'px'}"></image>-->
|
||||
<view class="publish-info-box">
|
||||
<view class="">
|
||||
<text class="publish-info-vloger-name">@{{item.vlogerName}}</text>
|
||||
<text class="publish-info-content">{{item.content}}</text>
|
||||
<view class="publish-info-music-box">
|
||||
<image src="/static/images/icon-fire.png" class="icon-fire"></image>
|
||||
<text class="muisc-words">{{item.vlogerName}}的原声创作</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="" style="flex-direction: row;">
|
||||
<!-- <image src="/static/images/cd-play-4.gif"
|
||||
style="width: 150rpx;height: 150rpx;opacity: 0.8;"></image> -->
|
||||
<image v-if="!isIOS" src="/static/images/icon-cd.png"
|
||||
class="play-cd" style="width: 120rpx;height: 120rpx;"></image>
|
||||
<image v-if="isIOS" :src="'https://imooc-news.oss-cn-shanghai.aliyuncs.com/image/cd-play-4.gif?time='+times"
|
||||
class="play-cd"></image>
|
||||
</view>
|
||||
</view>
|
||||
<!-- 视频展示右侧的操作按钮,头像 - 点赞 - 评论 - 转发 -->
|
||||
<view class="operation-box">
|
||||
<view class="operation-face-box">
|
||||
<image :src="item.vlogerFace" class="user-face" @click="goUserInfoSeeSee(item.vlogerId)"></image>
|
||||
</view>
|
||||
<image v-if="!item.doIFollowVloger"
|
||||
src="/static/images/icon-follow.png"
|
||||
@click="followMe(item.vlogerId)"
|
||||
class="follow-me"></image>
|
||||
<view class="like-box">
|
||||
<image v-if="!item.doILikeThisVlog"
|
||||
src="/static/images/icon-unlike.png"
|
||||
@click="likeOrDislikeVlog(1)"
|
||||
class="icon"></image>
|
||||
<image v-if="item.doILikeThisVlog"
|
||||
src="/static/images/icon-like.png"
|
||||
@click="likeOrDislikeVlog(0)"
|
||||
class="icon"></image>
|
||||
<text class="some-counts">{{item.likeCounts}}</text>
|
||||
</view>
|
||||
<view class="comment-and-share-box">
|
||||
<image src="/static/images/icon-comments.png" @click="commentToggle" class="icon"></image>
|
||||
<!-- <text class="some-counts">{{item.commentsCounts}}</text> -->
|
||||
<text class="some-counts">{{thisVlogTotalComentCounts}}</text>
|
||||
</view>
|
||||
<view class="comment-and-share-box">
|
||||
<image src="/static/images/icon-share.png" @click="shareToggle" class="icon"></image>
|
||||
<text class="some-counts">分享</text>
|
||||
</view>
|
||||
</view>
|
||||
</cell>
|
||||
</list>
|
||||
<!-- </uni-list> -->
|
||||
|
||||
<view v-if="thisVlog != null && thisVlog != {}">
|
||||
<!-- 底部评论窗口popup -->
|
||||
<uni-popup ref="comment" type="comment">
|
||||
<uni-popup-comments :thisVlogerId="thisVlogerId" :thisVlogId="thisVlogId"></uni-popup-comments>
|
||||
</uni-popup>
|
||||
<uni-popup ref="share" background-color="#fff" type="share">
|
||||
<uni-popup-share
|
||||
:thisVlogerId="thisVlogerId"
|
||||
:thisVlogId="thisVlogId"
|
||||
:vlogUrl="thisVlog.url"
|
||||
:isPrivate="thisVlog.isPrivate"
|
||||
></uni-popup-share>
|
||||
</uni-popup>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
var app = getApp();
|
||||
export default {
|
||||
props: {
|
||||
screenHeight: {
|
||||
default: 0
|
||||
},
|
||||
src: {
|
||||
default: false
|
||||
},
|
||||
playFollowStatus: {
|
||||
default: false
|
||||
},
|
||||
playStatus: {
|
||||
default: false
|
||||
},
|
||||
videoList: {
|
||||
default: []
|
||||
},
|
||||
refreshList: {
|
||||
default: []
|
||||
},
|
||||
pagingList: {
|
||||
default: []
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
thisVlog: {}, // 当前的短视频对象
|
||||
thisVlogId: "", // 当前的短视频主键id
|
||||
thisVlogerId: "", // 当前的短视频博主的主键id
|
||||
|
||||
refreshing: false,
|
||||
showRefreshLoading: "hide",
|
||||
|
||||
playerCur: 0,
|
||||
page: 0,
|
||||
totalPage: 0,
|
||||
playerList: this.videoList,
|
||||
thisVlogTotalComentCounts: 0,
|
||||
|
||||
videoContext: {},
|
||||
|
||||
currentIndex: 0,
|
||||
contentOffsetY: 0,
|
||||
|
||||
times: new Date().getTime(),
|
||||
|
||||
objectFit: "fill",
|
||||
isIOS: uni.getSystemInfoSync().platform == "ios"
|
||||
}
|
||||
},
|
||||
created() {
|
||||
if(!this.isIOS) {
|
||||
this.objectFit = "cover";
|
||||
}
|
||||
|
||||
// 查询首页短视频列表
|
||||
this.displayVideoPaging(this.page + 1, true);
|
||||
|
||||
var videoContext = uni.createVideoContext('myFollowVideo');
|
||||
this.videoContext = videoContext;
|
||||
},
|
||||
watch: {
|
||||
refreshList(value) {
|
||||
var me = this;
|
||||
var newList = value;
|
||||
if (newList != null && newList != undefined && newList.length > 0) {
|
||||
me.playerList = newList;
|
||||
}
|
||||
|
||||
// 重置
|
||||
this.playerCur = 0;
|
||||
this.currentIndex = 0;
|
||||
this.contentOffsetY =0;
|
||||
},
|
||||
|
||||
playFollowStatus: function(val) {
|
||||
var me = this;
|
||||
|
||||
if (!val) {
|
||||
me.videoContext.pause();
|
||||
} else {
|
||||
me.videoContext.play();
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
freshCommentCounts() {
|
||||
var me = this;
|
||||
var userId = getApp().getUserInfoSession().id;
|
||||
var serverUrl = app.globalData.serverUrl;
|
||||
|
||||
|
||||
if (me.playerList == null || me.playerList == undefined || me.playerList.length == 0) {
|
||||
return;
|
||||
}
|
||||
var currentIndex = me.playerCur;
|
||||
var vlog = me.playerList[currentIndex];
|
||||
var vlogId = vlog.vlogId;
|
||||
|
||||
var serverUrl = app.globalData.serverUrl;
|
||||
uni.request({
|
||||
method: "GET",
|
||||
url: serverUrl + "/comment/counts?vlogId=" + vlogId,
|
||||
success(result) {
|
||||
|
||||
if (result.data.status == 200) {
|
||||
me.thisVlogTotalComentCounts = result.data.data;
|
||||
} else {
|
||||
me.thisVlogTotalComentCounts = 0;
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
likeOrDislikeVlog(isLike) {
|
||||
var me = this;
|
||||
if (isLike == 1) {
|
||||
// 喜欢/点赞视频
|
||||
|
||||
var myUserInfo = getApp().getUserInfoSession();
|
||||
if (myUserInfo == null) {
|
||||
uni.showToast({
|
||||
duration: 3000,
|
||||
title: "请登录~",
|
||||
icon: "none"
|
||||
});
|
||||
uni.navigateTo({
|
||||
url: "../loginRegist/loginRegist",
|
||||
animationType: "slide-in-bottom",
|
||||
success() {
|
||||
me.loginWords = "请登录"
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
var userId = getApp().getUserInfoSession().id;
|
||||
var serverUrl = app.globalData.serverUrl;
|
||||
var currentIndex = me.playerCur;
|
||||
var vlog = me.playerList[currentIndex];
|
||||
|
||||
uni.request({
|
||||
method: "POST",
|
||||
header: {
|
||||
headerUserId: userId,
|
||||
headerUserToken: app.getUserSessionToken()
|
||||
},
|
||||
url: serverUrl + "/vlog/like?userId=" + userId + "&vlogerId=" + vlog.vlogerId + "&vlogId=" + vlog.vlogId,
|
||||
success(result) {
|
||||
|
||||
if (result.data.status == 200) {
|
||||
me.reLikePlayList(vlog.vlogId);
|
||||
me.refreshVlogCounts();
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: result.data.msg,
|
||||
icon: "none",
|
||||
duration: 3000
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
} else if (isLike == 0) {
|
||||
// 取消喜欢/点赞视频
|
||||
|
||||
var myUserInfo = getApp().getUserInfoSession();
|
||||
if (myUserInfo == null) {
|
||||
uni.showToast({
|
||||
duration: 3000,
|
||||
title: "请登录~",
|
||||
icon: "none"
|
||||
});
|
||||
uni.navigateTo({
|
||||
url: "../loginRegist/loginRegist",
|
||||
animationType: "slide-in-bottom",
|
||||
success() {
|
||||
me.loginWords = "请登录"
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
var userId = getApp().getUserInfoSession().id;
|
||||
var serverUrl = app.globalData.serverUrl;
|
||||
var currentIndex = me.playerCur;
|
||||
var vlog = me.playerList[currentIndex];
|
||||
|
||||
uni.request({
|
||||
method: "POST",
|
||||
header: {
|
||||
headerUserId: userId,
|
||||
headerUserToken: app.getUserSessionToken()
|
||||
},
|
||||
url: serverUrl + "/vlog/unlike?userId=" + userId + "&vlogerId=" + vlog.vlogerId + "&vlogId=" + vlog.vlogId,
|
||||
success(result) {
|
||||
|
||||
if (result.data.status == 200) {
|
||||
me.reDislikePlayList(vlog.vlogId);
|
||||
me.refreshVlogCounts();
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: result.data.msg,
|
||||
icon: "none",
|
||||
duration: 3000
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
},
|
||||
|
||||
// 喜欢/点赞的list重新设置
|
||||
reLikePlayList(vlogId) {
|
||||
var me = this;
|
||||
var playerList = me.playerList;
|
||||
// 关注以后,循环当前playerList,修改对应粉丝关系的doIFollowVloger改为true
|
||||
for (var i = 0 ; i < playerList.length ; i ++) {
|
||||
var vlog = playerList[i];
|
||||
if (vlog.vlogId == vlogId) {
|
||||
vlog.doILikeThisVlog = true;
|
||||
playerList.splice(i,1, vlog);
|
||||
}
|
||||
}
|
||||
me.playerList = playerList;
|
||||
},
|
||||
reDislikePlayList(vlogId) {
|
||||
var me = this;
|
||||
var playerList = me.playerList;
|
||||
// 关注以后,循环当前playerList,修改对应粉丝关系的doIFollowVloger改为true
|
||||
for (var i = 0 ; i < playerList.length ; i ++) {
|
||||
var vlog = playerList[i];
|
||||
if (vlog.vlogId == vlogId) {
|
||||
vlog.doILikeThisVlog = false;
|
||||
playerList.splice(i,1, vlog);
|
||||
}
|
||||
}
|
||||
me.playerList = playerList;
|
||||
},
|
||||
|
||||
// 关注后的list重新设置
|
||||
reFollowPlayList(vlogerId) {
|
||||
var me = this;
|
||||
var playerList = me.playerList;
|
||||
|
||||
// 关注以后,循环当前playerList,修改对应粉丝关系的doIFollowVloger改为true
|
||||
for (var i = 0 ; i < playerList.length ; i ++) {
|
||||
var vlog = playerList[i];
|
||||
if (vlog.vlogerId == vlogerId) {
|
||||
vlog.doIFollowVloger = true;
|
||||
playerList.splice(i,1, vlog);
|
||||
}
|
||||
}
|
||||
me.playerList = playerList;
|
||||
},
|
||||
// 取关后的list重新设置
|
||||
reCancelPlayList(vlogerId) {
|
||||
var me = this;
|
||||
var playerList = me.playerList;
|
||||
|
||||
// 关注以后,循环当前playerList,修改对应粉丝关系的doIFollowVloger改为true
|
||||
for (var i = 0 ; i < playerList.length ; i ++) {
|
||||
var vlog = playerList[i];
|
||||
if (vlog.vlogerId == vlogerId) {
|
||||
vlog.doIFollowVloger = false;
|
||||
playerList.splice(i,1, vlog);
|
||||
}
|
||||
}
|
||||
me.playerList = playerList;
|
||||
},
|
||||
|
||||
|
||||
// 关注博主
|
||||
followMe(vlogerId) {
|
||||
var me = this;
|
||||
var myUserInfo = getApp().getUserInfoSession();
|
||||
if (myUserInfo == null) {
|
||||
uni.showToast({
|
||||
duration: 3000,
|
||||
title: "请登录~",
|
||||
icon: "none"
|
||||
});
|
||||
|
||||
uni.navigateTo({
|
||||
url: "../loginRegist/loginRegist",
|
||||
animationType: "slide-in-bottom",
|
||||
success() {
|
||||
me.loginWords = "请登录"
|
||||
}
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
var userId = getApp().getUserInfoSession().id;
|
||||
var serverUrl = app.globalData.serverUrl;
|
||||
uni.request({
|
||||
method: "POST",
|
||||
header: {
|
||||
headerUserId: userId,
|
||||
headerUserToken: app.getUserSessionToken()
|
||||
},
|
||||
url: serverUrl + "/fans/follow?myId=" + userId + "&vlogerId=" + vlogerId,
|
||||
success(result) {
|
||||
|
||||
if (result.data.status == 200) {
|
||||
me.reFollowPlayList(vlogerId);
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: result.data.msg,
|
||||
icon: "none",
|
||||
duration: 3000
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
},
|
||||
|
||||
goUserInfoSeeSee(userId) {
|
||||
uni.setStorageSync("userPageId", userId);
|
||||
uni.navigateTo({
|
||||
url: "/pages/me/vlogerInfo?userPageId=" + userId
|
||||
})
|
||||
},
|
||||
|
||||
listScroll(e) {
|
||||
if(e.contentOffset.y > 0) {
|
||||
this.$emit("showLoading");
|
||||
}
|
||||
},
|
||||
|
||||
downloadVlog() {
|
||||
|
||||
var me = this;
|
||||
var serverUrl = app.globalData.serverUrl;
|
||||
var currentIndex = me.playerCur;
|
||||
var vlog = me.playerList[currentIndex];
|
||||
var pendingLength = vlog.url;
|
||||
|
||||
},
|
||||
|
||||
copyLink() {
|
||||
var me = me;
|
||||
},
|
||||
|
||||
showQRCode() {
|
||||
var me = me;
|
||||
},
|
||||
|
||||
changeVlogToPrivate() {
|
||||
var me = me;
|
||||
},
|
||||
|
||||
onrefresh(e) {
|
||||
this.refreshing = true;
|
||||
setTimeout(() => {
|
||||
this.refreshing = false;
|
||||
this.$emit("hideLoading");
|
||||
this.refreshText = '↓ Pull To Refresh'
|
||||
}, 300)
|
||||
|
||||
// 通过list组件的下拉刷新触发当前所在页面的下拉刷新
|
||||
uni.startPullDownRefresh();
|
||||
},
|
||||
|
||||
onplay: function(e) {
|
||||
if (uni.getSystemInfoSync().platform == 'ios') {
|
||||
// this.$emit("play", 0.1);
|
||||
this.doplay(0.1);
|
||||
}
|
||||
},
|
||||
onerror: function(err) {
|
||||
},
|
||||
timeupdate: function(e) {
|
||||
if (e.detail.currentTime > 0.2) {
|
||||
// this.$emit("play", e.detail.currentTime);
|
||||
this.doplay(e.detail.currentTime);
|
||||
}
|
||||
},
|
||||
|
||||
playOrPause() {
|
||||
var me = this;
|
||||
var playFollowStatus = this.playFollowStatus;
|
||||
|
||||
if (!playFollowStatus) {
|
||||
me.videoContext.pause();
|
||||
} else {
|
||||
me.videoContext.play();
|
||||
}
|
||||
this.playFollowStatus = !playFollowStatus;
|
||||
},
|
||||
|
||||
|
||||
scroll: function(e) {
|
||||
let originalIndex = this.currentIndex;
|
||||
let isNext = false;
|
||||
if (e.contentOffset.y < this.contentOffsetY) {
|
||||
isNext = true;
|
||||
}
|
||||
this.contentOffsetY = e.contentOffset.y;
|
||||
|
||||
var num = this.playerList.length;
|
||||
|
||||
this.currentIndex = Math.round(Math.abs(this.contentOffsetY) / (e.contentSize.height / num));
|
||||
this.onchange(this.currentIndex);
|
||||
|
||||
this.times = new Date().getTime();
|
||||
|
||||
// 判断如果视频列表总长度-当前下标,少于3个,则开始分页查询后续的视频,并且追加到现有list中
|
||||
if ((this.playerList.length - this.currentIndex) < 3) {
|
||||
// 如果要分页的page和总数totalPage相等,则没有更多
|
||||
if (this.page == this.totalPage) {
|
||||
return;
|
||||
}
|
||||
this.displayVideoPaging(this.page + 1, false);
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
// 分页查询新的list,并且追加到现有list中
|
||||
displayVideoPaging(page, needClearList) {
|
||||
|
||||
// 查询首页短视频列表
|
||||
var me = this;
|
||||
var myUserInfo = getApp().getUserInfoSession();
|
||||
var userId = "";
|
||||
if (myUserInfo != null) {
|
||||
userId = myUserInfo.id;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
var serverUrl = app.globalData.serverUrl;
|
||||
uni.request({
|
||||
method: "GET",
|
||||
header: {
|
||||
headerUserId: userId,
|
||||
headerUserToken: app.getUserSessionToken()
|
||||
},
|
||||
url: serverUrl + "/vlog/followList?myId=" + userId + "&page=" + page + "&pageSize=10",
|
||||
success(result) {
|
||||
|
||||
if (result.data.status == 200) {
|
||||
var vlogList = result.data.data.rows;
|
||||
var totalPage = result.data.data.total;
|
||||
if (needClearList) {
|
||||
me.playerList = [];
|
||||
}
|
||||
me.playerList = me.playerList.concat(vlogList);
|
||||
me.page = page;
|
||||
me.totalPage = totalPage;
|
||||
|
||||
if (needClearList) {
|
||||
me.setThisVlogInfo();
|
||||
me.freshCommentCounts();
|
||||
}
|
||||
me.doTimer();
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: result.data.msg,
|
||||
icon: "none",
|
||||
duration: 3000
|
||||
});
|
||||
}
|
||||
|
||||
},
|
||||
complete() {
|
||||
// me.doTimer();
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
doTimer() {
|
||||
var me = this;
|
||||
var t = setTimeout(() => {
|
||||
|
||||
if (me.playerList != null && me.playerList != undefined && me.playerList.length > 0) {
|
||||
me.videoContext.pause();
|
||||
me.playFollowStatus = false;
|
||||
}
|
||||
}, 3000)
|
||||
},
|
||||
|
||||
doplay: function(time) {
|
||||
if (time > 0) {
|
||||
this.playerList[this.playerCur].play = true;
|
||||
}
|
||||
},
|
||||
onchange: function(index) {
|
||||
if (index != this.playerCur) {
|
||||
this.playerList[this.playerCur].play = false;
|
||||
this.playerCur = index;
|
||||
}
|
||||
|
||||
this.refreshVlogCounts();
|
||||
this.setThisVlogInfo();
|
||||
this.freshCommentCounts();
|
||||
},
|
||||
|
||||
// 设置当前vlog的信息
|
||||
setThisVlogInfo() {
|
||||
var me = this;
|
||||
var serverUrl = app.globalData.serverUrl;
|
||||
|
||||
if (me.playerList != null && me.playerList != undefined && me.playerList.length > 0) {
|
||||
var currentIndex = me.playerCur;
|
||||
var vlog = me.playerList[currentIndex];
|
||||
this.thisVlog = vlog;
|
||||
this.thisVlogId = vlog.vlogId;
|
||||
this.thisVlogerId = vlog.vlogerId;
|
||||
}
|
||||
},
|
||||
|
||||
refreshVlogCounts() {
|
||||
// 查询当前点赞数,重新赋值给当前视频
|
||||
var me = this;
|
||||
var serverUrl = app.globalData.serverUrl;
|
||||
var currentIndex = me.playerCur;
|
||||
var vlog = me.playerList[currentIndex];
|
||||
uni.request({
|
||||
method: "POST",
|
||||
url: serverUrl + "/vlog/totalLikedCounts?vlogId=" + vlog.vlogId,
|
||||
success(result) {
|
||||
if (result.data.status == 200) {
|
||||
var counts = result.data.data;
|
||||
me.reChangeVlogLikedCountsInPlayList(vlog.vlogId, counts);
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
reChangeVlogLikedCountsInPlayList(vlogId, counts) {
|
||||
var me = this;
|
||||
var playerList = me.playerList;
|
||||
// 关注以后,循环当前playerList,修改对应粉丝关系的doIFollowVloger改为true
|
||||
for (var i = 0 ; i < playerList.length ; i ++) {
|
||||
var vlog = playerList[i];
|
||||
if (vlog.vlogId == vlogId) {
|
||||
vlog.likeCounts = counts;
|
||||
playerList.splice(i,1, vlog);
|
||||
}
|
||||
}
|
||||
me.playerList = playerList;
|
||||
},
|
||||
|
||||
commentToggle() {
|
||||
this.$refs.comment.open();
|
||||
uni.hideTabBar({
|
||||
animation: true
|
||||
});
|
||||
},
|
||||
shareToggle() {
|
||||
this.$refs.share.open();
|
||||
uni.hideTabBar({
|
||||
animation: true
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
|
41
config/api.js
Normal file
@ -0,0 +1,41 @@
|
||||
/**
|
||||
* base : 基础业务API
|
||||
* buyer : 买家API
|
||||
*/
|
||||
// 开发环境
|
||||
const dev = {
|
||||
// common: "https://common-api.pickmall.cn",
|
||||
// buyer: "https://buyer-api.pickmall.cn",
|
||||
|
||||
common: "http://192.168.1.200:8890",
|
||||
buyer: "http://192.168.1.200:8888",
|
||||
vlog: "http://192.168.1.86:8099"
|
||||
|
||||
};
|
||||
// 生产环境
|
||||
const prod = {
|
||||
// common: "https://common-api.pickmall.cn",
|
||||
// buyer: "https://buyer-api.pickmall.cn",
|
||||
common: "http://192.168.1.200:8890",
|
||||
buyer: "http://192.168.1.200:8888",
|
||||
vlog: "http://192.168.1.86:8099"
|
||||
};
|
||||
|
||||
//默认生产环境
|
||||
let api = dev;
|
||||
//如果是开发环境
|
||||
if (process.env.NODE_ENV == "development") {
|
||||
api = dev;
|
||||
} else {
|
||||
api = prod;
|
||||
}
|
||||
//微信小程序,app的打包方式建议为生产环境,所以这块直接条件编译赋值
|
||||
// #ifdef MP-WEIXIN || APP-PLUS
|
||||
api = prod;
|
||||
// #endif
|
||||
|
||||
api.buyer += "/buyer";
|
||||
api.common += "/common";
|
||||
export default {
|
||||
...api,
|
||||
};
|
16
config/config.js
Normal file
@ -0,0 +1,16 @@
|
||||
const name = "lilishop"; //全局商城name
|
||||
const schemeName = 'lilishop' //唤醒app需要的schemeName
|
||||
export default {
|
||||
name: name,
|
||||
schemeLink: `${schemeName}://`, //唤起app地址
|
||||
downloadLink: "https://pickmall.cn/download-page/index.html", //下载地址,下载app的地址
|
||||
shareLink: "https://m-b2b2c.pickmall.cn", //分享地址,也就是在h5中默认的复制地址
|
||||
appid: "wx6f10f29075dc1b0b", //小程序唯一凭证,即 AppID,可在「微信公众平台 - 设置 - 开发设置」页中获得。(需要已经成为开发者,且帐号没有异常状态)
|
||||
appSecret: "6dfbe0c72380dce5d49d65b3c91059b1", //可在 manifest.json 查看
|
||||
aMapKey: "d649892b3937a5ad20b76dacb2bcb5bd", //在高德中申请web端key
|
||||
scanAuthNavigation:['https://m-b2b2c.pickmall.cn/'], //扫码认证跳转域名配置 会根据此处配置的路由进行跳转
|
||||
iosAppId:"id1564638363", //AppStore的应用地址id 具体在分享->拷贝链接中查看
|
||||
logo:"https://lilishop-oss.oss-cn-beijing.aliyuncs.com/4c864e133c2944efad1f7282ac8a3b9e.png", //logo地址
|
||||
customerServiceMobile:"13161366885", //客服电话
|
||||
customerServiceEmail:"lili@lili.com" //客服邮箱
|
||||
};
|
123
hybrid/html/advertise/advertise.html
Normal file
@ -0,0 +1,123 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>广告启动页</title>
|
||||
<link rel="stylesheet" href="../css/advertise.css">
|
||||
<!-- uni 的 SDK -->
|
||||
<script type="text/javascript" src="../js/webView.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div class="content">
|
||||
<div class="con">
|
||||
<img id="img" src="../imgs/domain.jpeg">
|
||||
</div>
|
||||
<div class="btn" id="timer">
|
||||
<div id="info">跳过</div>
|
||||
<div class="circleProgress_wrapper btn">
|
||||
<div class="wrapper right">
|
||||
<div class="circleProgress rightcircle"></div>
|
||||
</div>
|
||||
<div class="wrapper left">
|
||||
<div class="circleProgress leftcircle"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
<script>
|
||||
|
||||
// var currentAd = {
|
||||
// operation_type: 'NONE'
|
||||
// };
|
||||
|
||||
|
||||
// function getRandom(start, end, fixed = 0) {
|
||||
// let differ = end - start
|
||||
// let random = Math.random()
|
||||
// return (start + differ * random).toFixed(fixed)
|
||||
// }
|
||||
|
||||
// document.addEventListener('plusready', function () {
|
||||
// var xhr = new plus.net.XMLHttpRequest();
|
||||
// xhr.onreadystatechange = function () {
|
||||
// switch (xhr.readyState) {
|
||||
// case 0:
|
||||
// break;
|
||||
// case 1:
|
||||
// break;
|
||||
// case 2:
|
||||
// break;
|
||||
// case 3:
|
||||
// break;
|
||||
// case 4:
|
||||
// if (xhr.status == 200) {
|
||||
// let res = JSON.parse(xhr.responseText);
|
||||
// if (res.length > 0) {
|
||||
// currentAd = res[getRandom(0, res.length - 1)];
|
||||
// document.getElementById("img").src = currentAd.pic_url;
|
||||
// } else {
|
||||
// plus.webview.currentWebview().close();
|
||||
// }
|
||||
// //alert("xhr请求成功:" + xhr.responseText);
|
||||
// } else {
|
||||
// plus.webview.currentWebview().close();
|
||||
// //alert("xhr请求失败:" + xhr.readyState);
|
||||
// }
|
||||
// break;
|
||||
// default:
|
||||
// break;
|
||||
// }
|
||||
// }
|
||||
// // xhr.open("GET", "https://api-buyer-app.yinbei.cn/advertisement");
|
||||
// xhr.send();
|
||||
// }, false);
|
||||
|
||||
|
||||
// document.addEventListener('UniAppJSBridgeReady', function () {
|
||||
// document.querySelector('.con').addEventListener('click', function (e) {
|
||||
// if (e.isTrusted) {
|
||||
// plus.webview.currentWebview().close();
|
||||
// switch (currentAd.operation_type) {
|
||||
// // 链接地址
|
||||
// case 'URL':
|
||||
// plus.webview.open(currentAd.operation_url)
|
||||
// break;
|
||||
// // 商品
|
||||
// case 'GOODS':
|
||||
// uni.navigateTo({
|
||||
// url: `/pages/product/goods?id=` + currentAd.operation_param
|
||||
// });
|
||||
// break;
|
||||
// // 关键字
|
||||
// case 'KEYWORD':
|
||||
// uni.navigateTo({
|
||||
// url: '/pages/recommend/recommend' + currentAd.operation_param
|
||||
// });
|
||||
// break;
|
||||
// // 店铺
|
||||
// case 'store':
|
||||
// uni.navigateTo({
|
||||
// url: `/pages/product/shopPage?id=` + currentAd.operation_param
|
||||
// });
|
||||
// break;
|
||||
// // 分类
|
||||
// case 'CATEGORY':
|
||||
// uni.navigateTo({
|
||||
// url: '/pages/category/category'
|
||||
// });
|
||||
// break;
|
||||
// default:
|
||||
// break;
|
||||
// }
|
||||
// }
|
||||
// });
|
||||
// document.querySelector('.btn').addEventListener('click', function (e) {
|
||||
// if (e.isTrusted) {
|
||||
// plus.webview.currentWebview().close();
|
||||
// }
|
||||
// });
|
||||
// });
|
||||
</script>
|
||||
</html>
|
149
hybrid/html/css/advertise.css
Normal file
@ -0,0 +1,149 @@
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
html,
|
||||
body {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.content {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.content .con {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
font-size: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
#img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#timer {
|
||||
display: inline-block;
|
||||
position: fixed;
|
||||
top: 40px;
|
||||
right: 10px;
|
||||
}
|
||||
|
||||
#info {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
line-height: 36px;
|
||||
border-radius: 50%;
|
||||
background-color: rgba(0, 0, 0, 0.3);
|
||||
text-align: center;
|
||||
color: #FFFFFF;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.circleProgress_wrapper {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.wrapper {
|
||||
width: 18px;
|
||||
height: 36px;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.right {
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.left {
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.circleProgress {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border: 2px solid #FFFFFF;
|
||||
border-radius: 50%;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
-webkit-transform: rotate(45deg);
|
||||
}
|
||||
|
||||
.rightcircle {
|
||||
border-top: 2px solid #03A9F4;
|
||||
border-right: 2px solid #03A9F4;
|
||||
right: 0;
|
||||
-webkit-animation: circleProgressLoad_right 4s linear;
|
||||
/*动画停留在最后一帧*/
|
||||
animation-fill-mode: forwards;
|
||||
-moz-animation-fill-mode: forwards;
|
||||
-webkit-animation-fill-mode: forwards;
|
||||
-o-animation-fill-mode: forwards;
|
||||
}
|
||||
|
||||
.leftcircle {
|
||||
border-bottom: 2px solid #03A9F4;
|
||||
border-left: 2px solid #03A9F4;
|
||||
left: 0;
|
||||
-webkit-animation: circleProgressLoad_left 4s linear;
|
||||
/*动画停留在最后一帧*/
|
||||
animation-fill-mode: forwards;
|
||||
-moz-animation-fill-mode: forwards;
|
||||
-webkit-animation-fill-mode: forwards;
|
||||
-o-animation-fill-mode: forwards;
|
||||
}
|
||||
|
||||
@-webkit-keyframes circleProgressLoad_right {
|
||||
0% {
|
||||
border-top: 2px solid #03A9F4;
|
||||
border-right: 2px solid #03A9F4;
|
||||
-webkit-transform: rotate(45deg);
|
||||
}
|
||||
|
||||
50% {
|
||||
border-top: 2px solid #03A9F4;
|
||||
border-right: 2px solid #03A9F4;
|
||||
border-left: 2px solid #FFFFFF;
|
||||
border-bottom: 2px solid #FFFFFF;
|
||||
-webkit-transform: rotate(225deg);
|
||||
}
|
||||
|
||||
100% {
|
||||
border-left: 2px solid #FFFFFF;
|
||||
border-bottom: 2px solid #FFFFFF;
|
||||
-webkit-transform: rotate(225deg);
|
||||
}
|
||||
}
|
||||
|
||||
@-webkit-keyframes circleProgressLoad_left {
|
||||
0% {
|
||||
border-bottom: 2px solid #03A9F4;
|
||||
border-left: 2px solid #03A9F4;
|
||||
-webkit-transform: rotate(45deg);
|
||||
}
|
||||
|
||||
50% {
|
||||
border-bottom: 2px solid #03A9F4;
|
||||
border-left: 2px solid #03A9F4;
|
||||
border-top: 2px solid #FFFFFF;
|
||||
border-right: 2px solid #FFFFFF;
|
||||
-webkit-transform: rotate(45deg);
|
||||
}
|
||||
|
||||
100% {
|
||||
border-top: 2px solid #FFFFFF;
|
||||
border-right: 2px solid #FFFFFF;
|
||||
-webkit-transform: rotate(225deg);
|
||||
}
|
||||
}
|
94
hybrid/html/error.html
Normal file
@ -0,0 +1,94 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>服务器异常</title>
|
||||
|
||||
<script type="text/javascript" src="./js/webView.js"></script>
|
||||
<script type="text/javascript">
|
||||
// H5 plus事件处理
|
||||
var ws = null;
|
||||
|
||||
function plusReady() {
|
||||
// Android处理返回键
|
||||
plus.key.addEventListener('backbutton', function() {
|
||||
(history.length == 1) && ws.close();
|
||||
var c = setTimeout(function() {
|
||||
ws.close();
|
||||
}, 1000);
|
||||
window.onbeforeunload = function() {
|
||||
clearTimeout(c);
|
||||
}
|
||||
history.go(-2);
|
||||
}, false);
|
||||
ws = plus.webview.currentWebview();
|
||||
}
|
||||
if (window.plus) {
|
||||
plusReady();
|
||||
} else {
|
||||
document.addEventListener('plusready', plusReady, false);
|
||||
}
|
||||
document.addEventListener('touchstart', function() {
|
||||
return false;
|
||||
}, true);
|
||||
// 禁止选择
|
||||
document.oncontextmenu = function() {
|
||||
return false;
|
||||
};
|
||||
</script>
|
||||
<style>
|
||||
html,
|
||||
body {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.main {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
line-height: 80px;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.text {
|
||||
font-size: 14px;
|
||||
color: #999999;
|
||||
}
|
||||
|
||||
img {
|
||||
width: 150px;
|
||||
height: 130px;
|
||||
}
|
||||
|
||||
button {
|
||||
border: 1px solid #1ABC9C;
|
||||
border-radius: 2px;
|
||||
background-color: #FFFFFF;
|
||||
color: #1ABC9C;
|
||||
width: 100px;
|
||||
height: 30px;
|
||||
margin-bottom: 30px;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.go-index {
|
||||
margin-top: 20px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="main">
|
||||
<div class="text">服务器异常</div>
|
||||
<button onclick="if(history.length == 1){ws.close();}else{ws.back();ws.back();}">点击刷新</button>
|
||||
<button onclick="ws.close()">点击关闭</button>
|
||||
<button onclick="plus.runtime.restart()">点击重启</button>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
BIN
hybrid/html/imgs/domain.jpeg
Normal file
After Width: | Height: | Size: 52 KiB |
1
hybrid/html/js/webView.js
Normal file
@ -0,0 +1 @@
|
||||
!function(e,n){"object"==typeof exports&&"undefined"!=typeof module?module.exports=n():"function"==typeof define&&define.amd?define(n):(e=e||self).uni=n()}(this,(function(){"use strict";try{var e={};Object.defineProperty(e,"passive",{get:function(){!0}}),window.addEventListener("test-passive",null,e)}catch(e){}var n=Object.prototype.hasOwnProperty;function t(e,t){return n.call(e,t)}var i=[],a=function(e,n){var t={options:{timestamp:+new Date},name:e,arg:n};if(window.__dcloud_weex_postMessage||window.__dcloud_weex_){if("postMessage"===e){var a={data:[n]};return window.__dcloud_weex_postMessage?window.__dcloud_weex_postMessage(a):window.__dcloud_weex_.postMessage(JSON.stringify(a))}var o={type:"WEB_INVOKE_APPSERVICE",args:{data:t,webviewIds:i}};window.__dcloud_weex_postMessage?window.__dcloud_weex_postMessageToService(o):window.__dcloud_weex_.postMessageToService(JSON.stringify(o))}if(!window.plus)return window.parent.postMessage({type:"WEB_INVOKE_APPSERVICE",data:t,pageId:""},"*");if(0===i.length){var r=plus.webview.currentWebview();if(!r)throw new Error("plus.webview.currentWebview() is undefined");var d=r.parent(),s="";s=d?d.id:r.id,i.push(s)}if(plus.webview.getWebviewById("__uniapp__service"))plus.webview.postMessageToUniNView({type:"WEB_INVOKE_APPSERVICE",args:{data:t,webviewIds:i}},"__uniapp__service");else{var w=JSON.stringify(t);plus.webview.getLaunchWebview().evalJS('UniPlusBridge.subscribeHandler("'.concat("WEB_INVOKE_APPSERVICE",'",').concat(w,",").concat(JSON.stringify(i),");"))}},o={navigateTo:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},n=e.url;a("navigateTo",{url:encodeURI(n)})},navigateBack:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},n=e.delta;a("navigateBack",{delta:parseInt(n)||1})},switchTab:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},n=e.url;a("switchTab",{url:encodeURI(n)})},reLaunch:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},n=e.url;a("reLaunch",{url:encodeURI(n)})},redirectTo:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},n=e.url;a("redirectTo",{url:encodeURI(n)})},getEnv:function(e){window.plus?e({plus:!0}):e({h5:!0})},postMessage:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};a("postMessage",e.data||{})}},r=/uni-app/i.test(navigator.userAgent),d=/Html5Plus/i.test(navigator.userAgent),s=/complete|loaded|interactive/;var w=window.my&&navigator.userAgent.indexOf("AlipayClient")>-1;var u=window.swan&&window.swan.webView&&/swan/i.test(navigator.userAgent);var c=window.qq&&window.qq.miniProgram&&/QQ/i.test(navigator.userAgent)&&/miniProgram/i.test(navigator.userAgent);var g=window.tt&&window.tt.miniProgram&&/toutiaomicroapp/i.test(navigator.userAgent);var v=window.wx&&window.wx.miniProgram&&/micromessenger/i.test(navigator.userAgent)&&/miniProgram/i.test(navigator.userAgent);var p=window.qa&&/quickapp/i.test(navigator.userAgent);for(var l,_=function(){window.UniAppJSBridge=!0,document.dispatchEvent(new CustomEvent("UniAppJSBridgeReady",{bubbles:!0,cancelable:!0}))},f=[function(e){if(r||d)return window.__dcloud_weex_postMessage||window.__dcloud_weex_?document.addEventListener("DOMContentLoaded",e):window.plus&&s.test(document.readyState)?setTimeout(e,0):document.addEventListener("plusready",e),o},function(e){if(v)return window.WeixinJSBridge&&window.WeixinJSBridge.invoke?setTimeout(e,0):document.addEventListener("WeixinJSBridgeReady",e),window.wx.miniProgram},function(e){if(c)return window.QQJSBridge&&window.QQJSBridge.invoke?setTimeout(e,0):document.addEventListener("QQJSBridgeReady",e),window.qq.miniProgram},function(e){if(w){document.addEventListener("DOMContentLoaded",e);var n=window.my;return{navigateTo:n.navigateTo,navigateBack:n.navigateBack,switchTab:n.switchTab,reLaunch:n.reLaunch,redirectTo:n.redirectTo,postMessage:n.postMessage,getEnv:n.getEnv}}},function(e){if(u)return document.addEventListener("DOMContentLoaded",e),window.swan.webView},function(e){if(g)return document.addEventListener("DOMContentLoaded",e),window.tt.miniProgram},function(e){if(p){window.QaJSBridge&&window.QaJSBridge.invoke?setTimeout(e,0):document.addEventListener("QaJSBridgeReady",e);var n=window.qa;return{navigateTo:n.navigateTo,navigateBack:n.navigateBack,switchTab:n.switchTab,reLaunch:n.reLaunch,redirectTo:n.redirectTo,postMessage:n.postMessage,getEnv:n.getEnv}}},function(e){return document.addEventListener("DOMContentLoaded",e),o}],m=0;m<f.length&&!(l=f[m](_));m++);l||(l={});var E="undefined"!=typeof uni?uni:{};if(!E.navigateTo)for(var b in l)t(l,b)&&(E[b]=l[b]);return E.webView=l,E}));
|
BIN
icon.png
Executable file
After Width: | Height: | Size: 8.8 KiB |
31
js_sdk/amap-wx.130.js
Normal file
@ -0,0 +1,31 @@
|
||||
function AMapWX(a){this.key=a.key;this.requestConfig={key:a.key,s:"rsx",platform:"WXJS",appname:a.key,sdkversion:"1.2.0",logversion:"2.0"};this.MeRequestConfig={key:a.key,serviceName:"https://restapi.amap.com/rest/me"}}
|
||||
AMapWX.prototype.getWxLocation=function(a,b){wx.getLocation({type:"gcj02",success:function(c){c=c.longitude+","+c.latitude;wx.setStorage({key:"userLocation",data:c});b(c)},fail:function(c){wx.getStorage({key:"userLocation",success:function(d){d.data&&b(d.data)}});a.fail({errCode:"0",errMsg:c.errMsg||""})}})};
|
||||
AMapWX.prototype.getMEKeywordsSearch=function(a){if(!a.options)return a.fail({errCode:"0",errMsg:"\u7f3a\u5c11\u5fc5\u8981\u53c2\u6570"});var b=a.options,c=this.MeRequestConfig,d={key:c.key,s:"rsx",platform:"WXJS",appname:a.key,sdkversion:"1.2.0",logversion:"2.0"};b.layerId&&(d.layerId=b.layerId);b.keywords&&(d.keywords=b.keywords);b.city&&(d.city=b.city);b.filter&&(d.filter=b.filter);b.sortrule&&(d.sortrule=b.sortrule);b.pageNum&&(d.pageNum=b.pageNum);b.pageSize&&(d.pageSize=b.pageSize);b.sig&&(d.sig=
|
||||
b.sig);wx.request({url:c.serviceName+"/cpoint/datasearch/local",data:d,method:"GET",header:{"content-type":"application/json"},success:function(e){(e=e.data)&&e.status&&"1"===e.status&&0===e.code?a.success(e.data):a.fail({errCode:"0",errMsg:e})},fail:function(e){a.fail({errCode:"0",errMsg:e.errMsg||""})}})};
|
||||
AMapWX.prototype.getMEIdSearch=function(a){if(!a.options)return a.fail({errCode:"0",errMsg:"\u7f3a\u5c11\u5fc5\u8981\u53c2\u6570"});var b=a.options,c=this.MeRequestConfig,d={key:c.key,s:"rsx",platform:"WXJS",appname:a.key,sdkversion:"1.2.0",logversion:"2.0"};b.layerId&&(d.layerId=b.layerId);b.id&&(d.id=b.id);b.sig&&(d.sig=b.sig);wx.request({url:c.serviceName+"/cpoint/datasearch/id",data:d,method:"GET",header:{"content-type":"application/json"},success:function(e){(e=e.data)&&e.status&&"1"===e.status&&
|
||||
0===e.code?a.success(e.data):a.fail({errCode:"0",errMsg:e})},fail:function(e){a.fail({errCode:"0",errMsg:e.errMsg||""})}})};
|
||||
AMapWX.prototype.getMEPolygonSearch=function(a){if(!a.options)return a.fail({errCode:"0",errMsg:"\u7f3a\u5c11\u5fc5\u8981\u53c2\u6570"});var b=a.options,c=this.MeRequestConfig,d={key:c.key,s:"rsx",platform:"WXJS",appname:a.key,sdkversion:"1.2.0",logversion:"2.0"};b.layerId&&(d.layerId=b.layerId);b.keywords&&(d.keywords=b.keywords);b.polygon&&(d.polygon=b.polygon);b.filter&&(d.filter=b.filter);b.sortrule&&(d.sortrule=b.sortrule);b.pageNum&&(d.pageNum=b.pageNum);b.pageSize&&(d.pageSize=b.pageSize);
|
||||
b.sig&&(d.sig=b.sig);wx.request({url:c.serviceName+"/cpoint/datasearch/polygon",data:d,method:"GET",header:{"content-type":"application/json"},success:function(e){(e=e.data)&&e.status&&"1"===e.status&&0===e.code?a.success(e.data):a.fail({errCode:"0",errMsg:e})},fail:function(e){a.fail({errCode:"0",errMsg:e.errMsg||""})}})};
|
||||
AMapWX.prototype.getMEaroundSearch=function(a){if(!a.options)return a.fail({errCode:"0",errMsg:"\u7f3a\u5c11\u5fc5\u8981\u53c2\u6570"});var b=a.options,c=this.MeRequestConfig,d={key:c.key,s:"rsx",platform:"WXJS",appname:a.key,sdkversion:"1.2.0",logversion:"2.0"};b.layerId&&(d.layerId=b.layerId);b.keywords&&(d.keywords=b.keywords);b.center&&(d.center=b.center);b.radius&&(d.radius=b.radius);b.filter&&(d.filter=b.filter);b.sortrule&&(d.sortrule=b.sortrule);b.pageNum&&(d.pageNum=b.pageNum);b.pageSize&&
|
||||
(d.pageSize=b.pageSize);b.sig&&(d.sig=b.sig);wx.request({url:c.serviceName+"/cpoint/datasearch/around",data:d,method:"GET",header:{"content-type":"application/json"},success:function(e){(e=e.data)&&e.status&&"1"===e.status&&0===e.code?a.success(e.data):a.fail({errCode:"0",errMsg:e})},fail:function(e){a.fail({errCode:"0",errMsg:e.errMsg||""})}})};
|
||||
AMapWX.prototype.getGeo=function(a){var b=this.requestConfig,c=a.options;b={key:this.key,extensions:"all",s:b.s,platform:b.platform,appname:this.key,sdkversion:b.sdkversion,logversion:b.logversion};c.address&&(b.address=c.address);c.city&&(b.city=c.city);c.batch&&(b.batch=c.batch);c.sig&&(b.sig=c.sig);wx.request({url:"https://restapi.amap.com/v3/geocode/geo",data:b,method:"GET",header:{"content-type":"application/json"},success:function(d){(d=d.data)&&d.status&&"1"===d.status?a.success(d):a.fail({errCode:"0",
|
||||
errMsg:d})},fail:function(d){a.fail({errCode:"0",errMsg:d.errMsg||""})}})};
|
||||
AMapWX.prototype.getRegeo=function(a){function b(d){var e=c.requestConfig;wx.request({url:"https://restapi.amap.com/v3/geocode/regeo",data:{key:c.key,location:d,extensions:"all",s:e.s,platform:e.platform,appname:c.key,sdkversion:e.sdkversion,logversion:e.logversion},method:"GET",header:{"content-type":"application/json"},success:function(g){if(g.data.status&&"1"==g.data.status){g=g.data.regeocode;var h=g.addressComponent,f=[],k=g.roads[0].name+"\u9644\u8fd1",m=d.split(",")[0],n=d.split(",")[1];if(g.pois&&
|
||||
g.pois[0]){k=g.pois[0].name+"\u9644\u8fd1";var l=g.pois[0].location;l&&(m=parseFloat(l.split(",")[0]),n=parseFloat(l.split(",")[1]))}h.provice&&f.push(h.provice);h.city&&f.push(h.city);h.district&&f.push(h.district);h.streetNumber&&h.streetNumber.street&&h.streetNumber.number?(f.push(h.streetNumber.street),f.push(h.streetNumber.number)):f.push(g.roads[0].name);f=f.join("");a.success([{iconPath:a.iconPath,width:a.iconWidth,height:a.iconHeight,name:f,desc:k,longitude:m,latitude:n,id:0,regeocodeData:g}])}else a.fail({errCode:g.data.infocode,
|
||||
errMsg:g.data.info})},fail:function(g){a.fail({errCode:"0",errMsg:g.errMsg||""})}})}var c=this;a.location?b(a.location):c.getWxLocation(a,function(d){b(d)})};
|
||||
AMapWX.prototype.getWeather=function(a){function b(g){var h="base";a.type&&"forecast"==a.type&&(h="all");wx.request({url:"https://restapi.amap.com/v3/weather/weatherInfo",data:{key:d.key,city:g,extensions:h,s:e.s,platform:e.platform,appname:d.key,sdkversion:e.sdkversion,logversion:e.logversion},method:"GET",header:{"content-type":"application/json"},success:function(f){if(f.data.status&&"1"==f.data.status)if(f.data.lives){if((f=f.data.lives)&&0<f.length){f=f[0];var k={city:{text:"\u57ce\u5e02",data:f.city},
|
||||
weather:{text:"\u5929\u6c14",data:f.weather},temperature:{text:"\u6e29\u5ea6",data:f.temperature},winddirection:{text:"\u98ce\u5411",data:f.winddirection+"\u98ce"},windpower:{text:"\u98ce\u529b",data:f.windpower+"\u7ea7"},humidity:{text:"\u6e7f\u5ea6",data:f.humidity+"%"}};k.liveData=f;a.success(k)}}else f.data.forecasts&&f.data.forecasts[0]&&a.success({forecast:f.data.forecasts[0]});else a.fail({errCode:f.data.infocode,errMsg:f.data.info})},fail:function(f){a.fail({errCode:"0",errMsg:f.errMsg||""})}})}
|
||||
function c(g){wx.request({url:"https://restapi.amap.com/v3/geocode/regeo",data:{key:d.key,location:g,extensions:"all",s:e.s,platform:e.platform,appname:d.key,sdkversion:e.sdkversion,logversion:e.logversion},method:"GET",header:{"content-type":"application/json"},success:function(h){if(h.data.status&&"1"==h.data.status){h=h.data.regeocode;if(h.addressComponent)var f=h.addressComponent.adcode;else h.aois&&0<h.aois.length&&(f=h.aois[0].adcode);b(f)}else a.fail({errCode:h.data.infocode,errMsg:h.data.info})},
|
||||
fail:function(h){a.fail({errCode:"0",errMsg:h.errMsg||""})}})}var d=this,e=d.requestConfig;a.city?b(a.city):d.getWxLocation(a,function(g){c(g)})};
|
||||
AMapWX.prototype.getPoiAround=function(a){function b(e){e={key:c.key,location:e,s:d.s,platform:d.platform,appname:c.key,sdkversion:d.sdkversion,logversion:d.logversion};a.querytypes&&(e.types=a.querytypes);a.querykeywords&&(e.keywords=a.querykeywords);wx.request({url:"https://restapi.amap.com/v3/place/around",data:e,method:"GET",header:{"content-type":"application/json"},success:function(g){if(g.data.status&&"1"==g.data.status){if((g=g.data)&&g.pois){for(var h=[],f=0;f<g.pois.length;f++){var k=0==
|
||||
f?a.iconPathSelected:a.iconPath;h.push({latitude:parseFloat(g.pois[f].location.split(",")[1]),longitude:parseFloat(g.pois[f].location.split(",")[0]),iconPath:k,width:22,height:32,id:f,name:g.pois[f].name,address:g.pois[f].address})}a.success({markers:h,poisData:g.pois})}}else a.fail({errCode:g.data.infocode,errMsg:g.data.info})},fail:function(g){a.fail({errCode:"0",errMsg:g.errMsg||""})}})}var c=this,d=c.requestConfig;a.location?b(a.location):c.getWxLocation(a,function(e){b(e)})};
|
||||
AMapWX.prototype.getStaticmap=function(a){function b(e){c.push("location="+e);a.zoom&&c.push("zoom="+a.zoom);a.size&&c.push("size="+a.size);a.scale&&c.push("scale="+a.scale);a.markers&&c.push("markers="+a.markers);a.labels&&c.push("labels="+a.labels);a.paths&&c.push("paths="+a.paths);a.traffic&&c.push("traffic="+a.traffic);e="https://restapi.amap.com/v3/staticmap?"+c.join("&");a.success({url:e})}var c=[];c.push("key="+this.key);var d=this.requestConfig;c.push("s="+d.s);c.push("platform="+d.platform);
|
||||
c.push("appname="+d.appname);c.push("sdkversion="+d.sdkversion);c.push("logversion="+d.logversion);a.location?b(a.location):this.getWxLocation(a,function(e){b(e)})};
|
||||
AMapWX.prototype.getInputtips=function(a){var b=Object.assign({},this.requestConfig);a.location&&(b.location=a.location);a.keywords&&(b.keywords=a.keywords);a.type&&(b.type=a.type);a.city&&(b.city=a.city);a.citylimit&&(b.citylimit=a.citylimit);wx.request({url:"https://restapi.amap.com/v3/assistant/inputtips",data:b,method:"GET",header:{"content-type":"application/json"},success:function(c){c&&c.data&&c.data.tips&&a.success({tips:c.data.tips})},fail:function(c){a.fail({errCode:"0",errMsg:c.errMsg||
|
||||
""})}})};
|
||||
AMapWX.prototype.getDrivingRoute=function(a){var b=Object.assign({},this.requestConfig);a.origin&&(b.origin=a.origin);a.destination&&(b.destination=a.destination);a.strategy&&(b.strategy=a.strategy);a.waypoints&&(b.waypoints=a.waypoints);a.avoidpolygons&&(b.avoidpolygons=a.avoidpolygons);a.avoidroad&&(b.avoidroad=a.avoidroad);wx.request({url:"https://restapi.amap.com/v3/direction/driving",data:b,method:"GET",header:{"content-type":"application/json"},success:function(c){c&&c.data&&c.data.route&&a.success({paths:c.data.route.paths,
|
||||
taxi_cost:c.data.route.taxi_cost||""})},fail:function(c){a.fail({errCode:"0",errMsg:c.errMsg||""})}})};
|
||||
AMapWX.prototype.getWalkingRoute=function(a){var b=Object.assign({},this.requestConfig);a.origin&&(b.origin=a.origin);a.destination&&(b.destination=a.destination);wx.request({url:"https://restapi.amap.com/v3/direction/walking",data:b,method:"GET",header:{"content-type":"application/json"},success:function(c){c&&c.data&&c.data.route&&a.success({paths:c.data.route.paths})},fail:function(c){a.fail({errCode:"0",errMsg:c.errMsg||""})}})};
|
||||
AMapWX.prototype.getTransitRoute=function(a){var b=Object.assign({},this.requestConfig);a.origin&&(b.origin=a.origin);a.destination&&(b.destination=a.destination);a.strategy&&(b.strategy=a.strategy);a.city&&(b.city=a.city);a.cityd&&(b.cityd=a.cityd);wx.request({url:"https://restapi.amap.com/v3/direction/transit/integrated",data:b,method:"GET",header:{"content-type":"application/json"},success:function(c){c&&c.data&&c.data.route&&(c=c.data.route,a.success({distance:c.distance||"",taxi_cost:c.taxi_cost||
|
||||
"",transits:c.transits}))},fail:function(c){a.fail({errCode:"0",errMsg:c.errMsg||""})}})};
|
||||
AMapWX.prototype.getRidingRoute=function(a){var b=Object.assign({},this.requestConfig);a.origin&&(b.origin=a.origin);a.destination&&(b.destination=a.destination);wx.request({url:"https://restapi.amap.com/v3/direction/riding",data:b,method:"GET",header:{"content-type":"application/json"},success:function(c){c&&c.data&&c.data.route&&a.success({paths:c.data.route.paths})},fail:function(c){a.fail({errCode:"0",errMsg:c.errMsg||""})}})};module.exports.AMapWX=AMapWX;
|
42
js_sdk/h5-copy/h5-copy.js
Normal file
@ -0,0 +1,42 @@
|
||||
export function h5Copy(content) {
|
||||
|
||||
if (!document.queryCommandSupported('copy')) {
|
||||
// 不支持
|
||||
return false
|
||||
}
|
||||
|
||||
let textarea = document.createElement("textarea")
|
||||
textarea.value = content
|
||||
textarea.readOnly = "readOnly"
|
||||
document.body.appendChild(textarea)
|
||||
textarea.select() // 选择对象
|
||||
textarea.setSelectionRange(0, content.length) //核心
|
||||
let result = document.execCommand("copy") // 执行浏览器复制命令
|
||||
textarea.remove()
|
||||
return result
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 获取系统剪贴板内容
|
||||
*/
|
||||
export function getClipboardData() {
|
||||
return new Promise((success, fail) => {
|
||||
// #ifndef H5
|
||||
uni.getClipboardData({
|
||||
success: ({ data }) => success(data),
|
||||
fail
|
||||
})
|
||||
// #endif
|
||||
|
||||
// #ifdef H5
|
||||
try {
|
||||
navigator.clipboard.readText().then(success).catch(fail)
|
||||
} catch (error) {
|
||||
fail(error)
|
||||
}
|
||||
// #endif
|
||||
})
|
||||
}
|
116
js_sdk/lili-pay/wx-pay.js
Normal file
@ -0,0 +1,116 @@
|
||||
/**
|
||||
* 微信小程序支付
|
||||
* 此处针对于微信小程序开发的支付插件
|
||||
* 第一次支付成功后会跳出订阅的消息 如果用户拒绝或同意都会跳转到支付成功页面
|
||||
* 如果点击订阅 会将状态写进缓存 之后不再提醒。
|
||||
*
|
||||
* @param {sn,price}
|
||||
*/
|
||||
|
||||
import { initiatePay } from "@/api/trade";
|
||||
import { getWeChatMpMessage } from "@/api/message.js";
|
||||
class LiLiWXPay {
|
||||
constructor(...payList) {
|
||||
this.data = payList[0];
|
||||
console.log(payList);
|
||||
// 调用支付
|
||||
this.pay = () => {
|
||||
uni.showLoading({
|
||||
title: "加载中",
|
||||
});
|
||||
|
||||
let submitData = {
|
||||
sn: this.data.sn,
|
||||
orderType: this.data.orderType || "TRADE",
|
||||
clientType: "WECHAT_MP",
|
||||
};
|
||||
const paymentMethod = "WECHAT";
|
||||
const paymentClient = "MP";
|
||||
// 调用支付
|
||||
initiatePay(paymentMethod, paymentClient, submitData).then((res) => {
|
||||
let response = res.data.result;
|
||||
uni.hideLoading();
|
||||
uni.requestPayment({
|
||||
provider: "wxpay",
|
||||
appid: response.appid,
|
||||
timeStamp: response.timeStamp,
|
||||
nonceStr: response.nonceStr,
|
||||
package: response.package,
|
||||
signType: response.signType,
|
||||
paySign: response.paySign,
|
||||
success: (e) => {
|
||||
uni.showToast({
|
||||
icon: "none",
|
||||
title: "支付成功!",
|
||||
});
|
||||
sendMessage(payList[0].price);
|
||||
},
|
||||
fail: (e) => {
|
||||
this.exception = e;
|
||||
// 支付异常或支付失败之后跳转到订单页面
|
||||
uni.showModal({
|
||||
content: "支付失败,如果您已支付,请勿反复支付",
|
||||
showCancel: false,
|
||||
success: () => {
|
||||
uni.redirectTo({
|
||||
url: "/pages/order/myOrder?status=0",
|
||||
});
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
});
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function sendMessage(price) {
|
||||
//判断用户是否已经进行了订阅
|
||||
if (!uni.getStorageSync("acceptSubscribeMessage")) {
|
||||
//订阅消息
|
||||
getWeChatMpMessage().then((res) => {
|
||||
var message = res.data.result;
|
||||
var templateid = message.map((item) => item.code);
|
||||
uni.requestSubscribeMessage({
|
||||
tmplIds: templateid,
|
||||
success: (res) => {
|
||||
for (let key in res) {
|
||||
// 表示用户拒绝订阅该信息
|
||||
if (res[key] == "reject") {
|
||||
this.checked = false;
|
||||
} else {
|
||||
uni.setStorageSync("acceptSubscribeMessage", res);
|
||||
}
|
||||
}
|
||||
},
|
||||
fail: (res) => {
|
||||
uni.removeStorageSync("acceptSubscribeMessage");
|
||||
this.checked = false;
|
||||
},
|
||||
complete: () => {
|
||||
/**
|
||||
* 已经支付成功
|
||||
*/
|
||||
uni.redirectTo({
|
||||
url:
|
||||
"/pages/cart/payment/success?paymentMethod=WECHAT" +
|
||||
"&payPrice=" +
|
||||
price,
|
||||
});
|
||||
},
|
||||
});
|
||||
});
|
||||
} else {
|
||||
/**
|
||||
* 已经支付成功
|
||||
*/
|
||||
uni.redirectTo({
|
||||
url:
|
||||
"/pages/cart/payment/success?paymentMethod=WECHAT" +
|
||||
"&payPrice=" +
|
||||
price,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default LiLiWXPay;
|
38
js_sdk/u-draw-poster/draw-poster.d.ts
vendored
Normal file
@ -0,0 +1,38 @@
|
||||
import { Canvas, DrawPosterCanvasCtx, CreateImagePathOptions, DrawPosterBuildOpts, DrawPosterUseOpts, drawPosterExtends, DrawPosterUseCtxOpts } from "./utils/interface";
|
||||
declare type DrawPosterInstanceType = InstanceType<typeof DrawPoster> & drawPosterExtends;
|
||||
declare class DrawPoster {
|
||||
canvas: Canvas;
|
||||
ctx: DrawPosterCanvasCtx;
|
||||
canvasId: string;
|
||||
loading: boolean;
|
||||
debugging: boolean;
|
||||
loadingText: string;
|
||||
createText: string;
|
||||
[key: string]: any;
|
||||
private executeOnions;
|
||||
private stopStatus;
|
||||
private drawType;
|
||||
/** 构建器, 构建返回当前实例, 并挂载多个方法 */
|
||||
constructor(canvas: Canvas, ctx: DrawPosterCanvasCtx, canvasId: string, loading: boolean, debugging: boolean, loadingText: string, createText: string, tips: boolean);
|
||||
/** 提示器, 传入消息与数据 */
|
||||
private debuggingLog;
|
||||
/** 传入挂载配置对象, 添加扩展方法 */
|
||||
static use: (opts: DrawPosterUseOpts) => void;
|
||||
/** 传入挂载配置对象, 添加绘画扩展方法 */
|
||||
static useCtx: (opts: DrawPosterUseCtxOpts) => void;
|
||||
/** 构建绘制海报矩形方法, 传入canvas选择器或配置对象, 返回绘制对象 */
|
||||
static build: (options: string | DrawPosterBuildOpts, tips?: boolean) => Promise<DrawPosterInstanceType>;
|
||||
/** 构建多个绘制海报矩形方法, 传入选择器或配置对象的数组, 返回多个绘制对象 */
|
||||
static buildAll: (optionsAll: (string | DrawPosterBuildOpts)[]) => Promise<{
|
||||
[key: string]: DrawPosterInstanceType;
|
||||
}>;
|
||||
/** 绘制器, 接收执行器函数, 添加到绘制容器中 */
|
||||
draw: (execute: (ctx: DrawPosterCanvasCtx) => Promise<any> | void) => void;
|
||||
/** 等待创建绘画, 成功后清空绘制器容器 */
|
||||
awaitCreate: () => Promise<boolean[]>;
|
||||
/** 创建canvas本地地址 @returns {string} 本地地址 */
|
||||
createImagePath: (baseOptions?: CreateImagePathOptions) => Promise<string>;
|
||||
/** 停止当前绘画, 调用则停止当前绘画堆栈的绘画 */
|
||||
stop: () => void;
|
||||
}
|
||||
export default DrawPoster;
|
194
js_sdk/u-draw-poster/draw-poster.js
Normal file
@ -0,0 +1,194 @@
|
||||
import uni from "./utils/global";
|
||||
import { handleBuildOpts, extendMount } from "./utils/utils";
|
||||
import { getCanvas2dContext } from "./utils/wx-utils";
|
||||
// 扩展挂载储存
|
||||
let drawPosterExtend = {};
|
||||
let drawCtxPosterExtend = {};
|
||||
class DrawPoster {
|
||||
/** 构建器, 构建返回当前实例, 并挂载多个方法 */
|
||||
constructor(canvas, ctx, canvasId, loading, debugging, loadingText, createText, tips) {
|
||||
var _a;
|
||||
this.canvas = canvas;
|
||||
this.ctx = ctx;
|
||||
|
||||
this.canvasId = canvasId;
|
||||
this.loading = loading;
|
||||
this.debugging = debugging;
|
||||
this.loadingText = loadingText;
|
||||
this.createText = createText;
|
||||
this.executeOnions = [];
|
||||
this.stopStatus = false;
|
||||
/** 提示器, 传入消息与数据 */
|
||||
this.debuggingLog = (message, data, color = "#3489fd") => {
|
||||
if (this.debugging) {
|
||||
if (data) {
|
||||
console.log(`%c${this.canvasId} -> ${message}`, `color: ${color}`, data);
|
||||
}
|
||||
else {
|
||||
console.log(`%c${this.canvasId} -> ${message}`, `color: ${color}`);
|
||||
}
|
||||
}
|
||||
};
|
||||
/** 绘制器, 接收执行器函数, 添加到绘制容器中 */
|
||||
this.draw = (execute) => {
|
||||
const length = this.executeOnions.length;
|
||||
this.executeOnions.push(async () => {
|
||||
var _a, _b;
|
||||
try {
|
||||
this.ctx.save();
|
||||
await execute(this.ctx);
|
||||
this.ctx.restore();
|
||||
return true;
|
||||
}
|
||||
catch (error) {
|
||||
const isOutError = ((_b = (_a = error === null || error === void 0 ? void 0 : error.message) === null || _a === void 0 ? void 0 : _a.search) === null || _b === void 0 ? void 0 : _b.call(_a, `'nodeId' of undefined`)) >= 0;
|
||||
!isOutError && console.error(`${this.canvasId} -> 绘画栈(${length}),绘制错误:`, error);
|
||||
return false;
|
||||
}
|
||||
});
|
||||
};
|
||||
/** 等待创建绘画, 成功后清空绘制器容器 */
|
||||
this.awaitCreate = async () => {
|
||||
this.debuggingLog('绘制海报中...');
|
||||
this.loading && uni.showLoading({ title: this.loadingText });
|
||||
const tips = [];
|
||||
for (let i = 0; i < this.executeOnions.length; i++) {
|
||||
const execute = this.executeOnions[i];
|
||||
tips.push(await execute());
|
||||
}
|
||||
this.executeOnions = [];
|
||||
this.debuggingLog('绘制状况', tips);
|
||||
// 当前绘制为 type2 绘制
|
||||
if (this.drawType === 'type2d') {
|
||||
this.loading && uni.hideLoading();
|
||||
|
||||
return tips;
|
||||
}
|
||||
// 当前绘制为 context 绘制
|
||||
return await new Promise((resolve) => {
|
||||
this.ctx.draw(true, () => {
|
||||
resolve(tips);
|
||||
this.loading && uni.hideLoading();
|
||||
});
|
||||
});
|
||||
};
|
||||
/** 创建canvas本地地址 @returns {string} 本地地址 */
|
||||
this.createImagePath = async (baseOptions = {}) => {
|
||||
const { canvas, canvasId, executeOnions, awaitCreate } = this;
|
||||
executeOnions.length && await awaitCreate();
|
||||
// 如果当前为停止状态
|
||||
if (this.stopStatus) {
|
||||
this.stopStatus = false;
|
||||
return '---stop createImagePath---';
|
||||
}
|
||||
this.loading && uni.showLoading({ title: this.createText });
|
||||
const options = Object.assign({}, baseOptions);
|
||||
if (this.drawType === 'context')
|
||||
options.canvasId = canvasId;
|
||||
if (this.drawType === 'type2d')
|
||||
options.canvas = canvas;
|
||||
|
||||
console.log(options)
|
||||
return new Promise((resolve, reject) => {
|
||||
options.success = (res) => {
|
||||
resolve(res.tempFilePath);
|
||||
this.loading && uni.hideLoading();
|
||||
this.debuggingLog('绘制成功 🎉', res, '#19be6b');
|
||||
};
|
||||
options.fail = (err) => {
|
||||
reject(err);
|
||||
this.loading && uni.hideLoading();
|
||||
this.debuggingLog('绘制失败 🌟', err, '#fa3534');
|
||||
};
|
||||
uni.canvasToTempFilePath(options);
|
||||
});
|
||||
};
|
||||
/** 停止当前绘画, 调用则停止当前绘画堆栈的绘画 */
|
||||
this.stop = () => {
|
||||
this.executeOnions = [];
|
||||
this.stopStatus = true;
|
||||
};
|
||||
if (!canvas || !ctx || !canvasId) {
|
||||
throw new Error("DrawPoster Error: Use DrawPoster.build(string | ops) to build drawPoster instance objects");
|
||||
}
|
||||
// 判断当前绘制类型
|
||||
ctx.drawType = this.drawType = (ctx.draw) ? 'context' : 'type2d';
|
||||
// 挂载全局实例, 绘画扩展
|
||||
extendMount(this.ctx, drawCtxPosterExtend, (extend, target) => {
|
||||
var _a;
|
||||
(_a = target === null || target === void 0 ? void 0 : target.init) === null || _a === void 0 ? void 0 : _a.call(target, this.canvas, this.ctx);
|
||||
return (...args) => extend(this.canvas, this.ctx, ...args);
|
||||
});
|
||||
extendMount(this, drawPosterExtend, (extend, target) => {
|
||||
var _a;
|
||||
(_a = target === null || target === void 0 ? void 0 : target.init) === null || _a === void 0 ? void 0 : _a.call(target, this);
|
||||
return (...args) => extend(this, ...args);
|
||||
});
|
||||
// 当离开页面时, 自动调用停止绘画
|
||||
const _this = this;
|
||||
const pages = getCurrentPages();
|
||||
const page = pages[pages.length - 1];
|
||||
// 查询标识, 不存在, 在替换页面卸载回调, 避免产生死循环
|
||||
if (!((_a = page === null || page === void 0 ? void 0 : page.onUnload) === null || _a === void 0 ? void 0 : _a.identification)) {
|
||||
page.oldOnUnload = page.onUnload;
|
||||
page.onUnload = function () {
|
||||
_this === null || _this === void 0 ? void 0 : _this.stop();
|
||||
page.oldOnUnload();
|
||||
};
|
||||
page.onUnload.identification = true;
|
||||
}
|
||||
tips && this.debuggingLog('构建完成', { canvas, ctx, selector: canvasId }, '#19be6b');
|
||||
}
|
||||
}
|
||||
/** 传入挂载配置对象, 添加扩展方法 */
|
||||
DrawPoster.use = (opts) => {
|
||||
if (opts.name)
|
||||
drawPosterExtend[opts.name] = opts;
|
||||
};
|
||||
/** 传入挂载配置对象, 添加绘画扩展方法 */
|
||||
DrawPoster.useCtx = (opts) => {
|
||||
if (opts.name)
|
||||
drawCtxPosterExtend[opts.name] = opts;
|
||||
};
|
||||
/** 构建绘制海报矩形方法, 传入canvas选择器或配置对象, 返回绘制对象 */
|
||||
DrawPoster.build = async (options, tips = true) => {
|
||||
var _a, _b, _c, _d, _e;
|
||||
const config = handleBuildOpts(options);
|
||||
// 初始化监测当前页面绘制对象
|
||||
const pages = getCurrentPages();
|
||||
const page = pages[pages.length - 1];
|
||||
const gcanvas = DrawPoster.prototype['gcanvas'];
|
||||
if (page[config.selector + '__dp']) {
|
||||
return page[config.selector + '__dp'];
|
||||
}
|
||||
if (config.gcanvas) {
|
||||
if (!gcanvas)
|
||||
console.error('--- 当前未引入gcanvas扩展, 将自动切换为普通 canvas ---');
|
||||
else
|
||||
gcanvas.enable((_b = (_a = config.componentThis) === null || _a === void 0 ? void 0 : _a.$refs) === null || _b === void 0 ? void 0 : _b[config.selector], {
|
||||
bridge: gcanvas.WeexBridge
|
||||
});
|
||||
}
|
||||
// 获取canvas实例
|
||||
const canvas = config.gcanvas && gcanvas ?
|
||||
gcanvas.enable((_d = (_c = config.componentThis) === null || _c === void 0 ? void 0 : _c.$refs) === null || _d === void 0 ? void 0 : _d[config.selector], {
|
||||
bridge: gcanvas.WeexBridge
|
||||
}) :
|
||||
await getCanvas2dContext(config.selector, config.componentThis);
|
||||
const ctx = (((_e = canvas.getContext) === null || _e === void 0 ? void 0 : _e.call(canvas, "2d")) || uni.createCanvasContext(config.selector, config.componentThis));
|
||||
const dp = new DrawPoster(canvas, ctx, config.selector, config.loading, config.debugging, config.loadingText, config.createText, tips);
|
||||
// 储存当前绘制对象
|
||||
page[config.selector + '__dp'] = dp;
|
||||
return page[config.selector + '__dp'];
|
||||
};
|
||||
/** 构建多个绘制海报矩形方法, 传入选择器或配置对象的数组, 返回多个绘制对象 */
|
||||
DrawPoster.buildAll = async (optionsAll) => {
|
||||
const dpsArr = await Promise.all(optionsAll.map(async (options) => {
|
||||
return await DrawPoster.build(options, false);
|
||||
}));
|
||||
const dpsObj = {};
|
||||
dpsArr.forEach(dp => dpsObj[dp.canvasId] = dp);
|
||||
console.log("%cdraw-poster 构建完成:", "#E3712A", dpsObj);
|
||||
return dpsObj;
|
||||
};
|
||||
export default DrawPoster;
|
17
js_sdk/u-draw-poster/extends/create-from-list/index.d.ts
vendored
Normal file
@ -0,0 +1,17 @@
|
||||
import { DrawPosterUseOpts } from '../../utils/interface';
|
||||
export interface CreateLayerOpts {
|
||||
background?: string;
|
||||
self?: boolean;
|
||||
line?: boolean;
|
||||
lineHeight?: number;
|
||||
}
|
||||
export interface DrawRowOpt {
|
||||
text?: string;
|
||||
font?: string;
|
||||
color?: string;
|
||||
center?: boolean;
|
||||
width?: number;
|
||||
}
|
||||
declare const _default: DrawPosterUseOpts;
|
||||
/** 绘制表单扩展方法 */
|
||||
export default _default;
|
140
js_sdk/u-draw-poster/extends/create-from-list/index.js
Normal file
@ -0,0 +1,140 @@
|
||||
/** 绘制表单扩展方法 */
|
||||
export default {
|
||||
name: 'createLayer',
|
||||
init: (dp) => {
|
||||
dp.from = {
|
||||
height: 0,
|
||||
padding: 8,
|
||||
margin: 0
|
||||
};
|
||||
dp.setFromOptions = (opts) => {
|
||||
if (typeof opts.height !== 'undefined') {
|
||||
dp.from.height = opts.height;
|
||||
}
|
||||
if (typeof opts.margin !== 'undefined') {
|
||||
dp.from.margin = opts.margin;
|
||||
}
|
||||
if (typeof opts.padding !== 'undefined') {
|
||||
dp.from.padding = opts.padding;
|
||||
}
|
||||
};
|
||||
},
|
||||
handle: (dp, afferOpts, rowList) => {
|
||||
// 当前配置(头部偏移量, 列内边距, 表单外边距)
|
||||
const height = dp.from.height;
|
||||
const margin = dp.from.margin;
|
||||
const padding = dp.from.padding;
|
||||
// 当前层宽度
|
||||
const containerWidth = dp.canvas.width - (margin * 2);
|
||||
// 基本层配置
|
||||
const opts = Object.assign({ background: "#fff", columnY: height || margin, self: true, line: true, lineHeight: 0, border: true }, afferOpts);
|
||||
// 基本列配置
|
||||
const baseRowOpts = {
|
||||
text: "",
|
||||
font: "24px sans-serif",
|
||||
color: "#333",
|
||||
center: false,
|
||||
width: 0,
|
||||
};
|
||||
// 累计最高的列为标准定义为层高度
|
||||
let maxRowHeight = 0;
|
||||
// 累计固定栅格列偏移量
|
||||
let columnOffsetX = margin;
|
||||
// 创建行绘制任务
|
||||
const drawLayerInfos = rowList.map((afferRowOpts = {}, index) => {
|
||||
const rowOpts = Object.assign(Object.assign({}, baseRowOpts), afferRowOpts);
|
||||
let columnX = 0; // 每列的X轴
|
||||
let columnW = 0; // 每列的宽度
|
||||
let fontOffsetX = 0; // 字体偏移X轴
|
||||
let fontMaxWidth = 100; // 字体最大宽度
|
||||
opts.lineHeight = opts.lineHeight || Number(rowOpts.font.replace(/[^0-9.]/g, ""));
|
||||
if (opts.self) {
|
||||
// 自适应栅格格子计算
|
||||
columnX = containerWidth - (containerWidth / (index + 1)) + margin;
|
||||
columnW = containerWidth / rowList.length;
|
||||
if (columnX > 0 && columnX < containerWidth - columnW) {
|
||||
columnX = (columnW * index) + margin;
|
||||
}
|
||||
fontOffsetX = rowOpts.center ? columnX + (columnW / 2) : columnX + padding;
|
||||
fontMaxWidth = columnW - (padding * 3);
|
||||
}
|
||||
if (!opts.self) {
|
||||
// 固定栅格格子计算
|
||||
columnW = rowOpts.width;
|
||||
columnX = columnOffsetX;
|
||||
fontMaxWidth = columnW - (padding * 3);
|
||||
fontOffsetX = rowOpts.center ? columnOffsetX + (rowOpts.width / 2) : columnOffsetX + padding;
|
||||
columnOffsetX += rowOpts.width;
|
||||
}
|
||||
dp.ctx.font = rowOpts.font;
|
||||
const drawFontInfos = dp.ctx.fillWarpText({
|
||||
text: rowOpts.text,
|
||||
maxWidth: fontMaxWidth,
|
||||
lineHeight: opts.lineHeight,
|
||||
x: fontOffsetX,
|
||||
y: opts.columnY,
|
||||
layer: 10,
|
||||
notFillText: true
|
||||
});
|
||||
// 当前行的高度
|
||||
const rowHeight = (opts.lineHeight * drawFontInfos.length) + (padding * 3);
|
||||
// 若该列高度大于累计高度, 将累计高度替换
|
||||
if (rowHeight > maxRowHeight) {
|
||||
maxRowHeight = rowHeight;
|
||||
}
|
||||
return {
|
||||
font: rowOpts.font,
|
||||
center: rowOpts.center,
|
||||
color: rowOpts.color,
|
||||
border: opts.border,
|
||||
background: opts.background,
|
||||
lineHeight: opts.lineHeight,
|
||||
line: opts.line,
|
||||
drawFontInfos,
|
||||
columnY: opts.columnY,
|
||||
columnX,
|
||||
columnW,
|
||||
columnH: maxRowHeight,
|
||||
margin,
|
||||
padding
|
||||
};
|
||||
});
|
||||
// 将行绘制任务添加至绘制容器中
|
||||
dp.draw((ctx) => drawLayerInfos.forEach((rowOpts, index) => {
|
||||
ctx.font = rowOpts.font;
|
||||
ctx.fillStyle = rowOpts.background;
|
||||
ctx.strokeStyle = "#333";
|
||||
ctx.textBaseline = "middle";
|
||||
ctx.textAlign = 'left';
|
||||
if (rowOpts.center) {
|
||||
ctx.textAlign = "center";
|
||||
}
|
||||
ctx.fillRect(rowOpts.columnX, rowOpts.columnY, rowOpts.columnW, rowOpts.columnH);
|
||||
if (rowOpts.border) {
|
||||
dp.ctx.strokeRect(margin, rowOpts.columnY, dp.canvas.width - margin, maxRowHeight);
|
||||
}
|
||||
if (rowOpts.line && rowOpts.columnX !== margin) {
|
||||
ctx.moveTo(rowOpts.columnX, rowOpts.columnY);
|
||||
ctx.lineTo(rowOpts.columnX, rowOpts.columnY + rowOpts.columnH);
|
||||
ctx.stroke();
|
||||
ctx.beginPath();
|
||||
}
|
||||
ctx.fillStyle = rowOpts.color;
|
||||
rowOpts.drawFontInfos.forEach(fontInfo => {
|
||||
// 计算每行字体绘制y轴长度
|
||||
// y(当前列置顶轴) + (rowOpts.columnH(当前列最高长度) / 2) - (((总列数-1) * 行高) / 2)
|
||||
const textTotal = rowOpts.drawFontInfos.length - 1;
|
||||
const textMiddleY = (textTotal * rowOpts.lineHeight) / 2;
|
||||
let fontOffsetY = fontInfo.y + (rowOpts.columnH / 2);
|
||||
fontOffsetY -= textMiddleY;
|
||||
ctx.fillText(fontInfo.text, fontInfo.x, fontOffsetY);
|
||||
});
|
||||
}));
|
||||
if (opts.columnY === 0 || opts.columnY === margin) {
|
||||
maxRowHeight += margin;
|
||||
}
|
||||
// 叠加高度
|
||||
dp.from.height += maxRowHeight;
|
||||
return maxRowHeight;
|
||||
},
|
||||
};
|
4
js_sdk/u-draw-poster/extends/create-gcanvas/index.d.ts
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
import { DrawPosterUseOpts } from '../../utils/interface';
|
||||
export * from './gcanvas';
|
||||
declare const _default: DrawPosterUseOpts;
|
||||
export default _default;
|
9
js_sdk/u-draw-poster/extends/create-gcanvas/index.js
Normal file
@ -0,0 +1,9 @@
|
||||
import { WeexBridge, enable, Image } from './gcanvas';
|
||||
export * from './gcanvas';
|
||||
import DrawPoster from "../../draw-poster";
|
||||
DrawPoster.prototype['gcanvas'] = {
|
||||
WeexBridge,
|
||||
enable,
|
||||
Image
|
||||
};
|
||||
export default {};
|
12
js_sdk/u-draw-poster/extends/draw-function/draw-image-fit.d.ts
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
import { DrawPosterUseCtxOpts } from '../../utils/interface';
|
||||
import { ObjectFit, ObjectPosition, Size } from "../../utils/object-sizing";
|
||||
export interface ImageFitOption {
|
||||
radius?: number;
|
||||
objectFit?: ObjectFit;
|
||||
intrinsicSize?: Size;
|
||||
specifiedSize?: Size;
|
||||
intrinsicPosition?: ObjectPosition;
|
||||
specifiedPosition?: [number, number];
|
||||
}
|
||||
declare const _default: DrawPosterUseCtxOpts;
|
||||
export default _default;
|
25
js_sdk/u-draw-poster/extends/draw-function/draw-image-fit.js
Normal file
@ -0,0 +1,25 @@
|
||||
import { calculateConcreteRect } from "../../utils/object-sizing";
|
||||
import uni from "../../utils/global";
|
||||
export default {
|
||||
name: 'drawImageFit',
|
||||
handle: async (canvas, ctx, url, options) => {
|
||||
var _a, _b, _c;
|
||||
const [error, imageInfo] = await uni.getImageInfo({ src: url });
|
||||
// 配置默认值
|
||||
const style = Object.assign({ radius: 0, objectFit: 'cover', intrinsicSize: { width: (_a = imageInfo === null || imageInfo === void 0 ? void 0 : imageInfo.width) !== null && _a !== void 0 ? _a : 100, height: (_b = imageInfo === null || imageInfo === void 0 ? void 0 : imageInfo.height) !== null && _b !== void 0 ? _b : 100 }, specifiedSize: { width: 100, height: 100 }, intrinsicPosition: ['center', 'center'], specifiedPosition: [0, 0] }, options);
|
||||
// 计算图片尺寸
|
||||
const drawImageInfo = calculateConcreteRect(style, style.intrinsicSize, style.specifiedSize);
|
||||
// 如有圆角, 则进行裁剪
|
||||
if (style.radius > 0) {
|
||||
ctx.save();
|
||||
(_c = ctx.setFillStyle) === null || _c === void 0 ? void 0 : _c.call(ctx, 'transparent');
|
||||
ctx.fillStyle = 'transparent';
|
||||
ctx.fillRoundRect(style.specifiedPosition[0], style.specifiedPosition[1], style.specifiedSize.width, style.specifiedSize.height, style.radius);
|
||||
ctx.clip();
|
||||
}
|
||||
const result = await ctx.drawImage(url, ...Object.values(drawImageInfo));
|
||||
if (style.radius > 0)
|
||||
ctx.restore();
|
||||
return result;
|
||||
}
|
||||
};
|
4
js_sdk/u-draw-poster/extends/draw-function/draw-image.d.ts
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
import { DrawPosterUseCtxOpts } from '../../utils/interface';
|
||||
declare const _default: DrawPosterUseCtxOpts;
|
||||
/** 等待绘制图片原型方法 */
|
||||
export default _default;
|
42
js_sdk/u-draw-poster/extends/draw-function/draw-image.js
Normal file
@ -0,0 +1,42 @@
|
||||
import { downloadImgUrl } from '../../utils/wx-utils';
|
||||
/** 等待绘制图片原型方法 */
|
||||
export default {
|
||||
name: 'drawImage',
|
||||
init: (canvas, ctx) => {
|
||||
ctx.drawImageProto = ctx.drawImage;
|
||||
},
|
||||
handle: async (canvas, ctx, url, sx, sy, sh, sw, dx, dy, dh, dw) => {
|
||||
// 下载路径
|
||||
const path = await downloadImgUrl(url);
|
||||
// 标记当前绘画存在图片绘制
|
||||
let result = false;
|
||||
// 基本绘制方法, 如果是 fit 方式, 则传入所有参数, 不然则只传入四个参数
|
||||
const baseDrawImage = (imageResource) => {
|
||||
const isFit = typeof dx === 'number' && typeof dw === 'number';
|
||||
if (isFit) {
|
||||
ctx.drawImageProto(imageResource, sx, sy, sh, sw, dx, dy, dh, dw);
|
||||
}
|
||||
else {
|
||||
ctx.drawImageProto(imageResource, sx, sy, sh, sw);
|
||||
}
|
||||
};
|
||||
// 如果是 context 绘制方式, 则直接绘制
|
||||
if (ctx.drawType === 'context') {
|
||||
baseDrawImage(path);
|
||||
result = true;
|
||||
}
|
||||
// 如果是 type2d 绘制方式, 则等待图片绘制完毕
|
||||
if (ctx.drawType === 'type2d') {
|
||||
result = await new Promise(resolve => {
|
||||
const image = canvas.createImage();
|
||||
image.src = path;
|
||||
image.onload = () => {
|
||||
baseDrawImage(image);
|
||||
resolve(true);
|
||||
};
|
||||
image.onerror = () => resolve(false);
|
||||
});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
};
|
4
js_sdk/u-draw-poster/extends/draw-function/draw-round-image.d.ts
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
import { DrawPosterUseCtxOpts } from '../../utils/interface';
|
||||
declare const _default: DrawPosterUseCtxOpts;
|
||||
/** 绘制圆角图片原型方法 */
|
||||
export default _default;
|
@ -0,0 +1,15 @@
|
||||
/** 绘制圆角图片原型方法 */
|
||||
export default {
|
||||
name: 'drawRoundImage',
|
||||
handle: async (canvas, ctx, url, x, y, w, h, r = 15) => {
|
||||
var _a;
|
||||
ctx.save();
|
||||
(_a = ctx.setFillStyle) === null || _a === void 0 ? void 0 : _a.call(ctx, 'transparent');
|
||||
ctx.fillStyle = 'transparent';
|
||||
ctx.fillRoundRect(x, y, w, h, r);
|
||||
ctx.clip();
|
||||
const result = await ctx.drawImage(url, x, y, w, h);
|
||||
ctx.restore();
|
||||
return result;
|
||||
}
|
||||
};
|
4
js_sdk/u-draw-poster/extends/draw-function/fill-round-rect.d.ts
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
import { DrawPosterUseCtxOpts } from '../../utils/interface';
|
||||
declare const _default: DrawPosterUseCtxOpts;
|
||||
/** 绘制填充圆角矩形方法 */
|
||||
export default _default;
|
@ -0,0 +1,7 @@
|
||||
/** 绘制填充圆角矩形方法 */
|
||||
export default {
|
||||
name: 'fillRoundRect',
|
||||
handle: (canvas, ctx, x, y, w, h, r) => {
|
||||
ctx.roundRect(x, y, w, h, r, true);
|
||||
}
|
||||
};
|
4
js_sdk/u-draw-poster/extends/draw-function/fill-warp-text.d.ts
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
import { DrawPosterUseCtxOpts } from '../../utils/interface';
|
||||
declare const _default: DrawPosterUseCtxOpts;
|
||||
/** 绘制换行字体原型方法 */
|
||||
export default _default;
|
76
js_sdk/u-draw-poster/extends/draw-function/fill-warp-text.js
Normal file
@ -0,0 +1,76 @@
|
||||
/** 绘制换行字体原型方法 */
|
||||
export default {
|
||||
name: 'fillWarpText',
|
||||
handle: (canvas, ctx, config) => {
|
||||
const newConfig = config = Object.assign({ maxWidth: 100, layer: 2, lineHeight: Number(ctx.font.replace(/[^0-9.]/g, '')), x: 0, y: Number(ctx.font.replace(/[^0-9.]/g, '')) / 1.2, splitText: '', notFillText: false }, config);
|
||||
const { text, splitText, maxWidth, layer, lineHeight, notFillText, x, y } = newConfig;
|
||||
// 当字符串为空时, 抛出错误
|
||||
if (!text) {
|
||||
throw Error('warpFillText Error: text is empty string');
|
||||
}
|
||||
// 分割所有单个字符串
|
||||
const chr = text.split(splitText);
|
||||
// 存入的每行字体的容器
|
||||
let row = [];
|
||||
// 判断字符串
|
||||
let timp = '';
|
||||
if (splitText) {
|
||||
row = chr;
|
||||
}
|
||||
else {
|
||||
// 遍历所有字符串, 填充行容器
|
||||
for (let i = 0; i < chr.length; i++) {
|
||||
// 当超出行列时, 停止执行遍历, 节省计算时间
|
||||
if (row.length > layer) {
|
||||
break;
|
||||
}
|
||||
if (ctx.measureText(timp).width < maxWidth) {
|
||||
// 如果超出长度, 添加进row数组
|
||||
timp += chr[i];
|
||||
}
|
||||
else {
|
||||
// 如超出一行长度, 则换行, 并清除容器
|
||||
i--;
|
||||
row.push(timp);
|
||||
timp = '';
|
||||
}
|
||||
}
|
||||
// 如有剩下字体, 则在最后时添加一行
|
||||
if (timp) {
|
||||
row.push(timp);
|
||||
}
|
||||
// 如果数组长度大于指定行数
|
||||
if (row.length > layer) {
|
||||
row = row.slice(0, layer);
|
||||
// 结束的索引
|
||||
const end = layer - 1;
|
||||
for (let i = 0; i < row[end].length; i++) {
|
||||
const currentWidth = ctx.measureText(`${row[end]}...`).width;
|
||||
if (currentWidth > maxWidth) {
|
||||
// 加上... 当前宽度大于最大宽度时, 去除一位字符串
|
||||
const strEnd = row[end].length - 1;
|
||||
row[end] = row[end].slice(0, strEnd);
|
||||
}
|
||||
else {
|
||||
row[end] += '...';
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// 储存并返回绘制信息
|
||||
const drawInfos = row.map((item, index) => {
|
||||
const info = {
|
||||
text: item,
|
||||
y: y + index * lineHeight,
|
||||
x: x,
|
||||
};
|
||||
// 默认执行绘制信息
|
||||
if (!notFillText) {
|
||||
ctx.fillText(info.text, info.x, info.y);
|
||||
}
|
||||
return info;
|
||||
});
|
||||
return drawInfos;
|
||||
}
|
||||
};
|
7
js_sdk/u-draw-poster/extends/draw-function/index.d.ts
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
export { default as drawImage } from "./draw-image";
|
||||
export { default as roundRect } from "./round-rect";
|
||||
export { default as fillRoundRect } from "./fill-round-rect";
|
||||
export { default as strokeRoundRect } from "./stroke-round-rect";
|
||||
export { default as fillWarpText } from "./fill-warp-text";
|
||||
export { default as drawRoundImage } from "./draw-round-image";
|
||||
export { default as drawImageFit } from "./draw-image-fit";
|
15
js_sdk/u-draw-poster/extends/draw-function/index.js
Normal file
@ -0,0 +1,15 @@
|
||||
/*
|
||||
* @Author: Mr.Mao
|
||||
* @LastEditors: Mr.Mao
|
||||
* @Date: 2020-11-11 20:43:33
|
||||
* @LastEditTime: 2021-01-02 00:16:59
|
||||
* @Description:
|
||||
* @任何一个傻子都能写出让电脑能懂的代码,而只有好的程序员可以写出让人能看懂的代码
|
||||
*/
|
||||
export { default as drawImage } from "./draw-image";
|
||||
export { default as roundRect } from "./round-rect";
|
||||
export { default as fillRoundRect } from "./fill-round-rect";
|
||||
export { default as strokeRoundRect } from "./stroke-round-rect";
|
||||
export { default as fillWarpText } from "./fill-warp-text";
|
||||
export { default as drawRoundImage } from "./draw-round-image";
|
||||
export { default as drawImageFit } from "./draw-image-fit";
|
4
js_sdk/u-draw-poster/extends/draw-function/round-rect.d.ts
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
import { DrawPosterUseCtxOpts } from '../../utils/interface';
|
||||
declare const _default: DrawPosterUseCtxOpts;
|
||||
/** 绘制圆角矩形原型方法 */
|
||||
export default _default;
|
41
js_sdk/u-draw-poster/extends/draw-function/round-rect.js
Normal file
@ -0,0 +1,41 @@
|
||||
/** 绘制圆角矩形原型方法 */
|
||||
export default {
|
||||
name: 'roundRect',
|
||||
handle: (canvas, ctx, x, y, w, h, r = 15, fill = false, stroke = false) => {
|
||||
if (r === 0) {
|
||||
if (stroke)
|
||||
ctx.strokeRect(x, y, w, h);
|
||||
if (fill)
|
||||
ctx.fillRect(x, y, w, h);
|
||||
return;
|
||||
}
|
||||
if (w < 2 * r) {
|
||||
r = w / 2;
|
||||
}
|
||||
if (h < 2 * r) {
|
||||
r = h / 2;
|
||||
}
|
||||
// 开始绘制
|
||||
ctx.beginPath();
|
||||
ctx.arc(x + r, y + r, r, Math.PI, Math.PI * 1.5);
|
||||
// 移动复制
|
||||
ctx.moveTo(x + r, y);
|
||||
ctx.lineTo(x + w - r, y);
|
||||
ctx.lineTo(x + w, y + r);
|
||||
// (x,y,z,j,f) x,y圆心z半径,j起始弧度f,终止弧度
|
||||
ctx.arc(x + w - r, y + r, r, Math.PI * 1.5, Math.PI * 2);
|
||||
ctx.lineTo(x + w, y + h - r);
|
||||
ctx.lineTo(x + w - r, y + h);
|
||||
ctx.arc(x + w - r, y + h - r, r, 0, Math.PI * 0.5);
|
||||
ctx.lineTo(x + r, y + h);
|
||||
ctx.lineTo(x, y + h - r);
|
||||
ctx.arc(x + r, y + h - r, r, Math.PI * 0.5, Math.PI);
|
||||
ctx.lineTo(x, y + r);
|
||||
ctx.lineTo(x + r, y);
|
||||
if (stroke)
|
||||
ctx.stroke();
|
||||
if (fill)
|
||||
ctx.fill();
|
||||
ctx.closePath();
|
||||
}
|
||||
};
|
4
js_sdk/u-draw-poster/extends/draw-function/stroke-round-rect.d.ts
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
import { DrawPosterUseCtxOpts } from '../../utils/interface';
|
||||
declare const _default: DrawPosterUseCtxOpts;
|
||||
/** 绘制填充圆角矩形方法 */
|
||||
export default _default;
|
@ -0,0 +1,7 @@
|
||||
/** 绘制填充圆角矩形方法 */
|
||||
export default {
|
||||
name: 'strokeRoundRect',
|
||||
handle: (canvas, ctx, x, y, w, h, r) => {
|
||||
ctx.roundRect(x, y, w, h, r, false, true);
|
||||
}
|
||||
};
|
101
js_sdk/u-draw-poster/extends/draw-painter/index.d.ts
vendored
Normal file
@ -0,0 +1,101 @@
|
||||
import { DrawPosterUseOpts } from '../../utils/interface';
|
||||
import { ImageFitOption } from '../draw-function/draw-image-fit';
|
||||
/** 矩形基本信息 */
|
||||
interface PainterItemSize {
|
||||
/** 容器的宽度,固定值 */
|
||||
width: number;
|
||||
/** 容器的高度,固定值 */
|
||||
height: number;
|
||||
}
|
||||
/** 元素位置信息 */
|
||||
interface PainterItemSite {
|
||||
/** 元素锚点距左边的距离; 默认: 0 */
|
||||
left?: number;
|
||||
/** 元素锚点距上边的距离; 默认: 0 */
|
||||
top?: number;
|
||||
}
|
||||
/** 绘制图片信息 */
|
||||
interface PainterImageInfo extends PainterItemSize, PainterItemSite {
|
||||
/** 绘制图片元素 */
|
||||
type: 'image';
|
||||
/** 图片地址 */
|
||||
src: string;
|
||||
/** 图片自适应, 可参考 css 属性 object-fit */
|
||||
objectFit?: ImageFitOption['objectFit'];
|
||||
/** 图片在元素容器中显示的位置,可参考 css 属性 object-position */
|
||||
position?: ImageFitOption['intrinsicPosition'];
|
||||
/** 圆角尺寸; 默认: 0 */
|
||||
radius?: number;
|
||||
}
|
||||
/** 绘制矩形信息 */
|
||||
interface PainterRectInfo extends PainterItemSize, PainterItemSite {
|
||||
/** 绘制矩形元素 */
|
||||
type: "rect";
|
||||
/** 矩形背景颜色; 默认: "#000" */
|
||||
background?: string;
|
||||
/** 圆角尺寸; 默认: 0 */
|
||||
radius?: number;
|
||||
}
|
||||
/** 绘制单行文字信息 */
|
||||
interface PainterTextInfo extends PainterItemSite {
|
||||
/** 绘制文本元素 */
|
||||
type: "text";
|
||||
/** 文本颜色; 默认: "#000" */
|
||||
color?: string;
|
||||
/** 字体; 默认: "serial" */
|
||||
fontFamily?: string;
|
||||
/** 字号(单位rpx); 默认: 30 rpx */
|
||||
fontSize?: number;
|
||||
/** 字重; 默认: "normal" 可选项: "bold" */
|
||||
fontWeight?: string;
|
||||
/** 字型 默认: "normal" 可选项: "italic" */
|
||||
fontStyle?: string;
|
||||
/** 元素的宽度(单位rpx), 水平排布时影响后一个元素的位置,为 null 时根据文字实际占用的宽度计算 */
|
||||
width?: number;
|
||||
/** 文本内容 */
|
||||
content: string;
|
||||
}
|
||||
/** 绘制多行文字信息 */
|
||||
interface PainterLineFeedTextInfo extends PainterItemSite {
|
||||
/** 绘制换行文本元素 */
|
||||
type: "line-feed-text";
|
||||
/** 文本颜色; 默认: "#000" */
|
||||
color?: string;
|
||||
/** 字体; 默认: "serial" */
|
||||
fontFamily?: string;
|
||||
/** 字号(单位rpx); 默认: 30 rpx */
|
||||
fontSize?: number;
|
||||
/** 字重; 默认: "normal" 可选项: "bold" */
|
||||
fontWeight?: string;
|
||||
/** 字型 默认: "normal" 可选项: "italic" */
|
||||
fontStyle?: string;
|
||||
/** 文本块的宽度,不能为空 */
|
||||
width: number;
|
||||
/** 行高; 默认取当前文字行高 */
|
||||
lineHeight?: number;
|
||||
/** 文本最大行数,超出即显示省略号; 默认3行 */
|
||||
lineClamp?: number;
|
||||
/** 文本内容 */
|
||||
content: string;
|
||||
}
|
||||
/** 绘制二维码信息 */
|
||||
interface PainterQrCodeInfo extends PainterItemSite {
|
||||
/** 绘制换行文本元素 */
|
||||
type: "qr-code";
|
||||
/** 二维码尺寸 */
|
||||
size: number;
|
||||
/** 二维码内容 */
|
||||
content: string;
|
||||
/** 边距,二维码实际尺寸会根据所设边距值进行缩放调整(默认:5) */
|
||||
margin?: number;
|
||||
/** 背景色(默认:'#ffffff')*/
|
||||
backgroundColor?: string;
|
||||
/** 前景色(默认:'#000000') */
|
||||
foregroundColor?: string;
|
||||
}
|
||||
export interface PainterContainerOption extends PainterItemSize {
|
||||
/** 绘制项的数组 */
|
||||
contents: Array<PainterImageInfo | PainterRectInfo | PainterTextInfo | PainterLineFeedTextInfo | PainterQrCodeInfo>;
|
||||
}
|
||||
declare const _default: DrawPosterUseOpts;
|
||||
export default _default;
|
73
js_sdk/u-draw-poster/extends/draw-painter/index.js
Normal file
@ -0,0 +1,73 @@
|
||||
export default {
|
||||
name: 'painter',
|
||||
handle: (dp, option) => {
|
||||
dp.canvas.width = option.width;
|
||||
dp.canvas.height = option.height;
|
||||
dp.draw(async (ctx) => {
|
||||
for (let i = 0; i < option.contents.length; i++) {
|
||||
ctx.save();
|
||||
const drawInfo = option.contents[i];
|
||||
const { left = 0, top = 0 } = drawInfo;
|
||||
if (drawInfo.type === 'rect') {
|
||||
ctx.fillStyle = drawInfo.background || '#000000';
|
||||
ctx.fillRoundRect(left, top, drawInfo.width, drawInfo.height, drawInfo.radius || 0);
|
||||
}
|
||||
if (drawInfo.type === 'image') {
|
||||
await ctx.drawImageFit(drawInfo.src, {
|
||||
objectFit: drawInfo.objectFit || 'cover',
|
||||
intrinsicPosition: drawInfo.position || ['center', 'center'],
|
||||
specifiedPosition: [left, top],
|
||||
specifiedSize: {
|
||||
width: drawInfo.width,
|
||||
height: drawInfo.height
|
||||
},
|
||||
radius: drawInfo.radius
|
||||
});
|
||||
}
|
||||
if (drawInfo.type === 'text') {
|
||||
ctx.fillStyle = drawInfo.color || '#000000';
|
||||
ctx.font = `\
|
||||
${drawInfo.fontStyle || 'normal'} \
|
||||
${drawInfo.fontWeight || 'normal'} \
|
||||
${drawInfo.fontSize || 30} \
|
||||
${drawInfo.fontFamily || 'serial'}\
|
||||
`;
|
||||
ctx.fillText(drawInfo.content, left, top, drawInfo.width);
|
||||
}
|
||||
if (drawInfo.type === 'line-feed-text') {
|
||||
ctx.fillStyle = drawInfo.color || '#000000';
|
||||
ctx.font = `\
|
||||
${drawInfo.fontStyle || 'normal'} \
|
||||
${drawInfo.fontWeight || 'normal'} \
|
||||
${drawInfo.fontSize || 30} \
|
||||
${drawInfo.fontFamily || 'serial'}\
|
||||
`;
|
||||
ctx.fillWarpText({
|
||||
x: drawInfo.left,
|
||||
y: drawInfo.top,
|
||||
layer: drawInfo.lineClamp,
|
||||
lineHeight: drawInfo.lineHeight,
|
||||
maxWidth: drawInfo.width,
|
||||
text: drawInfo.content
|
||||
});
|
||||
}
|
||||
if (drawInfo.type === 'qr-code') {
|
||||
if (typeof ctx.drawQrCode !== 'function') {
|
||||
console.error('--- 当前未引入qr-code扩展, 将自动省略该二维码绘制 ---');
|
||||
return false;
|
||||
}
|
||||
ctx.drawQrCode({
|
||||
x: left,
|
||||
y: top,
|
||||
size: drawInfo.size,
|
||||
text: drawInfo.content,
|
||||
margin: drawInfo.margin || 5,
|
||||
backgroundColor: drawInfo.backgroundColor || '#ffffff',
|
||||
foregroundColor: drawInfo.foregroundColor || '#000000',
|
||||
});
|
||||
}
|
||||
ctx.restore();
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
6
js_sdk/u-draw-poster/extends/draw-qr-code/index.d.ts
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
declare const _default: {
|
||||
name: string;
|
||||
handle: any;
|
||||
errorCorrectLevel: any;
|
||||
};
|
||||
export default _default;
|
6
js_sdk/u-draw-poster/extends/draw-qr-code/index.js
Normal file
@ -0,0 +1,6 @@
|
||||
import uQRCode from "./uQRCode";
|
||||
export default {
|
||||
name: "drawQrCode",
|
||||
handle: uQRCode.make.bind(uQRCode),
|
||||
errorCorrectLevel: uQRCode.errorCorrectLevel
|
||||
};
|
10
js_sdk/u-draw-poster/extends/draw-qr-code/uQRCode.d.ts
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
/*
|
||||
* @Author: Mr.Mao
|
||||
* @LastEditors: Mr.Mao
|
||||
* @Date: 2021-01-02 13:30:58
|
||||
* @LastEditTime: 2021-01-02 13:31:27
|
||||
* @Description:
|
||||
* @任何一个傻子都能写出让电脑能懂的代码,而只有好的程序员可以写出让人能看懂的代码
|
||||
*/
|
||||
declare const uQRCode: any
|
||||
export default uQRCode
|
1355
js_sdk/u-draw-poster/extends/draw-qr-code/uQRCode.js
Normal file
11
js_sdk/u-draw-poster/index.d.ts
vendored
Normal file
@ -0,0 +1,11 @@
|
||||
import { DrawPosterBuildOpts } from "./utils/interface";
|
||||
import DrawPoster from "./draw-poster";
|
||||
import drawQrCode from "./extends/draw-qr-code/index";
|
||||
import createFromList from './extends/create-from-list/index';
|
||||
import drawPainter from './extends/draw-painter/index';
|
||||
declare const useDrawPoster: (options: string | DrawPosterBuildOpts) => Promise<DrawPoster & import("./utils/interface").drawPosterExtends>;
|
||||
declare const useDrawPosters: (optionsAll: (string | DrawPosterBuildOpts)[]) => Promise<{
|
||||
[key: string]: DrawPoster & import("./utils/interface").drawPosterExtends;
|
||||
}>;
|
||||
export { DrawPoster, useDrawPoster, useDrawPosters, drawQrCode, drawPainter, createFromList };
|
||||
export default DrawPoster;
|
22
js_sdk/u-draw-poster/index.js
Normal file
@ -0,0 +1,22 @@
|
||||
import * as dfucs from "./extends/draw-function/index";
|
||||
import DrawPoster from "./draw-poster";
|
||||
import drawQrCode from "./extends/draw-qr-code/index";
|
||||
import createFromList from './extends/create-from-list/index';
|
||||
import drawPainter from './extends/draw-painter/index';
|
||||
DrawPoster.useCtx(dfucs.drawImage);
|
||||
DrawPoster.useCtx(dfucs.fillWarpText);
|
||||
DrawPoster.useCtx(dfucs.roundRect);
|
||||
DrawPoster.useCtx(dfucs.fillRoundRect);
|
||||
DrawPoster.useCtx(dfucs.strokeRoundRect);
|
||||
DrawPoster.useCtx(dfucs.drawRoundImage);
|
||||
DrawPoster.useCtx(dfucs.drawImageFit);
|
||||
const useDrawPoster = async (options) => {
|
||||
const dp = await DrawPoster.build(options);
|
||||
return dp;
|
||||
};
|
||||
const useDrawPosters = async (optionsAll) => {
|
||||
const dps = await DrawPoster.buildAll(optionsAll);
|
||||
return dps;
|
||||
};
|
||||
export { DrawPoster, useDrawPoster, useDrawPosters, drawQrCode, drawPainter, createFromList };
|
||||
export default DrawPoster;
|
13
js_sdk/u-draw-poster/package.json
Normal file
@ -0,0 +1,13 @@
|
||||
{
|
||||
"id": "u-draw-poster",
|
||||
"name": "u-draw-poster uniVue2|3适用 海报绘制工具",
|
||||
"version": "1.1.5",
|
||||
"description": "全端支持,内置多种海报绘制方法、表单绘制、二维码生成,图片裁剪。原生开发体验,上手快,不污染组件数据",
|
||||
"keywords": [
|
||||
"海报",
|
||||
"绘制",
|
||||
"分享",
|
||||
"小程序",
|
||||
"canvas"
|
||||
]
|
||||
}
|
7
js_sdk/u-draw-poster/utils/global.d.ts
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
/// <reference types="@dcloudio/types" />
|
||||
/** 当前环境类型 */
|
||||
export declare type UniPlatforms = 'app-plus' | 'app-plus-nvue' | 'h5' | 'mp-weixin' | 'mp-alipay' | 'mp-baidu' | 'mp-toutiao' | 'mp-qq' | 'mp-360' | 'mp' | 'quickapp-webview' | 'quickapp-webview-union' | 'quickapp-webview-huawei' | undefined;
|
||||
export declare const PLATFORM: UniPlatforms;
|
||||
/** 全局对象 */
|
||||
declare const _uni: UniApp.Uni;
|
||||
export default _uni;
|
11
js_sdk/u-draw-poster/utils/global.js
Normal file
@ -0,0 +1,11 @@
|
||||
var _a;
|
||||
export const PLATFORM = typeof process !== 'undefined' ? (_a = process === null || process === void 0 ? void 0 : process.env) === null || _a === void 0 ? void 0 : _a.VUE_APP_PLATFORM : undefined;
|
||||
/** 全局对象 */
|
||||
const _uni = (function () {
|
||||
if (typeof uni != "undefined")
|
||||
return uni;
|
||||
if (typeof wx != "undefined")
|
||||
return wx;
|
||||
return uni;
|
||||
})();
|
||||
export default _uni;
|
175
js_sdk/u-draw-poster/utils/interface.d.ts
vendored
Normal file
@ -0,0 +1,175 @@
|
||||
/// <reference types="@dcloudio/types" />
|
||||
import DrawPoster from "../draw-poster";
|
||||
import { ImageFitOption } from '../extends/draw-function/draw-image-fit';
|
||||
import { CreateLayerOpts, DrawRowOpt } from "../extends/create-from-list";
|
||||
import { PainterContainerOption } from "../extends/draw-painter";
|
||||
/** 绘制容器 */
|
||||
export declare type Execute = Array<() => Promise<boolean>>;
|
||||
export interface drawPosterExtends {
|
||||
from: {
|
||||
height: number;
|
||||
padding: number;
|
||||
margin: number;
|
||||
};
|
||||
createLayer: (afferOpts: CreateLayerOpts, rowList: DrawRowOpt[]) => number;
|
||||
setFromOptions: (opts: Partial<{
|
||||
height: number;
|
||||
padding: number;
|
||||
margin: number;
|
||||
}>) => void;
|
||||
gcanvas: {
|
||||
WeexBridge: any;
|
||||
Image: any;
|
||||
enable: (el: any, options: {
|
||||
bridge?: any;
|
||||
debug?: boolean;
|
||||
disableAutoSwap?: any;
|
||||
disableComboCommands?: any;
|
||||
}) => Canvas;
|
||||
};
|
||||
painter: (option: PainterContainerOption) => void;
|
||||
}
|
||||
/** 构建器配置 */
|
||||
export interface DrawPosterBuildOpts {
|
||||
/** 查询选择器; 注意不需要加# */
|
||||
selector: string;
|
||||
/** 选取组件范围 */
|
||||
componentThis?: any;
|
||||
/** 绘制类型为2d绘制, 默认开启, 在微信小程序的时候动态加载 */
|
||||
type2d?: boolean;
|
||||
/** 是否在绘制时进行加载提示 */
|
||||
loading?: boolean;
|
||||
/** 当存在绘制图片时, 等待绘画完毕的时间(秒)仅App中生效
|
||||
*
|
||||
* 具体查看文档说明:https://github.com/TuiMao233/uni-draw-poster
|
||||
*/
|
||||
drawImageTime?: number;
|
||||
/** 是否开启调试模式 */
|
||||
debugging?: boolean;
|
||||
/** 加载提示文字 */
|
||||
loadingText?: string;
|
||||
/** 创建图片提示文字 */
|
||||
createText?: string;
|
||||
/** 是否启动gcanvas(nvue) */
|
||||
gcanvas?: boolean;
|
||||
}
|
||||
/** 绘制换行配置 */
|
||||
export interface FillWarpTextOpts {
|
||||
text: string;
|
||||
maxWidth?: number;
|
||||
lineHeight?: number;
|
||||
layer?: number;
|
||||
x?: number;
|
||||
y?: number;
|
||||
splitText?: string;
|
||||
notFillText?: boolean;
|
||||
}
|
||||
/** 绘制二维码配置 */
|
||||
export interface DrawQrCodeOpts {
|
||||
text: string;
|
||||
x?: number;
|
||||
y?: number;
|
||||
size?: number;
|
||||
margin?: number;
|
||||
backgroundColor?: string;
|
||||
foregroundColor?: string;
|
||||
}
|
||||
/** 绘制换行, 单行信息 */
|
||||
export interface FillWarpTextItemInfo {
|
||||
text: string;
|
||||
y: number;
|
||||
x: number;
|
||||
}
|
||||
/** 绘制画笔 */
|
||||
export interface DrawPosterCanvasCtx extends UniApp.CanvasContext {
|
||||
[key: string]: any;
|
||||
createImageData: () => ImageData;
|
||||
textAlign: CanvasTextDrawingStyles["textAlign"];
|
||||
textBaseline: CanvasTextDrawingStyles["textBaseline"];
|
||||
transform: CanvasTransform["transform"];
|
||||
/** 绘制图片原型 */
|
||||
drawImageProto: UniApp.CanvasContext['drawImage'];
|
||||
/** 当前绘制类型 */
|
||||
drawType: 'context' | 'type2d';
|
||||
/** 等待绘制图片
|
||||
*
|
||||
* 说明文档: https://tuimao233.gitee.io/mao-blog/my-extends/u-draw-poste
|
||||
*/
|
||||
drawImage(url: string, dx?: number | undefined, dy?: number | undefined, dWidth?: number | undefined, dHeigt?: number | undefined, sx?: number | undefined, sy?: number | undefined, sWidth?: number | undefined, sHeight?: number | undefined): Promise<boolean>;
|
||||
/** 绘制圆角图片
|
||||
*
|
||||
* 说明文档: https://tuimao233.gitee.io/mao-blog/my-extends/u-draw-poste
|
||||
*/
|
||||
drawRoundImage(url: string, x: number, y: number, w: number, h: number, r?: number): Promise<boolean>;
|
||||
/** 绘制 Object-Fit 模式图片
|
||||
*
|
||||
* 说明文档: https://tuimao233.gitee.io/mao-blog/my-extends/u-draw-poste
|
||||
*/
|
||||
drawImageFit(url: string, opts?: ImageFitOption): Promise<boolean>;
|
||||
/** 绘制换行字体
|
||||
*
|
||||
* 说明文档: https://tuimao233.gitee.io/mao-blog/my-extends/u-draw-poste
|
||||
*/
|
||||
fillWarpText(options: FillWarpTextOpts): Array<FillWarpTextItemInfo>;
|
||||
/** 绘制圆角矩形(原型)
|
||||
*
|
||||
*/
|
||||
roundRect(x: number, y: number, w: number, h: number, r: number, fill?: boolean, stroke?: boolean): void;
|
||||
/** 绘制圆角矩形(填充)
|
||||
*
|
||||
* 说明文档: https://tuimao233.gitee.io/mao-blog/my-extends/u-draw-poste
|
||||
*/
|
||||
fillRoundRect(x: number, y: number, w: number, h: number, r: number): void;
|
||||
/** 绘制圆角矩形(边框)
|
||||
*
|
||||
* 说明文档: https://tuimao233.gitee.io/mao-blog/my-extends/u-draw-poste
|
||||
*/
|
||||
strokeRoundRect(x: number, y: number, w: number, h: number, r: number): void;
|
||||
/** 绘制二维码
|
||||
*
|
||||
* 说明文档: https://tuimao233.gitee.io/mao-blog/my-extends/u-draw-poste
|
||||
*/
|
||||
drawQrCode(options: DrawQrCodeOpts): void;
|
||||
}
|
||||
/** Canvas2d实例 */
|
||||
export interface Canvas {
|
||||
width: number;
|
||||
height: number;
|
||||
getContext(contextType: "2d" | "webgl"): DrawPosterCanvasCtx | WebGLRenderingContext;
|
||||
createImage(): {
|
||||
src: string;
|
||||
width: number;
|
||||
height: number;
|
||||
onload: () => void;
|
||||
onerror: () => void;
|
||||
};
|
||||
requestAnimationFrame(callback: Function): number;
|
||||
cancelAnimationFrame(requestID: number): void;
|
||||
createImageData(): ImageData;
|
||||
createPath2D(path: Path2D): Path2D;
|
||||
toDataURL(type: string, encoderOptions: number): string;
|
||||
}
|
||||
/** 创建图片路径配置项 */
|
||||
export interface CreateImagePathOptions {
|
||||
x?: number;
|
||||
y?: number;
|
||||
width?: number;
|
||||
height?: number;
|
||||
destWidth?: number;
|
||||
destHeight?: number;
|
||||
}
|
||||
/** 绘制实例扩展配置 */
|
||||
export interface DrawPosterUseOpts {
|
||||
name: string;
|
||||
init?: (dp: InstanceType<typeof DrawPoster>) => void;
|
||||
handle: (dp: InstanceType<typeof DrawPoster>, ...args: any[]) => any;
|
||||
createImage?: (dp: InstanceType<typeof DrawPoster>) => void;
|
||||
[key: string]: any;
|
||||
}
|
||||
/** 绘制画笔实例扩展配置 */
|
||||
export interface DrawPosterUseCtxOpts {
|
||||
name: string;
|
||||
init?: (canvas: Canvas, ctx: DrawPosterCanvasCtx) => void;
|
||||
handle: (canvas: Canvas, ctx: DrawPosterCanvasCtx, ...args: any[]) => any;
|
||||
[key: string]: any;
|
||||
}
|
1
js_sdk/u-draw-poster/utils/interface.js
Normal file
@ -0,0 +1 @@
|
||||
export {};
|
38
js_sdk/u-draw-poster/utils/object-sizing.d.ts
vendored
Normal file
@ -0,0 +1,38 @@
|
||||
export declare type ObjectFit = "contain" | "cover";
|
||||
export declare type ObjectPosition = ["left" | "center" | "right", "top" | "center" | "bottom"];
|
||||
export interface Size {
|
||||
width: number;
|
||||
height: number;
|
||||
}
|
||||
/**
|
||||
* 用于计算图片的宽高比例
|
||||
* @see https://drafts.csswg.org/css-images-3/#sizing-terms
|
||||
*
|
||||
* ## 名词解释
|
||||
* ### intrinsic dimensions
|
||||
* 图片本身的尺寸
|
||||
*
|
||||
* ### specified size
|
||||
* 用户指定的元素尺寸
|
||||
*
|
||||
* ### concrete object size
|
||||
* 应用了 `objectFit` 之后图片的显示尺寸
|
||||
*
|
||||
* ### default object size
|
||||
*/
|
||||
export declare function calculateConcreteRect(style: {
|
||||
/** @see https://developer.mozilla.org/zh-CN/docs/Web/CSS/object-fit */
|
||||
objectFit?: ObjectFit;
|
||||
/** @see https://developer.mozilla.org/zh-CN/docs/Web/CSS/object-position */
|
||||
intrinsicPosition?: ObjectPosition;
|
||||
specifiedPosition?: [number, number];
|
||||
}, intrinsicSize: Size, specifiedSize: Size): {
|
||||
sx: number;
|
||||
sy: number;
|
||||
sw: number;
|
||||
sh: number;
|
||||
dx: number;
|
||||
dy: number;
|
||||
dw: number;
|
||||
dh: number;
|
||||
};
|
78
js_sdk/u-draw-poster/utils/object-sizing.js
Normal file
@ -0,0 +1,78 @@
|
||||
/**
|
||||
* 用于计算图片的宽高比例
|
||||
* @see https://drafts.csswg.org/css-images-3/#sizing-terms
|
||||
*
|
||||
* ## 名词解释
|
||||
* ### intrinsic dimensions
|
||||
* 图片本身的尺寸
|
||||
*
|
||||
* ### specified size
|
||||
* 用户指定的元素尺寸
|
||||
*
|
||||
* ### concrete object size
|
||||
* 应用了 `objectFit` 之后图片的显示尺寸
|
||||
*
|
||||
* ### default object size
|
||||
*/
|
||||
export function calculateConcreteRect(style, intrinsicSize, specifiedSize) {
|
||||
var _a, _b;
|
||||
const isContain = style.objectFit === 'contain';
|
||||
const specifiedPosition = style.specifiedPosition || [0, 0];
|
||||
// ratio 越大表示矩形越"扁"
|
||||
let intrinsicRatio = intrinsicSize.width / intrinsicSize.height;
|
||||
let specifiedRatio = specifiedSize.width / specifiedSize.height;
|
||||
/** 图片原始尺寸与最终尺寸之比 */
|
||||
let concreteScale = 1;
|
||||
if (intrinsicRatio > specifiedRatio && style.objectFit == "contain" ||
|
||||
intrinsicRatio <= specifiedRatio && style.objectFit == "cover")
|
||||
// 图片较"胖"时完整显示图片,图片较"瘦"时完全覆盖容器
|
||||
// 这两种情况下有 concreteRect.width = specifiedSize.width
|
||||
// 因为 concreteRect.width = intrinsicSize.width * concreteScale
|
||||
// 所以:
|
||||
concreteScale = specifiedSize.width / intrinsicSize.width;
|
||||
else if (intrinsicRatio > specifiedRatio && style.objectFit == "cover" ||
|
||||
intrinsicRatio <= specifiedRatio && style.objectFit == "contain")
|
||||
// 图片较"瘦"时完整显示图片,图片较"胖"时完全覆盖容器
|
||||
// 这两种情况下有 concreteRect.height = specifiedSize.height
|
||||
// 因为 concreteRect.height = intrinsicSize.height * concreteScale
|
||||
// 所以:
|
||||
concreteScale = specifiedSize.height / intrinsicSize.height;
|
||||
else
|
||||
throw new Error("Unkonwn concreteScale");
|
||||
let concreteRectWidth = intrinsicSize.width * concreteScale;
|
||||
let concreteRectHeight = intrinsicSize.height * concreteScale;
|
||||
// 这里可以把 left top 的计算想象成投影
|
||||
let xRelativeOrigin = { left: 0, center: .5, right: 1 }[((_a = style.intrinsicPosition) === null || _a === void 0 ? void 0 : _a[0]) || "center"];
|
||||
let yRelativeOrigin = { top: 0, center: .5, bottom: 1 }[((_b = style.intrinsicPosition) === null || _b === void 0 ? void 0 : _b[1]) || "center"];
|
||||
let concreteRectLeft = (specifiedSize.width - concreteRectWidth) * xRelativeOrigin;
|
||||
let concreteRectTop = (specifiedSize.height - concreteRectHeight) * yRelativeOrigin;
|
||||
if (isContain) {
|
||||
concreteRectLeft += specifiedPosition[0];
|
||||
concreteRectTop += specifiedPosition[1];
|
||||
}
|
||||
// 这里有两个坐标系,一个是 specified (dist) 的坐标系,一个是 intrinsic (src) 的坐标系
|
||||
// 这里将两个坐标系的点位置进行变换
|
||||
// 例: 带入 x=0, y=0, 得到的结果就是 specifiedRect 的左上角在 intrinsic 坐标系下的坐标位置
|
||||
// 在 specified 坐标系下, intrinsic 的零点在 (concreteRectLeft, concreteRectTop), 缩放为 concreteScale
|
||||
// 所以有 x_dist = x_src * concreteScale + concreteRectLeft
|
||||
// y_dist = y_src * concreteScale + concreteRectTop
|
||||
let dist2src = (distX, distY) => [
|
||||
/* srcX = */ (distX - concreteRectLeft) / concreteScale,
|
||||
/* srcY = */ (distY - concreteRectTop) / concreteScale
|
||||
];
|
||||
let [srcLeft, srcTop] = dist2src(0, 0);
|
||||
// srcRight = 图片 specified 框右边在 src 坐标系下的 x 坐标
|
||||
// srcBottom = 图片 specified 框下边在 src 坐标系下的 y 坐标
|
||||
let [srcRight, srcBottom] = dist2src(specifiedSize.width, specifiedSize.height);
|
||||
// 这里要对 src 和 disc 两个框进行约束
|
||||
return {
|
||||
sx: Math.max(srcLeft, 0),
|
||||
sy: Math.max(srcTop, 0),
|
||||
sw: Math.min(srcRight - srcLeft, intrinsicSize.width),
|
||||
sh: Math.min(srcBottom - srcTop, intrinsicSize.height),
|
||||
dx: isContain ? Math.max(concreteRectLeft, 0) : specifiedPosition[0],
|
||||
dy: isContain ? Math.max(concreteRectTop, 0) : specifiedPosition[1],
|
||||
dw: Math.min(concreteRectWidth, specifiedSize.width),
|
||||
dh: Math.min(concreteRectHeight, specifiedSize.height)
|
||||
};
|
||||
}
|