first commit
21
.gitignore
vendored
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
|
||||||
|
node_modules
|
||||||
|
.DS_Store
|
||||||
|
dist
|
||||||
|
*.local
|
||||||
|
|
||||||
|
# Editor directories and files
|
||||||
|
.idea
|
||||||
|
*.suo
|
||||||
|
*.ntvs*
|
||||||
|
*.njsproj
|
||||||
|
*.sln
|
||||||
|
*.sw?
|
||||||
20
.hbuilderx/launch.json
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
{ // launch.json 配置了启动调试时相关设置,configurations下节点名称可为 app-plus/h5/mp-weixin/mp-baidu/mp-alipay/mp-qq/mp-toutiao/mp-360/
|
||||||
|
// launchtype项可配置值为local或remote, local代表前端连本地云函数,remote代表前端连云端云函数
|
||||||
|
"version": "0.0",
|
||||||
|
"configurations": [{
|
||||||
|
"app-plus" :
|
||||||
|
{
|
||||||
|
"launchtype" : "local"
|
||||||
|
},
|
||||||
|
"default" :
|
||||||
|
{
|
||||||
|
"launchtype" : "local"
|
||||||
|
},
|
||||||
|
"mp-weixin" :
|
||||||
|
{
|
||||||
|
"launchtype" : "local"
|
||||||
|
},
|
||||||
|
"type" : "uniCloud"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
20
index.html
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<script>
|
||||||
|
var coverSupport = 'CSS' in window && typeof CSS.supports === 'function' && (CSS.supports('top: env(a)') ||
|
||||||
|
CSS.supports('top: constant(a)'))
|
||||||
|
document.write(
|
||||||
|
'<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0' +
|
||||||
|
(coverSupport ? ', viewport-fit=cover' : '') + '" />')
|
||||||
|
</script>
|
||||||
|
<title></title>
|
||||||
|
<!--preload-links-->
|
||||||
|
<!--app-context-->
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="app"><!--app-html--></div>
|
||||||
|
<script type="module" src="/src/main.ts"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
13367
package-lock.json
generated
Normal file
80
package.json
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
{
|
||||||
|
"name": "uni-preset-vue",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"scripts": {
|
||||||
|
"dev:app": "uni -p app",
|
||||||
|
"dev:app-android": "uni -p app-android",
|
||||||
|
"dev:app-ios": "uni -p app-ios",
|
||||||
|
"dev:custom": "uni -p",
|
||||||
|
"dev:h5": "uni",
|
||||||
|
"dev:h5:ssr": "uni --ssr",
|
||||||
|
"dev:mp-alipay": "uni -p mp-alipay",
|
||||||
|
"dev:mp-baidu": "uni -p mp-baidu",
|
||||||
|
"dev:mp-jd": "uni -p mp-jd",
|
||||||
|
"dev:mp-kuaishou": "uni -p mp-kuaishou",
|
||||||
|
"dev:mp-lark": "uni -p mp-lark",
|
||||||
|
"dev:mp-qq": "uni -p mp-qq",
|
||||||
|
"dev:mp-toutiao": "uni -p mp-toutiao",
|
||||||
|
"dev:mp-weixin": "uni -p mp-weixin",
|
||||||
|
"dev:mp-xhs": "uni -p mp-xhs",
|
||||||
|
"dev:quickapp-webview": "uni -p quickapp-webview",
|
||||||
|
"dev:quickapp-webview-huawei": "uni -p quickapp-webview-huawei",
|
||||||
|
"dev:quickapp-webview-union": "uni -p quickapp-webview-union",
|
||||||
|
"build:app": "uni build -p app",
|
||||||
|
"build:app-android": "uni build -p app-android",
|
||||||
|
"build:app-ios": "uni build -p app-ios",
|
||||||
|
"build:custom": "uni build -p",
|
||||||
|
"build:h5": "uni build",
|
||||||
|
"build:h5:ssr": "uni build --ssr",
|
||||||
|
"build:mp-alipay": "uni build -p mp-alipay",
|
||||||
|
"build:mp-baidu": "uni build -p mp-baidu",
|
||||||
|
"build:mp-jd": "uni build -p mp-jd",
|
||||||
|
"build:mp-kuaishou": "uni build -p mp-kuaishou",
|
||||||
|
"build:mp-lark": "uni build -p mp-lark",
|
||||||
|
"build:mp-qq": "uni build -p mp-qq",
|
||||||
|
"build:mp-toutiao": "uni build -p mp-toutiao",
|
||||||
|
"build:mp-weixin": "uni build -p mp-weixin",
|
||||||
|
"build:mp-xhs": "uni build -p mp-xhs",
|
||||||
|
"build:quickapp-webview": "uni build -p quickapp-webview",
|
||||||
|
"build:quickapp-webview-huawei": "uni build -p quickapp-webview-huawei",
|
||||||
|
"build:quickapp-webview-union": "uni build -p quickapp-webview-union",
|
||||||
|
"type-check": "vue-tsc --noEmit"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@climblee/uv-ui": "^1.1.20",
|
||||||
|
"@dcloudio/uni-app": "3.0.0-4010520240507001",
|
||||||
|
"@dcloudio/uni-app-plus": "3.0.0-4010520240507001",
|
||||||
|
"@dcloudio/uni-components": "3.0.0-4010520240507001",
|
||||||
|
"@dcloudio/uni-h5": "3.0.0-4010520240507001",
|
||||||
|
"@dcloudio/uni-mp-alipay": "3.0.0-4010520240507001",
|
||||||
|
"@dcloudio/uni-mp-baidu": "3.0.0-4010520240507001",
|
||||||
|
"@dcloudio/uni-mp-jd": "3.0.0-4010520240507001",
|
||||||
|
"@dcloudio/uni-mp-kuaishou": "3.0.0-4010520240507001",
|
||||||
|
"@dcloudio/uni-mp-lark": "3.0.0-4010520240507001",
|
||||||
|
"@dcloudio/uni-mp-qq": "3.0.0-4010520240507001",
|
||||||
|
"@dcloudio/uni-mp-toutiao": "3.0.0-4010520240507001",
|
||||||
|
"@dcloudio/uni-mp-weixin": "3.0.0-4010520240507001",
|
||||||
|
"@dcloudio/uni-mp-xhs": "3.0.0-4010520240507001",
|
||||||
|
"@dcloudio/uni-quickapp-webview": "3.0.0-4010520240507001",
|
||||||
|
"clipboard": "^2.0.11",
|
||||||
|
"dayjs": "^1.11.13",
|
||||||
|
"echarts": "^5.5.1",
|
||||||
|
"uview-plus": "^3.3.54",
|
||||||
|
"vue": "^3.4.21",
|
||||||
|
"vue-i18n": "^9.1.9"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@dcloudio/types": "^3.4.8",
|
||||||
|
"@dcloudio/uni-automator": "3.0.0-4010520240507001",
|
||||||
|
"@dcloudio/uni-cli-shared": "3.0.0-4010520240507001",
|
||||||
|
"@dcloudio/uni-stacktracey": "3.0.0-4010520240507001",
|
||||||
|
"@dcloudio/vite-plugin-uni": "3.0.0-4010520240507001",
|
||||||
|
"@vue/runtime-core": "^3.4.21",
|
||||||
|
"@vue/tsconfig": "^0.1.3",
|
||||||
|
"sass": "1.63.2",
|
||||||
|
"sass-loader": "10.4.1",
|
||||||
|
"typescript": "^4.9.4",
|
||||||
|
"vite": "5.2.8",
|
||||||
|
"vue-tsc": "^1.0.24"
|
||||||
|
}
|
||||||
|
}
|
||||||
10
shims-uni.d.ts
vendored
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
/// <reference types='@dcloudio/types' />
|
||||||
|
import 'vue'
|
||||||
|
|
||||||
|
declare module '@vue/runtime-core' {
|
||||||
|
type Hooks = App.AppInstance & Page.PageInstance;
|
||||||
|
|
||||||
|
interface ComponentCustomOptions extends Hooks {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
50
src/App.vue
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { onLaunch, onShow, onHide } from "@dcloudio/uni-app";
|
||||||
|
import { NvpMerchService } from '@/Service/Nvp/NvpMerchService'
|
||||||
|
onLaunch(() => {
|
||||||
|
uni.hideTabBar()
|
||||||
|
getUpData()
|
||||||
|
console.log("App Launch");
|
||||||
|
});
|
||||||
|
onShow(() => {
|
||||||
|
console.log("App Show");
|
||||||
|
});
|
||||||
|
onHide(() => {
|
||||||
|
console.log("App Hide");
|
||||||
|
});
|
||||||
|
|
||||||
|
const getUpData=()=>{
|
||||||
|
// #ifdef APP
|
||||||
|
plus.runtime.getProperty(plus.runtime.appid, (wgtinfo) => {
|
||||||
|
NvpMerchService.GetAppVersion().then(res=>{
|
||||||
|
console.log('wgtinfo.versionCode',wgtinfo.versionCode);
|
||||||
|
if (res.data.version > wgtinfo.versionCode) {
|
||||||
|
setTimeout(function() {
|
||||||
|
uni.navigateTo({
|
||||||
|
url: "/pages/upData/upData?info=" +
|
||||||
|
encodeURIComponent(
|
||||||
|
JSON.stringify(res.data))
|
||||||
|
})
|
||||||
|
}, 1000)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
// #endif
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
<style lang="scss">
|
||||||
|
|
||||||
|
@import "uview-plus/index.scss";
|
||||||
|
@import "colorui/main.css";
|
||||||
|
@import "colorui/icon.css";
|
||||||
|
page {
|
||||||
|
--nav-mian: #F84F28; //全局颜色
|
||||||
|
--nav-vice: #F59D77; //副颜色
|
||||||
|
--nav-diluted: #F2C0A3; //淡颜色
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
</style>
|
||||||
9
src/Service/BaseConfig.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
export class BaseConfig {
|
||||||
|
// protected static servesUrl: string = "http://192.168.0.190:8806";//线下
|
||||||
|
|
||||||
|
protected static servesUrl: string = "http://vp.xypays.cn";
|
||||||
|
protected static imgUrl: string = "http://vp.cloud.xypays.cn";
|
||||||
|
protected static mediaUrl: string = "http://byc1.xypays.cn/";
|
||||||
|
protected static uploadUrl: string = "/TencentCos/GetUpLoadInfo";
|
||||||
|
protected static payuploadUrl: string = "http://pay.xypays.cn";
|
||||||
|
}
|
||||||
36
src/Service/Nvp/NvpAddressService.ts
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
import { Service } from '@/Service/Service';
|
||||||
|
/*****登录接口*****/
|
||||||
|
class NvpAddressService {
|
||||||
|
private static GetAddressListPath: string = '/Address/GetAddressList';
|
||||||
|
/*****收货地址*****/
|
||||||
|
static GetAddressList(page: number) {
|
||||||
|
var result = Service.Request(this.GetAddressListPath, "GET", { page });
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static DelUserAddressPath: string = '/Address/DelUserAddress';
|
||||||
|
/*****删除地址*****/
|
||||||
|
static DelUserAddress(uaId: string) {
|
||||||
|
var result = Service.Request(this.DelUserAddressPath, "POST", { uaId });
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static AddUserAddressPath: string = '/Address/AddUserAddress';
|
||||||
|
/*****添加地址*****/
|
||||||
|
static AddUserAddress(uaId: string, name: string, phone: string, province: string, city: string, county: string, address: string, isDefault: number) {
|
||||||
|
var result = Service.Request(this.AddUserAddressPath, "POST", { uaId, name, phone, province, city, county, address, isDefault });
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static UpdateUserAddressPath: string = '/Address/UpdateUserAddress';
|
||||||
|
/*****修改默认地址*****/
|
||||||
|
static UpdateUserAddress(uaId: string) {
|
||||||
|
var result = Service.Request(this.UpdateUserAddressPath, "POST", { uaId });
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
export {
|
||||||
|
Service,
|
||||||
|
NvpAddressService
|
||||||
|
}
|
||||||
43
src/Service/Nvp/NvpAgentService.ts
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
import { Service } from '@/Service/Service';
|
||||||
|
/*****代理端接口*****/
|
||||||
|
class NvpAgentService {
|
||||||
|
private static LoginPath: string = '/Agent/Login';
|
||||||
|
/*****登录接口*****/
|
||||||
|
static Login(name: string, pwd: string) {
|
||||||
|
var result = Service.Request(this.LoginPath, "POST", { name, pwd });
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static GetAgentAccInfoPath: string = '/Agent/GetAgentAccInfo';
|
||||||
|
/*****账户接口*****/
|
||||||
|
static GetAgentAccInfo() {
|
||||||
|
var result = Service.Request(this.GetAgentAccInfoPath, "GET", "");
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static GetAgentAccLogPath: string = '/Agent/GetAgentAccLog';
|
||||||
|
/*****账户记录*****/
|
||||||
|
static GetAgentAccLog(code: string, page: number) {
|
||||||
|
var result = Service.Request(this.GetAgentAccLogPath, "GET", { code, page });
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static GetAgentMerchPath: string = '/Agent/GetAgentMerch';
|
||||||
|
/*****获取代理开通商家*****/
|
||||||
|
static GetAgentMerch(type: number, page: number) {
|
||||||
|
var result = Service.Request(this.GetAgentMerchPath, "GET", { type, page });
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static GetAgentHomePath: string = '/Agent/GetAgentHome';
|
||||||
|
/*****代理主页*****/
|
||||||
|
static GetAgentHome() {
|
||||||
|
var result = Service.Request(this.GetAgentHomePath, "GET", { });
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
export {
|
||||||
|
Service,
|
||||||
|
NvpAgentService
|
||||||
|
}
|
||||||
156
src/Service/Nvp/NvpApplyService.ts
Normal file
@@ -0,0 +1,156 @@
|
|||||||
|
import { Service } from '@/Service/Service';
|
||||||
|
/*****用户接口*****/
|
||||||
|
class NvpApplyService {
|
||||||
|
// private static WithDrawPath: string = '/With/WithDraw';
|
||||||
|
// /*****佣金提现*****/
|
||||||
|
// static WithDraw(money: number, name: string, account: string) {
|
||||||
|
// var result = Service.Request(this.WithDrawPath, "POST", { money, name, account });
|
||||||
|
// return result;
|
||||||
|
// }
|
||||||
|
|
||||||
|
private static GetSiteMccCodeListPath: string = '/Apply/GetSiteMccCodeList';
|
||||||
|
/*****获取mcc列表*****/
|
||||||
|
static GetSiteMccCodeList(mercType:string, mchType: string) {
|
||||||
|
var result = Service.Request(this.GetSiteMccCodeListPath, "GET", {mercType, mchType });
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
private static GetBankTypeListPath: string = '/Apply/GetBankTypeList';
|
||||||
|
/*****获取银行列表*****/
|
||||||
|
static GetBankTypeList(name:string) {
|
||||||
|
var result = Service.Request(this.GetBankTypeListPath, "GET", {name});
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static GetAreaListPath: string = '/Apply/GetAreaList';
|
||||||
|
/*****获取银行地区列表*****/
|
||||||
|
static GetAreaList(areaCode:string) {
|
||||||
|
var result = Service.Request(this.GetAreaListPath, "GET", { areaCode});
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
private static GetBankCodeListPath: string = '/Apply/GetBankCodeList';
|
||||||
|
/*****获取银行代码列表*****/
|
||||||
|
static GetBankCodeList(bankType:string,cityCode:string,name:string) {
|
||||||
|
var result = Service.Request(this.GetBankCodeListPath, "GET", {bankType,cityCode,name});
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static SendApplyMerchPath: string = '/Apply/SendApplyMerch';
|
||||||
|
/*****进价提交*****/
|
||||||
|
static SendApplyMerch(para:any) {
|
||||||
|
var result = Service.Request(this.SendApplyMerchPath, "POST", para);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static GetAgentMerchLogPath: string = '/Apply/GetAgentMerchLog';
|
||||||
|
/*****获取待审核*****/
|
||||||
|
static GetAgentMerchLog() {
|
||||||
|
var result = Service.Request(this.GetAgentMerchLogPath, "GET", {});
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
private static AuditApplyPath: string = '/Apply/AuditApply';
|
||||||
|
/*****确认资料*****/
|
||||||
|
static AuditApply(outId:string) {
|
||||||
|
var result = Service.Request(this.AuditApplyPath, "POST", {outId});
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static SetPayFeePath: string = '/Apply/SetPayFee';
|
||||||
|
/*****确认资料*****/
|
||||||
|
static SetPayFee(outId:string) {
|
||||||
|
var result = Service.Request(this.SetPayFeePath, "POST", {outId});
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static GetAssortListPath: string = '/Apply/GetAssortList';
|
||||||
|
/*****获取v派分类*****/
|
||||||
|
static GetAssortList(code:string) {
|
||||||
|
var result = Service.Request(this.GetAssortListPath, "GET", {code,parent:'0'});
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static GetAgentMerchPath: string = '/Agent/GetAgentMerch';
|
||||||
|
/*****获取已开通商家*****/
|
||||||
|
static GetAgentMerch(type:number,page:number) {
|
||||||
|
var result = Service.Request(this.GetAgentMerchPath, "GET", {type,page});
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static GetAppMerchInfoPath: string = '/Apply/GetAppMerchInfo';
|
||||||
|
/*****获取银盛已填写信息*****/
|
||||||
|
static GetAppMerchInfo(outId:string) {
|
||||||
|
var result = Service.Request(this.GetAppMerchInfoPath, "GET", {outId});
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static UpdateMerchPath: string = '/Apply/UpdateMerch';
|
||||||
|
/*****修改银盛信息*****/
|
||||||
|
static UpdateMerch(obj:any) {
|
||||||
|
var result = Service.Request(this.UpdateMerchPath, "POST", obj);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static UploadImgPath: string = '/Apply/UploadImg';
|
||||||
|
/*****修改银盛图片*****/
|
||||||
|
static UploadImg(outId:string,picType:string,img:string) {
|
||||||
|
var result = Service.Request(this.UploadImgPath, "POST", {outId,picType,img});
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static GetAgentApplyPath: string = '/Apply/GetAgentApply';
|
||||||
|
/*****获取添加的商家*****/
|
||||||
|
static GetAgentApply(serch:string,page:number) {
|
||||||
|
var result = Service.Request(this.GetAgentApplyPath, "GET", {serch,page});
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static GetCategoryPath: string = '/Apply/GetCategory';
|
||||||
|
/*****获取商家类型*****/
|
||||||
|
static GetCategory() {
|
||||||
|
var result = Service.Request(this.GetCategoryPath, "GET", {});
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static AddMerchInfoPath: string = '/Apply/AddMerchInfo';
|
||||||
|
/*****添加商户*****/
|
||||||
|
static AddMerchInfo(obj:any) {
|
||||||
|
var result = Service.Request(this.AddMerchInfoPath, "POST", obj);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static BandAppIdPath: string = '/Agent/BandAppId';
|
||||||
|
/*****绑定appid*****/
|
||||||
|
static BandAppId(merchId:string) {
|
||||||
|
var result = Service.Request(this.BandAppIdPath, "POST", {merchId});
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
export {
|
||||||
|
Service,
|
||||||
|
NvpApplyService
|
||||||
|
}
|
||||||
50
src/Service/Nvp/NvpBankService.ts
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
import { Service } from '@/Service/Service';
|
||||||
|
/*****登录接口*****/
|
||||||
|
class NvpBankService {
|
||||||
|
private static GetPageBankListPath: string = '/Bank/GetPageBankList';
|
||||||
|
/*****用户银行卡列表*****/
|
||||||
|
static GetPageBankList(page: number) {
|
||||||
|
var result = Service.Request(this.GetPageBankListPath, "GET", { page });
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static GetBankListPath: string = '/Bank/GetBankList';
|
||||||
|
/*****用户银行卡列表*****/
|
||||||
|
static GetBankList() {
|
||||||
|
var result = Service.Request(this.GetBankListPath, "GET", "");
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static GetUnitBankListPath: string = '/Bank/GetUnitBankList';
|
||||||
|
/*****银行卡列表*****/
|
||||||
|
static GetUnitBankList() {
|
||||||
|
var result = Service.Request(this.GetUnitBankListPath, "GET", "");
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static AddUserBankPath: string = '/Bank/AddUserBank';
|
||||||
|
/*****添加银行卡*****/
|
||||||
|
static AddUserBank(account: string, bank: string, name: string) {
|
||||||
|
var result = Service.Request(this.AddUserBankPath, "POST", { account, bank, name });
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static UpdateBankPath: string = '/Bank/UpdateBank';
|
||||||
|
/*****修改银行卡*****/
|
||||||
|
static UpdateBank(ubId: string, bank: string, name: string, account: string) {
|
||||||
|
var result = Service.Request(this.UpdateBankPath, "POST", { ubId, bank, name, account });
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static DelUserBankPath: string = '/Bank/DelUserBank';
|
||||||
|
/*****删除银行卡*****/
|
||||||
|
static DelUserBank(ubId: string) {
|
||||||
|
var result = Service.Request(this.DelUserBankPath, "POST", { ubId });
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
export {
|
||||||
|
Service,
|
||||||
|
NvpBankService
|
||||||
|
}
|
||||||
36
src/Service/Nvp/NvpLoginService.ts
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
import { Service } from '@/Service/Service';
|
||||||
|
/*****登录接口*****/
|
||||||
|
class NvpLoginService {
|
||||||
|
private static GetWxLoginOpenIdPath: string = '/Login/GetWxLoginOpenId';
|
||||||
|
/*****小程序根据code获取openId*****/
|
||||||
|
static GetWxLoginOpenId(code: string, type: number) {
|
||||||
|
var result = Service.Request(this.GetWxLoginOpenIdPath, "GET", { code, type });
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static AppletLoginPath: string = '/Login/AppletLogin';
|
||||||
|
/*****普通登陆接口(小程序)*****/
|
||||||
|
static AppletLogin(openId: string, channel: string) {
|
||||||
|
var result = Service.Request(this.AppletLoginPath, "POST", { openId, channel });
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static RegistPath: string = '/Login/Regist';
|
||||||
|
/*****注册接口*****/
|
||||||
|
static Regist(remNo: string, openId: string, uniopenId: string, channel: string, type: number) {
|
||||||
|
var result = Service.Request(this.RegistPath, "POST", { remNo, openId, uniopenId, channel, type });
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static GetNumberPhonePath: string = '/Login/GetNumberPhone';
|
||||||
|
/*****获取手机号*****/
|
||||||
|
static GetNumberPhone(code: string) {
|
||||||
|
var result = Service.Request(this.GetNumberPhonePath, "GET", { code });
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
export {
|
||||||
|
Service,
|
||||||
|
NvpLoginService
|
||||||
|
}
|
||||||
32
src/Service/Nvp/NvpMachineService.ts
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
import { Service } from '@/Service/Service';
|
||||||
|
/*****设备接口*****/
|
||||||
|
class NvpMachineService {
|
||||||
|
|
||||||
|
private static GetMerchListPath: string = '/Machine/GetMerchList';
|
||||||
|
/*****商户设备*****/
|
||||||
|
static GetMerchList(merchId: string) {
|
||||||
|
var result = Service.Request(this.GetMerchListPath, "GET", { merchId });
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static DelMerchMachinePath: string = '/Machine/DelMerchMachine';
|
||||||
|
/*****删除设备*****/
|
||||||
|
static DelMerchMachine(merchId: string, machineId: string) {
|
||||||
|
var result = Service.Request(this.DelMerchMachinePath, "POST", { merchId, machineId });
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static AddMachinePath: string = '/Machine/AddMachine';
|
||||||
|
/*****添加设备*****/
|
||||||
|
static AddMachine(merchId: string, payCode: string) {
|
||||||
|
var result = Service.Request(this.AddMachinePath, "POST", { merchId, payCode });
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
export {
|
||||||
|
Service,
|
||||||
|
NvpMachineService
|
||||||
|
}
|
||||||
106
src/Service/Nvp/NvpMerchService.ts
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
import { Service } from '@/Service/Service';
|
||||||
|
/*****用户接口*****/
|
||||||
|
class NvpMerchService {
|
||||||
|
|
||||||
|
|
||||||
|
private static GetMerchInfoPath: string = '/Merch/GetMerchInfo';
|
||||||
|
/*****获取商家信息*****/
|
||||||
|
static GetMerchInfo(merchId: string,lon:number,lat:number) {
|
||||||
|
var result = Service.Request(this.GetMerchInfoPath, "GET", { merchId,lon,lat});
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static GetMerchOrderPath: string = '/Merch/GetMerchOrder';
|
||||||
|
/*****获取商家营业数据*****/
|
||||||
|
static GetMerchOrder(merchId: string) {
|
||||||
|
var result = Service.Request(this.GetMerchOrderPath, "GET", { merchId });
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static UpdateMerchInfoPath: string = '/Merch/UpdateMerchInfo';
|
||||||
|
/*****修改商家信息*****/
|
||||||
|
static UpdateMerchInfo(merchId: string, logo: string ,name:string, phone:string,province:string,city:string,county:string,address:string,lon:string,lat:string,sTime:string,eTime:string,showImg:string) {
|
||||||
|
var result = Service.Request(this.UpdateMerchInfoPath, "POST", { merchId, logo,name ,phone,province,city,county,address,lon,lat,sTime,eTime,showImg});
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
private static GetMerchAccPath: string = '/Merch/GetMerchAcc';
|
||||||
|
/*****获取商家佣金数据*****/
|
||||||
|
static GetMerchAcc(merchId: string) {
|
||||||
|
var result = Service.Request(this.GetMerchAccPath, "GET", { merchId });
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static GetMerchAccLogPath: string = '/Merch/GetMerchAccLog';
|
||||||
|
/*****获取商家佣金记录*****/
|
||||||
|
static GetMerchAccLog(merchId: string,op:string,page:number) {
|
||||||
|
var result = Service.Request(this.GetMerchAccLogPath, "GET", { merchId,op,page });
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
private static MerchWithDrawPath: string = '/Merch/MerchWithDraw';
|
||||||
|
/*****商家佣金提现*****/
|
||||||
|
static MerchWithDraw(merchId: string, money: number ,name:string, account:string) {
|
||||||
|
var result = Service.Request(this.MerchWithDrawPath, "POST", { merchId, money,name,account});
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static GetMerchWithLogPath: string = '/Merch/GetMerchWithLog';
|
||||||
|
/*****商家佣金提现记录*****/
|
||||||
|
static GetMerchWithLog(merchId: string) {
|
||||||
|
var result = Service.Request(this.GetMerchWithLogPath, "GET", { merchId });
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static GetMerchTicketListPath: string = '/Merch/GetMerchTicketList';
|
||||||
|
/*****商家优惠券列表*****/
|
||||||
|
static GetMerchTicketList(merchId: string) {
|
||||||
|
var result = Service.Request(this.GetMerchTicketListPath, "GET", { merchId });
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static AddMerchTicketPath: string = '/Merch/AddMerchTicket';
|
||||||
|
/*****商家优惠券修改添加*****/
|
||||||
|
static AddMerchTicket(ticketId: string, merchId: string ,code:string, atkAcc:number,redAcc:number,count:number,useTime:Number,state:Number,addTime:string,endTime:string) {
|
||||||
|
var result = Service.Request(this.AddMerchTicketPath, "POST", { ticketId, merchId,code,atkAcc,redAcc,count,useTime,state,addTime,endTime});
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
private static DelMerchTicketPath: string = '/Merch/DelMerchTicket';
|
||||||
|
/*****商家删除优惠券*****/
|
||||||
|
static DelMerchTicket(ticketId: string) {
|
||||||
|
var result = Service.Request(this.DelMerchTicketPath, "POST", { ticketId});
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static GetTicketInfoPath: string = '/Merch/GetTicketInfo';
|
||||||
|
/*****获取优惠券详情*****/
|
||||||
|
static GetTicketInfo(ticketId: string) {
|
||||||
|
var result = Service.Request(this.GetTicketInfoPath, "GET", { ticketId });
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
private static GetAppVersionPath: string = '/Login/GetAppVersion';
|
||||||
|
/*****更新*****/
|
||||||
|
static GetAppVersion() {
|
||||||
|
var result = Service.Request(this.GetAppVersionPath, "GET", {type:2});
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export {
|
||||||
|
Service,
|
||||||
|
NvpMerchService
|
||||||
|
}
|
||||||
43
src/Service/Nvp/NvpPubService.ts
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
import { Service } from '@/Service/Service';
|
||||||
|
/*****公共接口*****/
|
||||||
|
class NvpPubService {
|
||||||
|
private static GetIndexPath: string = '/Pub/GetIndex';
|
||||||
|
/*****主页信息*****/
|
||||||
|
static GetIndex() {
|
||||||
|
var result = Service.Request(this.GetIndexPath, "GET", "");
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static GetIndexDataPath: string = '/Pub/GetIndexData';
|
||||||
|
/*****获取首页数据*****/
|
||||||
|
static GetIndexData(lon: number, lat: number, city: string, county: string, sort: number, page: number) {
|
||||||
|
var result = Service.Request(this.GetIndexDataPath, "GET", { lon, lat, city, county, sort, page });
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static GetMenuDataPath: string = '/Pub/GetMenuData';
|
||||||
|
/*****获取分类*****/
|
||||||
|
static GetMenuData(type: string, parent: string) {
|
||||||
|
var result = Service.Request(this.GetMenuDataPath, "GET", { type, parent });
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static GetMerchDataPath: string = '/Pub/GetMerchData';
|
||||||
|
/*****获取店铺*****/
|
||||||
|
static GetMerchData(code: string, serch: string, assId: string, lon: number, lat: number, city: string, county: string, sort: number, page: number, limit: number) {
|
||||||
|
var result = Service.Request(this.GetMerchDataPath, "GET", { code, serch, assId, lon, lat, city, county, sort, page, limit });
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static GetRandomMerchPath: string = '/Pub/GetRandomMerch';
|
||||||
|
/*****随机获取商家*****/
|
||||||
|
static GetRandomMerch(count: number, lon: number, lat: number, city: string, county: string) {
|
||||||
|
var result = Service.Request(this.GetRandomMerchPath, "GET", { count, lon, lat, city, county });
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
export {
|
||||||
|
Service,
|
||||||
|
NvpPubService
|
||||||
|
}
|
||||||
22
src/Service/Nvp/NvpTencentCosService.ts
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import { Service } from '@/Service/Service';
|
||||||
|
/*****腾讯云存储*****/
|
||||||
|
class NvpTencentCosService {
|
||||||
|
private static GetAuthorizationPath: string = '/TencentCos/GetAuthorization';
|
||||||
|
/*****获取云存储配置*****/
|
||||||
|
static GetAuthorization() {
|
||||||
|
var result = Service.Request(this.GetAuthorizationPath, "GET", "");
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static GetUpLoadInfoPath: string = '/TencentCos/GetUpLoadInfo';
|
||||||
|
/*****获取上传地址*****/
|
||||||
|
static GetUpLoadInfo(code: string, fileName: string, desire: string) {
|
||||||
|
var result = Service.Request(this.GetUpLoadInfoPath, "GET", { code, fileName, desire });
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
export {
|
||||||
|
Service,
|
||||||
|
NvpTencentCosService
|
||||||
|
}
|
||||||
43
src/Service/Nvp/NvpUserService.ts
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
import { Service } from '@/Service/Service';
|
||||||
|
/*****用户接口*****/
|
||||||
|
class NvpUserService {
|
||||||
|
private static GetUserInfoPath: string = '/User/GetUserInfo';
|
||||||
|
/*****用户基础信息*****/
|
||||||
|
static GetUserInfo() {
|
||||||
|
var result = Service.Request(this.GetUserInfoPath, "GET", "");
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static UpdateUserInfoPath: string = '/User/UpdateUserInfo';
|
||||||
|
/*****修改用户信息*****/
|
||||||
|
static UpdateUserInfo(par: string, smsCode: string, type: number) {
|
||||||
|
var result = Service.Request(this.UpdateUserInfoPath, "POST", { par, smsCode, type });
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static GetUserAccLogPath: string = '/User/GetUserAccLog';
|
||||||
|
/*****获取账户记录*****/
|
||||||
|
static GetUserAccLog(code: string, accType: string, page: number) {
|
||||||
|
var result = Service.Request(this.GetUserAccLogPath, "GET", { code, accType, page });
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static GetUserCommissionInfoPath: string = '/User/GetUserCommissionInfo';
|
||||||
|
/*****用户佣金账户*****/
|
||||||
|
static GetUserCommissionInfo() {
|
||||||
|
var result = Service.Request(this.GetUserCommissionInfoPath, "GET", "");
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static GetUserCommissionLogPath: string = '/User/GetUserCommissionLog';
|
||||||
|
/*****用户佣金记录*****/
|
||||||
|
static GetUserCommissionLog(code: string, accType: string, page: number) {
|
||||||
|
var result = Service.Request(this.GetUserCommissionLogPath, "GET", { code, accType, page });
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
export {
|
||||||
|
Service,
|
||||||
|
NvpUserService
|
||||||
|
}
|
||||||
22
src/Service/Nvp/NvpWithService.ts
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import { Service } from '@/Service/Service';
|
||||||
|
/*****用户接口*****/
|
||||||
|
class NvpWithService {
|
||||||
|
private static WithDrawPath: string = '/With/WithDraw';
|
||||||
|
/*****佣金提现*****/
|
||||||
|
static WithDraw(money: number, name: string, account: string) {
|
||||||
|
var result = Service.Request(this.WithDrawPath, "POST", { money, name, account });
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static GetUserWithLogPath: string = '/With/GetUserWithLog';
|
||||||
|
/*****用户提现记录*****/
|
||||||
|
static GetUserWithLog(page: number) {
|
||||||
|
var result = Service.Request(this.GetUserWithLogPath, "GET", { page });
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
export {
|
||||||
|
Service,
|
||||||
|
NvpWithService
|
||||||
|
}
|
||||||
320
src/Service/Service.ts
Normal file
@@ -0,0 +1,320 @@
|
|||||||
|
import { HttpRequest, StoreAssist, UploadAssist, ResultData } from '@/common/Common';
|
||||||
|
import { BaseConfig } from './BaseConfig';
|
||||||
|
export class Service extends BaseConfig {
|
||||||
|
//获取API地址
|
||||||
|
static ApiUrl(path : string) {
|
||||||
|
return `${this.servesUrl}${path}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
//获取图片地址
|
||||||
|
static GetpayImg(path : string) {
|
||||||
|
if (path.startsWith('http') || path.startsWith('https')) {
|
||||||
|
return path;
|
||||||
|
} else {
|
||||||
|
return `${this.payuploadUrl}${path}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//获取图片地址
|
||||||
|
static GetMateUrlByImg(path : string) {
|
||||||
|
if (path.startsWith('http') || path.startsWith('https')) {
|
||||||
|
return path;
|
||||||
|
} else {
|
||||||
|
return `${this.imgUrl}${path}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//获取音视频地址
|
||||||
|
static GetMateUrlByMedia(path : string) {
|
||||||
|
if (path.startsWith('http') || path.startsWith('https')) {
|
||||||
|
return path;
|
||||||
|
} else {
|
||||||
|
return `${this.mediaUrl}${path}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//获取登录账号token
|
||||||
|
static GetUserToken() {
|
||||||
|
return Service.GetStorageCache('token');
|
||||||
|
}
|
||||||
|
// 获取登录状态
|
||||||
|
static GetUserIsLogin() {
|
||||||
|
var token = this.GetUserToken();
|
||||||
|
if (token == null || token == '') {
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//设置登录账户Token
|
||||||
|
static SetUserToken(token : string) {
|
||||||
|
this.SetStorageCache('token', token);
|
||||||
|
}
|
||||||
|
//清理登录账户Token
|
||||||
|
static OffUserToken() {
|
||||||
|
Service.DelStorageCache('token');
|
||||||
|
uni.$emit('ImComOff', 'user');
|
||||||
|
this.ClearUserStateData();
|
||||||
|
}
|
||||||
|
//获取登录账号状态信息
|
||||||
|
static GetUserStateData() {
|
||||||
|
return Service.GetStorageCache('StateDomain');
|
||||||
|
}
|
||||||
|
//设置当前登录账号状态信息
|
||||||
|
static SetUserStateData() {
|
||||||
|
return Service.GetStorageCache('StateDomain');
|
||||||
|
}
|
||||||
|
//清理当前登录账号状态信息
|
||||||
|
static ClearUserStateData() {
|
||||||
|
Service.DelStorageCache('StateDomain');
|
||||||
|
}
|
||||||
|
|
||||||
|
//获取缓存
|
||||||
|
static GetStorageCache(key : string) {
|
||||||
|
return StoreAssist.Get(key);
|
||||||
|
}
|
||||||
|
//删除缓存
|
||||||
|
static DelStorageCache(key : string) {
|
||||||
|
StoreAssist.Delete(key);
|
||||||
|
}
|
||||||
|
//设置缓存
|
||||||
|
static SetStorageCache(key : string, data : any) {
|
||||||
|
StoreAssist.Set(key, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*****以下是基础方法调用与拦截器*****/
|
||||||
|
|
||||||
|
static Request(url : string, method : 'GET' | 'POST' | 'PUT' | undefined, data : object | any) {
|
||||||
|
const token = Service.GetUserToken();
|
||||||
|
|
||||||
|
const _url = Service.ApiUrl(url);
|
||||||
|
var result = HttpRequest.RequestWithToken(_url, method, token, data).then((retResult : any) => {
|
||||||
|
if (retResult.statusCode == '200') {
|
||||||
|
var obj = retResult.data;
|
||||||
|
if (obj.code == 401) {
|
||||||
|
//过期
|
||||||
|
this.OffUserToken();
|
||||||
|
this.Msg('登录过期,请重新登录')
|
||||||
|
this.GoPage('/pages/login/login')
|
||||||
|
return Promise.reject();
|
||||||
|
} else if (obj.code == 40101) {
|
||||||
|
//失效
|
||||||
|
this.OffUserToken();
|
||||||
|
this.GoPageDelse('/pages/mine/login/login');
|
||||||
|
return Promise.reject();
|
||||||
|
} else if (obj.code == 1004) {
|
||||||
|
//资源不存在
|
||||||
|
this.GoPageDelse('/pages/AppSet/404/404');
|
||||||
|
return Promise.reject();
|
||||||
|
// return new ResultData(-1, '', '');
|
||||||
|
} else if (obj.code == 40188) {
|
||||||
|
//无权限
|
||||||
|
|
||||||
|
this.GoPageDelse('/pages/AppSet/40188/40188');
|
||||||
|
return Promise.reject();
|
||||||
|
// return new ResultData(-1, '', '');
|
||||||
|
} else if (obj.code == 1008) {
|
||||||
|
//业务提示
|
||||||
|
return new ResultData(obj.code, obj.msg, obj.data);
|
||||||
|
} else {
|
||||||
|
return new ResultData(obj.code, obj.msg, obj.data);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return new ResultData(-1, '', '');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
/*****以下是腾讯云oss上传*****/
|
||||||
|
static UpLoadMedia(code : string, fileName : string, desire : string, path : string) {
|
||||||
|
var result = this.Request(this.uploadUrl, 'GET', { code, fileName, desire }).then((retResult) => {
|
||||||
|
if (retResult.code == 0) {
|
||||||
|
var upOk = UploadAssist.Upload(retResult.data.url, path, retResult.data.cosData).then((upRet : any) => {
|
||||||
|
if (upRet.statusCode == 200) {
|
||||||
|
const retData : any = { code: retResult.data.code, file: retResult.data.file, cache: retResult.data.cache };
|
||||||
|
return new ResultData(0, '上传成功!', retData);
|
||||||
|
} else {
|
||||||
|
this.Msg('上传失败!');
|
||||||
|
return new ResultData(-1, '', '');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return upOk;
|
||||||
|
} else {
|
||||||
|
this.Msg('上传失败!');
|
||||||
|
return new ResultData(-1, retResult.msg,retResult.data);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/***********消息操作**************/
|
||||||
|
static Msg(message : any, icon ?: any) : void {
|
||||||
|
if (icon != null) {
|
||||||
|
uni.showToast({
|
||||||
|
title: message,
|
||||||
|
icon: icon
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
uni.showToast({
|
||||||
|
title: message,
|
||||||
|
icon: 'none'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static Alert(msg : string, cb ?: any) {
|
||||||
|
uni.showModal({
|
||||||
|
title: '提示',
|
||||||
|
content: msg,
|
||||||
|
showCancel: false,
|
||||||
|
cancelText: '取消',
|
||||||
|
confirmText: '确定',
|
||||||
|
success: res => {
|
||||||
|
if (res.confirm) {
|
||||||
|
cb && cb();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
static LoadIng(text : any) : void {
|
||||||
|
uni.showLoading({
|
||||||
|
title: text,
|
||||||
|
icon: 'none'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static LoadClose() : void {
|
||||||
|
uni.hideLoading();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**********跳转操作*********/
|
||||||
|
|
||||||
|
|
||||||
|
static GoPageTab(path : string) : void {
|
||||||
|
uni.switchTab({
|
||||||
|
url: path
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**********跳转操作*********/
|
||||||
|
static GoPage(path : string) : void {
|
||||||
|
uni.navigateTo({
|
||||||
|
url: path, //跳转的页面
|
||||||
|
success: function (res) {
|
||||||
|
// 通过eventChannel向被打开页面传送数据
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
/**********跳转并删除当前页面操作*********/
|
||||||
|
static GoPageDelse(path : string) : void {
|
||||||
|
uni.redirectTo({
|
||||||
|
url: path //跳转的页面
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**********返回上一页*********/
|
||||||
|
static GoPageBack() : void {
|
||||||
|
uni.navigateBack({ delta: 1 });
|
||||||
|
}
|
||||||
|
|
||||||
|
/*****获取图片base64*****/
|
||||||
|
static UpLoadMediaBase64(path : string) {
|
||||||
|
return new Promise(function (resolve, reject) {
|
||||||
|
uni.uploadFile({
|
||||||
|
url: 'http://cloud.pccsh.com/DefUp/UploadFileImgBase64', //仅为示例,非真实的接口地址
|
||||||
|
filePath: path,
|
||||||
|
name: 'file',
|
||||||
|
success: (uploadFileRes) => {
|
||||||
|
resolve(uploadFileRes);
|
||||||
|
},
|
||||||
|
fail: (err) => {
|
||||||
|
reject(err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
/*****获取图片位置信息*****/
|
||||||
|
//获取时间戳
|
||||||
|
static GetTimeSpan(milliSecond : number) {
|
||||||
|
return Date.now() + milliSecond;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 时间戳处理
|
||||||
|
static formatDate(time : any, type : number) : string {
|
||||||
|
const date = new Date(time);
|
||||||
|
const year = date.getFullYear();
|
||||||
|
const month = String(date.getMonth() + 1).padStart(2, '0'); // 月份从0开始,所以加1,并用0填充
|
||||||
|
const day = String(date.getDate()).padStart(2, '0'); // 用0填充
|
||||||
|
const hours = String(date.getHours()).padStart(2, '0'); // 用0填充
|
||||||
|
const minutes = String(date.getMinutes()).padStart(2, '0'); // 用0填充
|
||||||
|
const seconds = String(date.getSeconds()).padStart(2, '0'); // 用0填充
|
||||||
|
if (type == 0) {
|
||||||
|
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
|
||||||
|
}
|
||||||
|
else if (type == 1) {
|
||||||
|
return `${year}-${month}-${day} ${hours}:${minutes}`;
|
||||||
|
} else if (type == 2) {
|
||||||
|
return `${year}-${month}-${day}`;
|
||||||
|
} else if (type == 3) {
|
||||||
|
return `${hours}:${minutes}`;
|
||||||
|
} else if (type == 4) {
|
||||||
|
return `${year}${month}${day}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
else {
|
||||||
|
return `${hours}:${minutes}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*****节流*****/
|
||||||
|
static throttle(fn: () => void, time: number) {
|
||||||
|
let canRun: boolean = true;
|
||||||
|
return function () {
|
||||||
|
if (!canRun) return;
|
||||||
|
canRun = false;
|
||||||
|
setTimeout(() => {
|
||||||
|
fn(); //可以不执行
|
||||||
|
canRun = true;
|
||||||
|
}, time);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
/*****防抖*****/
|
||||||
|
static debounce<T extends (...args: any[]) => void>(fn: T, time: number): (...args: Parameters<T>) => void {
|
||||||
|
let timerId: NodeJS.Timeout | null = null;
|
||||||
|
|
||||||
|
return (...args: Parameters<T>) => {
|
||||||
|
if (timerId) {
|
||||||
|
clearTimeout(timerId);
|
||||||
|
}
|
||||||
|
|
||||||
|
timerId = setTimeout(() => {
|
||||||
|
fn(...args); // 执行传入的函数
|
||||||
|
timerId = null; // 清除定时器ID
|
||||||
|
}, time);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// 普通图片上传
|
||||||
|
static uploadH5(path, dic, callback) {
|
||||||
|
console.log(this.payuploadUrl,'xxx')
|
||||||
|
uni.uploadFile({
|
||||||
|
url: this.payuploadUrl+'/Upload/UploadFile',
|
||||||
|
method: "POST",
|
||||||
|
header: {
|
||||||
|
'Authorization': 'Bearer ' + Service.GetUserToken(),
|
||||||
|
},
|
||||||
|
formData: {
|
||||||
|
"path": dic,
|
||||||
|
},
|
||||||
|
filePath: path,
|
||||||
|
name: 'file',
|
||||||
|
success: (data) => {
|
||||||
|
let info = data.data
|
||||||
|
callback(info)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
184
src/colorui/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)
|
||||||
|
}
|
||||||
|
}
|
||||||
65
src/colorui/components/cu-custom.vue
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
<template>
|
||||||
|
<view>
|
||||||
|
<view class="cu-custom" :style="[{height:CustomBar + 'px'}]">
|
||||||
|
<view class="cu-bar fixed" :style="style" :class="[bgImage!=''?'none-bg text-white bg-img':'',bgColor]">
|
||||||
|
<view class="action" @tap="BackPage" v-if="isBack">
|
||||||
|
<text class="cuIcon-back"></text>
|
||||||
|
<slot name="backText"></slot>
|
||||||
|
</view>
|
||||||
|
<view class="content" :style="[{top:StatusBar + 'px'}]">
|
||||||
|
<slot name="content"></slot>
|
||||||
|
</view>
|
||||||
|
<slot name="right"></slot>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
StatusBar: this.StatusBar,
|
||||||
|
CustomBar: this.CustomBar
|
||||||
|
};
|
||||||
|
},
|
||||||
|
name: 'cu-custom',
|
||||||
|
computed: {
|
||||||
|
style() {
|
||||||
|
var StatusBar= this.StatusBar;
|
||||||
|
var CustomBar= this.CustomBar;
|
||||||
|
var bgImage = this.bgImage;
|
||||||
|
var style = `height:${CustomBar}px;padding-top:${StatusBar}px;`;
|
||||||
|
if (this.bgImage) {
|
||||||
|
style = `${style}background-image:url(${bgImage});`;
|
||||||
|
}
|
||||||
|
return style
|
||||||
|
}
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
bgColor: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
isBack: {
|
||||||
|
type: [Boolean, String],
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
bgImage: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
BackPage() {
|
||||||
|
uni.navigateBack({
|
||||||
|
delta: 1
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
|
||||||
|
</style>
|
||||||
1226
src/colorui/icon.css
Normal file
3912
src/colorui/main.css
Normal file
13
src/common/Common.ts
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import {ResultData} from "./Domain/ResultData";
|
||||||
|
import {StoreAssist} from "./Unit/StorageAssist";
|
||||||
|
import {HttpRequest} from "./Unit/HttpRequest";
|
||||||
|
import { StringAssist } from "./Unit/StringAssist";
|
||||||
|
import { UploadAssist } from "./Unit/UploadAssist";
|
||||||
|
|
||||||
|
export {
|
||||||
|
ResultData,
|
||||||
|
HttpRequest,
|
||||||
|
StoreAssist,
|
||||||
|
StringAssist,
|
||||||
|
UploadAssist
|
||||||
|
}
|
||||||
10
src/common/Domain/ResultData.ts
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
export class ResultData {
|
||||||
|
public code: number=-1;
|
||||||
|
public msg: string="";
|
||||||
|
public data: any;
|
||||||
|
constructor(code:number,msg:string,data:any) {
|
||||||
|
this.code=code;
|
||||||
|
this.msg = msg;
|
||||||
|
this.data = data;
|
||||||
|
}
|
||||||
|
}
|
||||||
64
src/common/Unit/HttpRequest.ts
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
export class HttpRequest {
|
||||||
|
|
||||||
|
/***普通请求方法***/
|
||||||
|
static Request(url: string, method: "GET" | "POST" | "PUT" | undefined, data: object | any) {
|
||||||
|
return new Promise(function(resolve, reject) {
|
||||||
|
let header = {
|
||||||
|
'content-type': method == 'POST' || method == 'PUT' ? 'application/x-www-form-urlencoded' : 'application/json; charset=utf-8',
|
||||||
|
'Access-Control-Allow-Origin': '*'
|
||||||
|
};
|
||||||
|
uni.request({
|
||||||
|
url: url,
|
||||||
|
method: method,
|
||||||
|
data: data,
|
||||||
|
header: header,
|
||||||
|
success(res: any) {
|
||||||
|
if (res.statusCode == "200") {
|
||||||
|
resolve(res.data);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
resolve(res);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
fail(err) {
|
||||||
|
//请求失败
|
||||||
|
uni.showToast({
|
||||||
|
title: '无法连接到服务器',
|
||||||
|
icon: 'none'
|
||||||
|
})
|
||||||
|
reject(err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
/***带Token的请求方法***/
|
||||||
|
static RequestWithToken(url: string, method: "GET" | "POST" | "PUT" | undefined, token: string, data: object | any) {
|
||||||
|
return new Promise(function(resolve, reject) {
|
||||||
|
let header = {
|
||||||
|
'content-type': method == 'POST' || method == 'PUT' ? 'application/x-www-form-urlencoded' : 'application/json; charset=utf-8',
|
||||||
|
'Authorization': 'Bearer ' + token,//token获取
|
||||||
|
'Access-Control-Allow-Origin': '*'
|
||||||
|
};
|
||||||
|
uni.request({
|
||||||
|
url: url,
|
||||||
|
method: method,
|
||||||
|
data: data,
|
||||||
|
header: header,
|
||||||
|
success(res: any) {
|
||||||
|
resolve(res);
|
||||||
|
},
|
||||||
|
fail(err) {
|
||||||
|
//请求失败
|
||||||
|
uni.showToast({
|
||||||
|
title: '无法连接到服务器',
|
||||||
|
icon: 'none'
|
||||||
|
})
|
||||||
|
reject(err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
14
src/common/Unit/StorageAssist.ts
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
export class StoreAssist{
|
||||||
|
static Get(key:string):any
|
||||||
|
{
|
||||||
|
return uni.getStorageSync(key);
|
||||||
|
}
|
||||||
|
static Set(key:string,value:any):void
|
||||||
|
{
|
||||||
|
uni.setStorageSync(key, value);
|
||||||
|
}
|
||||||
|
static Delete(key:string):void
|
||||||
|
{
|
||||||
|
uni.removeStorageSync(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
13
src/common/Unit/StoreAssist.ts
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
// import { createPinia, defineStore } from "pinia";
|
||||||
|
// import piniaPluginPersistedstate from "pinia-plugin-persistedstate";
|
||||||
|
// export class StoreAssist{
|
||||||
|
// private pinia:any=createPinia();
|
||||||
|
// constructor() {
|
||||||
|
// this.pinia.use(piniaPluginPersistedstate);
|
||||||
|
// }
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// }
|
||||||
|
|
||||||
|
|
||||||
98
src/common/Unit/StringAssist.ts
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
export class StringAssist {
|
||||||
|
static NoHtml(html: string): string {
|
||||||
|
return html;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static PhoneToStr (e:string) {
|
||||||
|
return e.substring(0,3)+'****'+e.substring((e.length-2),(e.length))
|
||||||
|
}
|
||||||
|
// 数量过万处理
|
||||||
|
static NumToStr (sum:number) {
|
||||||
|
if(sum>=10000){
|
||||||
|
return (sum/10000).toFixed('2')+'w'
|
||||||
|
}else{
|
||||||
|
return sum
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// 帖子距离现在多久
|
||||||
|
static DiffTimeTostring(dateTime: string): string {
|
||||||
|
let result = 0;
|
||||||
|
let time = Date.parse(dateTime);
|
||||||
|
let timestamp = Date.parse(new Date().toString());
|
||||||
|
if ((timestamp - time) / 1000 < 60) {
|
||||||
|
result = (timestamp - time) / 1000;
|
||||||
|
result = result < 0 ? 0 : result;
|
||||||
|
return result.toFixed(0) + '秒前';
|
||||||
|
} else if ((timestamp - time) / 1000 / 60 < 60) {
|
||||||
|
return ((timestamp - time) / 1000 / 60).toFixed(0) + '分钟前';
|
||||||
|
} else if ((timestamp - time) / 1000 / 60 / 60 < 24) {
|
||||||
|
return ((timestamp - time) / 1000 / 60 / 60).toFixed(0) + '小时前';
|
||||||
|
} else if ((timestamp - time) / 1000 / 60 / 60 / 24 < 31) {
|
||||||
|
return ((timestamp - time) / 1000 / 60 / 60 / 24).toFixed(0) + '天前';
|
||||||
|
} else {
|
||||||
|
return this.formatDate(time, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static formatDate(time:any,type:number):string
|
||||||
|
{
|
||||||
|
const date = new Date(time);
|
||||||
|
const year = date.getFullYear();
|
||||||
|
const month = String(date.getMonth() + 1).padStart(2, '0'); // 月份从0开始,所以加1,并用0填充
|
||||||
|
const day = String(date.getDate()).padStart(2, '0'); // 用0填充
|
||||||
|
const hours = String(date.getHours()).padStart(2, '0'); // 用0填充
|
||||||
|
const minutes = String(date.getMinutes()).padStart(2, '0'); // 用0填充
|
||||||
|
const seconds = String(date.getSeconds()).padStart(2, '0'); // 用0填充
|
||||||
|
if(type==0)
|
||||||
|
{
|
||||||
|
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
|
||||||
|
}
|
||||||
|
else if(type==1)
|
||||||
|
{
|
||||||
|
return `${year}-${month}-${day} ${hours}:${minutes}`;
|
||||||
|
}else if(type==2)
|
||||||
|
{
|
||||||
|
return `${year}-${month}-${day}`;
|
||||||
|
}else if(type==3){
|
||||||
|
return `${month}-${day} ${hours}:${minutes}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
else{
|
||||||
|
return `${hours}:${minutes}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// 聊天时间显示
|
||||||
|
static ChatTimeTostring(dateTime: string,upTime:string): string {
|
||||||
|
let time = Date.parse(dateTime);
|
||||||
|
let timestamp = Date.parse(upTime);
|
||||||
|
if (( time - timestamp) / 1000 / 60 < 10) {
|
||||||
|
return '0';
|
||||||
|
} else{
|
||||||
|
return this.formatDate(time, 3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// 去除两侧的空格
|
||||||
|
|
||||||
|
static trim(str:string){
|
||||||
|
const reg = /^\s+|\s+$/g;
|
||||||
|
return str.replace(reg,'');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 计算两个时分秒差值
|
||||||
|
static timesfm(dateTime: string,upTime:string){
|
||||||
|
|
||||||
|
let time = Date.parse('2000-01-01 '+dateTime);
|
||||||
|
let timestamp = Date.parse('2000-01-01 '+upTime);
|
||||||
|
return ((timestamp - time) / 1000 / 60).toFixed(0) + '分钟';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
19
src/common/Unit/UploadAssist.ts
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
export class UploadAssist {
|
||||||
|
static Upload(url: string, path: string, fromData: any) {
|
||||||
|
return new Promise(function(resolve, reject) {
|
||||||
|
uni.uploadFile({
|
||||||
|
url: url, //仅为示例,非真实的接口地址
|
||||||
|
filePath: path,
|
||||||
|
name: 'file',
|
||||||
|
formData: fromData,
|
||||||
|
success: (uploadFileRes) => {
|
||||||
|
resolve(uploadFileRes);
|
||||||
|
},
|
||||||
|
fail: (err) => {
|
||||||
|
reject(err);
|
||||||
|
|
||||||
|
},
|
||||||
|
});
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
54
src/components/ImageCropper.vue
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
<template>
|
||||||
|
<view>
|
||||||
|
<view class="" v-if="props.show">
|
||||||
|
<qf-image-cropper :src="props.url" :width="props.width" :height="props.height" :radius="20"
|
||||||
|
@crop="handleCrop"></qf-image-cropper>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, watch } from 'vue';
|
||||||
|
import { onBeforeUnmount, onMounted, reactive } from 'vue';
|
||||||
|
import { Service, BycAdminService } from '@/Service/Byc/BycAdminService'
|
||||||
|
// const props = defineProps({
|
||||||
|
// show : string,
|
||||||
|
// // url : string,
|
||||||
|
// // width : number,
|
||||||
|
// // height:number,
|
||||||
|
// // upType:string,
|
||||||
|
// // retFun:Function
|
||||||
|
// });
|
||||||
|
|
||||||
|
|
||||||
|
let props = defineProps(["show","url",'width','height','upType',"retFun","imgName"]);
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
const handleCrop = (e) => {
|
||||||
|
let arr = e.tempFilePath.split('.')
|
||||||
|
let name = arr[arr.length - 1]
|
||||||
|
Service.UpLoadMedia(props.upType, name, '', e.tempFilePath).then(rest => {
|
||||||
|
if (rest.code == 0) {
|
||||||
|
uni.showToast({
|
||||||
|
title: rest.msg,
|
||||||
|
icon: 'none'
|
||||||
|
})
|
||||||
|
props.retFun( {name:props.imgName,url:rest.data.file})
|
||||||
|
} else {
|
||||||
|
uni.showToast({
|
||||||
|
title: rest.msg,
|
||||||
|
icon: 'none'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
|
||||||
|
</style>
|
||||||
299
src/components/liy-select/liy-select.vue
Normal file
@@ -0,0 +1,299 @@
|
|||||||
|
<template>
|
||||||
|
<view class="content">
|
||||||
|
<view class="liy-select-fixed" :class="openClass">
|
||||||
|
<view class="liy-search-warp">
|
||||||
|
<view class="liy-search-input">
|
||||||
|
<view class="lsi-warp">
|
||||||
|
<image src="../../static/liy-select/images/search.png" mode="widthFix"
|
||||||
|
class="lsi-icon"></image>
|
||||||
|
<input class="lsi-input" v-model="keyword" placeholder="请输入搜索内容" @input="getParamsList" />
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<scroll-view scroll-y="true" class="scroll-Y" @scrolltolower="lower">
|
||||||
|
<view class="liy-search-list" v-if="outPutList.length > 0">
|
||||||
|
<view class="liy-search-li" v-for="(item,index) in outPutList" :key="index"
|
||||||
|
@click="mapSelectMenu(item,index)">
|
||||||
|
<view class="liy-search-left">
|
||||||
|
<view class="liy-search-title" v-if="titleKey">{{item[titleKey]}}
|
||||||
|
</view>
|
||||||
|
<view class="liy-search-title" v-else>未选择标题
|
||||||
|
</view>
|
||||||
|
<view class="liy-search-desc" v-if="subtitleKey">{{item[subtitleKey]}}</view>
|
||||||
|
</view>
|
||||||
|
<view class="liy-search-icon" v-if="mapSelectIndex == index">
|
||||||
|
<image class="lsi-icon" src="../../static/liy-select/images/check_mark.png"
|
||||||
|
mode="widthFix"></image>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view class="liy-loading">
|
||||||
|
<image class="liy-loading-img" src="../../static/liy-select/images/complete.png"
|
||||||
|
mode="widthFix"></image>
|
||||||
|
<view class="liy-loading-text">加载完成</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view v-else class="liy-void">
|
||||||
|
<image class="liy-void-img" src="../../static/liy-select/images/void.png"
|
||||||
|
mode="widthFix"></image>
|
||||||
|
<view class="liy-void-text">未查询到内容</view>
|
||||||
|
</view>
|
||||||
|
</scroll-view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view class="liy-overlay" @click="closeOverlay" v-if="open == true"></view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name:'liy-select',
|
||||||
|
props:{
|
||||||
|
list:{
|
||||||
|
type:Array,
|
||||||
|
default:[]
|
||||||
|
},
|
||||||
|
titleKey:{
|
||||||
|
type:[String,null],
|
||||||
|
default: null
|
||||||
|
},
|
||||||
|
subtitleKey:{
|
||||||
|
type:[String,null],
|
||||||
|
default:null
|
||||||
|
},
|
||||||
|
open:{
|
||||||
|
type:Boolean,
|
||||||
|
default:false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
mapSelectIndex: null,
|
||||||
|
keyword: "",
|
||||||
|
outPutList: [],
|
||||||
|
openClass:""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
list (newList, oldList) {
|
||||||
|
this.getParamsList();
|
||||||
|
},
|
||||||
|
open(newOpen,oldOpen){
|
||||||
|
if(newOpen == true){
|
||||||
|
this.openClass = "show";
|
||||||
|
}else{
|
||||||
|
this.openClass = "hide";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.getParamsList();
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
mapSelectMenu(item,index) {
|
||||||
|
this.mapSelectIndex = index;
|
||||||
|
this.$emit("change",item,index)
|
||||||
|
this.$emit("close");
|
||||||
|
},
|
||||||
|
getParamsList() {
|
||||||
|
var selectList = this.list;
|
||||||
|
this.mapSelectIndex = null;
|
||||||
|
if (!this.keyword) {
|
||||||
|
this.outPutList = selectList;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
var arr = [];
|
||||||
|
for (var i = 0; i < selectList.length; i++) {
|
||||||
|
let item = selectList[i];
|
||||||
|
if (item[this.titleKey].indexOf(this.keyword) > -1) {
|
||||||
|
arr.push(item);
|
||||||
|
} else {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.outPutList = arr;
|
||||||
|
},
|
||||||
|
lower() {
|
||||||
|
|
||||||
|
},
|
||||||
|
closeOverlay() {
|
||||||
|
this.$emit("close");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.liy-overlay {
|
||||||
|
transition-duration: 300ms;
|
||||||
|
transition-timing-function: ease-out;
|
||||||
|
position: fixed;
|
||||||
|
inset: 0px;
|
||||||
|
z-index: 100;
|
||||||
|
background-color: rgba(0, 0, 0, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.liy-select-fixed {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
z-index: 150;
|
||||||
|
transition: transform .5s ease-in-out;
|
||||||
|
transform: translateY(calc(100% + 40rpx));
|
||||||
|
&.show {
|
||||||
|
animation: showHandler 0.3s forwards;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.hide {
|
||||||
|
animation: hideHandler 0.3s forwards;
|
||||||
|
}
|
||||||
|
|
||||||
|
.liy-search-warp {
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 10rpx 10rpx 0 0;
|
||||||
|
|
||||||
|
.liy-search-input {
|
||||||
|
padding: 30rpx 40rpx;
|
||||||
|
font-size: 28rpx;
|
||||||
|
|
||||||
|
.lsi-warp {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
border: 2rpx solid #e3e3e3;
|
||||||
|
padding: 16rpx 20rpx;
|
||||||
|
border-radius: 50rpx;
|
||||||
|
|
||||||
|
.lsi-icon {
|
||||||
|
width: 30rpx;
|
||||||
|
height: 30rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lsi-input {
|
||||||
|
width: 90%;
|
||||||
|
padding-left: 16rpx;
|
||||||
|
font-size: 28rpx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.liy-search-list {
|
||||||
|
.liy-search-li {
|
||||||
|
padding: 20rpx 30rpx;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
border-bottom: 1rpx solid #f7f7f7;
|
||||||
|
min-height: 50rpx;
|
||||||
|
|
||||||
|
.liy-search-left {
|
||||||
|
width: 90%;
|
||||||
|
|
||||||
|
.liy-search-title {
|
||||||
|
font-size: 28rpx;
|
||||||
|
line-height: 36rpx;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.liy-search-desc {
|
||||||
|
padding-top: 10rpx;
|
||||||
|
font-size: 24rpx;
|
||||||
|
line-height: 36rpx;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.liy-search-icon {
|
||||||
|
.lsi-icon {
|
||||||
|
width: 40rpx;
|
||||||
|
height: 40rpx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.scroll-Y {
|
||||||
|
height: 500rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.liy-loading {
|
||||||
|
color: #333;
|
||||||
|
font-size: 26rpx;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 20rpx 0;
|
||||||
|
|
||||||
|
.liy-loading-img {
|
||||||
|
width: 32rpx;
|
||||||
|
height: 32rpx;
|
||||||
|
// animation: turn 3s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.liy-loading-text {
|
||||||
|
padding-left: 12rpx;
|
||||||
|
padding-right: 32rpx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes turn {
|
||||||
|
0% {
|
||||||
|
transform: rotate(0deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
25% {
|
||||||
|
transform: rotate(90deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
50% {
|
||||||
|
transform: rotate(180deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
75% {
|
||||||
|
transform: rotate(270deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes showHandler {
|
||||||
|
0% {
|
||||||
|
transform: translateY(calc(100% + 40rpx));
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes hideHandler {
|
||||||
|
0% {
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
100% {
|
||||||
|
transform: translateY(calc(100% + 40rpx));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.liy-void {
|
||||||
|
text-align: center;
|
||||||
|
padding-top: 60rpx;
|
||||||
|
|
||||||
|
.liy-void-img {
|
||||||
|
width: 40%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.liy-void-text {
|
||||||
|
padding-top: 30rpx;
|
||||||
|
font-size: 26rpx;
|
||||||
|
color: #909090;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
8
src/env.d.ts
vendored
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
/// <reference types="vite/client" />
|
||||||
|
|
||||||
|
declare module '*.vue' {
|
||||||
|
import { DefineComponent } from 'vue'
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/ban-types
|
||||||
|
const component: DefineComponent<{}, {}, any>
|
||||||
|
export default component
|
||||||
|
}
|
||||||
13
src/main.ts
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import { createSSRApp } from "vue";
|
||||||
|
import App from "./App.vue";
|
||||||
|
|
||||||
|
import uviewPlus from 'uview-plus'
|
||||||
|
|
||||||
|
export function createApp() {
|
||||||
|
const app = createSSRApp(App);
|
||||||
|
|
||||||
|
app.use(uviewPlus)
|
||||||
|
return {
|
||||||
|
app,
|
||||||
|
};
|
||||||
|
}
|
||||||
143
src/manifest.json
Normal file
@@ -0,0 +1,143 @@
|
|||||||
|
{
|
||||||
|
"name" : "科讯代购",
|
||||||
|
"appid" : "__UNI__06C2D6A",
|
||||||
|
"description" : "",
|
||||||
|
"versionName" : "1.0.8",
|
||||||
|
"versionCode" : 108,
|
||||||
|
"transformPx" : false,
|
||||||
|
/* 5+App特有相关 */
|
||||||
|
"app-plus" : {
|
||||||
|
"compatible" : {
|
||||||
|
"ignoreVersion" : true //true表示忽略版本检查提示框,HBuilderX1.9.0及以上版本支持
|
||||||
|
},
|
||||||
|
"usingComponents" : true,
|
||||||
|
"nvueStyleCompiler" : "uni-app",
|
||||||
|
"compilerVersion" : 3,
|
||||||
|
"splashscreen" : {
|
||||||
|
"alwaysShowBeforeRender" : false,
|
||||||
|
"waiting" : false,
|
||||||
|
"autoclose" : true,
|
||||||
|
"delay" : 0
|
||||||
|
},
|
||||||
|
/* 模块配置 */
|
||||||
|
"modules" : {
|
||||||
|
"Barcode" : {},
|
||||||
|
"Maps" : {},
|
||||||
|
"Geolocation" : {}
|
||||||
|
},
|
||||||
|
/* 应用发布信息 */
|
||||||
|
"distribute" : {
|
||||||
|
/* android打包配置 */
|
||||||
|
"android" : {
|
||||||
|
"permissions" : [
|
||||||
|
"<uses-permission android:name=\"android.permission.CHANGE_NETWORK_STATE\"/>",
|
||||||
|
"<uses-permission android:name=\"android.permission.MOUNT_UNMOUNT_FILESYSTEMS\"/>",
|
||||||
|
"<uses-permission android:name=\"android.permission.VIBRATE\"/>",
|
||||||
|
"<uses-permission android:name=\"android.permission.READ_LOGS\"/>",
|
||||||
|
"<uses-permission android:name=\"android.permission.ACCESS_WIFI_STATE\"/>",
|
||||||
|
"<uses-feature android:name=\"android.hardware.camera.autofocus\"/>",
|
||||||
|
"<uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\"/>",
|
||||||
|
"<uses-permission android:name=\"android.permission.CAMERA\"/>",
|
||||||
|
"<uses-permission android:name=\"android.permission.GET_ACCOUNTS\"/>",
|
||||||
|
"<uses-permission android:name=\"android.permission.READ_PHONE_STATE\"/>",
|
||||||
|
"<uses-permission android:name=\"android.permission.CHANGE_WIFI_STATE\"/>",
|
||||||
|
"<uses-permission android:name=\"android.permission.WAKE_LOCK\"/>",
|
||||||
|
"<uses-permission android:name=\"android.permission.FLASHLIGHT\"/>",
|
||||||
|
"<uses-feature android:name=\"android.hardware.camera\"/>",
|
||||||
|
"<uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>"
|
||||||
|
],
|
||||||
|
"minSdkVersion" : 25,
|
||||||
|
"targetSdkVersion" : 25,
|
||||||
|
"abiFilters" : [ "armeabi-v7a", "arm64-v8a", "x86" ]
|
||||||
|
},
|
||||||
|
/* ios打包配置 */
|
||||||
|
"ios" : {
|
||||||
|
"idfa" : false,
|
||||||
|
"dSYMs" : false
|
||||||
|
},
|
||||||
|
/* SDK配置 */
|
||||||
|
"sdkConfigs" : {
|
||||||
|
"ad" : {},
|
||||||
|
"maps" : {
|
||||||
|
"amap" : {
|
||||||
|
"name" : "amapZAvZjTHj",
|
||||||
|
"appkey_ios" : "3caf9e6f01b0085be1e75e0d0e281fe7",
|
||||||
|
"appkey_android" : "3caf9e6f01b0085be1e75e0d0e281fe7"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"geolocation" : {
|
||||||
|
"amap" : {
|
||||||
|
"name" : "amapZAvZjTHj",
|
||||||
|
"__platform__" : [ "android" ],
|
||||||
|
"appkey_ios" : "",
|
||||||
|
"appkey_android" : "3caf9e6f01b0085be1e75e0d0e281fe7"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"icons" : {
|
||||||
|
"android" : {
|
||||||
|
"hdpi" : "unpackage/res/icons/72x72.png",
|
||||||
|
"xhdpi" : "unpackage/res/icons/96x96.png",
|
||||||
|
"xxhdpi" : "unpackage/res/icons/144x144.png",
|
||||||
|
"xxxhdpi" : "unpackage/res/icons/192x192.png"
|
||||||
|
},
|
||||||
|
"ios" : {
|
||||||
|
"appstore" : "unpackage/res/icons/1024x1024.png",
|
||||||
|
"ipad" : {
|
||||||
|
"app" : "unpackage/res/icons/76x76.png",
|
||||||
|
"app@2x" : "unpackage/res/icons/152x152.png",
|
||||||
|
"notification" : "unpackage/res/icons/20x20.png",
|
||||||
|
"notification@2x" : "unpackage/res/icons/40x40.png",
|
||||||
|
"proapp@2x" : "unpackage/res/icons/167x167.png",
|
||||||
|
"settings" : "unpackage/res/icons/29x29.png",
|
||||||
|
"settings@2x" : "unpackage/res/icons/58x58.png",
|
||||||
|
"spotlight" : "unpackage/res/icons/40x40.png",
|
||||||
|
"spotlight@2x" : "unpackage/res/icons/80x80.png"
|
||||||
|
},
|
||||||
|
"iphone" : {
|
||||||
|
"app@2x" : "unpackage/res/icons/120x120.png",
|
||||||
|
"app@3x" : "unpackage/res/icons/180x180.png",
|
||||||
|
"notification@2x" : "unpackage/res/icons/40x40.png",
|
||||||
|
"notification@3x" : "unpackage/res/icons/60x60.png",
|
||||||
|
"settings@2x" : "unpackage/res/icons/58x58.png",
|
||||||
|
"settings@3x" : "unpackage/res/icons/87x87.png",
|
||||||
|
"spotlight@2x" : "unpackage/res/icons/80x80.png",
|
||||||
|
"spotlight@3x" : "unpackage/res/icons/120x120.png"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/* 快应用特有相关 */
|
||||||
|
"quickapp" : {},
|
||||||
|
/* 小程序特有相关 */
|
||||||
|
"mp-weixin" : {
|
||||||
|
"appid" : "",
|
||||||
|
"setting" : {
|
||||||
|
"urlCheck" : false
|
||||||
|
},
|
||||||
|
"usingComponents" : true
|
||||||
|
},
|
||||||
|
"mp-alipay" : {
|
||||||
|
"usingComponents" : true
|
||||||
|
},
|
||||||
|
"mp-baidu" : {
|
||||||
|
"usingComponents" : true
|
||||||
|
},
|
||||||
|
"mp-toutiao" : {
|
||||||
|
"usingComponents" : true
|
||||||
|
},
|
||||||
|
"uniStatistics" : {
|
||||||
|
"enable" : false
|
||||||
|
},
|
||||||
|
"vueVersion" : "3",
|
||||||
|
"h5" : {
|
||||||
|
"sdkConfigs" : {
|
||||||
|
"maps" : {
|
||||||
|
"qqmap" : {
|
||||||
|
"key" : "7DIBZ-K4HCJ-ZR2FE-FOOOP-SALFT-RLFYW"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
16
src/package.json
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"id": "liy-select",
|
||||||
|
"name": "可搜索的下拉选择框",
|
||||||
|
"displayName": "可搜索的下拉选择框",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "通过搜索关键字可以进行 下拉筛选",
|
||||||
|
"keywords": [
|
||||||
|
"uni-ui|select"
|
||||||
|
],
|
||||||
|
"dcloudext": {
|
||||||
|
"category": [
|
||||||
|
"前端组件",
|
||||||
|
"通用组件"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
53
src/pages.json
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
{
|
||||||
|
"easycom": {
|
||||||
|
// 注意一定要放在custom里,否则无效,https://ask.dcloud.net.cn/question/131175
|
||||||
|
"custom": {
|
||||||
|
"^u--(.*)": "uview-plus/components/u-$1/u-$1.vue",
|
||||||
|
"^up-(.*)": "uview-plus/components/u-$1/u-$1.vue",
|
||||||
|
"^u-([^-].*)": "uview-plus/components/u-$1/u-$1.vue"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"pages": [ //pages数组中第一项表示应用启动页,参考:https://uniapp.dcloud.io/collocation/pages
|
||||||
|
|
||||||
|
{
|
||||||
|
|
||||||
|
"path": "pages/index/index",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "门店运营",
|
||||||
|
"navigationBarBackgroundColor": "#36394D",
|
||||||
|
"navigationStyle": "custom",
|
||||||
|
"backgroundColor": "#F8F8F8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path" : "pages/index/user",
|
||||||
|
"style" :
|
||||||
|
{
|
||||||
|
"navigationBarTitleText" : "我的"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"globalStyle": {
|
||||||
|
"navigationBarTextStyle": "white",
|
||||||
|
"navigationBarTitleText": "v派商家",
|
||||||
|
"navigationBarBackgroundColor": "#F84F28",
|
||||||
|
"backgroundColor": "#F8F8F8"
|
||||||
|
},
|
||||||
|
"tabBar": {
|
||||||
|
"color": "#000",
|
||||||
|
"selectedColor": "#000",
|
||||||
|
"backgroundColor": "#FFFFFF",
|
||||||
|
"list": [ {
|
||||||
|
"pagePath": "pages/index/index",
|
||||||
|
"iconPath": "static/tab/01.png",
|
||||||
|
"selectedIconPath": "static/tab/02.png",
|
||||||
|
"text": "主页"
|
||||||
|
}, {
|
||||||
|
"pagePath": "pages/index/user",
|
||||||
|
"iconPath": "static/tab/01.png",
|
||||||
|
"selectedIconPath": "static/tab/02.png",
|
||||||
|
"text": "我的"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
29
src/pages/index/index.vue
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
<template>
|
||||||
|
<view>
|
||||||
|
|
||||||
|
11111
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref } from 'vue';
|
||||||
|
import { onShow, onLoad } from '@dcloudio/uni-app';
|
||||||
|
import { NvpAgentService, Service } from '@/Service/Nvp/NvpAgentService';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
onShow(() => {
|
||||||
|
|
||||||
|
})
|
||||||
|
onLoad(() => {
|
||||||
|
|
||||||
|
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
page {
|
||||||
|
background-color: #fdfdfd;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
19
src/pages/index/user.vue
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
<template>
|
||||||
|
<view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { onShow,onLoad } from "@dcloudio/uni-app";
|
||||||
|
onLoad(() => {
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
onShow(() => {
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
</style>
|
||||||
6
src/shime-uni.d.ts
vendored
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
export {}
|
||||||
|
|
||||||
|
declare module "vue" {
|
||||||
|
type Hooks = App.AppInstance & Page.PageInstance;
|
||||||
|
interface ComponentCustomOptions extends Hooks {}
|
||||||
|
}
|
||||||
BIN
src/static/liy-select/images/check_mark.png
Normal file
|
After Width: | Height: | Size: 33 KiB |
BIN
src/static/liy-select/images/complete.png
Normal file
|
After Width: | Height: | Size: 53 KiB |
BIN
src/static/liy-select/images/loading.png
Normal file
|
After Width: | Height: | Size: 24 KiB |
BIN
src/static/liy-select/images/search.png
Normal file
|
After Width: | Height: | Size: 39 KiB |
BIN
src/static/liy-select/images/void.png
Normal file
|
After Width: | Height: | Size: 53 KiB |
BIN
src/static/tab/01.png
Normal file
|
After Width: | Height: | Size: 5.4 KiB |
BIN
src/static/tab/02.png
Normal file
|
After Width: | Height: | Size: 5.6 KiB |
BIN
src/static/tab/03.png
Normal file
|
After Width: | Height: | Size: 8.4 KiB |
BIN
src/static/tab/04.png
Normal file
|
After Width: | Height: | Size: 8.7 KiB |
BIN
src/static/tab/05.png
Normal file
|
After Width: | Height: | Size: 6.8 KiB |
BIN
src/static/tab/06.png
Normal file
|
After Width: | Height: | Size: 7.0 KiB |
BIN
src/static/tab/07.png
Normal file
|
After Width: | Height: | Size: 6.6 KiB |
BIN
src/static/tab/08.png
Normal file
|
After Width: | Height: | Size: 6.8 KiB |
BIN
src/static/tab/09.png
Normal file
|
After Width: | Height: | Size: 5.9 KiB |
BIN
src/static/tab/10.png
Normal file
|
After Width: | Height: | Size: 6.1 KiB |
1
src/types/uview.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
declare module "uview-plus"
|
||||||
78
src/uni.scss
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
/**
|
||||||
|
* 这里是uni-app内置的常用样式变量
|
||||||
|
*
|
||||||
|
* uni-app 官方扩展插件及插件市场(https://ext.dcloud.net.cn)上很多三方插件均使用了这些样式变量
|
||||||
|
* 如果你是插件开发者,建议你使用scss预处理,并在插件代码中直接使用这些变量(无需 import 这个文件),方便用户通过搭积木的方式开发整体风格一致的App
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
@import 'uview-plus/theme.scss';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 如果你是App开发者(插件使用者),你可以通过修改这些变量来定制自己的插件主题,实现自定义主题功能
|
||||||
|
*
|
||||||
|
* 如果你的项目同样使用了scss预处理,你也可以直接在你的 scss 代码中使用如下变量,同时无需 import 这个文件
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* 颜色变量 */
|
||||||
|
|
||||||
|
/* 行为相关颜色 */
|
||||||
|
$uni-color-primary: #007aff;
|
||||||
|
$uni-color-success: #4cd964;
|
||||||
|
$uni-color-warning: #f0ad4e;
|
||||||
|
$uni-color-error: #dd524d;
|
||||||
|
|
||||||
|
/* 文字基本颜色 */
|
||||||
|
$uni-text-color: #333; // 基本色
|
||||||
|
$uni-text-color-inverse: #fff; // 反色
|
||||||
|
$uni-text-color-grey: #999; // 辅助灰色,如加载更多的提示信息
|
||||||
|
$uni-text-color-placeholder: #808080;
|
||||||
|
$uni-text-color-disable: #c0c0c0;
|
||||||
|
|
||||||
|
/* 背景颜色 */
|
||||||
|
$uni-bg-color: #fff;
|
||||||
|
$uni-bg-color-grey: #f8f8f8;
|
||||||
|
$uni-bg-color-hover: #f1f1f1; // 点击状态颜色
|
||||||
|
$uni-bg-color-mask: rgba(0, 0, 0, 0.4); // 遮罩颜色
|
||||||
|
|
||||||
|
/* 边框颜色 */
|
||||||
|
$uni-border-color: #c8c7cc;
|
||||||
|
|
||||||
|
/* 尺寸变量 */
|
||||||
|
|
||||||
|
/* 文字尺寸 */
|
||||||
|
$uni-font-size-sm: 12px;
|
||||||
|
$uni-font-size-base: 14px;
|
||||||
|
$uni-font-size-lg: 16;
|
||||||
|
|
||||||
|
/* 图片尺寸 */
|
||||||
|
$uni-img-size-sm: 20px;
|
||||||
|
$uni-img-size-base: 26px;
|
||||||
|
$uni-img-size-lg: 40px;
|
||||||
|
|
||||||
|
/* Border Radius */
|
||||||
|
$uni-border-radius-sm: 2px;
|
||||||
|
$uni-border-radius-base: 3px;
|
||||||
|
$uni-border-radius-lg: 6px;
|
||||||
|
$uni-border-radius-circle: 50%;
|
||||||
|
|
||||||
|
/* 水平间距 */
|
||||||
|
$uni-spacing-row-sm: 5px;
|
||||||
|
$uni-spacing-row-base: 10px;
|
||||||
|
$uni-spacing-row-lg: 15px;
|
||||||
|
|
||||||
|
/* 垂直间距 */
|
||||||
|
$uni-spacing-col-sm: 4px;
|
||||||
|
$uni-spacing-col-base: 8px;
|
||||||
|
$uni-spacing-col-lg: 12px;
|
||||||
|
|
||||||
|
/* 透明度 */
|
||||||
|
$uni-opacity-disabled: 0.3; // 组件禁用态的透明度
|
||||||
|
|
||||||
|
/* 文章场景相关 */
|
||||||
|
$uni-color-title: #2c405a; // 文章标题颜色
|
||||||
|
$uni-font-size-title: 20px;
|
||||||
|
$uni-color-subtitle: #555; // 二级标题颜色
|
||||||
|
$uni-font-size-subtitle: 18px;
|
||||||
|
$uni-color-paragraph: #3f536e; // 文章段落颜色
|
||||||
|
$uni-font-size-paragraph: 15px;
|
||||||
372
src/uni_modules/lime-echart/components/l-echart/canvas.js
Normal file
@@ -0,0 +1,372 @@
|
|||||||
|
const cacheChart = {}
|
||||||
|
const fontSizeReg = /([\d\.]+)px/;
|
||||||
|
class EventEmit {
|
||||||
|
constructor() {
|
||||||
|
this.__events = {};
|
||||||
|
}
|
||||||
|
on(type, listener) {
|
||||||
|
if (!type || !listener) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const events = this.__events[type] || [];
|
||||||
|
events.push(listener);
|
||||||
|
this.__events[type] = events;
|
||||||
|
}
|
||||||
|
emit(type, e) {
|
||||||
|
if (type.constructor === Object) {
|
||||||
|
e = type;
|
||||||
|
type = e && e.type;
|
||||||
|
}
|
||||||
|
if (!type) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const events = this.__events[type];
|
||||||
|
if (!events || !events.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
events.forEach((listener) => {
|
||||||
|
listener.call(this, e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
off(type, listener) {
|
||||||
|
const __events = this.__events;
|
||||||
|
const events = __events[type];
|
||||||
|
if (!events || !events.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!listener) {
|
||||||
|
delete __events[type];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (let i = 0, len = events.length; i < len; i++) {
|
||||||
|
if (events[i] === listener) {
|
||||||
|
events.splice(i, 1);
|
||||||
|
i--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
class Image {
|
||||||
|
constructor() {
|
||||||
|
this.currentSrc = null
|
||||||
|
this.naturalHeight = 0
|
||||||
|
this.naturalWidth = 0
|
||||||
|
this.width = 0
|
||||||
|
this.height = 0
|
||||||
|
this.tagName = 'IMG'
|
||||||
|
}
|
||||||
|
set src(src) {
|
||||||
|
this.currentSrc = src
|
||||||
|
uni.getImageInfo({
|
||||||
|
src,
|
||||||
|
success: (res) => {
|
||||||
|
this.naturalWidth = this.width = res.width
|
||||||
|
this.naturalHeight = this.height = res.height
|
||||||
|
this.onload()
|
||||||
|
},
|
||||||
|
fail: () => {
|
||||||
|
this.onerror()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
get src() {
|
||||||
|
return this.currentSrc
|
||||||
|
}
|
||||||
|
}
|
||||||
|
class OffscreenCanvas {
|
||||||
|
constructor(ctx, com, canvasId) {
|
||||||
|
this.tagName = 'canvas'
|
||||||
|
this.com = com
|
||||||
|
this.canvasId = canvasId
|
||||||
|
this.ctx = ctx
|
||||||
|
}
|
||||||
|
set width(w) {
|
||||||
|
this.com.offscreenWidth = w
|
||||||
|
}
|
||||||
|
set height(h) {
|
||||||
|
this.com.offscreenHeight = h
|
||||||
|
}
|
||||||
|
get width() {
|
||||||
|
return this.com.offscreenWidth || 0
|
||||||
|
}
|
||||||
|
get height() {
|
||||||
|
return this.com.offscreenHeight || 0
|
||||||
|
}
|
||||||
|
getContext(type) {
|
||||||
|
return this.ctx
|
||||||
|
}
|
||||||
|
getImageData() {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
this.com.$nextTick(() => {
|
||||||
|
uni.canvasGetImageData({
|
||||||
|
x:0,
|
||||||
|
y:0,
|
||||||
|
width: this.com.offscreenWidth,
|
||||||
|
height: this.com.offscreenHeight,
|
||||||
|
canvasId: this.canvasId,
|
||||||
|
success: (res) => {
|
||||||
|
resolve(res)
|
||||||
|
},
|
||||||
|
fail: (err) => {
|
||||||
|
reject(err)
|
||||||
|
},
|
||||||
|
}, this.com)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export class Canvas {
|
||||||
|
constructor(ctx, com, isNew, canvasNode={}) {
|
||||||
|
cacheChart[com.canvasId] = {ctx}
|
||||||
|
this.canvasId = com.canvasId;
|
||||||
|
this.chart = null;
|
||||||
|
this.isNew = isNew
|
||||||
|
this.tagName = 'canvas'
|
||||||
|
this.canvasNode = canvasNode;
|
||||||
|
this.com = com;
|
||||||
|
if (!isNew) {this._initStyle(ctx)}
|
||||||
|
this._initEvent();
|
||||||
|
this._ee = new EventEmit()
|
||||||
|
}
|
||||||
|
getContext(type) {
|
||||||
|
if (type === '2d') {
|
||||||
|
return this.ctx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setChart(chart) {
|
||||||
|
this.chart = chart;
|
||||||
|
}
|
||||||
|
createOffscreenCanvas(param){
|
||||||
|
if(!this.children) {
|
||||||
|
this.com.isOffscreenCanvas = true
|
||||||
|
this.com.offscreenWidth = param.width||300
|
||||||
|
this.com.offscreenHeight = param.height||300
|
||||||
|
const com = this.com
|
||||||
|
const canvasId = this.com.offscreenCanvasId
|
||||||
|
const context = uni.createCanvasContext(canvasId, this.com)
|
||||||
|
this._initStyle(context)
|
||||||
|
this.children = new OffscreenCanvas(context, com, canvasId)
|
||||||
|
}
|
||||||
|
return this.children
|
||||||
|
}
|
||||||
|
appendChild(child) {
|
||||||
|
console.log('child', child)
|
||||||
|
}
|
||||||
|
dispatchEvent(type, e) {
|
||||||
|
if(typeof type == 'object') {
|
||||||
|
this._ee.emit(type.type, type);
|
||||||
|
} else {
|
||||||
|
this._ee.emit(type, e);
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
attachEvent() {
|
||||||
|
}
|
||||||
|
detachEvent() {
|
||||||
|
}
|
||||||
|
addEventListener(type, listener) {
|
||||||
|
this._ee.on(type, listener)
|
||||||
|
}
|
||||||
|
removeEventListener(type, listener) {
|
||||||
|
this._ee.off(type, listener)
|
||||||
|
}
|
||||||
|
_initCanvas(zrender, ctx) {
|
||||||
|
zrender.util.getContext = function() {
|
||||||
|
return ctx;
|
||||||
|
};
|
||||||
|
zrender.util.$override('measureText', function(text, font) {
|
||||||
|
ctx.font = font || '12px sans-serif';
|
||||||
|
return ctx.measureText(text, font);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
_initStyle(ctx, child) {
|
||||||
|
const styles = [
|
||||||
|
'fillStyle',
|
||||||
|
'strokeStyle',
|
||||||
|
'fontSize',
|
||||||
|
'globalAlpha',
|
||||||
|
'opacity',
|
||||||
|
'textAlign',
|
||||||
|
'textBaseline',
|
||||||
|
'shadow',
|
||||||
|
'lineWidth',
|
||||||
|
'lineCap',
|
||||||
|
'lineJoin',
|
||||||
|
'lineDash',
|
||||||
|
'miterLimit',
|
||||||
|
'font'
|
||||||
|
];
|
||||||
|
const colorReg = /#([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])\b/g;
|
||||||
|
styles.forEach(style => {
|
||||||
|
Object.defineProperty(ctx, style, {
|
||||||
|
set: value => {
|
||||||
|
if (style === 'font' && fontSizeReg.test(value)) {
|
||||||
|
const match = fontSizeReg.exec(value);
|
||||||
|
ctx.setFontSize(match[1]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (style === 'opacity') {
|
||||||
|
ctx.setGlobalAlpha(value)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (style !== 'fillStyle' && style !== 'strokeStyle' || value !== 'none' && value !== null) {
|
||||||
|
// #ifdef H5 || APP-PLUS || MP-BAIDU
|
||||||
|
if(typeof value == 'object') {
|
||||||
|
if (value.hasOwnProperty('colorStop') || value.hasOwnProperty('colors')) {
|
||||||
|
ctx['set' + style.charAt(0).toUpperCase() + style.slice(1)](value);
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// #endif
|
||||||
|
// #ifdef MP-TOUTIAO
|
||||||
|
if(colorReg.test(value)) {
|
||||||
|
value = value.replace(colorReg, '#$1$1$2$2$3$3')
|
||||||
|
}
|
||||||
|
// #endif
|
||||||
|
ctx['set' + style.charAt(0).toUpperCase() + style.slice(1)](value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
if(!this.isNew && !child) {
|
||||||
|
ctx.uniDrawImage = ctx.drawImage
|
||||||
|
ctx.drawImage = (...a) => {
|
||||||
|
a[0] = a[0].src
|
||||||
|
ctx.uniDrawImage(...a)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(!ctx.createRadialGradient) {
|
||||||
|
ctx.createRadialGradient = function() {
|
||||||
|
return ctx.createCircularGradient(...[...arguments].slice(-3))
|
||||||
|
};
|
||||||
|
}
|
||||||
|
// 字节不支持
|
||||||
|
if (!ctx.strokeText) {
|
||||||
|
ctx.strokeText = (...a) => {
|
||||||
|
ctx.fillText(...a)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 钉钉不支持
|
||||||
|
if (!ctx.measureText) {
|
||||||
|
const strLen = (str) => {
|
||||||
|
let len = 0;
|
||||||
|
for (let i = 0; i < str.length; i++) {
|
||||||
|
if (str.charCodeAt(i) > 0 && str.charCodeAt(i) < 128) {
|
||||||
|
len++;
|
||||||
|
} else {
|
||||||
|
len += 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return len;
|
||||||
|
}
|
||||||
|
ctx.measureText = (text, font) => {
|
||||||
|
let fontSize = 12;
|
||||||
|
if (font) {
|
||||||
|
fontSize = parseInt(font.match(/([\d\.]+)px/)[1])
|
||||||
|
}
|
||||||
|
fontSize /= 2;
|
||||||
|
return {
|
||||||
|
width: strLen(text) * fontSize
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_initEvent(e) {
|
||||||
|
this.event = {};
|
||||||
|
const eventNames = [{
|
||||||
|
wxName: 'touchStart',
|
||||||
|
ecName: 'mousedown'
|
||||||
|
}, {
|
||||||
|
wxName: 'touchMove',
|
||||||
|
ecName: 'mousemove'
|
||||||
|
}, {
|
||||||
|
wxName: 'touchEnd',
|
||||||
|
ecName: 'mouseup'
|
||||||
|
}, {
|
||||||
|
wxName: 'touchEnd',
|
||||||
|
ecName: 'click'
|
||||||
|
}];
|
||||||
|
|
||||||
|
eventNames.forEach(name => {
|
||||||
|
this.event[name.wxName] = e => {
|
||||||
|
const touch = e.touches[0];
|
||||||
|
this.chart.getZr().handler.dispatch(name.ecName, {
|
||||||
|
zrX: name.wxName === 'tap' ? touch.clientX : touch.x,
|
||||||
|
zrY: name.wxName === 'tap' ? touch.clientY : touch.y
|
||||||
|
});
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
set width(w) {
|
||||||
|
this.canvasNode.width = w
|
||||||
|
}
|
||||||
|
set height(h) {
|
||||||
|
this.canvasNode.height = h
|
||||||
|
}
|
||||||
|
|
||||||
|
get width() {
|
||||||
|
return this.canvasNode.width || 0
|
||||||
|
}
|
||||||
|
get height() {
|
||||||
|
return this.canvasNode.height || 0
|
||||||
|
}
|
||||||
|
get ctx() {
|
||||||
|
return cacheChart[this.canvasId]['ctx'] || null
|
||||||
|
}
|
||||||
|
set chart(chart) {
|
||||||
|
cacheChart[this.canvasId]['chart'] = chart
|
||||||
|
}
|
||||||
|
get chart() {
|
||||||
|
return cacheChart[this.canvasId]['chart'] || null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function dispatch(name, {x,y, wheelDelta}) {
|
||||||
|
this.dispatch(name, {
|
||||||
|
zrX: x,
|
||||||
|
zrY: y,
|
||||||
|
zrDelta: wheelDelta,
|
||||||
|
preventDefault: () => {},
|
||||||
|
stopPropagation: () =>{}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
export function setCanvasCreator(echarts, {canvas, node}) {
|
||||||
|
// echarts.setCanvasCreator(() => canvas);
|
||||||
|
echarts.registerPreprocessor(option => {
|
||||||
|
if (option && option.series) {
|
||||||
|
if (option.series.length > 0) {
|
||||||
|
option.series.forEach(series => {
|
||||||
|
series.progressive = 0;
|
||||||
|
});
|
||||||
|
} else if (typeof option.series === 'object') {
|
||||||
|
option.series.progressive = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
function loadImage(src, onload, onerror) {
|
||||||
|
let img = null
|
||||||
|
if(node && node.createImage) {
|
||||||
|
img = node.createImage()
|
||||||
|
img.onload = onload.bind(img);
|
||||||
|
img.onerror = onerror.bind(img);
|
||||||
|
img.src = src;
|
||||||
|
return img
|
||||||
|
} else {
|
||||||
|
img = new Image()
|
||||||
|
img.onload = onload.bind(img)
|
||||||
|
img.onerror = onerror.bind(img);
|
||||||
|
img.src = src
|
||||||
|
return img
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(echarts.setPlatformAPI) {
|
||||||
|
echarts.setPlatformAPI({
|
||||||
|
loadImage: canvas.setChart ? loadImage : null,
|
||||||
|
createCanvas(){
|
||||||
|
return canvas
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
517
src/uni_modules/lime-echart/components/l-echart/l-echart.vue
Normal file
@@ -0,0 +1,517 @@
|
|||||||
|
<template>
|
||||||
|
<view class="lime-echart" :style="customStyle" v-if="canvasId" ref="limeEchart">
|
||||||
|
<!-- #ifndef APP-NVUE -->
|
||||||
|
<canvas
|
||||||
|
class="lime-echart__canvas"
|
||||||
|
v-if="use2dCanvas"
|
||||||
|
type="2d"
|
||||||
|
:id="canvasId"
|
||||||
|
:style="canvasStyle"
|
||||||
|
:disable-scroll="isDisableScroll"
|
||||||
|
@touchstart="touchStart"
|
||||||
|
@touchmove="touchMove"
|
||||||
|
@touchend="touchEnd"
|
||||||
|
/>
|
||||||
|
<canvas
|
||||||
|
class="lime-echart__canvas"
|
||||||
|
v-else-if="isPc"
|
||||||
|
:style="canvasStyle"
|
||||||
|
:id="canvasId"
|
||||||
|
:canvas-id="canvasId"
|
||||||
|
:disable-scroll="isDisableScroll"
|
||||||
|
@mousedown="touchStart"
|
||||||
|
@mousemove="touchMove"
|
||||||
|
@mouseup="touchEnd"
|
||||||
|
/>
|
||||||
|
<canvas
|
||||||
|
class="lime-echart__canvas"
|
||||||
|
v-else
|
||||||
|
:width="nodeWidth"
|
||||||
|
:height="nodeHeight"
|
||||||
|
:style="canvasStyle"
|
||||||
|
:canvas-id="canvasId"
|
||||||
|
:id="canvasId"
|
||||||
|
:disable-scroll="isDisableScroll"
|
||||||
|
@touchstart="touchStart"
|
||||||
|
@touchmove="touchMove"
|
||||||
|
@touchend="touchEnd"
|
||||||
|
/>
|
||||||
|
<canvas v-if="isOffscreenCanvas" :style="offscreenStyle" :canvas-id="offscreenCanvasId"></canvas>
|
||||||
|
<!-- #endif -->
|
||||||
|
<!-- #ifdef APP-NVUE -->
|
||||||
|
<web-view
|
||||||
|
class="lime-echart__canvas"
|
||||||
|
:id="canvasId"
|
||||||
|
:style="canvasStyle"
|
||||||
|
:webview-styles="webviewStyles"
|
||||||
|
ref="webview"
|
||||||
|
src="/uni_modules/lime-echart/static/index.html"
|
||||||
|
@pagefinish="finished = true"
|
||||||
|
@onPostMessage="onMessage"
|
||||||
|
></web-view>
|
||||||
|
<!-- #endif -->
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// #ifdef VUE3
|
||||||
|
// #ifdef APP-PLUS
|
||||||
|
global = {}
|
||||||
|
// #endif
|
||||||
|
// #endif
|
||||||
|
// #ifndef APP-NVUE
|
||||||
|
import {Canvas, setCanvasCreator, dispatch} from './canvas';
|
||||||
|
import { compareVersion, wrapTouch, devicePixelRatio ,sleep} from './utils';
|
||||||
|
// #endif
|
||||||
|
// #ifdef APP-NVUE
|
||||||
|
import { base64ToPath, sleep } from './utils';
|
||||||
|
// #endif
|
||||||
|
const charts = {}
|
||||||
|
const echartsObj = {}
|
||||||
|
export default {
|
||||||
|
name: 'lime-echart',
|
||||||
|
props: {
|
||||||
|
// #ifdef MP-WEIXIN || MP-TOUTIAO
|
||||||
|
type: {
|
||||||
|
type: String,
|
||||||
|
default: '2d'
|
||||||
|
},
|
||||||
|
// #endif
|
||||||
|
// #ifdef APP-NVUE
|
||||||
|
webviewStyles: Object,
|
||||||
|
// hybrid: Boolean,
|
||||||
|
// #endif
|
||||||
|
customStyle: String,
|
||||||
|
isDisableScroll: Boolean,
|
||||||
|
isClickable: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
enableHover: Boolean,
|
||||||
|
beforeDelay: {
|
||||||
|
type: Number,
|
||||||
|
default: 30
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
// #ifdef MP-WEIXIN || MP-TOUTIAO || MP-ALIPAY
|
||||||
|
use2dCanvas: true,
|
||||||
|
// #endif
|
||||||
|
// #ifndef MP-WEIXIN || MP-TOUTIAO || MP-ALIPAY
|
||||||
|
use2dCanvas: false,
|
||||||
|
// #endif
|
||||||
|
width: null,
|
||||||
|
height: null,
|
||||||
|
nodeWidth: null,
|
||||||
|
nodeHeight: null,
|
||||||
|
canvasNode: null,
|
||||||
|
config: {},
|
||||||
|
inited: false,
|
||||||
|
finished: false,
|
||||||
|
file: '',
|
||||||
|
platform: '',
|
||||||
|
isPc: false,
|
||||||
|
isDown: false,
|
||||||
|
isOffscreenCanvas: false,
|
||||||
|
offscreenWidth: 0,
|
||||||
|
offscreenHeight: 0
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
canvasId() {
|
||||||
|
return `lime-echart${this._ && this._.uid || this._uid}`
|
||||||
|
},
|
||||||
|
offscreenCanvasId() {
|
||||||
|
return `${this.canvasId}_offscreen`
|
||||||
|
},
|
||||||
|
offscreenStyle() {
|
||||||
|
return `width:${this.offscreenWidth}px;height: ${this.offscreenHeight}px; position: fixed; left: 99999px; background: red`
|
||||||
|
},
|
||||||
|
canvasStyle() {
|
||||||
|
return this.width && this.height ? ('width:' + this.width + 'px;height:' + this.height + 'px') : ''
|
||||||
|
}
|
||||||
|
},
|
||||||
|
beforeDestroy() {
|
||||||
|
this.clear()
|
||||||
|
this.dispose()
|
||||||
|
// #ifdef H5
|
||||||
|
if(this.isPc) {
|
||||||
|
document.removeEventListener('mousewheel')
|
||||||
|
}
|
||||||
|
// #endif
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
// #ifdef H5
|
||||||
|
if(!('ontouchstart' in window)) {
|
||||||
|
this.isPc = true
|
||||||
|
document.addEventListener('mousewheel', (e) => {
|
||||||
|
if(this.chart) {
|
||||||
|
const touch = this.getTouch(e)
|
||||||
|
const handler = this.chart.getZr().handler;
|
||||||
|
dispatch.call(handler, 'mousewheel', touch)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
// #endif
|
||||||
|
// #ifdef MP-WEIXIN || MP-TOUTIAO || MP-ALIPAY
|
||||||
|
const { SDKVersion, version, platform, environment } = uni.getSystemInfoSync();
|
||||||
|
// #endif
|
||||||
|
// #ifdef MP-WEIXIN
|
||||||
|
this.isPC = /windows/i.test(platform)
|
||||||
|
this.use2dCanvas = this.type === '2d' && compareVersion(SDKVersion, '2.9.2') >= 0 && !((/ios/i.test(platform) && /7.0.20/.test(version)) || /wxwork/i.test(environment)) //&& !this.isPC;
|
||||||
|
// #endif
|
||||||
|
// #ifdef MP-TOUTIAO
|
||||||
|
this.isPC = /devtools/i.test(platform)
|
||||||
|
this.use2dCanvas = this.type === '2d' && compareVersion(SDKVersion, '1.78.0') >= 0;
|
||||||
|
// #endif
|
||||||
|
// #ifdef MP-ALIPAY
|
||||||
|
this.use2dCanvas = this.type === '2d' && compareVersion(my.SDKVersion, '2.7.0') >= 0;
|
||||||
|
// #endif
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.$emit('finished')
|
||||||
|
})
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
// #ifdef APP-NVUE
|
||||||
|
onMessage(e) {
|
||||||
|
const res = e?.detail?.data[0] || null;
|
||||||
|
if (res?.event) {
|
||||||
|
if(res.event === 'inited') {
|
||||||
|
this.inited = true
|
||||||
|
}
|
||||||
|
this.$emit(res.event, JSON.parse(res.data));
|
||||||
|
} else if(res?.file){
|
||||||
|
this.file = res.data
|
||||||
|
} else if(!res[0] && JSON.stringify(res[0]) != '{}'){
|
||||||
|
console.error(res);
|
||||||
|
} else {
|
||||||
|
console.log(...res)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// #endif
|
||||||
|
setChart(callback) {
|
||||||
|
if(!this.chart) {
|
||||||
|
console.warn(`组件还未初始化,请先使用 init`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if(typeof callback === 'function' && this.chart) {
|
||||||
|
callback(this.chart);
|
||||||
|
}
|
||||||
|
// #ifdef APP-NVUE
|
||||||
|
if(typeof callback === 'function') {
|
||||||
|
this.$refs.webview.evalJs(`setChart(${JSON.stringify(callback.toString())}, ${JSON.stringify(this.roptions)})`);
|
||||||
|
}
|
||||||
|
// #endif
|
||||||
|
},
|
||||||
|
setOption() {
|
||||||
|
if (!this.chart || !this.chart.setOption) {
|
||||||
|
console.warn(`组件还未初始化,请先使用 init`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// #ifndef APP-NVUE
|
||||||
|
this.chart.setOption(...arguments);
|
||||||
|
// #endif
|
||||||
|
// #ifdef APP-NVUE
|
||||||
|
this.$refs.webview.evalJs(`setOption(${JSON.stringify(arguments)})`);
|
||||||
|
// #endif
|
||||||
|
},
|
||||||
|
showLoading() {
|
||||||
|
if(this.chart) {
|
||||||
|
// #ifndef APP-NVUE
|
||||||
|
this.chart.showLoading(...arguments)
|
||||||
|
// #endif
|
||||||
|
// #ifdef APP-NVUE
|
||||||
|
this.$refs.webview.evalJs(`showLoading(${JSON.stringify(arguments)})`);
|
||||||
|
// #endif
|
||||||
|
}
|
||||||
|
},
|
||||||
|
hideLoading() {
|
||||||
|
if(this.chart) {
|
||||||
|
// #ifndef APP-NVUE
|
||||||
|
this.chart.hideLoading()
|
||||||
|
// #endif
|
||||||
|
// #ifdef APP-NVUE
|
||||||
|
this.$refs.webview.evalJs(`hideLoading()`);
|
||||||
|
// #endif
|
||||||
|
}
|
||||||
|
},
|
||||||
|
clear() {
|
||||||
|
if(this.chart) {
|
||||||
|
// #ifndef APP-NVUE
|
||||||
|
this.chart.clear()
|
||||||
|
// #endif
|
||||||
|
// #ifdef APP-NVUE
|
||||||
|
this.$refs.webview.evalJs(`clear()`);
|
||||||
|
// #endif
|
||||||
|
}
|
||||||
|
},
|
||||||
|
dispose() {
|
||||||
|
if(this.chart) {
|
||||||
|
// #ifndef APP-NVUE
|
||||||
|
this.chart.dispose()
|
||||||
|
// #endif
|
||||||
|
// #ifdef APP-NVUE
|
||||||
|
this.$refs.webview.evalJs(`dispose()`);
|
||||||
|
// #endif
|
||||||
|
}
|
||||||
|
},
|
||||||
|
resize(size) {
|
||||||
|
if(size && size.width && size.height) {
|
||||||
|
this.height = size.height
|
||||||
|
this.width = size.width
|
||||||
|
if(this.chart) {this.chart.resize(size)}
|
||||||
|
// #ifdef APP-NVUE
|
||||||
|
this.$refs.webview.evalJs(`resize(${size})`);
|
||||||
|
// #endif
|
||||||
|
} else {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
// #ifndef APP-NVUE
|
||||||
|
uni.createSelectorQuery()
|
||||||
|
.in(this)
|
||||||
|
.select(`.lime-echart`)
|
||||||
|
.boundingClientRect()
|
||||||
|
.exec(res => {
|
||||||
|
if (res) {
|
||||||
|
let { width, height } = res[0];
|
||||||
|
this.width = width = width || 300;
|
||||||
|
this.height = height = height || 300;
|
||||||
|
this.chart.resize({width, height})
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// #endif
|
||||||
|
// #ifdef APP-NVUE
|
||||||
|
this.$refs.webview.evalJs(`resize()`);
|
||||||
|
// #endif
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
},
|
||||||
|
canvasToTempFilePath(args = {}) {
|
||||||
|
// #ifndef APP-NVUE
|
||||||
|
const { use2dCanvas, canvasId, canvasNode } = this;
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const copyArgs = Object.assign({
|
||||||
|
canvasId,
|
||||||
|
success: resolve,
|
||||||
|
fail: reject
|
||||||
|
}, args);
|
||||||
|
if (use2dCanvas) {
|
||||||
|
delete copyArgs.canvasId;
|
||||||
|
copyArgs.canvas = canvasNode;
|
||||||
|
}
|
||||||
|
uni.canvasToTempFilePath(copyArgs, this);
|
||||||
|
});
|
||||||
|
// #endif
|
||||||
|
// #ifdef APP-NVUE
|
||||||
|
this.file = ''
|
||||||
|
this.$refs.webview.evalJs(`canvasToTempFilePath()`);
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
this.$watch('file', async (file) => {
|
||||||
|
if(file) {
|
||||||
|
const tempFilePath = await base64ToPath(file)
|
||||||
|
resolve(args.success({tempFilePath}))
|
||||||
|
} else {
|
||||||
|
reject(args.fail({error: ``}))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
// #endif
|
||||||
|
},
|
||||||
|
async init(echarts, ...args) {
|
||||||
|
// #ifdef APP-NVUE
|
||||||
|
if(arguments && !arguments.length) {
|
||||||
|
console.error('缺少参数:init(theme?:string, opts?: object, callback: function)')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// #endif
|
||||||
|
// #ifndef APP-NVUE
|
||||||
|
if(arguments && arguments.length < 1) {
|
||||||
|
console.error('缺少参数:init(echarts, theme?:string, opts?: object, callback: function)')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// #endif
|
||||||
|
let theme=null,opts={},callback;
|
||||||
|
|
||||||
|
Array.from(arguments).forEach(item => {
|
||||||
|
if(typeof item === 'function') {
|
||||||
|
callback = item
|
||||||
|
}
|
||||||
|
if(['string'].includes(typeof item)) {
|
||||||
|
theme = item
|
||||||
|
}
|
||||||
|
if(typeof item === 'object') {
|
||||||
|
opts = item
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if(this.beforeDelay) {
|
||||||
|
await sleep(this.beforeDelay)
|
||||||
|
}
|
||||||
|
let config = await this.getContext();
|
||||||
|
// #ifndef APP-NVUE
|
||||||
|
setCanvasCreator(echarts, config)
|
||||||
|
this.chart = echarts.init(config.canvas, theme, Object.assign({}, config, opts))
|
||||||
|
if(typeof callback === 'function') {
|
||||||
|
callback(this.chart)
|
||||||
|
} else {
|
||||||
|
return this.chart
|
||||||
|
// console.info('callback 非 function')
|
||||||
|
}
|
||||||
|
// #endif
|
||||||
|
// #ifdef APP-NVUE
|
||||||
|
if(callback) {
|
||||||
|
this.chart = {
|
||||||
|
setOption: (options) => {
|
||||||
|
this.roptions = options
|
||||||
|
}
|
||||||
|
}
|
||||||
|
callback(this.chart)
|
||||||
|
this.$refs.webview.evalJs(`init(${JSON.stringify(callback.toString())}, ${JSON.stringify(this.roptions)}, ${JSON.stringify(opts)}, ${theme})`)
|
||||||
|
} else {
|
||||||
|
console.error('callback 非 function')
|
||||||
|
}
|
||||||
|
// #endif
|
||||||
|
},
|
||||||
|
getContext() {
|
||||||
|
// #ifdef APP-NVUE
|
||||||
|
if(this.finished) {
|
||||||
|
return Promise.resolve(this.finished)
|
||||||
|
}
|
||||||
|
return new Promise(resolve => {
|
||||||
|
this.$watch('finished', (val) => {
|
||||||
|
if(val) {
|
||||||
|
resolve(this.finished)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
// #endif
|
||||||
|
// #ifndef APP-NVUE
|
||||||
|
const { use2dCanvas } = this;
|
||||||
|
let dpr = devicePixelRatio
|
||||||
|
if (use2dCanvas) {
|
||||||
|
return new Promise(resolve => {
|
||||||
|
uni.createSelectorQuery()
|
||||||
|
.in(this)
|
||||||
|
.select(`#${this.canvasId}`)
|
||||||
|
.fields({
|
||||||
|
node: true,
|
||||||
|
size: true
|
||||||
|
})
|
||||||
|
.exec(res => {
|
||||||
|
let { node, width, height } = res[0];
|
||||||
|
this.width = width = width || 300;
|
||||||
|
this.height = height = height || 300;
|
||||||
|
const ctx = node.getContext('2d');
|
||||||
|
const canvas = new Canvas(ctx, this, true, node);
|
||||||
|
this.canvasNode = node
|
||||||
|
resolve({ canvas, width, height, devicePixelRatio: dpr, node });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return new Promise(resolve => {
|
||||||
|
uni.createSelectorQuery()
|
||||||
|
.in(this)
|
||||||
|
.select(`#${this.canvasId}`)
|
||||||
|
.boundingClientRect()
|
||||||
|
.exec(res => {
|
||||||
|
if (res) {
|
||||||
|
let { width, height } = res[0];
|
||||||
|
this.width = width = width || 300;
|
||||||
|
this.height = height = height || 300;
|
||||||
|
// #ifdef MP-TOUTIAO
|
||||||
|
dpr = !this.isPC ? devicePixelRatio : 1// 1.25
|
||||||
|
// #endif
|
||||||
|
// #ifndef MP-ALIPAY || MP-TOUTIAO
|
||||||
|
dpr = this.isPC ? devicePixelRatio : 1
|
||||||
|
// #endif
|
||||||
|
// #ifdef MP-ALIPAY || MP-LARK
|
||||||
|
dpr = devicePixelRatio
|
||||||
|
// #endif
|
||||||
|
this.rect = res[0]
|
||||||
|
this.nodeWidth = width * dpr;
|
||||||
|
this.nodeHeight = height * dpr;
|
||||||
|
const ctx = uni.createCanvasContext(this.canvasId, this);
|
||||||
|
const canvas = new Canvas(ctx, this, false);
|
||||||
|
resolve({ canvas, width, height, devicePixelRatio: dpr });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
// #endif
|
||||||
|
},
|
||||||
|
// #ifndef APP-NVUE
|
||||||
|
getRelative(e) {
|
||||||
|
return {x: e.pageX - this.rect.left, y: e.pageY - this.rect.top, wheelDelta: e.wheelDelta}
|
||||||
|
},
|
||||||
|
getTouch(e) {
|
||||||
|
return e.touches && e.touches[0] && e.touches[0].x ? e.touches[0] : this.getRelative(e);
|
||||||
|
},
|
||||||
|
touchStart(e) {
|
||||||
|
this.isDown = true
|
||||||
|
if (this.chart && ((e.touches.length > 0 || e.touches['0']) && e.type != 'mousemove' || e.type == 'mousedown')) {
|
||||||
|
const touch = this.getTouch(e)
|
||||||
|
this.startX = touch.x
|
||||||
|
this.startY = touch.y
|
||||||
|
this.startT = new Date()
|
||||||
|
const handler = this.chart.getZr().handler;
|
||||||
|
dispatch.call(handler, 'mousedown', touch)
|
||||||
|
dispatch.call(handler, 'mousemove', touch)
|
||||||
|
handler.processGesture(wrapTouch(e), 'start');
|
||||||
|
clearTimeout(this.endTimer);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
touchMove(e) {
|
||||||
|
if(this.isPc && this.enableHover && !this.isDown) {this.isDown = true}
|
||||||
|
if (this.chart && ((e.touches.length > 0 || e.touches['0']) && e.type != 'mousemove' || e.type == 'mousemove' && this.isDown)) {
|
||||||
|
const handler = this.chart.getZr().handler;
|
||||||
|
dispatch.call(handler, 'mousemove', this.getTouch(e))
|
||||||
|
handler.processGesture(wrapTouch(e), 'change');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
touchEnd(e) {
|
||||||
|
this.isDown = false
|
||||||
|
if (this.chart) {
|
||||||
|
const {x} = e.changedTouches && e.changedTouches[0] || {}
|
||||||
|
const touch = (x ? e.changedTouches[0] : this.getRelative(e)) || {};
|
||||||
|
const handler = this.chart.getZr().handler;
|
||||||
|
const isClick = Math.abs(touch.x - this.startX) < 10 && new Date() - this.startT < 200;
|
||||||
|
dispatch.call(handler, 'mouseup', touch)
|
||||||
|
handler.processGesture(wrapTouch(e), 'end');
|
||||||
|
if(isClick) {
|
||||||
|
dispatch.call(handler, 'click', touch)
|
||||||
|
} else {
|
||||||
|
this.endTimer = setTimeout(() => {
|
||||||
|
dispatch.call(handler, 'mousemove', {x: 999999999,y: 999999999});
|
||||||
|
dispatch.call(handler, 'mouseup', {x: 999999999,y: 999999999});
|
||||||
|
},50)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// #endif
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<style scoped>
|
||||||
|
.lime-echart {
|
||||||
|
position: relative;
|
||||||
|
/* #ifndef APP-NVUE */
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
/* #endif */
|
||||||
|
/* #ifdef APP-NVUE */
|
||||||
|
flex: 1;
|
||||||
|
/* #endif */
|
||||||
|
}
|
||||||
|
.lime-echart__canvas {
|
||||||
|
/* #ifndef APP-NVUE */
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
/* #endif */
|
||||||
|
/* #ifdef APP-NVUE */
|
||||||
|
flex: 1;
|
||||||
|
/* #endif */
|
||||||
|
}
|
||||||
|
</style>
|
||||||
74
src/uni_modules/lime-echart/components/l-echart/utils.js
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
// #ifndef APP-NVUE
|
||||||
|
// 计算版本
|
||||||
|
export function compareVersion(v1, v2) {
|
||||||
|
v1 = v1.split('.')
|
||||||
|
v2 = v2.split('.')
|
||||||
|
const len = Math.max(v1.length, v2.length)
|
||||||
|
while (v1.length < len) {
|
||||||
|
v1.push('0')
|
||||||
|
}
|
||||||
|
while (v2.length < len) {
|
||||||
|
v2.push('0')
|
||||||
|
}
|
||||||
|
for (let i = 0; i < len; i++) {
|
||||||
|
const num1 = parseInt(v1[i], 10)
|
||||||
|
const num2 = parseInt(v2[i], 10)
|
||||||
|
|
||||||
|
if (num1 > num2) {
|
||||||
|
return 1
|
||||||
|
} else if (num1 < num2) {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
export function wrapTouch(event) {
|
||||||
|
for (let i = 0; i < event.touches.length; ++i) {
|
||||||
|
const touch = event.touches[i];
|
||||||
|
touch.offsetX = touch.x;
|
||||||
|
touch.offsetY = touch.y;
|
||||||
|
}
|
||||||
|
return event;
|
||||||
|
}
|
||||||
|
export const devicePixelRatio = wx.getSystemInfoSync().pixelRatio
|
||||||
|
// #endif
|
||||||
|
// #ifdef APP-NVUE
|
||||||
|
export function base64ToPath(base64) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const [, format, bodyData] = /data:image\/(\w+);base64,(.*)/.exec(base64) || [];
|
||||||
|
const bitmap = new plus.nativeObj.Bitmap('bitmap' + Date.now())
|
||||||
|
bitmap.loadBase64Data(base64, () => {
|
||||||
|
if (!format) {
|
||||||
|
reject(new Error('ERROR_BASE64SRC_PARSE'))
|
||||||
|
}
|
||||||
|
const time = new Date().getTime();
|
||||||
|
const filePath = `_doc/uniapp_temp/${time}.${format}`
|
||||||
|
|
||||||
|
bitmap.save(filePath, {},
|
||||||
|
() => {
|
||||||
|
bitmap.clear()
|
||||||
|
resolve(filePath)
|
||||||
|
},
|
||||||
|
(error) => {
|
||||||
|
bitmap.clear()
|
||||||
|
console.error(`${JSON.stringify(error)}`)
|
||||||
|
reject(error)
|
||||||
|
})
|
||||||
|
}, (error) => {
|
||||||
|
bitmap.clear()
|
||||||
|
console.error(`${JSON.stringify(error)}`)
|
||||||
|
reject(error)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
// #endif
|
||||||
|
|
||||||
|
|
||||||
|
export function sleep(time) {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
setTimeout(() => {
|
||||||
|
resolve(true)
|
||||||
|
},time)
|
||||||
|
})
|
||||||
|
}
|
||||||
22
src/uni_modules/piaoyi-cityPicker/changelog.md
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
## 1.1.7(2024-10-29)
|
||||||
|
修复底部露出部分组件bug
|
||||||
|
## 1.1.6(2024-08-20)
|
||||||
|
更新本地数据源为最新数据源
|
||||||
|
## 1.1.5(2024-06-12)
|
||||||
|
使用说明文档优化
|
||||||
|
## 1.1.4(2024-06-12)
|
||||||
|
增加问题反馈描述
|
||||||
|
## 1.1.3(2024-02-29)
|
||||||
|
更新使用文档
|
||||||
|
## 1.1.2(2024-01-16)
|
||||||
|
解决Vue3项目导入导出报错问题
|
||||||
|
## 1.1.1(2023-12-06)
|
||||||
|
defaultValue可以传入defaultValue:['河北省','唐山市','丰南区']数组类型以及defaultValue: '420103'地区编码字符串类型
|
||||||
|
## 1.1.0(2023-12-05)
|
||||||
|
即默认值传入地区编码,也支持传入中文省市区数组
|
||||||
|
## 1.0.9(2023-12-04)
|
||||||
|
优化
|
||||||
|
## 1.0.8(2023-10-24)
|
||||||
|
修复东菀市和中山市下各镇的行政编码错误问题
|
||||||
|
## 1.0.4(2023-09-15)
|
||||||
|
改为uni_modules规范
|
||||||
@@ -0,0 +1,281 @@
|
|||||||
|
<template>
|
||||||
|
<view class="pupop">
|
||||||
|
<view class="popup-box" :animation="animationData">
|
||||||
|
<view class="pupop-btn">
|
||||||
|
<view @tap="cancel">取消</view>
|
||||||
|
<view @tap="confirm" style="color: #2979ff;">确定</view>
|
||||||
|
</view>
|
||||||
|
<picker-view :value="value" :indicator-style="indicatorStyle" @change="bindChange" class="picker-view">
|
||||||
|
<picker-view-column>
|
||||||
|
<view class="item" v-for="(item,index) in provinceList" :key="index">{{item.name}}</view>
|
||||||
|
</picker-view-column>
|
||||||
|
<picker-view-column>
|
||||||
|
<view class="item" v-for="(item,index) in cityList" :key="index">{{item.name}}</view>
|
||||||
|
</picker-view-column>
|
||||||
|
<picker-view-column v-if="column == 3">
|
||||||
|
<view class="item" v-for="(item,index) in areaList" :key="index">{{item.name}}</view>
|
||||||
|
</picker-view-column>
|
||||||
|
</picker-view>
|
||||||
|
</view>
|
||||||
|
<view @tap="close" @touchmove.stop.prevent :class="visible ? 'pupop-model' : 'pupop-models'"></view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import {
|
||||||
|
addressList
|
||||||
|
} from './cityData.js'
|
||||||
|
export default {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
value: [],
|
||||||
|
addressList,
|
||||||
|
provinceList: [],
|
||||||
|
cityList: [],
|
||||||
|
areaList: [],
|
||||||
|
indicatorStyle: `height: 50px;`,
|
||||||
|
provinceIndex: 0,
|
||||||
|
cityIndex: 0,
|
||||||
|
areaIndex: 0,
|
||||||
|
animationData: {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
column: {
|
||||||
|
type: Number,
|
||||||
|
default: 3
|
||||||
|
},
|
||||||
|
defaultValue: {
|
||||||
|
default: () => ''
|
||||||
|
},
|
||||||
|
visible: {
|
||||||
|
type: Boolean,
|
||||||
|
default: () => false
|
||||||
|
},
|
||||||
|
maskCloseAble: {
|
||||||
|
type: Boolean,
|
||||||
|
default: () => true
|
||||||
|
},
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
visible (val) {
|
||||||
|
this.changeActive()
|
||||||
|
},
|
||||||
|
defaultValue() {
|
||||||
|
this.init()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.init()
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
init () {
|
||||||
|
var provinceList = []
|
||||||
|
addressList.filter(item => {
|
||||||
|
provinceList.push({
|
||||||
|
code: item.code,
|
||||||
|
name: item.name
|
||||||
|
})
|
||||||
|
})
|
||||||
|
this.provinceList = [...provinceList]
|
||||||
|
if (!this.defaultValue) {
|
||||||
|
this.cityList = addressList[0].children
|
||||||
|
this.areaList = addressList[0].children[0].children
|
||||||
|
} else {
|
||||||
|
var city = {11:"北京",12:"天津",13:"河北",14:"山西",15:"内蒙古",21:"辽宁",22:"吉林",23:"黑龙江 ",31:"上海",32:"江苏",33:"浙江",34:"安徽",35:"福建",36:"江西",37:"山东",41:"河南",42:"湖北 ",43:"湖南",44:"广东",45:"广西",46:"海南",50:"重庆",51:"四川",52:"贵州",53:"云南",54:"西藏 ",61:"陕西",62:"甘肃",63:"青海",64:"宁夏",65:"新疆",71:"台湾",81:"香港",82:"澳门",91:"国外 "}
|
||||||
|
if (Array.isArray(this.defaultValue) && this.defaultValue.length >= 2) {
|
||||||
|
console.log(this.defaultValue)
|
||||||
|
var province = this.defaultValue[0]
|
||||||
|
var city = this.defaultValue[1]
|
||||||
|
this.provinceIndex = 0
|
||||||
|
this.cityIndex = 0
|
||||||
|
this.areaIndex = 0
|
||||||
|
this.provinceIndex = addressList.findIndex(obj => {
|
||||||
|
return obj.name == province
|
||||||
|
})
|
||||||
|
this.provinceIndex = this.provinceIndex >= 0 ? this.provinceIndex : 0
|
||||||
|
this.cityList = addressList[this.provinceIndex].children
|
||||||
|
this.cityIndex = this.cityList.findIndex(obj => {
|
||||||
|
return obj.name == city
|
||||||
|
})
|
||||||
|
this.cityIndex = this.cityIndex >= 0 ? this.cityIndex : 0
|
||||||
|
this.areaList = this.cityList[this.cityIndex].children
|
||||||
|
if (this.defaultValue.length > 2) {
|
||||||
|
this.areaIndex = this.areaList.findIndex(obj => {
|
||||||
|
return obj.name == this.defaultValue[2]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
this.areaIndex = this.areaIndex >= 0 ? this.areaIndex : 0
|
||||||
|
this.$nextTick(() => {
|
||||||
|
if (this.column == 3) {
|
||||||
|
this.value = JSON.parse(JSON.stringify([this.provinceIndex, this.cityIndex, this.areaIndex]))
|
||||||
|
} else if (this.column == 2) {
|
||||||
|
this.value = JSON.parse(JSON.stringify([this.provinceIndex, this.cityIndex]))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else if (/^\d{6}$/.test(this.defaultValue)) {
|
||||||
|
var province = this.defaultValue.substr(0, 2)
|
||||||
|
var city = this.defaultValue.substr(0, 4)
|
||||||
|
this.provinceIndex = 0
|
||||||
|
this.cityIndex = 0
|
||||||
|
this.areaIndex = 0
|
||||||
|
this.provinceIndex = addressList.findIndex(obj => {
|
||||||
|
return obj.code == province
|
||||||
|
})
|
||||||
|
this.provinceIndex = this.provinceIndex >= 0 ? this.provinceIndex : 0
|
||||||
|
this.cityList = addressList[this.provinceIndex].children
|
||||||
|
this.cityIndex = this.cityList.findIndex(obj => {
|
||||||
|
return obj.code == city
|
||||||
|
})
|
||||||
|
this.cityIndex = this.cityIndex >= 0 ? this.cityIndex : 0
|
||||||
|
this.areaList = this.cityList[this.cityIndex].children
|
||||||
|
this.areaIndex = this.areaList.findIndex(obj => {
|
||||||
|
return obj.code == this.defaultValue
|
||||||
|
})
|
||||||
|
this.areaIndex = this.areaIndex >= 0 ? this.areaIndex : 0
|
||||||
|
this.$nextTick(() => {
|
||||||
|
if (this.column == 3) {
|
||||||
|
this.value = JSON.parse(JSON.stringify([this.provinceIndex, this.cityIndex, this.areaIndex]))
|
||||||
|
} else if (this.column == 2) {
|
||||||
|
this.value = JSON.parse(JSON.stringify([this.provinceIndex, this.cityIndex]))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
uni.showToast({
|
||||||
|
title: '地区编码格式不正确',
|
||||||
|
icon: 'none'
|
||||||
|
})
|
||||||
|
console.log('地区编码格式不正确')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.changeActive()
|
||||||
|
},
|
||||||
|
confirm () {
|
||||||
|
if (this.column == 3) {
|
||||||
|
this.$emit('confirm', {
|
||||||
|
code: addressList[this.provinceIndex].children[this.cityIndex].children[this.areaIndex].code,
|
||||||
|
name: addressList[this.provinceIndex].name + addressList[this.provinceIndex].children[this.cityIndex].name + addressList[this.provinceIndex].children[this.cityIndex].children[this.areaIndex].name,
|
||||||
|
provinceName: addressList[this.provinceIndex].name,
|
||||||
|
cityName: addressList[this.provinceIndex].children[this.cityIndex].name,
|
||||||
|
areaName: addressList[this.provinceIndex].children[this.cityIndex].children[this.areaIndex].name
|
||||||
|
})
|
||||||
|
} else if (this.column == 2) {
|
||||||
|
this.$emit('confirm', {
|
||||||
|
code: addressList[this.provinceIndex].children[this.cityIndex].children[this.areaIndex].code.substring(0, 4) + '00',
|
||||||
|
name: addressList[this.provinceIndex].name + addressList[this.provinceIndex].children[this.cityIndex].name,
|
||||||
|
provinceName: addressList[this.provinceIndex].name,
|
||||||
|
cityName: addressList[this.provinceIndex].children[this.cityIndex].name
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
uni.showToast({
|
||||||
|
title: '目前column只能传2和3',
|
||||||
|
icon: 'none'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
cancel () {
|
||||||
|
this.$emit('cancel')
|
||||||
|
},
|
||||||
|
// 动画
|
||||||
|
changeActive () {
|
||||||
|
var active = '-415px'
|
||||||
|
if (this.visible) {
|
||||||
|
active = 0
|
||||||
|
}
|
||||||
|
var animation = uni.createAnimation({
|
||||||
|
duration: 400,
|
||||||
|
timingFunction: 'linear'
|
||||||
|
})
|
||||||
|
animation.bottom(active).step()
|
||||||
|
this.animationData = animation.export()
|
||||||
|
},
|
||||||
|
bindChange(e) {
|
||||||
|
e.detail.value[0] = e.detail.value[0] || 0
|
||||||
|
e.detail.value[1] = e.detail.value[1] || 0
|
||||||
|
e.detail.value[2] = e.detail.value[2] || 0
|
||||||
|
if (e.detail.value[0] != this.provinceIndex) {
|
||||||
|
// console.log('监听第一列')
|
||||||
|
this.provinceChange(e.detail.value[0])
|
||||||
|
} else if (e.detail.value[1] != this.cityIndex) {
|
||||||
|
// console.log('监听第二列')
|
||||||
|
this.cityChange(e.detail.value[1])
|
||||||
|
} else if (e.detail.value[2] != this.areaIndex) {
|
||||||
|
// console.log('监听第三列')
|
||||||
|
this.areaChange(e.detail.value[2])
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// 监听第一列变化
|
||||||
|
provinceChange (e) {
|
||||||
|
this.provinceIndex = e
|
||||||
|
this.cityIndex = 0
|
||||||
|
this.areaIndex = 0
|
||||||
|
this.value = [...[e, 0, 0]]
|
||||||
|
this.cityList = addressList[e].children
|
||||||
|
this.areaList = addressList[e].children[0].children
|
||||||
|
},
|
||||||
|
// 监听第二列变化
|
||||||
|
cityChange (e) {
|
||||||
|
this.cityIndex = e
|
||||||
|
this.areaIndex = 0
|
||||||
|
// console.log(this.cityIndex, this.areaIndex)
|
||||||
|
this.value = [...[this.provinceIndex, e, 0]]
|
||||||
|
this.cityList = addressList[this.provinceIndex].children
|
||||||
|
this.areaList = addressList[this.provinceIndex].children[e].children
|
||||||
|
},
|
||||||
|
// 监听第三列变化
|
||||||
|
areaChange (e) {
|
||||||
|
this.areaIndex = e
|
||||||
|
},
|
||||||
|
// 点击模态框
|
||||||
|
close () {
|
||||||
|
if (this.maskCloseAble) {
|
||||||
|
this.cancel()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.pupop {
|
||||||
|
.popup-box {
|
||||||
|
position: fixed;
|
||||||
|
left: 0;
|
||||||
|
bottom: -315px;
|
||||||
|
z-index: 99999;
|
||||||
|
background: #fff;
|
||||||
|
padding-bottom: 50px;
|
||||||
|
.pupop-btn{
|
||||||
|
height: 40px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 0 30upx;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.pupop-model {
|
||||||
|
position: fixed;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
z-index: 9999;
|
||||||
|
background: rgba(0, 0, 0, .5);
|
||||||
|
}
|
||||||
|
.pupop-models{
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.picker-view {
|
||||||
|
width: 750rpx;
|
||||||
|
height: 225px;
|
||||||
|
margin-top: 20rpx;
|
||||||
|
}
|
||||||
|
.item {
|
||||||
|
height: 50px;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
text-align: center;
|
||||||
|
line-height: 50px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
17
src/uni_modules/piaoyi-cityPicker/package.json
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"id": "piaoyi-cityPicker",
|
||||||
|
"name": " data-cityPicker省市区地址选择器",
|
||||||
|
"displayName": " data-cityPicker省市区地址选择器(兼容vue3)",
|
||||||
|
"version": "1.1.7",
|
||||||
|
"description": "常用省市区选择器,可反选,自定义模态框、兼容vue3",
|
||||||
|
"keywords": [
|
||||||
|
"省市区",
|
||||||
|
"选择器",
|
||||||
|
"可反选",
|
||||||
|
"自定义模态框和弹框",
|
||||||
|
"兼容vue3"
|
||||||
|
],
|
||||||
|
"dcloudext": {
|
||||||
|
"type": "component-vue"
|
||||||
|
}
|
||||||
|
}
|
||||||
106
src/uni_modules/piaoyi-cityPicker/readme.md
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
## 1.0.7(2023-09-15)
|
||||||
|
改为uni_modules规范
|
||||||
|
### cityPicker 省市区选择器(兼容vue3)
|
||||||
|
|
||||||
|
**使用方法:**
|
||||||
|
|
||||||
|
```
|
||||||
|
<template>
|
||||||
|
<view>
|
||||||
|
<button @tap="open">打开</button>
|
||||||
|
<cityPicker :column="column" :default-value="defaultValue" :mask-close-able="maskCloseAble" @confirm="confirm" @cancel="cancel" :visible="visible"/>
|
||||||
|
<view>{{str}}</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import cityPicker from '@/uni_modules/piaoyi-cityPicker/components/piaoyi-cityPicker/piaoyi-cityPicker'
|
||||||
|
export default {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
visible: false,
|
||||||
|
maskCloseAble: true,
|
||||||
|
str: '',
|
||||||
|
defaultValue: '420103',
|
||||||
|
// defaultValue: ['河北省','唐山市','丰南区'],
|
||||||
|
column: 3
|
||||||
|
}
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
cityPicker
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
open () {
|
||||||
|
this.visible = true
|
||||||
|
},
|
||||||
|
confirm (val) {
|
||||||
|
console.log(val)
|
||||||
|
this.str = JSON.stringify(val)
|
||||||
|
this.visible = false
|
||||||
|
},
|
||||||
|
cancel () {
|
||||||
|
this.visible = false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onShareAppMessage(res) {
|
||||||
|
if (res.from === 'button') { // 来自页面内分享按钮
|
||||||
|
console.log(res.target)
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
title: 'data-cityPicker省市区地址选择器!',
|
||||||
|
path: '/pages/cityPicker/cityPicker'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onShareTimeline(res) {
|
||||||
|
if (res.from === 'button') { // 来自页面内分享按钮
|
||||||
|
console.log(res.target)
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
title: 'data-cityPicker省市区地址选择器!'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
|
||||||
|
</style>
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
### 注:近期收到使用用户反馈,存在以下一个问题(如有好的建议,期待私信,谢谢)
|
||||||
|
|
||||||
|
1、之前只支持默认值传入地区编码,已更新可以支持传入中文省市区数组
|
||||||
|
|
||||||
|
defaultValue可以传入defaultValue:['河北省','唐山市','丰南区']数组类型以及defaultValue: '420103'地区编码字符串类型
|
||||||
|
|
||||||
|
可以使用const reg =/([\u4e00-\u9fa5]+省|自治区|[\u4e00-\u9fa5]+市|[\u4e00-\u9fa5]+区)/g;将自己的省市区数据进行处理为数组再传入
|
||||||
|
|
||||||
|
2、有些用户反馈vue3下watch监听有问题
|
||||||
|
|
||||||
|
我自己创建一个vue项目,导入插件后,按照示例原封不动进行测试,没有发现问题; 发现有问题的朋友可以提供一下可以复现的demo给我,我这边看看具体什么情况
|
||||||
|
|
||||||
|
3、有些用户返回无法关闭弹框
|
||||||
|
|
||||||
|
不要把插件放到scroll-view里面,请务必放到最外层进行使用
|
||||||
|
|
||||||
|
#### 事件说明
|
||||||
|
|
||||||
|
| 事件名 | 返回值 | 描述 |
|
||||||
|
| :---------: | :----: | :------------: |
|
||||||
|
| @confirm | 对象(code,完整地区名称) | 点击确定的回调 |
|
||||||
|
| @cancel | 无 | 点击取消的回调 |
|
||||||
|
|
||||||
|
#### Prop
|
||||||
|
|
||||||
|
| 参数名称 | 描述 |
|
||||||
|
| -------- | ------------------------------ |
|
||||||
|
| visible | 控制选择器显示和隐藏 |
|
||||||
|
| column | 可选值2和3,2是省市两列选择;3是省市区三列选择 |
|
||||||
|
| maskCloseAble | 点击模态框是否关闭弹框 |
|
||||||
|
| defaultValue | 初始地区编码(例:420102或者['河北省','唐山市','丰南区']) |
|
||||||
|
|
||||||
|
### 数据来源:[点击查看省市区数据来源](https://github.com/modood/Administrative-divisions-of-China/blob/master/dist/pca-code.json)
|
||||||
|
### 可接定制化组件开发
|
||||||
|
### 右侧有本人代表作小程序二维码,可以扫码体验
|
||||||
|
### 如使用过程中有问题或有一些好的建议,欢迎加QQ群互相学习交流:120594820
|
||||||
72
src/uni_modules/qf-image-cropper/changelog.md
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
## 2.2.5(2024-07-30)
|
||||||
|
* 修复 当 checkRange=true 时,拖动四个伸缩角放大图片时还可能会超出或未到边界的问题
|
||||||
|
* 修复 当 checkRange=false 时,图片旋转时会放大图片适应裁剪尺寸的问题
|
||||||
|
* 修复 当 checkRange=true 时,图片旋转 90° 或 270° 进行缩放可能会无法拖动图片的问题
|
||||||
|
## 2.2.4(2024-06-21)
|
||||||
|
* 新增 reverseRotatable 属性,是否支持逆向翻转
|
||||||
|
* 修复 `2.1.7` 版本导致旋转后图片没有自动适配裁剪框的问题
|
||||||
|
|
||||||
|
## 2.2.3(2024-06-21)
|
||||||
|
* 新增 gpu 属性,是否开启硬件加速,图片缩放过程中如果出现元素的“留影”或“重影”效果,可通过该方式解决或减轻这一问题
|
||||||
|
* 修复 组件使用 `v-if` 并设置 `src` 属性时可能会出现图片渲染位置存在偏差的问题
|
||||||
|
|
||||||
|
## 2.2.2(2024-06-21)
|
||||||
|
* 优化 组件实例 chooseImage 方法支持传参
|
||||||
|
* 修复 组件使用 `v-if` 时组件无非正常渲染的问题
|
||||||
|
|
||||||
|
## 2.2.1(2024-06-15)
|
||||||
|
* 修复 H5平台不支持手势拖动图片的问题
|
||||||
|
|
||||||
|
## 2.2.0(2024-05-31)
|
||||||
|
* 修复 APP平台 `vue2` 项目因 `2.1.9` 版本修复 `vue3` 项目bug而引发的问题
|
||||||
|
|
||||||
|
## 2.1.9(2024-05-29)
|
||||||
|
* 修复 APP平台 `vue3` 项目因 uniapp `renderjs` 中未支持条件编译,导致运行了H5平台代码报错的问题
|
||||||
|
|
||||||
|
## 2.1.8(2024-05-29)
|
||||||
|
* 新增 zIndex 属性,调整组件层级
|
||||||
|
* 新增 组件内容插槽
|
||||||
|
* 优化 微信小程序平台动态修改元素style时的多余内容
|
||||||
|
|
||||||
|
## 2.1.7(2024-05-28)
|
||||||
|
* 新增 checkRange 属性,当 checkRange=false 时允许图片位置超出裁剪边界
|
||||||
|
* 新增 minScale 属性,图片最小缩放倍数,当 minScale<0 时可使图片宽高不再受裁剪区域宽高限制
|
||||||
|
* 新增 backgroundColor 属性,生成图片背景色,如果裁剪区域没有完全包含在图片中时,不设置该属性生成图片存在一定的透明块
|
||||||
|
* 优化 动态修改图片宽高但没有传入src时,尺寸适应问题
|
||||||
|
* 修复 APP平台通过 `this.$ownerInstance` 获取组件实例时机过早,其值为 `undefined` 导致报错界面没有正常渲染的问题
|
||||||
|
|
||||||
|
## 2.1.6(2023-04-16)
|
||||||
|
* 修复 组件使用 v-show 指令会导致选择图片后初始位置严重偏位的问题
|
||||||
|
|
||||||
|
## 2.1.5(2023-04-15)
|
||||||
|
* 新增 兼容APP平台
|
||||||
|
|
||||||
|
## 2.1.4(2023-03-13)
|
||||||
|
* 新增 fileType 属性,用于指定生成文件的类型,只支持 'jpg' 或 'png',默认为 'png'
|
||||||
|
* 新增 delay 属性,微信小程序平台使用 `Canvas 2D` 绘制时控制图片从绘制到生成所需时间
|
||||||
|
* 优化 当生成图片的尺寸宽/高超过 Canvas 2D 最大限制(1365*1365)则将画布尺寸缩放在限制范围内绘制完成后输出目标尺寸
|
||||||
|
* 优化 旋转图标指示方向与实际旋转方向不符
|
||||||
|
|
||||||
|
## 2.1.3(2023-02-06)
|
||||||
|
* 优化 vue3支持
|
||||||
|
|
||||||
|
## 2.1.2(2023-02-03)
|
||||||
|
* 新增 navigation 属性,H5平台当 showAngle 为 true 时,使用插件的页面在 `page.json` 中配置了 "navigationStyle": "custom" 时,必须将此值设为 false ,否则四个可拉伸角的触发位置会有偏差
|
||||||
|
* 修复 H5平台部分设备(已知iPhone11以下机型)拍照的图片缩放时会闪动的问题
|
||||||
|
|
||||||
|
## 2.1.1(2022-12-06)
|
||||||
|
* 修复 横屏适配问题
|
||||||
|
|
||||||
|
## 2.1.0(2022-12-06)
|
||||||
|
* 新增 兼容H5平台,使用 renderjs 响应手势事件
|
||||||
|
|
||||||
|
## 2.0.0(2022-12-05)
|
||||||
|
* 重构 插件,使用 WXS 响应手势事件
|
||||||
|
* 新增 图片翻转
|
||||||
|
* 新增 拉伸裁剪框放大图片
|
||||||
|
* 新增 监听PC鼠标滚轮触发缩放
|
||||||
|
* 新增 圆形、圆角矩形的图片裁剪
|
||||||
|
* 优化 图片缩放,移动端以双指触摸中心点为缩放中心点,PC端以鼠标所在点为缩放中心点
|
||||||
|
* 优化 裁剪框样式
|
||||||
|
* 优化 图片位置拖动 支持边界回弹效果(滑动时可滑出边界,释放时回弹到边界)
|
||||||
|
* 优化 生成图片使用新版 Canvas 2D 接口
|
||||||
@@ -0,0 +1,855 @@
|
|||||||
|
/**
|
||||||
|
* 图片编辑器-手势监听
|
||||||
|
* 1. 支持编译到app-vue(uni-app 2.5.5及以上版本)、H5上
|
||||||
|
*/
|
||||||
|
/** 图片偏移量 */
|
||||||
|
var offset = { x: 0, y: 0 };
|
||||||
|
/** 图片缩放比例 */
|
||||||
|
var scale = 1;
|
||||||
|
/** 图片最小缩放比例 */
|
||||||
|
var minScale = 1;
|
||||||
|
/** 图片旋转角度 */
|
||||||
|
var rotate = 0;
|
||||||
|
/** 触摸点 */
|
||||||
|
var touches = [];
|
||||||
|
/** 图片布局信息 */
|
||||||
|
var img = {};
|
||||||
|
/** 系统信息 */
|
||||||
|
var sys = {};
|
||||||
|
/** 裁剪区域布局信息 */
|
||||||
|
var area = {};
|
||||||
|
/** 触摸行为类型 */
|
||||||
|
var touchType = '';
|
||||||
|
/** 操作角的位置 */
|
||||||
|
var activeAngle = 0;
|
||||||
|
/** 裁剪区域布局信息偏移量 */
|
||||||
|
var areaOffset = { left: 0, right: 0, top: 0, bottom: 0 };
|
||||||
|
/** 元素ID */
|
||||||
|
var elIds = {
|
||||||
|
'imageStyles': 'crop-image',
|
||||||
|
'maskStylesList': 'crop-mask-block',
|
||||||
|
'borderStyles': 'crop-border',
|
||||||
|
'circleBoxStyles': 'crop-circle-box',
|
||||||
|
'circleStyles': 'crop-circle',
|
||||||
|
'gridStylesList': 'crop-grid',
|
||||||
|
'angleStylesList': 'crop-angle',
|
||||||
|
}
|
||||||
|
/** 记录上次初始化时间戳,排除APP重复更新 */
|
||||||
|
var timestamp = 0;
|
||||||
|
/** vue3 renderjs 条件编译无效,以此方式区别 APP 和 H5 */
|
||||||
|
// #ifdef H5
|
||||||
|
var platform = 'H5';
|
||||||
|
// #endif
|
||||||
|
// #ifdef APP
|
||||||
|
var platform = 'APP';
|
||||||
|
// #endif
|
||||||
|
/** 容错值 */
|
||||||
|
var fault = 0.000001;
|
||||||
|
/**
|
||||||
|
* 获取a、b两数中的最小正数
|
||||||
|
* @param a
|
||||||
|
* @param b
|
||||||
|
*/
|
||||||
|
function minimum(a, b) {
|
||||||
|
if (a > 0 && b < 0) return a;
|
||||||
|
if (a < 0 && b > 0) return b;
|
||||||
|
if (a > 0 && b > 0) return Math.min(a, b);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 在容错访问内获取n近似值
|
||||||
|
* @param n
|
||||||
|
*/
|
||||||
|
function num(n) {
|
||||||
|
var m = parseFloat((n).toFixed(6));
|
||||||
|
return m === fault || m === -fault ? 0 : m;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 比较a值在容错值范围内是否等于b值
|
||||||
|
* @param a
|
||||||
|
* @param b
|
||||||
|
*/
|
||||||
|
function equalsByFault(a, b) {
|
||||||
|
return Math.abs(a - b) <= fault;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 比较a值在容错值范围内是否小于b值
|
||||||
|
* @param a
|
||||||
|
* @param b
|
||||||
|
*/
|
||||||
|
function lessThanByFault(a, b) {
|
||||||
|
var c = a - b;
|
||||||
|
return c < 0 ? c < -fault : c < fault;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 验证并获取有效最大值
|
||||||
|
* @param v
|
||||||
|
* @param max
|
||||||
|
* @param isInclude
|
||||||
|
* @param x
|
||||||
|
* @param y
|
||||||
|
* @param rate
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
function validMax(v, max, isInclude, x, y, rate) {
|
||||||
|
if(typeof max === 'number') {
|
||||||
|
if(isInclude && equalsByFault(max, y)) { // 宽高不等时,x轴用y轴值要做等比例转换
|
||||||
|
var n = num(max * rate);
|
||||||
|
if (n <= x) return n; // 转化后值在x轴最大值范围内
|
||||||
|
return x; // 转化后值超出x轴最大值范围则用最大值
|
||||||
|
}
|
||||||
|
return max;
|
||||||
|
}
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 样式对象转字符串
|
||||||
|
* @param {Object} style 样式对象
|
||||||
|
*/
|
||||||
|
function styleToString(style) {
|
||||||
|
if(typeof style === 'string') return style;
|
||||||
|
var str = '';
|
||||||
|
for (let k in style) {
|
||||||
|
str += k + ':' + style[k] + ';';
|
||||||
|
}
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {Object} instance 页面实例对象
|
||||||
|
* @param {Object} key 要修改样式的key
|
||||||
|
* @param {Object|Array} style 样式
|
||||||
|
*/
|
||||||
|
function setStyle(instance, key, style) {
|
||||||
|
// console.log('setStyle', instance, key, JSON.stringify(style))
|
||||||
|
// #ifdef APP-PLUS
|
||||||
|
if(platform === 'APP') {
|
||||||
|
if(Object.prototype.toString.call(style) === '[object Array]') {
|
||||||
|
for (var i = 0, len = style.length; i < len; i++) {
|
||||||
|
var el = window.document.getElementById(elIds[key] + '-' + (i + 1));
|
||||||
|
el && (el.style = styleToString(style[i]));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
var el = window.document.getElementById(elIds[key]);
|
||||||
|
el && (el.style = styleToString(style));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// #endif
|
||||||
|
// #ifdef H5
|
||||||
|
if(platform === 'H5') instance[key] = style;
|
||||||
|
// #endif
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 触发页面实例指定方法
|
||||||
|
* @param {Object} instance 页面实例对象
|
||||||
|
* @param {Object} name 方法名称
|
||||||
|
* @param {Object} obj 传递参数
|
||||||
|
*/
|
||||||
|
function callMethod(instance, name, obj) {
|
||||||
|
// #ifdef APP-PLUS
|
||||||
|
if(platform === 'APP') instance.callMethod(name, obj);
|
||||||
|
// #endif
|
||||||
|
// #ifdef H5
|
||||||
|
if(platform === 'H5') instance[name](obj);
|
||||||
|
// #endif
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 计算两点间距
|
||||||
|
* @param {Object} touches 触摸点信息
|
||||||
|
*/
|
||||||
|
function getDistanceByTouches(touches) {
|
||||||
|
// 根据勾股定理求两点间距离
|
||||||
|
var a = touches[1].pageX - touches[0].pageX;
|
||||||
|
var b = touches[1].pageY - touches[0].pageY;
|
||||||
|
var c = Math.sqrt(Math.pow(a, 2) + Math.pow(b, 2));
|
||||||
|
// 求两点间的中点坐标
|
||||||
|
// 1. a、b可能为负值
|
||||||
|
// 2. 在求a、b时,如用touches[1]减touches[0],则求中点坐标也得用touches[1]减a/2、b/2
|
||||||
|
// 3. 同理,在求a、b时,也可用touches[0]减touches[1],则求中点坐标也得用touches[0]减a/2、b/2
|
||||||
|
var x = touches[1].pageX - a / 2;
|
||||||
|
var y = touches[1].pageY - b / 2;
|
||||||
|
return { c, x, y };
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 修正取值
|
||||||
|
* @param {Object} a
|
||||||
|
* @param {Object} b
|
||||||
|
* @param {Object} c
|
||||||
|
* @param {Object} reverse 是否反向
|
||||||
|
*/
|
||||||
|
function correctValue(a, b, c, reverse) {
|
||||||
|
return num(reverse ? Math.max(Math.min(a, b), c) : Math.min(Math.max(a, b), c));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 旋转90°或270°时检查边界:限制 x、y 拖动范围,禁止滑出边界
|
||||||
|
* @param {Object} e 点坐标
|
||||||
|
* @param {Object} xReverse x是否反向
|
||||||
|
* @param {Object} yReverse y是否反向
|
||||||
|
*/
|
||||||
|
function checkRotateRange(e, xReverse, yReverse) {
|
||||||
|
var o = num((img.height - img.width) / 2); // 宽高差值一半
|
||||||
|
return {
|
||||||
|
x: correctValue(e.x, -img.height + o + area.width + area.left, area.left + o, xReverse),
|
||||||
|
y: correctValue(e.y, -img.width - o + area.height + area.top, area.top - o, yReverse)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查边界:限制 x、y 拖动范围,禁止滑出边界
|
||||||
|
* @param {Object} e 点坐标
|
||||||
|
*/
|
||||||
|
function checkRange(e) {
|
||||||
|
var r = rotate / 90 % 2;
|
||||||
|
if(r === 1) { // 因图片宽高可能不等,翻转 90° 或 270° 后图片宽高需反着计算,且左右和上下边界要根据差值做偏移
|
||||||
|
if (area.width === area.height) {
|
||||||
|
return checkRotateRange(e, img.height < area.height, img.width < area.width);
|
||||||
|
}
|
||||||
|
var isInclude = img.height < area.width && img.width < area.height; // 图片是否包含在裁剪区域内
|
||||||
|
if (img.width < area.height || img.height < area.width) {
|
||||||
|
if (area.width < area.height && img.width < img.height) {
|
||||||
|
return isInclude
|
||||||
|
? checkRotateRange(e, area.width < area.height, area.width < area.height)
|
||||||
|
: checkRotateRange(e, false, true);
|
||||||
|
}
|
||||||
|
if (area.height < area.width && img.height < img.width) {
|
||||||
|
return isInclude
|
||||||
|
? checkRotateRange(e, area.height < area.width, area.height < area.width)
|
||||||
|
: checkRotateRange(e, true, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (img.height >= area.width && img.width >= area.height) {
|
||||||
|
return checkRotateRange(e, false, false);
|
||||||
|
}
|
||||||
|
if (isInclude) {
|
||||||
|
return area.height < area.width
|
||||||
|
? checkRotateRange(e, true, true)
|
||||||
|
: checkRotateRange(e, area.width < area.height, area.width < area.height);
|
||||||
|
}
|
||||||
|
if (img.height < area.width && !img.width < area.height) {
|
||||||
|
return checkRotateRange(e, true, false);
|
||||||
|
}
|
||||||
|
if (!img.height < area.width && img.width < area.height) {
|
||||||
|
return checkRotateRange(e, false, true);
|
||||||
|
}
|
||||||
|
return checkRotateRange(e, img.height < area.height, img.width < area.width);
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
x: correctValue(e.x, -img.width + area.width + area.left, area.left, img.width < area.width),
|
||||||
|
y: correctValue(e.y, -img.height + area.height + area.top, area.top, img.height < area.height)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* 变更图片布局信息
|
||||||
|
* @param {Object} e 布局信息
|
||||||
|
*/
|
||||||
|
function changeImageRect(e) {
|
||||||
|
// console.log('changeImageRect', e)
|
||||||
|
offset.x += e.x || 0;
|
||||||
|
offset.y += e.y || 0;
|
||||||
|
if(e.check && area.checkRange) { // 检查边界
|
||||||
|
var point = checkRange(offset);
|
||||||
|
if(offset.x !== point.x || offset.y !== point.y) {
|
||||||
|
offset = point;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 因频繁修改 width/height 会造成大量的内存消耗,改为scale
|
||||||
|
// e.instance.imageStyles = {
|
||||||
|
// width: img.width + 'px',
|
||||||
|
// height: img.height + 'px',
|
||||||
|
// transform: 'translate(' + (offset.x + ox) + 'px, ' + (offset.y + ox) + 'px) rotate(' + rotate +'deg)'
|
||||||
|
// };
|
||||||
|
var ox = (img.width - img.oldWidth) / 2;
|
||||||
|
var oy = (img.height - img.oldHeight) / 2;
|
||||||
|
// e.instance.imageStyles = {
|
||||||
|
// width: img.oldWidth + 'px',
|
||||||
|
// height: img.oldHeight + 'px',
|
||||||
|
// transform: 'translate(' + (offset.x + ox) + 'px, ' + (offset.y + oy) + 'px) rotate(' + rotate +'deg) scale(' + scale + ')'
|
||||||
|
// };
|
||||||
|
setStyle(e.instance, 'imageStyles', {
|
||||||
|
width: img.oldWidth + 'px',
|
||||||
|
height: img.oldHeight + 'px',
|
||||||
|
transform: (img.gpu ? 'translateZ(0) ' : '') + 'translate(' + (offset.x + ox) + 'px, ' + (offset.y + oy) + 'px' + ') rotate(' + rotate +'deg) scale(' + scale + ')'
|
||||||
|
});
|
||||||
|
callMethod(e.instance, 'dataChange', {
|
||||||
|
width: img.width,
|
||||||
|
height: img.height,
|
||||||
|
x: offset.x,
|
||||||
|
y: offset.y,
|
||||||
|
rotate: rotate
|
||||||
|
});
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* 变更裁剪区域布局信息
|
||||||
|
* @param {Object} e 布局信息
|
||||||
|
*/
|
||||||
|
function changeAreaRect(e) {
|
||||||
|
// console.log('changeAreaRect', e)
|
||||||
|
// 变更蒙版样式
|
||||||
|
setStyle(e.instance, 'maskStylesList', [
|
||||||
|
{
|
||||||
|
left: 0,
|
||||||
|
width: (area.left + areaOffset.left) + 'px',
|
||||||
|
top: 0,
|
||||||
|
bottom: 0,
|
||||||
|
'z-index': area.zIndex + 2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
left: (area.right + areaOffset.right) + 'px',
|
||||||
|
right: 0,
|
||||||
|
top: 0,
|
||||||
|
bottom: 0,
|
||||||
|
'z-index': area.zIndex + 2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
left: (area.left + areaOffset.left) + 'px',
|
||||||
|
width: (area.width + areaOffset.right - areaOffset.left) + 'px',
|
||||||
|
top: 0,
|
||||||
|
height: (area.top + areaOffset.top) + 'px',
|
||||||
|
'z-index': area.zIndex + 2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
left: (area.left + areaOffset.left) + 'px',
|
||||||
|
width: (area.width + areaOffset.right - areaOffset.left) + 'px',
|
||||||
|
top: (area.bottom + areaOffset.bottom) + 'px',
|
||||||
|
// height: (area.top - areaOffset.bottom + sys.offsetBottom) + 'px',
|
||||||
|
bottom: 0,
|
||||||
|
'z-index': area.zIndex + 2
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
// 变更边框样式
|
||||||
|
if(area.showBorder) {
|
||||||
|
setStyle(e.instance, 'borderStyles', {
|
||||||
|
left: (area.left + areaOffset.left) + 'px',
|
||||||
|
top: (area.top + areaOffset.top) + 'px',
|
||||||
|
width: (area.width + areaOffset.right - areaOffset.left) + 'px',
|
||||||
|
height: (area.height + areaOffset.bottom - areaOffset.top) + 'px',
|
||||||
|
'z-index': area.zIndex + 3
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 变更参考线样式
|
||||||
|
if(area.showGrid) {
|
||||||
|
setStyle(e.instance, 'gridStylesList', [
|
||||||
|
{
|
||||||
|
'border-width': '1px 0 0 0',
|
||||||
|
left: (area.left + areaOffset.left) + 'px',
|
||||||
|
right: (area.right + areaOffset.right) + 'px',
|
||||||
|
top: (area.top + areaOffset.top + (area.height + areaOffset.bottom - areaOffset.top) / 3 - 0.5) + 'px',
|
||||||
|
width: (area.width + areaOffset.right - areaOffset.left) + 'px',
|
||||||
|
'z-index': area.zIndex + 3
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'border-width': '1px 0 0 0',
|
||||||
|
left: (area.left + areaOffset.left) + 'px',
|
||||||
|
right: (area.right + areaOffset.right) + 'px',
|
||||||
|
top: (area.top + areaOffset.top + (area.height + areaOffset.bottom - areaOffset.top) * 2 / 3 - 0.5) + 'px',
|
||||||
|
width: (area.width + areaOffset.right - areaOffset.left) + 'px',
|
||||||
|
'z-index': area.zIndex + 3
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'border-width': '0 1px 0 0',
|
||||||
|
top: (area.top + areaOffset.top) + 'px',
|
||||||
|
bottom: (area.bottom + areaOffset.bottom) + 'px',
|
||||||
|
left: (area.left + areaOffset.left + (area.width + areaOffset.right - areaOffset.left) / 3 - 0.5) + 'px',
|
||||||
|
height: (area.height + areaOffset.bottom - areaOffset.top) + 'px',
|
||||||
|
'z-index': area.zIndex + 3
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'border-width': '0 1px 0 0',
|
||||||
|
top: (area.top + areaOffset.top) + 'px',
|
||||||
|
bottom: (area.bottom + areaOffset.bottom) + 'px',
|
||||||
|
left: (area.left + areaOffset.left + (area.width + areaOffset.right - areaOffset.left) * 2 / 3 - 0.5) + 'px',
|
||||||
|
height: (area.height + areaOffset.bottom - areaOffset.top) + 'px',
|
||||||
|
'z-index': area.zIndex + 3
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 变更四个伸缩角样式
|
||||||
|
if(area.showAngle) {
|
||||||
|
setStyle(e.instance, 'angleStylesList', [
|
||||||
|
{
|
||||||
|
'border-width': area.angleBorderWidth + 'px 0 0 ' + area.angleBorderWidth + 'px',
|
||||||
|
left: (area.left + areaOffset.left - area.angleBorderWidth) + 'px',
|
||||||
|
top: (area.top + areaOffset.top - area.angleBorderWidth) + 'px',
|
||||||
|
'z-index': area.zIndex + 3
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'border-width': area.angleBorderWidth + 'px ' + area.angleBorderWidth + 'px 0 0',
|
||||||
|
left: (area.right + areaOffset.right - area.angleSize) + 'px',
|
||||||
|
top: (area.top + areaOffset.top - area.angleBorderWidth) + 'px',
|
||||||
|
'z-index': area.zIndex + 3
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'border-width': '0 0 ' + area.angleBorderWidth + 'px ' + area.angleBorderWidth + 'px',
|
||||||
|
left: (area.left + areaOffset.left - area.angleBorderWidth) + 'px',
|
||||||
|
top: (area.bottom + areaOffset.bottom - area.angleSize) + 'px',
|
||||||
|
'z-index': area.zIndex + 3
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'border-width': '0 ' + area.angleBorderWidth + 'px ' + area.angleBorderWidth + 'px 0',
|
||||||
|
left: (area.right + areaOffset.right - area.angleSize) + 'px',
|
||||||
|
top: (area.bottom + areaOffset.bottom - area.angleSize) + 'px',
|
||||||
|
'z-index': area.zIndex + 3
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 变更圆角样式
|
||||||
|
if(area.radius > 0) {
|
||||||
|
var radius = area.radius;
|
||||||
|
if(area.width === area.height && area.radius >= area.width / 2) { // 圆形
|
||||||
|
radius = (area.width / 2);
|
||||||
|
} else { // 圆角矩形
|
||||||
|
if(area.width !== area.height) { // 限制圆角半径不能超过短边的一半
|
||||||
|
radius = Math.min(area.width / 2, area.height / 2, radius);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setStyle(e.instance, 'circleBoxStyles', {
|
||||||
|
left: (area.left + areaOffset.left) + 'px',
|
||||||
|
top: (area.top + areaOffset.top) + 'px',
|
||||||
|
width: (area.width + areaOffset.right - areaOffset.left) + 'px',
|
||||||
|
height: (area.height + areaOffset.bottom - areaOffset.top) + 'px',
|
||||||
|
'z-index': area.zIndex + 2
|
||||||
|
});
|
||||||
|
setStyle(e.instance, 'circleStyles', {
|
||||||
|
'box-shadow': '0 0 0 ' + Math.max(area.width, area.height) + 'px rgba(51, 51, 51, 0.8)',
|
||||||
|
'border-radius': radius + 'px'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* 缩放图片
|
||||||
|
* @param {Object} e 布局信息
|
||||||
|
*/
|
||||||
|
function scaleImage(e) {
|
||||||
|
// console.log('scaleImage', e)
|
||||||
|
var last = scale;
|
||||||
|
scale = Math.min(Math.max(e.scale + scale, minScale), img.maxScale);
|
||||||
|
if(last !== scale) {
|
||||||
|
img.width = num(img.oldWidth * scale);
|
||||||
|
img.height = num(img.oldHeight * scale);
|
||||||
|
// 参考问题:有一个长4000px、宽4000px的四方形ABCD,A点的坐标固定在(-2000,-2000),
|
||||||
|
// 该四边形上有一个点E,坐标为(-100,-300),将该四方形复制一份并缩小到90%后,
|
||||||
|
// 新四边形的A点坐标为多少时可使新四边形的E点与原四边形的E点重合?
|
||||||
|
// 预期效果:从图中选取某点(参照物)为中心点进行缩放,缩放时无论图像怎么变化,该点位置始终固定不变
|
||||||
|
// 计算方法:以相同起点先计算缩放前后两点间的距离,再加上原图像偏移量即可
|
||||||
|
e.x = num((e.x - offset.x) * (1 - scale / last));
|
||||||
|
e.y = num((e.y - offset.y) * (1 - scale / last));
|
||||||
|
changeImageRect(e);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* 获取触摸点在哪个角
|
||||||
|
* @param {number} x 触摸点x轴坐标
|
||||||
|
* @param {number} y 触摸点y轴坐标
|
||||||
|
* @return {number} 角的位置:0=无;1=左上;2=右上;3=左下;4=右下;
|
||||||
|
*/
|
||||||
|
function getToucheAngle(x, y) {
|
||||||
|
// console.log('getToucheAngle', x, y, JSON.stringify(area))
|
||||||
|
var o = area.angleBorderWidth; // 需扩大触发范围则把 o 值加大即可
|
||||||
|
var oy = sys.navigation ? 0 : sys.windowTop;
|
||||||
|
if(y >= area.top - o + oy && y <= area.top + area.angleSize + o + oy) {
|
||||||
|
if(x >= area.left - o && x <= area.left + area.angleSize + o) {
|
||||||
|
return 1; // 左上角
|
||||||
|
} else if(x >= area.right - area.angleSize - o && x <= area.right + o) {
|
||||||
|
return 2; // 右上角
|
||||||
|
}
|
||||||
|
} else if(y >= area.bottom - area.angleSize - o + oy && y <= area.bottom + o + oy) {
|
||||||
|
if(x >= area.left - o && x <= area.left + area.angleSize + o) {
|
||||||
|
return 3; // 左下角
|
||||||
|
} else if(x >= area.right - area.angleSize - o && x <= area.right + o) {
|
||||||
|
return 4; // 右下角
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0; // 无触摸到角
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* 重置数据
|
||||||
|
*/
|
||||||
|
function resetData() {
|
||||||
|
offset = { x: 0, y: 0 };
|
||||||
|
scale = 1;
|
||||||
|
minScale = img.minScale;
|
||||||
|
rotate = 0;
|
||||||
|
};
|
||||||
|
function getTouchs(touches) {
|
||||||
|
var result = [];
|
||||||
|
var len = touches ? touches.length : 0
|
||||||
|
for (var i = 0; i < len; i++) {
|
||||||
|
result[i] = {
|
||||||
|
pageX: touches[i].pageX,
|
||||||
|
// h5无标题栏时,窗口顶部距离仍为标题栏高度,且触摸点y轴坐标还是有标题栏的值,即减去标题栏高度的值
|
||||||
|
pageY: touches[i].pageY + sys.windowTop
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
var mouseEvent = false;
|
||||||
|
export default {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
imageStyles: {},
|
||||||
|
maskStylesList: [{}, {}, {}, {}],
|
||||||
|
borderStyles: {},
|
||||||
|
gridStylesList: [{}, {}, {}, {}],
|
||||||
|
angleStylesList: [{}, {}, {}, {}],
|
||||||
|
circleBoxStyles: {},
|
||||||
|
circleStyles: {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
// 监听 PC 端鼠标滚轮
|
||||||
|
// #ifdef H5
|
||||||
|
platform === 'H5' && window.addEventListener('mousewheel', async (e) => {
|
||||||
|
var touchs = getTouchs([e])
|
||||||
|
img.src && scaleImage({
|
||||||
|
instance: await this.getInstance(),
|
||||||
|
check: true,
|
||||||
|
// 鼠标向上滚动时,deltaY 固定 -100,鼠标向下滚动时,deltaY 固定 100
|
||||||
|
scale: e.deltaY > 0 ? -0.05 : 0.05,
|
||||||
|
x: touchs[0].pageX,
|
||||||
|
y: touchs[0].pageY
|
||||||
|
});
|
||||||
|
});
|
||||||
|
// #endif
|
||||||
|
},
|
||||||
|
// #ifdef H5
|
||||||
|
mounted() {
|
||||||
|
platform === 'H5' && this.initH5Events();
|
||||||
|
},
|
||||||
|
// #endif
|
||||||
|
setPlatform(p) {
|
||||||
|
platform = p;
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
// #ifdef H5
|
||||||
|
getTouchEvent(e) {
|
||||||
|
e.touches = [
|
||||||
|
{ pageX: e.pageX, pageY: e.pageY }
|
||||||
|
];
|
||||||
|
return e;
|
||||||
|
},
|
||||||
|
initH5Events() {
|
||||||
|
const preview = document.getElementById('pic-preview');
|
||||||
|
preview?.addEventListener('mousedown', (e, ev) => {
|
||||||
|
mouseEvent = true;
|
||||||
|
this.touchstart(this.getTouchEvent(e));
|
||||||
|
});
|
||||||
|
preview?.addEventListener('mousemove', (e) => {
|
||||||
|
if (!mouseEvent) return;
|
||||||
|
this.touchmove(this.getTouchEvent(e));
|
||||||
|
});
|
||||||
|
preview?.addEventListener('mouseup', (e) => {
|
||||||
|
mouseEvent = false;
|
||||||
|
this.touchend(this.getTouchEvent(e))
|
||||||
|
});
|
||||||
|
preview?.addEventListener('mouseleave', (e) => {
|
||||||
|
mouseEvent = false;
|
||||||
|
this.touchend(this.getTouchEvent(e))
|
||||||
|
});
|
||||||
|
},
|
||||||
|
// #endif
|
||||||
|
async getInstance() {
|
||||||
|
// #ifdef APP-PLUS
|
||||||
|
if(platform === 'APP')
|
||||||
|
return this.$ownerInstance
|
||||||
|
? Promise.resolve(this.$ownerInstance)
|
||||||
|
: new Promise((resolve) => {
|
||||||
|
setTimeout(async () => {
|
||||||
|
resolve(await this.getInstance());
|
||||||
|
});
|
||||||
|
});
|
||||||
|
// #endif
|
||||||
|
// #ifdef H5
|
||||||
|
if(platform === 'H5')
|
||||||
|
return Promise.resolve(this);
|
||||||
|
// #endif
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 初始化:观察数据变更
|
||||||
|
* @param {Object} newVal 新数据
|
||||||
|
* @param {Object} oldVal 旧数据
|
||||||
|
* @param {Object} o 组件实例对象
|
||||||
|
*/
|
||||||
|
initObserver: async function(newVal, oldVal, o, i) {
|
||||||
|
// console.log('initObserver', newVal, oldVal, o, i)
|
||||||
|
if(newVal && (!img.src || timestamp !== newVal.timestamp)) {
|
||||||
|
timestamp = newVal.timestamp;
|
||||||
|
img = newVal.img;
|
||||||
|
sys = newVal.sys;
|
||||||
|
area = newVal.area;
|
||||||
|
minScale = img.minScale;
|
||||||
|
resetData();
|
||||||
|
const instance = await this.getInstance()
|
||||||
|
img.src && changeImageRect({
|
||||||
|
instance,
|
||||||
|
x: (sys.windowWidth - img.width) / 2,
|
||||||
|
y: (sys.windowHeight + sys.windowTop - sys.offsetBottom - img.height) / 2
|
||||||
|
});
|
||||||
|
changeAreaRect({
|
||||||
|
instance
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 鼠标滚轮滚动
|
||||||
|
* @param {Object} e 事件对象
|
||||||
|
* @param {Object} o 组件实例对象
|
||||||
|
*/
|
||||||
|
mousewheel: function(e, o) {
|
||||||
|
// h5平台 wheel 事件无法判断滚轮滑动方向,需使用 mousewheel
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 触摸开始
|
||||||
|
* @param {Object} e 事件对象
|
||||||
|
* @param {Object} o 组件实例对象
|
||||||
|
*/
|
||||||
|
touchstart: function(e, o) {
|
||||||
|
if(!img.src) return;
|
||||||
|
touches = getTouchs(e.touches);
|
||||||
|
activeAngle = area.showAngle ? getToucheAngle(touches[0].pageX, touches[0].pageY) : 0;
|
||||||
|
if(touches.length === 1 && activeAngle !== 0) {
|
||||||
|
touchType = 'stretch'; // 伸缩裁剪区域
|
||||||
|
} else {
|
||||||
|
touchType = '';
|
||||||
|
}
|
||||||
|
// console.log('touchstart', e, activeAngle)
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 触摸移动
|
||||||
|
* @param {Object} e 事件对象
|
||||||
|
* @param {Object} o 组件实例对象
|
||||||
|
*/
|
||||||
|
touchmove: async function(e, o) {
|
||||||
|
if(!img.src) return;
|
||||||
|
// console.log('touchmove', e, o)
|
||||||
|
e.touches = getTouchs(e.touches);
|
||||||
|
if(touchType === 'stretch') { // 触摸四个角进行拉伸
|
||||||
|
var point = e.touches[0];
|
||||||
|
var start = touches[0];
|
||||||
|
var x = point.pageX - start.pageX;
|
||||||
|
var y = point.pageY - start.pageY;
|
||||||
|
if(x !== 0 || y !== 0) {
|
||||||
|
var maxX = num(area.width * (1 - area.minScale));
|
||||||
|
var maxY = num(area.height * (1 - area.minScale));
|
||||||
|
// console.log(x, y, maxX, maxY, offset, area)
|
||||||
|
touches[0] = point;
|
||||||
|
var r = rotate / 90 % 2;
|
||||||
|
var m = r === 1 ? num((img.height - img.width) / 2) : 0; // 宽高差值一半
|
||||||
|
var xCompare = r === 1 ? lessThanByFault(img.height, area.width) : lessThanByFault(img.width, area.width);
|
||||||
|
var yCompare = r === 1 ? lessThanByFault(img.width, area.height) : lessThanByFault(img.height, area.height)
|
||||||
|
var isInclude = xCompare && yCompare;
|
||||||
|
var isIntersect = area.checkRange && (xCompare || yCompare); // 图片是否包含在裁剪区域内
|
||||||
|
var isReverse = !isInclude || num((offset.x - area.left) / area.width) <= num((offset.y - area.top) / area.height) || (area.width > area.height && img.width < img.height && r === 1);
|
||||||
|
switch(activeAngle) {
|
||||||
|
case 1: // 左上角
|
||||||
|
x = num(x + areaOffset.left);
|
||||||
|
y = num(y + areaOffset.top);
|
||||||
|
if(x >= 0 && y >= 0) { // 有效滑动
|
||||||
|
var t = num(offset.y + m - area.top);
|
||||||
|
var l = num(offset.x - m - area.left);
|
||||||
|
// && (offset.x + img.width < area.right || offset.y + img.height < area.bottom)
|
||||||
|
var max = isIntersect && ((l >= 0) || (t >= 0))
|
||||||
|
? minimum(t, l)
|
||||||
|
: false;
|
||||||
|
if(x > y && isReverse) { // 以x轴滑动距离为缩放基准
|
||||||
|
maxX = validMax(maxX, max, isInclude, l, t, area.width / area.height);
|
||||||
|
if(x > maxX) x = maxX;
|
||||||
|
y = num(x * area.height / area.width);
|
||||||
|
} else { // 以y轴滑动距离为缩放基准
|
||||||
|
maxY = validMax(maxY, max, isInclude, t, l, area.height / area.width);
|
||||||
|
if(y > maxY) y = maxY;
|
||||||
|
x = num(y * area.width / area.height);
|
||||||
|
}
|
||||||
|
areaOffset.left = x;
|
||||||
|
areaOffset.top = y;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 2: // 右上角
|
||||||
|
x = num(x + areaOffset.right);
|
||||||
|
y = num(y + areaOffset.top);
|
||||||
|
if(x <= 0 && y >= 0) { // 有效滑动
|
||||||
|
var w = (r === 1 ? img.height : img.width);
|
||||||
|
var t = num(offset.y + m - area.top);
|
||||||
|
var l = num(area.right + m - offset.x - w);
|
||||||
|
var max = isIntersect && ((t >= 0) || (l >= 0))
|
||||||
|
? minimum(t, l)
|
||||||
|
: false;
|
||||||
|
if(-x > y && isReverse) { // 以x轴滑动距离为缩放基准
|
||||||
|
maxX = validMax(maxX, max, isInclude, l, t, area.width / area.height);
|
||||||
|
if(-x > maxX) x = -maxX;
|
||||||
|
y = num(-x * area.height / area.width);
|
||||||
|
} else { // 以y轴滑动距离为缩放基准
|
||||||
|
maxY = validMax(maxY, max, isInclude, t, l, area.height / area.width);
|
||||||
|
if(y > maxY) y = maxY;
|
||||||
|
x = num(-y * area.width / area.height);
|
||||||
|
}
|
||||||
|
areaOffset.right = x;
|
||||||
|
areaOffset.top = y;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 3: // 左下角
|
||||||
|
x += num(x + areaOffset.left);
|
||||||
|
y += num(y + areaOffset.bottom);
|
||||||
|
if(x >= 0 && y <= 0) { // 有效滑动
|
||||||
|
var w = (r === 1 ? img.width : img.height);
|
||||||
|
var t = num(area.bottom - m - offset.y - w);
|
||||||
|
var l = num(offset.x - m - area.left);
|
||||||
|
var max = isIntersect && ((l >= 0) || (t >= 0))
|
||||||
|
? minimum(t, l)
|
||||||
|
: false;
|
||||||
|
if(x > -y && isReverse) { // 以x轴滑动距离为缩放基准
|
||||||
|
maxX = validMax(maxX, max, isInclude, l, t, area.width / area.height);
|
||||||
|
if(x > maxX) x = maxX;
|
||||||
|
y = num(-x * area.height / area.width);
|
||||||
|
} else { // 以y轴滑动距离为缩放基准
|
||||||
|
maxY = validMax(maxY, max, isInclude, t, l, area.height / area.width);
|
||||||
|
if(-y > maxY) y = -maxY;
|
||||||
|
x = num(-y * area.width / area.height);
|
||||||
|
}
|
||||||
|
areaOffset.left = x;
|
||||||
|
areaOffset.bottom = y;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 4: // 右下角
|
||||||
|
x = num(x + areaOffset.right);
|
||||||
|
y = num(y + areaOffset.bottom);
|
||||||
|
if(x <= 0 && y <= 0) { // 有效滑动
|
||||||
|
var w = (r === 1 ? img.height : img.width);
|
||||||
|
var h = (r === 1 ? img.width : img.height);
|
||||||
|
var t = num(area.bottom - offset.y - h - m);
|
||||||
|
var l = num(area.right + m - offset.x - w);
|
||||||
|
var max = isIntersect && ((l >= 0) || (t >= 0))
|
||||||
|
? minimum(t, l)
|
||||||
|
: false;
|
||||||
|
if(-x > -y && isReverse) { // 以x轴滑动距离为缩放基准
|
||||||
|
maxX = validMax(maxX, max, isInclude, l, t, area.width / area.height);
|
||||||
|
if(-x > maxX) x = -maxX;
|
||||||
|
y = num(x * area.height / area.width);
|
||||||
|
} else { // 以y轴滑动距离为缩放基准
|
||||||
|
maxY = validMax(maxY, max, isInclude, t, l, area.height / area.width);
|
||||||
|
if(-y > maxY) y = -maxY;
|
||||||
|
x = num(y * area.width / area.height);
|
||||||
|
}
|
||||||
|
areaOffset.right = x;
|
||||||
|
areaOffset.bottom = y;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// console.log(x, y, JSON.stringify(areaOffset))
|
||||||
|
changeAreaRect({
|
||||||
|
instance: await this.getInstance(),
|
||||||
|
});
|
||||||
|
// this.draw();
|
||||||
|
}
|
||||||
|
} else if (e.touches.length == 2) { // 双点触摸缩放
|
||||||
|
var start = getDistanceByTouches(touches);
|
||||||
|
var end = getDistanceByTouches(e.touches);
|
||||||
|
scaleImage({
|
||||||
|
instance: await this.getInstance(),
|
||||||
|
check: !area.bounce,
|
||||||
|
scale: (end.c - start.c) / 100,
|
||||||
|
x: end.x,
|
||||||
|
y: end.y
|
||||||
|
});
|
||||||
|
touchType = 'scale';
|
||||||
|
} else if(touchType === 'scale') {// 从双点触摸变成单点触摸 / 从缩放变成拖动
|
||||||
|
touchType = 'move';
|
||||||
|
} else {
|
||||||
|
changeImageRect({
|
||||||
|
instance: await this.getInstance(),
|
||||||
|
check: !area.bounce,
|
||||||
|
x: e.touches[0].pageX - touches[0].pageX,
|
||||||
|
y: e.touches[0].pageY - touches[0].pageY
|
||||||
|
});
|
||||||
|
touchType = 'move';
|
||||||
|
}
|
||||||
|
touches = e.touches;
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 触摸结束
|
||||||
|
* @param {Object} e 事件对象
|
||||||
|
* @param {Object} o 组件实例对象
|
||||||
|
*/
|
||||||
|
touchend: async function(e, o) {
|
||||||
|
if(!img.src) return;
|
||||||
|
if(touchType === 'stretch') { // 拉伸裁剪区域的四个角缩放
|
||||||
|
// 裁剪区域宽度被缩放到多少
|
||||||
|
var left = areaOffset.left;
|
||||||
|
var right = areaOffset.right;
|
||||||
|
var top = areaOffset.top;
|
||||||
|
var bottom = areaOffset.bottom;
|
||||||
|
var w = area.width + right - left;
|
||||||
|
var h = area.height + bottom - top;
|
||||||
|
// 图像放大倍数
|
||||||
|
var p = scale * (area.width / w) - scale;
|
||||||
|
// 复原裁剪区域
|
||||||
|
areaOffset = { left: 0, right: 0, top: 0, bottom: 0 };
|
||||||
|
changeAreaRect({
|
||||||
|
instance: await this.getInstance(),
|
||||||
|
});
|
||||||
|
scaleImage({
|
||||||
|
instance: await this.getInstance(),
|
||||||
|
scale: p,
|
||||||
|
x: area.left + left + (1 === activeAngle || 3 === activeAngle ? w : 0),
|
||||||
|
y: area.top + top + (1 === activeAngle || 2 === activeAngle ? h : 0)
|
||||||
|
});
|
||||||
|
} else if (area.bounce) { // 检查边界并矫正,实现拖动到边界时有回弹效果
|
||||||
|
changeImageRect({
|
||||||
|
instance: await this.getInstance(),
|
||||||
|
check: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 顺时针翻转图片90°
|
||||||
|
* @param {Object} e 事件对象
|
||||||
|
* @param {Object} o 组件实例对象
|
||||||
|
*/
|
||||||
|
rotateImage: async function(r) {
|
||||||
|
rotate = (rotate + (r || 90)) % 360;
|
||||||
|
|
||||||
|
if(img.minScale >= 1 && area.checkRange) {
|
||||||
|
// 因图片宽高可能不等,翻转后图片宽高需足够填满裁剪区域
|
||||||
|
minScale = 1;
|
||||||
|
if(img.width < area.height) {
|
||||||
|
minScale = area.height / img.oldWidth;
|
||||||
|
} else if(img.height < area.width) {
|
||||||
|
minScale = area.width / img.oldHeight;
|
||||||
|
}
|
||||||
|
if(minScale !== 1) {
|
||||||
|
scaleImage({
|
||||||
|
instance: await this.getInstance(),
|
||||||
|
scale: minScale - scale,
|
||||||
|
x: sys.windowWidth / 2,
|
||||||
|
y: (sys.windowHeight - sys.offsetBottom) / 2
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 由于拖动画布后会导致图片位置偏移,翻转时的旋转中心点需是图片区域+偏移区域的中心点
|
||||||
|
// 翻转x轴中心点 = (超出裁剪区域右侧的图片宽度 - 超出裁剪区域左侧的图片宽度) / 2
|
||||||
|
// 翻转y轴中心点 = (超出裁剪区域下方的图片宽度 - 超出裁剪区域上方的图片宽度) / 2
|
||||||
|
var ox = ((offset.x + img.width - area.right) - (area.left - offset.x)) / 2;
|
||||||
|
var oy = ((offset.y + img.height - area.bottom) - (area.top - offset.y)) / 2;
|
||||||
|
changeImageRect({
|
||||||
|
instance: await this.getInstance(),
|
||||||
|
check: true,
|
||||||
|
x: -ox - oy,
|
||||||
|
y: -oy + ox
|
||||||
|
});
|
||||||
|
},
|
||||||
|
rotateImage90: function() {
|
||||||
|
this.rotateImage(90)
|
||||||
|
},
|
||||||
|
rotateImage270: function() {
|
||||||
|
this.rotateImage(270)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,743 @@
|
|||||||
|
<template>
|
||||||
|
<view class="image-cropper" :style="{ zIndex }" @wheel="cropper.mousewheel">
|
||||||
|
<canvas v-if="use2d" type="2d" id="imgCanvas" class="img-canvas" :style="{
|
||||||
|
width: `${canvansWidth}px`,
|
||||||
|
height: `${canvansHeight}px`
|
||||||
|
}"></canvas>
|
||||||
|
<canvas v-else id="imgCanvas" canvas-id="imgCanvas" class="img-canvas" :style="{
|
||||||
|
width: `${canvansWidth}px`,
|
||||||
|
height: `${canvansHeight}px`
|
||||||
|
}"></canvas>
|
||||||
|
<view id="pic-preview" class="pic-preview" :change:init="cropper.initObserver" :init="initData" @touchstart="cropper.touchstart" @touchmove="cropper.touchmove" @touchend="cropper.touchend">
|
||||||
|
<image v-if="imgSrc" id="crop-image" class="crop-image" :style="cropper.imageStyles" :src="imgSrc" webp></image>
|
||||||
|
<view v-for="(item, index) in maskList" :key="item.id" :id="item.id" class="crop-mask-block" :style="cropper.maskStylesList[index]"></view>
|
||||||
|
<view v-if="showBorder" id="crop-border" class="crop-border" :style="cropper.borderStyles"></view>
|
||||||
|
<view v-if="radius > 0" id="crop-circle-box" class="crop-circle-box" :style="cropper.circleBoxStyles">
|
||||||
|
<view class="crop-circle" id="crop-circle" :style="cropper.circleStyles"></view>
|
||||||
|
</view>
|
||||||
|
<block v-if="showGrid">
|
||||||
|
<view v-for="(item, index) in gridList" :key="item.id" :id="item.id" class="crop-grid" :style="cropper.gridStylesList[index]"></view>
|
||||||
|
</block>
|
||||||
|
<block v-if="showAngle">
|
||||||
|
<view v-for="(item, index) in angleList" :key="item.id" :id="item.id" class="crop-angle" :style="cropper.angleStylesList[index]">
|
||||||
|
<view :style="[{
|
||||||
|
width: `${angleSize}px`,
|
||||||
|
height: `${angleSize}px`
|
||||||
|
}]"></view>
|
||||||
|
</view>
|
||||||
|
</block>
|
||||||
|
</view>
|
||||||
|
<slot />
|
||||||
|
<view class="fixed-bottom safe-area-inset-bottom" :style="{ zIndex: initData.area.zIndex + 99 }">
|
||||||
|
<view v-if="(rotatable || reverseRotatable) && !!imgSrc" class="action-bar">
|
||||||
|
<view v-if="reverseRotatable" class="rotate-icon" @click="cropper.rotateImage270"></view>
|
||||||
|
<view v-if="rotatable" class="rotate-icon is-reverse" @click="cropper.rotateImage90"></view>
|
||||||
|
</view>
|
||||||
|
<view v-if="!choosable" class="choose-btn" @click="cropClick">确定</view>
|
||||||
|
<block v-else-if="!!imgSrc">
|
||||||
|
<view class="rechoose" @click="chooseImage">重选</view>
|
||||||
|
<button class="button" size="mini" @click="cropClick">确定</button>
|
||||||
|
</block>
|
||||||
|
<view v-else class="choose-btn" @click="chooseImage">选择图片</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- #ifdef APP-VUE -->
|
||||||
|
<script module="cropper" lang="renderjs">
|
||||||
|
import cropper from './qf-image-cropper.render.js';
|
||||||
|
// vue3 app renderjs中条件编译无效
|
||||||
|
cropper.setPlatform('APP');
|
||||||
|
export default {
|
||||||
|
mixins: [ cropper ]
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<!-- #endif -->
|
||||||
|
<!-- #ifdef H5 -->
|
||||||
|
<script module="cropper" lang="renderjs">
|
||||||
|
import cropper from './qf-image-cropper.render.js';
|
||||||
|
export default {
|
||||||
|
mixins: [ cropper ]
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<!-- #endif -->
|
||||||
|
<!-- #ifdef MP-WEIXIN || MP-QQ -->
|
||||||
|
<script module="cropper" lang="wxs" src="./qf-image-cropper.wxs"></script>
|
||||||
|
<!-- #endif -->
|
||||||
|
<script>
|
||||||
|
/** 裁剪区域最大宽高所占屏幕宽度百分比 */
|
||||||
|
const AREA_SIZE = 75;
|
||||||
|
/** 图片默认宽高 */
|
||||||
|
const IMG_SIZE = 300;
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name:"qf-image-cropper",
|
||||||
|
// #ifdef MP-WEIXIN
|
||||||
|
options: {
|
||||||
|
// 表示启用样式隔离,在自定义组件内外,使用 class 指定的样式将不会相互影响
|
||||||
|
styleIsolation: "isolated"
|
||||||
|
},
|
||||||
|
// #endif
|
||||||
|
props: {
|
||||||
|
/** 图片资源地址 */
|
||||||
|
src: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
/** 裁剪宽度,有些平台或设备对于canvas的尺寸有限制,过大可能会导致无法正常绘制 */
|
||||||
|
width: {
|
||||||
|
type: Number,
|
||||||
|
default: IMG_SIZE
|
||||||
|
},
|
||||||
|
/** 裁剪高度,有些平台或设备对于canvas的尺寸有限制,过大可能会导致无法正常绘制 */
|
||||||
|
height: {
|
||||||
|
type: Number,
|
||||||
|
default: IMG_SIZE
|
||||||
|
},
|
||||||
|
/** 是否绘制裁剪区域边框 */
|
||||||
|
showBorder: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
/** 是否绘制裁剪区域网格参考线 */
|
||||||
|
showGrid: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
/** 是否展示四个支持伸缩的角 */
|
||||||
|
showAngle: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
/** 裁剪区域最小缩放倍数 */
|
||||||
|
areaScale: {
|
||||||
|
type: Number,
|
||||||
|
default: 0.3
|
||||||
|
},
|
||||||
|
/** 图片最小缩放倍数 */
|
||||||
|
minScale: {
|
||||||
|
type: Number,
|
||||||
|
default: 1
|
||||||
|
},
|
||||||
|
/** 图片最大缩放倍数 */
|
||||||
|
maxScale: {
|
||||||
|
type: Number,
|
||||||
|
default: 5
|
||||||
|
},
|
||||||
|
/** 检查图片位置是否超出裁剪边界,如果超出则会矫正位置 */
|
||||||
|
checkRange: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
/** 生成图片背景色:如果裁剪区域没有完全包含在图片中时,不设置该属性生成图片存在一定的透明块 */
|
||||||
|
backgroundColor: {
|
||||||
|
type: String
|
||||||
|
},
|
||||||
|
/** 是否有回弹效果:当 checkRange 为 true 时有效,拖动时可以拖出边界,释放时会弹回边界 */
|
||||||
|
bounce: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
/** 是否支持翻转 */
|
||||||
|
rotatable: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
/** 是否支持逆向翻转 */
|
||||||
|
reverseRotatable: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
/** 是否支持从本地选择素材 */
|
||||||
|
choosable: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
/** 是否开启硬件加速,图片缩放过程中如果出现元素的“留影”或“重影”效果,可通过该方式解决或减轻这一问题 */
|
||||||
|
gpu: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
/** 四个角尺寸,单位px */
|
||||||
|
angleSize: {
|
||||||
|
type: Number,
|
||||||
|
default: 20
|
||||||
|
},
|
||||||
|
/** 四个角边框宽度,单位px */
|
||||||
|
angleBorderWidth: {
|
||||||
|
type: Number,
|
||||||
|
default: 2
|
||||||
|
},
|
||||||
|
zIndex: {
|
||||||
|
type: [Number, String]
|
||||||
|
},
|
||||||
|
/** 裁剪图片圆角半径,单位px */
|
||||||
|
radius: {
|
||||||
|
type: Number,
|
||||||
|
default: 0
|
||||||
|
},
|
||||||
|
/** 生成文件的类型,只支持 'jpg' 或 'png'。默认为 'png' */
|
||||||
|
fileType: {
|
||||||
|
type: String,
|
||||||
|
default: 'png'
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 图片从绘制到生成所需时间,单位ms
|
||||||
|
* 微信小程序平台使用 `Canvas 2D` 绘制时有效
|
||||||
|
* 如绘制大图或出现裁剪图片空白等情况应适当调大该值,因 `Canvas 2d` 采用同步绘制,需自己把控绘制完成时间
|
||||||
|
*/
|
||||||
|
delay: {
|
||||||
|
type: Number,
|
||||||
|
default: 1000
|
||||||
|
},
|
||||||
|
// #ifdef H5
|
||||||
|
/**
|
||||||
|
* 页面是否是原生标题栏
|
||||||
|
* H5平台当 showAngle 为 true 时,使用插件的页面在 `page.json` 中配置了 "navigationStyle": "custom" 时,必须将此值设为 false ,否则四个可拉伸角的触发位置会有偏差。
|
||||||
|
* 注:因H5平台的窗口高度是包含标题栏的,而屏幕触摸点的坐标是不包含的
|
||||||
|
*/
|
||||||
|
navigation: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
}
|
||||||
|
// #endif
|
||||||
|
},
|
||||||
|
emits: ["crop"],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
// 用不同 id 使 v-for key 不重复
|
||||||
|
maskList: [
|
||||||
|
{ id: 'crop-mask-block-1' },
|
||||||
|
{ id: 'crop-mask-block-2' },
|
||||||
|
{ id: 'crop-mask-block-3' },
|
||||||
|
{ id: 'crop-mask-block-4' },
|
||||||
|
],
|
||||||
|
gridList: [
|
||||||
|
{ id: 'crop-grid-1' },
|
||||||
|
{ id: 'crop-grid-2' },
|
||||||
|
{ id: 'crop-grid-3' },
|
||||||
|
{ id: 'crop-grid-4' },
|
||||||
|
],
|
||||||
|
angleList: [
|
||||||
|
{ id: 'crop-angle-1' },
|
||||||
|
{ id: 'crop-angle-2' },
|
||||||
|
{ id: 'crop-angle-3' },
|
||||||
|
{ id: 'crop-angle-4' },
|
||||||
|
],
|
||||||
|
/** 本地缓存的图片路径 */
|
||||||
|
imgSrc: '',
|
||||||
|
/** 图片的裁剪宽度 */
|
||||||
|
imgWidth: IMG_SIZE,
|
||||||
|
/** 图片的裁剪高度 */
|
||||||
|
imgHeight: IMG_SIZE,
|
||||||
|
/** 裁剪区域最大宽度所占屏幕宽度百分比 */
|
||||||
|
widthPercent: AREA_SIZE,
|
||||||
|
/** 裁剪区域最大高度所占屏幕宽度百分比 */
|
||||||
|
heightPercent: AREA_SIZE,
|
||||||
|
/** 裁剪区域布局信息 */
|
||||||
|
area: {},
|
||||||
|
/** 未被缩放过的图片宽 */
|
||||||
|
oldWidth: 0,
|
||||||
|
/** 未被缩放过的图片高 */
|
||||||
|
oldHeight: 0,
|
||||||
|
/** 系统信息 */
|
||||||
|
sys: uni.getSystemInfoSync(),
|
||||||
|
scaleWidth: 0,
|
||||||
|
scaleHeight: 0,
|
||||||
|
rotate: 0,
|
||||||
|
offsetX: 0,
|
||||||
|
offsetY: 0,
|
||||||
|
use2d: false,
|
||||||
|
canvansWidth: 0,
|
||||||
|
canvansHeight: 0,
|
||||||
|
// imageStyles: {},
|
||||||
|
// maskStylesList: [{}, {}, {}, {}],
|
||||||
|
// borderStyles: {},
|
||||||
|
// gridStylesList: [{}, {}, {}, {}],
|
||||||
|
// angleStylesList: [{}, {}, {}, {}],
|
||||||
|
// circleBoxStyles: {},
|
||||||
|
// circleStyles: {},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
initData() {
|
||||||
|
// console.log('initData')
|
||||||
|
return {
|
||||||
|
timestamp: new Date().getTime(),
|
||||||
|
area: {
|
||||||
|
...this.area,
|
||||||
|
bounce: this.bounce,
|
||||||
|
showBorder: this.showBorder,
|
||||||
|
showGrid: this.showGrid,
|
||||||
|
showAngle: this.showAngle,
|
||||||
|
angleSize: this.angleSize,
|
||||||
|
angleBorderWidth: this.angleBorderWidth,
|
||||||
|
minScale: this.areaScale,
|
||||||
|
widthPercent: this.widthPercent,
|
||||||
|
heightPercent: this.heightPercent,
|
||||||
|
radius: this.radius,
|
||||||
|
checkRange: this.checkRange,
|
||||||
|
zIndex: +this.zIndex || 0,
|
||||||
|
},
|
||||||
|
sys: this.sys,
|
||||||
|
img: {
|
||||||
|
minScale: this.minScale,
|
||||||
|
maxScale: this.maxScale,
|
||||||
|
src: this.imgSrc,
|
||||||
|
width: this.oldWidth,
|
||||||
|
height: this.oldHeight,
|
||||||
|
oldWidth: this.oldWidth,
|
||||||
|
oldHeight: this.oldHeight,
|
||||||
|
gpu: this.gpu,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
imgProps() {
|
||||||
|
return {
|
||||||
|
width: this.width,
|
||||||
|
height: this.height,
|
||||||
|
src: this.src,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
imgProps: {
|
||||||
|
handler(val, oldVal) {
|
||||||
|
// 自定义裁剪尺,示例如下:
|
||||||
|
this.imgWidth = Number(val.width) || IMG_SIZE;
|
||||||
|
this.imgHeight = Number(val.height) || IMG_SIZE;
|
||||||
|
let use2d = true;
|
||||||
|
// #ifndef MP-WEIXIN
|
||||||
|
use2d = false;
|
||||||
|
// #endif
|
||||||
|
// if(use2d && (this.imgWidth > 1365 || this.imgHeight > 1365)) {
|
||||||
|
// use2d = false;
|
||||||
|
// }
|
||||||
|
let canvansWidth = this.imgWidth;
|
||||||
|
let canvansHeight = this.imgHeight;
|
||||||
|
let size = Math.max(canvansWidth, canvansHeight)
|
||||||
|
let scalc = 1;
|
||||||
|
if(size > 1365) {
|
||||||
|
scalc = 1365 / size;
|
||||||
|
}
|
||||||
|
this.canvansWidth = canvansWidth * scalc;
|
||||||
|
this.canvansHeight = canvansHeight * scalc;
|
||||||
|
this.use2d = use2d;
|
||||||
|
this.initArea();
|
||||||
|
const src = val.src || this.imgSrc;
|
||||||
|
src && this.initImage(src, oldVal === undefined);
|
||||||
|
},
|
||||||
|
immediate: true
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
/** 提供给wxs调用,用来接收图片变更数据 */
|
||||||
|
dataChange(e) {
|
||||||
|
// console.log('dataChange', e)
|
||||||
|
this.scaleWidth = e.width;
|
||||||
|
this.scaleHeight = e.height;
|
||||||
|
this.rotate = e.rotate;
|
||||||
|
this.offsetX = e.x;
|
||||||
|
this.offsetY = e.y;
|
||||||
|
},
|
||||||
|
/** 初始化裁剪区域布局信息 */
|
||||||
|
initArea() {
|
||||||
|
// 底部操作栏高度 = 底部底部操作栏内容高度 + 设备底部安全区域高度
|
||||||
|
this.sys.offsetBottom = uni.upx2px(100) + this.sys.safeAreaInsets.bottom;
|
||||||
|
// #ifndef H5
|
||||||
|
this.sys.windowTop = 0;
|
||||||
|
this.sys.navigation = true;
|
||||||
|
// #endif
|
||||||
|
// #ifdef H5
|
||||||
|
// h5平台的窗口高度是包含标题栏的
|
||||||
|
this.sys.windowTop = this.sys.windowTop || 44;
|
||||||
|
this.sys.navigation = this.navigation;
|
||||||
|
// #endif
|
||||||
|
let wp = this.widthPercent;
|
||||||
|
let hp = this.heightPercent;
|
||||||
|
if (this.imgWidth > this.imgHeight) {
|
||||||
|
hp = hp * this.imgHeight / this.imgWidth;
|
||||||
|
} else if (this.imgWidth < this.imgHeight) {
|
||||||
|
wp = wp * this.imgWidth / this.imgHeight;
|
||||||
|
}
|
||||||
|
const size = this.sys.windowWidth > this.sys.windowHeight ? this.sys.windowHeight : this.sys.windowWidth;
|
||||||
|
const width = size * wp / 100;
|
||||||
|
const height = size * hp / 100;
|
||||||
|
const left = (this.sys.windowWidth - width) / 2;
|
||||||
|
const right = left + width;
|
||||||
|
const top = (this.sys.windowHeight + this.sys.windowTop - this.sys.offsetBottom - height) / 2;
|
||||||
|
const bottom = this.sys.windowHeight + this.sys.windowTop - this.sys.offsetBottom - top;
|
||||||
|
this.area = { width, height, left, right, top, bottom };
|
||||||
|
this.scaleWidth = width;
|
||||||
|
this.scaleHeight = height;
|
||||||
|
},
|
||||||
|
/** 从本地选取图片 */
|
||||||
|
chooseImage(options) {
|
||||||
|
// #ifdef MP-WEIXIN || MP-JD
|
||||||
|
if(uni.chooseMedia) {
|
||||||
|
uni.chooseMedia({
|
||||||
|
...options,
|
||||||
|
count: 1,
|
||||||
|
mediaType: ['image'],
|
||||||
|
success: (res) => {
|
||||||
|
this.resetData();
|
||||||
|
this.initImage(res.tempFiles[0].tempFilePath);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// #endif
|
||||||
|
uni.chooseImage({
|
||||||
|
...options,
|
||||||
|
count: 1,
|
||||||
|
success: (res) => {
|
||||||
|
this.resetData();
|
||||||
|
this.initImage(res.tempFiles[0].path);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
/** 重置数据 */
|
||||||
|
resetData() {
|
||||||
|
this.imgSrc = '';
|
||||||
|
this.rotate = 0;
|
||||||
|
this.offsetX = 0;
|
||||||
|
this.offsetY = 0;
|
||||||
|
this.initArea();
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 初始化图片信息
|
||||||
|
* @param {String} url 图片链接
|
||||||
|
*/
|
||||||
|
initImage(url, isFirst) {
|
||||||
|
uni.getImageInfo({
|
||||||
|
src: url,
|
||||||
|
success: async (res) => {
|
||||||
|
if (isFirst && this.src === url) await (new Promise((resolve) => setTimeout(resolve, 50)));
|
||||||
|
this.imgSrc = res.path;
|
||||||
|
let scale = res.width / res.height;
|
||||||
|
let areaScale = this.area.width / this.area.height;
|
||||||
|
if (scale > 1) { // 横向图片
|
||||||
|
if (scale >= areaScale) { // 图片宽不小于目标宽,则高固定,宽自适应
|
||||||
|
this.scaleWidth = (this.scaleHeight / res.height) * this.scaleWidth * (res.width / this.scaleWidth);
|
||||||
|
} else { // 否则宽固定、高自适应
|
||||||
|
this.scaleHeight = res.height * this.scaleWidth / res.width;
|
||||||
|
}
|
||||||
|
} else { // 纵向图片
|
||||||
|
if (scale <= areaScale) { // 图片高不小于目标高,宽固定,高自适应
|
||||||
|
this.scaleHeight = (this.scaleWidth / res.width) * this.scaleHeight / (this.scaleHeight / res.height);
|
||||||
|
} else { // 否则高固定,宽自适应
|
||||||
|
this.scaleWidth = res.width * this.scaleHeight / res.height;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 记录原始宽高,为缩放比列做限制
|
||||||
|
this.oldWidth = +this.scaleWidth.toFixed(2);
|
||||||
|
this.oldHeight = +this.scaleHeight.toFixed(2);
|
||||||
|
},
|
||||||
|
fail: (err) => {
|
||||||
|
console.error(err)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 剪切图片圆角
|
||||||
|
* @param {Object} ctx canvas 的绘图上下文对象
|
||||||
|
* @param {Number} radius 圆角半径
|
||||||
|
* @param {Number} scale 生成图片的实际尺寸与截取区域比
|
||||||
|
* @param {Function} drawImage 执行剪切时所调用的绘图方法,入参为是否执行了剪切
|
||||||
|
*/
|
||||||
|
drawClipImage(ctx, radius, scale, drawImage) {
|
||||||
|
if(radius > 0) {
|
||||||
|
ctx.save();
|
||||||
|
ctx.beginPath();
|
||||||
|
const w = this.canvansWidth;
|
||||||
|
const h = this.canvansHeight;
|
||||||
|
if(w === h && radius >= w / 2) { // 圆形
|
||||||
|
ctx.arc(w / 2, h / 2, w / 2, 0, 2 * Math.PI);
|
||||||
|
} else { // 圆角矩形
|
||||||
|
if(w !== h) { // 限制圆角半径不能超过短边的一半
|
||||||
|
radius = Math.min(w / 2, h / 2, radius);
|
||||||
|
// radius = Math.min(Math.max(w, h) / 2, radius);
|
||||||
|
}
|
||||||
|
ctx.moveTo(radius, 0);
|
||||||
|
ctx.arcTo(w, 0, w, h, radius);
|
||||||
|
ctx.arcTo(w, h, 0, h, radius);
|
||||||
|
ctx.arcTo(0, h, 0, 0, radius);
|
||||||
|
ctx.arcTo(0, 0, w, 0, radius);
|
||||||
|
ctx.closePath();
|
||||||
|
}
|
||||||
|
ctx.clip();
|
||||||
|
drawImage && drawImage(true);
|
||||||
|
ctx.restore();
|
||||||
|
} else {
|
||||||
|
drawImage && drawImage(false);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 旋转图片
|
||||||
|
* @param {Object} ctx canvas 的绘图上下文对象
|
||||||
|
* @param {Number} rotate 旋转角度
|
||||||
|
* @param {Number} scale 生成图片的实际尺寸与截取区域比
|
||||||
|
*/
|
||||||
|
drawRotateImage(ctx, rotate, scale) {
|
||||||
|
if(rotate !== 0) {
|
||||||
|
// 1. 以图片中心点为旋转中心点
|
||||||
|
const x = this.scaleWidth * scale / 2;
|
||||||
|
const y = this.scaleHeight * scale / 2;
|
||||||
|
ctx.translate(x, y);
|
||||||
|
// 2. 旋转画布
|
||||||
|
ctx.rotate(rotate * Math.PI / 180);
|
||||||
|
// 3. 旋转完画布后恢复设置旋转中心时所做的偏移
|
||||||
|
ctx.translate(-x, -y);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
drawImage(ctx, image, callback) {
|
||||||
|
// 生成图片的实际尺寸与截取区域比
|
||||||
|
const scale = this.canvansWidth / this.area.width;
|
||||||
|
if(this.backgroundColor) {
|
||||||
|
if(ctx.setFillStyle) ctx.setFillStyle(this.backgroundColor);
|
||||||
|
else ctx.fillStyle = this.backgroundColor;
|
||||||
|
ctx.fillRect(0, 0, this.canvansWidth, this.canvansHeight);
|
||||||
|
}
|
||||||
|
this.drawClipImage(ctx, this.radius, scale, () => {
|
||||||
|
this.drawRotateImage(ctx, this.rotate, scale);
|
||||||
|
const r = this.rotate / 90;
|
||||||
|
ctx.drawImage(
|
||||||
|
image,
|
||||||
|
[
|
||||||
|
(this.offsetX - this.area.left),
|
||||||
|
(this.offsetY - this.area.top),
|
||||||
|
-(this.offsetX - this.area.left),
|
||||||
|
-(this.offsetY - this.area.top)
|
||||||
|
][r] * scale,
|
||||||
|
[
|
||||||
|
(this.offsetY - this.area.top),
|
||||||
|
-(this.offsetX - this.area.left),
|
||||||
|
-(this.offsetY - this.area.top),
|
||||||
|
(this.offsetX - this.area.left)
|
||||||
|
][r] * scale,
|
||||||
|
this.scaleWidth * scale,
|
||||||
|
this.scaleHeight * scale
|
||||||
|
);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 绘图
|
||||||
|
* @param {Object} canvas
|
||||||
|
* @param {Object} ctx canvas 的绘图上下文对象
|
||||||
|
* @param {String} src 图片路径
|
||||||
|
* @param {Function} callback 开始绘制时回调
|
||||||
|
*/
|
||||||
|
draw2DImage(canvas, ctx, src, callback) {
|
||||||
|
// console.log('draw2DImage', canvas, ctx, src, callback)
|
||||||
|
if(canvas) {
|
||||||
|
const image = canvas.createImage();
|
||||||
|
image.onload = () => {
|
||||||
|
this.drawImage(ctx, image);
|
||||||
|
// 如果觉得`生成时间过长`或`出现生成图片空白`可尝试调整延迟时间
|
||||||
|
callback && setTimeout(callback, this.delay);
|
||||||
|
};
|
||||||
|
image.onerror = (err) => {
|
||||||
|
console.error(err)
|
||||||
|
uni.hideLoading();
|
||||||
|
};
|
||||||
|
image.src = src;
|
||||||
|
} else {
|
||||||
|
this.drawImage(ctx, src);
|
||||||
|
setTimeout(() => {
|
||||||
|
ctx.draw(false, callback);
|
||||||
|
}, 200);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 画布转图片到本地缓存
|
||||||
|
* @param {Object} canvas
|
||||||
|
* @param {String} canvasId
|
||||||
|
*/
|
||||||
|
canvasToTempFilePath(canvas, canvasId) {
|
||||||
|
// console.log('canvasToTempFilePath', canvas, canvasId)
|
||||||
|
uni.canvasToTempFilePath({
|
||||||
|
canvas,
|
||||||
|
canvasId,
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
width: this.canvansWidth,
|
||||||
|
height: this.canvansHeight,
|
||||||
|
destWidth: this.imgWidth, // 必要,保证生成图片宽度不受设备分辨率影响
|
||||||
|
destHeight: this.imgHeight, // 必要,保证生成图片高度不受设备分辨率影响
|
||||||
|
fileType: this.fileType, // 目标文件的类型,默认png
|
||||||
|
success: (res) => {
|
||||||
|
// 生成的图片临时文件路径
|
||||||
|
this.handleImage(res.tempFilePath);
|
||||||
|
},
|
||||||
|
fail: (err) => {
|
||||||
|
uni.hideLoading();
|
||||||
|
uni.showToast({ title: '裁剪失败,生成图片异常!', icon: 'none' });
|
||||||
|
}
|
||||||
|
}, this);
|
||||||
|
},
|
||||||
|
/** 确认裁剪 */
|
||||||
|
cropClick() {
|
||||||
|
uni.showLoading({ title: '裁剪中...', mask: true });
|
||||||
|
if(!this.use2d) {
|
||||||
|
const ctx = uni.createCanvasContext('imgCanvas', this);
|
||||||
|
ctx.clearRect(0, 0, this.canvansWidth, this.canvansHeight);
|
||||||
|
this.draw2DImage(null, ctx, this.imgSrc, () => {
|
||||||
|
this.canvasToTempFilePath(null, 'imgCanvas');
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// #ifdef MP-WEIXIN
|
||||||
|
const query = uni.createSelectorQuery().in(this);
|
||||||
|
query.select('#imgCanvas')
|
||||||
|
.fields({ node: true, size: true })
|
||||||
|
.exec((res) => {
|
||||||
|
const canvas = res[0].node;
|
||||||
|
|
||||||
|
const dpr = uni.getSystemInfoSync().pixelRatio;
|
||||||
|
canvas.width = res[0].width * dpr;
|
||||||
|
canvas.height = res[0].height * dpr;
|
||||||
|
const ctx = canvas.getContext('2d');
|
||||||
|
ctx.scale(dpr, dpr);
|
||||||
|
ctx.clearRect(0, 0, this.canvansWidth, this.canvansHeight);
|
||||||
|
|
||||||
|
this.draw2DImage(canvas, ctx, this.imgSrc, () => {
|
||||||
|
this.canvasToTempFilePath(canvas);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
// #endif
|
||||||
|
},
|
||||||
|
handleImage(tempFilePath){
|
||||||
|
// 在H5平台下,tempFilePath 为 base64
|
||||||
|
// console.log(tempFilePath)
|
||||||
|
uni.hideLoading();
|
||||||
|
this.$emit('crop', { tempFilePath });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.image-cropper {
|
||||||
|
position: fixed;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
background-color: #000;
|
||||||
|
.img-canvas {
|
||||||
|
position: absolute !important;
|
||||||
|
transform: translateX(-100%);
|
||||||
|
}
|
||||||
|
.pic-preview {
|
||||||
|
width: 100%;
|
||||||
|
flex: 1;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
.crop-mask-block {
|
||||||
|
background-color: rgba(51, 51, 51, 0.8);
|
||||||
|
z-index: 2;
|
||||||
|
position: fixed;
|
||||||
|
box-sizing: border-box;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
.crop-circle-box {
|
||||||
|
position: fixed;
|
||||||
|
box-sizing: border-box;
|
||||||
|
z-index: 2;
|
||||||
|
pointer-events: none;
|
||||||
|
overflow: hidden;
|
||||||
|
.crop-circle {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.crop-image {
|
||||||
|
padding: 0 !important;
|
||||||
|
margin: 0 !important;
|
||||||
|
border-radius: 0 !important;
|
||||||
|
display: block !important;
|
||||||
|
backface-visibility: hidden;
|
||||||
|
}
|
||||||
|
.crop-border {
|
||||||
|
position: fixed;
|
||||||
|
border: 1px solid #fff;
|
||||||
|
box-sizing: border-box;
|
||||||
|
z-index: 3;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
.crop-grid {
|
||||||
|
position: fixed;
|
||||||
|
z-index: 3;
|
||||||
|
border-style: dashed;
|
||||||
|
border-color: #fff;
|
||||||
|
pointer-events: none;
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
.crop-angle {
|
||||||
|
position: fixed;
|
||||||
|
z-index: 3;
|
||||||
|
border-style: solid;
|
||||||
|
border-color: #fff;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.fixed-bottom {
|
||||||
|
position: fixed;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
z-index: 99;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
background-color: $uni-bg-color-grey;
|
||||||
|
|
||||||
|
.action-bar {
|
||||||
|
position: absolute;
|
||||||
|
top: -90rpx;
|
||||||
|
left: 10rpx;
|
||||||
|
display: flex;
|
||||||
|
.rotate-icon {
|
||||||
|
background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAAAXNSR0IArs4c6QAABCFJREFUaEPtml3IpVMUx3//ko/ChTIyiGFSMyhllI8bc4F85yuNC2FCqLmQC1+FZORiEkUMNW7UjKjJULgxV+NzSkxDhEkZgwsyigv119J63p7zvOc8z37OmXdOb51dz82711r7/99r7bXXXucVi3xokeNnRqCvB20fDmwAlgK/5bcD+FTSr33tHXQP2H4MeHQE0A+B5yRtLiUyDQJrgVc6AAaBpyV93kXkoBMIQLbfBS5NcK8BRwDXNcD+AdwnaVMbiWkRCPBBohpxHuK7M7865sclRdgNHVMhkF6IMIpwirFEUhzo8M7lwIvASTXEqyVtH8ZgagQSbOzsDknv18HZXpHn5IL8+94IOUm7miSmSqAttjPdbgGuTrnNktYsGgLpoYuAD2qg1zRTbG8P2D4SOC6/Q7vSHPALsE/S7wWy80RsPw/ckxMfSTq/LtRJwPbxwF3ASiCUTxwHCPAnEBfVF8AWSTtL7Ng+LfWOTfmlkn6udFsJ5K15R6a4kvX6yGyUFBvTOWzHXXFzCt4g6c1OArYj9iIGh43YgR+BvztXh1PSa4cMkd0jaVmXDduPAE+k3HpJD7cSGFKvfAc8FQUX8IOk/V2L1udtB/hTgdOBW4Aba/M7Ja1qs2f7euCNlHlZUlx4/495IWQ7Jl+qGbxX0gt9AHfJ2o6zFBVoNVrDKe+F3Sm8VdK1bQQ+A85JgXckXdkFaJx527cC9TpnVdvBtl3h2iapuhsGPdBw1b9xnUvaNw7AEh3bnwDnpuwGSfeP0rN9NvAMELXRXFkxEEK2nwQeSiOtRVQJwC4Z29cAW1Nuu6TVXTrN+SaBt4ErUug2Sa/2NdhH3vZy4NvU2S/p6D768w5xI3WOrAD7LtISFpGdIhVXKfaYvjd20wP13L9M0p4DBbaFRKToSLExVkr6qs+aIwlI6iwz+izUQqC+ab29PiMwqRcmPXczD8w8MFj1zg7xXEqbpdHCw7FgWSjafZL+KcQxtpjteCeflwYulFR/J3TabSslVkj6utPChAK2f6q9uZdLitKieLQRuExSvX9ZbLRUMFs09efpUZL+KtUfVo1GW/umNHC3pOhRLtiwfSbwZS6wV9IJfRdreuBBYH0a2STp9r4G+8jbXgc8mzoDT8VSO00ClwDv1ZR7XyylC4ec7ejaLUmdsV6Aw7oSbwFXpdFdks7qA6pU1na0aR6owgeIR/1cx63UzjAC0YXYVjMQHlkn6ZtSo21ytuPZGKFagQ/xsXZ/3iGuFrYdjafXG0DiQMeBi47c9/GV3BO247UV38n5o0UAP6xmu7jFOGxjRr66On5NPBDOCBsDTapxjHY1dyOcolNXnYlx1himE53p2PmNkxosevfavhg4Izt2k7TXPwZ2S6p6QZPin/2rwcQ7OKmBohCadJGF1P8PG6aaQBKVX/8AAAAASUVORK5CYII=');
|
||||||
|
background-size: 60% 60%;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-position: center;
|
||||||
|
width: 80rpx;
|
||||||
|
height: 80rpx;
|
||||||
|
&.is-reverse {
|
||||||
|
transform: rotateY(180deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.rechoose {
|
||||||
|
color: $uni-color-primary;
|
||||||
|
padding: 0 $uni-spacing-row-lg;
|
||||||
|
line-height: 100rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.choose-btn {
|
||||||
|
color: $uni-color-primary;
|
||||||
|
text-align: center;
|
||||||
|
line-height: 100rpx;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button {
|
||||||
|
margin: auto $uni-spacing-row-lg auto auto;
|
||||||
|
background-color: $uni-color-primary;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.safe-area-inset-bottom {
|
||||||
|
padding-bottom: 0;
|
||||||
|
padding-bottom: constant(safe-area-inset-bottom); // 兼容 IOS<11.2
|
||||||
|
padding-bottom: env(safe-area-inset-bottom); // 兼容 IOS>=11.2
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,727 @@
|
|||||||
|
/**
|
||||||
|
* 图片编辑器-手势监听
|
||||||
|
* 1. wxs 暂不支持 es6 语法
|
||||||
|
* 2. 支持编译到微信小程序、QQ小程序、app-vue、H5上(uni-app 2.2.5及以上版本)
|
||||||
|
*/
|
||||||
|
/** 图片偏移量 */
|
||||||
|
var offset = { x: 0, y: 0 };
|
||||||
|
/** 图片缩放比例 */
|
||||||
|
var scale = 1;
|
||||||
|
/** 图片最小缩放比例 */
|
||||||
|
var minScale = 1;
|
||||||
|
/** 图片旋转角度 */
|
||||||
|
var rotate = 0;
|
||||||
|
/** 触摸点 */
|
||||||
|
var touches = [];
|
||||||
|
/** 图片布局信息 */
|
||||||
|
var img = {};
|
||||||
|
/** 系统信息 */
|
||||||
|
var sys = {};
|
||||||
|
/** 裁剪区域布局信息 */
|
||||||
|
var area = {};
|
||||||
|
/** 触摸行为类型 */
|
||||||
|
var touchType = '';
|
||||||
|
/** 操作角的位置 */
|
||||||
|
var activeAngle = 0;
|
||||||
|
/** 裁剪区域布局信息偏移量 */
|
||||||
|
var areaOffset = { left: 0, right: 0, top: 0, bottom: 0 };
|
||||||
|
/** 容错值 */
|
||||||
|
var fault = 0.000001;
|
||||||
|
/**
|
||||||
|
* 获取a、b两数中的最小正数
|
||||||
|
* @param a
|
||||||
|
* @param b
|
||||||
|
*/
|
||||||
|
function minimum(a, b) {
|
||||||
|
if (a > 0 && b < 0) return a;
|
||||||
|
if (a < 0 && b > 0) return b;
|
||||||
|
if (a > 0 && b > 0) return Math.min(a, b);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 在容错访问内获取n近似值
|
||||||
|
* @param n
|
||||||
|
*/
|
||||||
|
function num(n) {
|
||||||
|
var m = parseFloat((n).toFixed(6));
|
||||||
|
return m === fault || m === -fault ? 0 : m;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 比较a值在容错值范围内是否等于b值
|
||||||
|
* @param a
|
||||||
|
* @param b
|
||||||
|
*/
|
||||||
|
function equalsByFault(a, b) {
|
||||||
|
return Math.abs(a - b) <= fault;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 比较a值在容错值范围内是否小于b值
|
||||||
|
* @param a
|
||||||
|
* @param b
|
||||||
|
*/
|
||||||
|
function lessThanByFault(a, b) {
|
||||||
|
var c = a - b;
|
||||||
|
return c < 0 ? c < -fault : c < fault;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 验证并获取有效最大值
|
||||||
|
* @param v
|
||||||
|
* @param max
|
||||||
|
* @param isInclude
|
||||||
|
* @param x
|
||||||
|
* @param y
|
||||||
|
* @param rate
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
function validMax(v, max, isInclude, x, y, rate) {
|
||||||
|
if(typeof max === 'number') {
|
||||||
|
if(isInclude && equalsByFault(max, y)) { // 宽高不等时,x轴用y轴值要做等比例转换
|
||||||
|
var n = num(max * rate);
|
||||||
|
if (n <= x) return n; // 转化后值在x轴最大值范围内
|
||||||
|
return x; // 转化后值超出x轴最大值范围则用最大值
|
||||||
|
}
|
||||||
|
return max;
|
||||||
|
}
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 计算两点间距
|
||||||
|
* @param {Object} touches 触摸点信息
|
||||||
|
*/
|
||||||
|
function getDistanceByTouches(touches) {
|
||||||
|
// 根据勾股定理求两点间距离
|
||||||
|
var a = touches[1].pageX - touches[0].pageX;
|
||||||
|
var b = touches[1].pageY - touches[0].pageY;
|
||||||
|
var c = Math.sqrt(Math.pow(a, 2) + Math.pow(b, 2));
|
||||||
|
// 求两点间的中点坐标
|
||||||
|
// 1. a、b可能为负值
|
||||||
|
// 2. 在求a、b时,如用touches[1]减touches[0],则求中点坐标也得用touches[1]减a/2、b/2
|
||||||
|
// 3. 同理,在求a、b时,也可用touches[0]减touches[1],则求中点坐标也得用touches[0]减a/2、b/2
|
||||||
|
var x = touches[1].pageX - a / 2;
|
||||||
|
var y = touches[1].pageY - b / 2;
|
||||||
|
return { c, x, y };
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* 修正取值
|
||||||
|
* @param {Object} a
|
||||||
|
* @param {Object} b
|
||||||
|
* @param {Object} c
|
||||||
|
* @param {Object} reverse 是否反向
|
||||||
|
*/
|
||||||
|
function correctValue(a, b, c, reverse) {
|
||||||
|
return num(reverse ? Math.max(Math.min(a, b), c) : Math.min(Math.max(a, b), c));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 旋转90°或270°时检查边界:限制 x、y 拖动范围,禁止滑出边界
|
||||||
|
* @param {Object} e 点坐标
|
||||||
|
* @param {Object} xReverse x是否反向
|
||||||
|
* @param {Object} yReverse y是否反向
|
||||||
|
*/
|
||||||
|
function checkRotateRange(e, xReverse, yReverse) {
|
||||||
|
var o = num((img.height - img.width) / 2); // 宽高差值一半
|
||||||
|
return {
|
||||||
|
x: correctValue(e.x, -img.height + o + area.width + area.left, area.left + o, xReverse),
|
||||||
|
y: correctValue(e.y, -img.width - o + area.height + area.top, area.top - o, yReverse)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查边界:限制 x、y 拖动范围,禁止滑出边界
|
||||||
|
* @param {Object} e 点坐标
|
||||||
|
*/
|
||||||
|
function checkRange(e) {
|
||||||
|
var r = rotate / 90 % 2;
|
||||||
|
if(r === 1) { // 因图片宽高可能不等,翻转 90° 或 270° 后图片宽高需反着计算,且左右和上下边界要根据差值做偏移
|
||||||
|
if (area.width === area.height) {
|
||||||
|
return checkRotateRange(e, img.height < area.height, img.width < area.width);
|
||||||
|
}
|
||||||
|
var isInclude = img.height < area.width && img.width < area.height; // 图片是否包含在裁剪区域内
|
||||||
|
if (img.width < area.height || img.height < area.width) {
|
||||||
|
if (area.width < area.height && img.width < img.height) {
|
||||||
|
return isInclude
|
||||||
|
? checkRotateRange(e, area.width < area.height, area.width < area.height)
|
||||||
|
: checkRotateRange(e, false, true);
|
||||||
|
}
|
||||||
|
if (area.height < area.width && img.height < img.width) {
|
||||||
|
return isInclude
|
||||||
|
? checkRotateRange(e, area.height < area.width, area.height < area.width)
|
||||||
|
: checkRotateRange(e, true, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (img.height >= area.width && img.width >= area.height) {
|
||||||
|
return checkRotateRange(e, false, false);
|
||||||
|
}
|
||||||
|
if (isInclude) {
|
||||||
|
return area.height < area.width
|
||||||
|
? checkRotateRange(e, true, true)
|
||||||
|
: checkRotateRange(e, area.width < area.height, area.width < area.height);
|
||||||
|
}
|
||||||
|
if (img.height < area.width && !img.width < area.height) {
|
||||||
|
return checkRotateRange(e, true, false);
|
||||||
|
}
|
||||||
|
if (!img.height < area.width && img.width < area.height) {
|
||||||
|
return checkRotateRange(e, false, true);
|
||||||
|
}
|
||||||
|
return checkRotateRange(e, img.height < area.height, img.width < area.width);
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
x: correctValue(e.x, -img.width + area.width + area.left, area.left, img.width < area.width),
|
||||||
|
y: correctValue(e.y, -img.height + area.height + area.top, area.top, img.height < area.height)
|
||||||
|
};
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* 变更图片布局信息
|
||||||
|
* @param {Object} e 布局信息
|
||||||
|
*/
|
||||||
|
function changeImageRect(e) {
|
||||||
|
offset.x += e.x || 0;
|
||||||
|
offset.y += e.y || 0;
|
||||||
|
var image = e.instance.selectComponent('.crop-image');
|
||||||
|
if(e.check && area.checkRange) { // 检查边界
|
||||||
|
var point = checkRange(offset);
|
||||||
|
if(offset.x !== point.x || offset.y !== point.y) {
|
||||||
|
offset = point;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// image.setStyle({
|
||||||
|
// width: img.width + 'px',
|
||||||
|
// height: img.height + 'px',
|
||||||
|
// transform: 'translate(' + offset.x + 'px, ' + offset.y + 'px) rotate(' + rotate +'deg)'
|
||||||
|
// });
|
||||||
|
var ox = (img.width - img.oldWidth) / 2;
|
||||||
|
var oy = (img.height - img.oldHeight) / 2;
|
||||||
|
image.setStyle({
|
||||||
|
width: img.oldWidth + 'px',
|
||||||
|
height: img.oldHeight + 'px',
|
||||||
|
transform: (img.gpu ? 'translateZ(0) ' : '') + 'translate(' + (offset.x + ox) + 'px, ' + (offset.y + oy) + 'px) rotate(' + rotate +'deg) scale(' + scale + ')'
|
||||||
|
});
|
||||||
|
|
||||||
|
e.instance.callMethod('dataChange', {
|
||||||
|
width: img.width,
|
||||||
|
height: img.height,
|
||||||
|
x: offset.x,
|
||||||
|
y: offset.y,
|
||||||
|
rotate: rotate
|
||||||
|
});
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* 变更裁剪区域布局信息
|
||||||
|
* @param {Object} e 布局信息
|
||||||
|
*/
|
||||||
|
function changeAreaRect(e) {
|
||||||
|
// 变更蒙版样式
|
||||||
|
var masks = e.instance.selectAllComponents('.crop-mask-block');
|
||||||
|
var maskStyles = [
|
||||||
|
{
|
||||||
|
left: 0,
|
||||||
|
width: (area.left + areaOffset.left) + 'px',
|
||||||
|
top: 0,
|
||||||
|
bottom: 0,
|
||||||
|
'z-index': area.zIndex + 2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
left: (area.right + areaOffset.right) + 'px',
|
||||||
|
right: 0,
|
||||||
|
top: 0,
|
||||||
|
bottom: 0,
|
||||||
|
'z-index': area.zIndex + 2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
left: (area.left + areaOffset.left) + 'px',
|
||||||
|
width: (area.width + areaOffset.right - areaOffset.left) + 'px',
|
||||||
|
top: 0,
|
||||||
|
height: (area.top + areaOffset.top) + 'px',
|
||||||
|
'z-index': area.zIndex + 2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
left: (area.left + areaOffset.left) + 'px',
|
||||||
|
width: (area.width + areaOffset.right - areaOffset.left) + 'px',
|
||||||
|
top: (area.bottom + areaOffset.bottom) + 'px',
|
||||||
|
// height: (area.top - areaOffset.bottom + sys.offsetBottom) + 'px',
|
||||||
|
bottom: 0,
|
||||||
|
'z-index': area.zIndex + 2
|
||||||
|
}
|
||||||
|
];
|
||||||
|
var len = masks.length;
|
||||||
|
for (var i = 0; i < len; i++) {
|
||||||
|
masks[i].setStyle(maskStyles[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 变更边框样式
|
||||||
|
if(area.showBorder) {
|
||||||
|
var border = e.instance.selectComponent('.crop-border');
|
||||||
|
border.setStyle({
|
||||||
|
left: (area.left + areaOffset.left) + 'px',
|
||||||
|
top: (area.top + areaOffset.top) + 'px',
|
||||||
|
width: (area.width + areaOffset.right - areaOffset.left) + 'px',
|
||||||
|
height: (area.height + areaOffset.bottom - areaOffset.top) + 'px',
|
||||||
|
'z-index': area.zIndex + 3
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 变更参考线样式
|
||||||
|
if(area.showGrid) {
|
||||||
|
var grids = e.instance.selectAllComponents('.crop-grid');
|
||||||
|
var gridStyles = [
|
||||||
|
{
|
||||||
|
'border-width': '1px 0 0 0',
|
||||||
|
left: (area.left + areaOffset.left) + 'px',
|
||||||
|
right: (area.right + areaOffset.right) + 'px',
|
||||||
|
top: (area.top + areaOffset.top + (area.height + areaOffset.bottom - areaOffset.top) / 3 - 0.5) + 'px',
|
||||||
|
width: (area.width + areaOffset.right - areaOffset.left) + 'px',
|
||||||
|
'z-index': area.zIndex + 3
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'border-width': '1px 0 0 0',
|
||||||
|
left: (area.left + areaOffset.left) + 'px',
|
||||||
|
right: (area.right + areaOffset.right) + 'px',
|
||||||
|
top: (area.top + areaOffset.top + (area.height + areaOffset.bottom - areaOffset.top) * 2 / 3 - 0.5) + 'px',
|
||||||
|
width: (area.width + areaOffset.right - areaOffset.left) + 'px',
|
||||||
|
'z-index': area.zIndex + 3
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'border-width': '0 1px 0 0',
|
||||||
|
top: (area.top + areaOffset.top) + 'px',
|
||||||
|
bottom: (area.bottom + areaOffset.bottom) + 'px',
|
||||||
|
left: (area.left + areaOffset.left + (area.width + areaOffset.right - areaOffset.left) / 3 - 0.5) + 'px',
|
||||||
|
height: (area.height + areaOffset.bottom - areaOffset.top) + 'px',
|
||||||
|
'z-index': area.zIndex + 3
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'border-width': '0 1px 0 0',
|
||||||
|
top: (area.top + areaOffset.top) + 'px',
|
||||||
|
bottom: (area.bottom + areaOffset.bottom) + 'px',
|
||||||
|
left: (area.left + areaOffset.left + (area.width + areaOffset.right - areaOffset.left) * 2 / 3 - 0.5) + 'px',
|
||||||
|
height: (area.height + areaOffset.bottom - areaOffset.top) + 'px',
|
||||||
|
'z-index': area.zIndex + 3
|
||||||
|
}
|
||||||
|
];
|
||||||
|
var len = grids.length;
|
||||||
|
for (var i = 0; i < len; i++) {
|
||||||
|
grids[i].setStyle(gridStyles[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 变更四个伸缩角样式
|
||||||
|
if(area.showAngle) {
|
||||||
|
var angles = e.instance.selectAllComponents('.crop-angle');
|
||||||
|
var angleStyles = [
|
||||||
|
{
|
||||||
|
'border-width': area.angleBorderWidth + 'px 0 0 ' + area.angleBorderWidth + 'px',
|
||||||
|
left: (area.left + areaOffset.left - area.angleBorderWidth) + 'px',
|
||||||
|
top: (area.top + areaOffset.top - area.angleBorderWidth) + 'px',
|
||||||
|
'z-index': area.zIndex + 3
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'border-width': area.angleBorderWidth + 'px ' + area.angleBorderWidth + 'px 0 0',
|
||||||
|
left: (area.right + areaOffset.right - area.angleSize) + 'px',
|
||||||
|
top: (area.top + areaOffset.top - area.angleBorderWidth) + 'px',
|
||||||
|
'z-index': area.zIndex + 3
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'border-width': '0 0 ' + area.angleBorderWidth + 'px ' + area.angleBorderWidth + 'px',
|
||||||
|
left: (area.left + areaOffset.left - area.angleBorderWidth) + 'px',
|
||||||
|
top: (area.bottom + areaOffset.bottom - area.angleSize) + 'px',
|
||||||
|
'z-index': area.zIndex + 3
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'border-width': '0 ' + area.angleBorderWidth + 'px ' + area.angleBorderWidth + 'px 0',
|
||||||
|
left: (area.right + areaOffset.right - area.angleSize) + 'px',
|
||||||
|
top: (area.bottom + areaOffset.bottom - area.angleSize) + 'px',
|
||||||
|
'z-index': area.zIndex + 3
|
||||||
|
}
|
||||||
|
];
|
||||||
|
var len = angles.length;
|
||||||
|
for (var i = 0; i < len; i++) {
|
||||||
|
angles[i].setStyle(angleStyles[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 变更圆角样式
|
||||||
|
if(area.radius > 0) {
|
||||||
|
var circleBox = e.instance.selectComponent('.crop-circle-box');
|
||||||
|
var circle = e.instance.selectComponent('.crop-circle');
|
||||||
|
var radius = area.radius;
|
||||||
|
if(area.width === area.height && area.radius >= area.width / 2) { // 圆形
|
||||||
|
radius = (area.width / 2);
|
||||||
|
} else { // 圆角矩形
|
||||||
|
if(area.width !== area.height) { // 限制圆角半径不能超过短边的一半
|
||||||
|
radius = Math.min(area.width / 2, area.height / 2, radius);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
circleBox.setStyle({
|
||||||
|
left: (area.left + areaOffset.left) + 'px',
|
||||||
|
top: (area.top + areaOffset.top) + 'px',
|
||||||
|
width: (area.width + areaOffset.right - areaOffset.left) + 'px',
|
||||||
|
height: (area.height + areaOffset.bottom - areaOffset.top) + 'px',
|
||||||
|
'z-index': area.zIndex + 2
|
||||||
|
});
|
||||||
|
circle.setStyle({
|
||||||
|
'box-shadow': '0 0 0 ' + Math.max(area.width, area.height) + 'px rgba(51, 51, 51, 0.8)',
|
||||||
|
'border-radius': radius + 'px'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* 缩放图片
|
||||||
|
* @param {Object} e 布局信息
|
||||||
|
*/
|
||||||
|
function scaleImage(e) {
|
||||||
|
var last = scale;
|
||||||
|
scale = Math.min(Math.max(e.scale + scale, minScale), img.maxScale);
|
||||||
|
if(last !== scale) {
|
||||||
|
img.width = num(img.oldWidth * scale);
|
||||||
|
img.height = num(img.oldHeight * scale);
|
||||||
|
// 参考问题:有一个长4000px、宽4000px的四方形ABCD,A点的坐标固定在(-2000,-2000),
|
||||||
|
// 该四边形上有一个点E,坐标为(-100,-300),将该四方形复制一份并缩小到90%后,
|
||||||
|
// 新四边形的A点坐标为多少时可使新四边形的E点与原四边形的E点重合?
|
||||||
|
// 预期效果:从图中选取某点(参照物)为中心点进行缩放,缩放时无论图像怎么变化,该点位置始终固定不变
|
||||||
|
// 计算方法:以相同起点先计算缩放前后两点间的距离,再加上原图像偏移量即可
|
||||||
|
e.x = num((e.x - offset.x) * (1 - scale / last));
|
||||||
|
e.y = num((e.y - offset.y) * (1 - scale / last));
|
||||||
|
changeImageRect(e);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* 获取触摸点在哪个角
|
||||||
|
* @param {number} x 触摸点x轴坐标
|
||||||
|
* @param {number} y 触摸点y轴坐标
|
||||||
|
* @return {number} 角的位置:0=无;1=左上;2=右上;3=左下;4=右下;
|
||||||
|
*/
|
||||||
|
function getToucheAngle(x, y) {
|
||||||
|
// console.log('getToucheAngle', x, y, JSON.stringify(area))
|
||||||
|
var o = area.angleBorderWidth; // 需扩大触发范围则把 o 值加大即可
|
||||||
|
if(y >= area.top - o && y <= area.top + area.angleSize + o) {
|
||||||
|
if(x >= area.left - o && x <= area.left + area.angleSize + o) {
|
||||||
|
return 1; // 左上角
|
||||||
|
} else if(x >= area.right - area.angleSize - o && x <= area.right + o) {
|
||||||
|
return 2; // 右上角
|
||||||
|
}
|
||||||
|
} else if(y >= area.bottom - area.angleSize - o && y <= area.bottom + o) {
|
||||||
|
if(x >= area.left - o && x <= area.left + area.angleSize + o) {
|
||||||
|
return 3; // 左下角
|
||||||
|
} else if(x >= area.right - area.angleSize - o && x <= area.right + o) {
|
||||||
|
return 4; // 右下角
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0; // 无触摸到角
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* 重置数据
|
||||||
|
*/
|
||||||
|
function resetData() {
|
||||||
|
offset = { x: 0, y: 0 };
|
||||||
|
scale = 1;
|
||||||
|
minScale = img.minScale;
|
||||||
|
rotate = 0;
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* 顺时针翻转图片90°
|
||||||
|
* @param {Object} e 事件对象
|
||||||
|
* @param {Object} o 组件实例对象
|
||||||
|
*/
|
||||||
|
function rotateImage(e, o, r) {
|
||||||
|
rotate = (rotate + r) % 360;
|
||||||
|
if(img.minScale >= 1 && area.checkRange) {
|
||||||
|
// 因图片宽高可能不等,翻转后图片宽高需足够填满裁剪区域
|
||||||
|
minScale = 1;
|
||||||
|
if(img.width < area.height) {
|
||||||
|
minScale = area.height / img.oldWidth;
|
||||||
|
} else if(img.height < area.width) {
|
||||||
|
minScale = area.width / img.oldHeight;
|
||||||
|
}
|
||||||
|
if(minScale !== 1) {
|
||||||
|
scaleImage({
|
||||||
|
instance: o,
|
||||||
|
scale: minScale - scale,
|
||||||
|
x: sys.windowWidth / 2,
|
||||||
|
y: (sys.windowHeight - sys.offsetBottom) / 2
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 由于拖动画布后会导致图片位置偏移,翻转时的旋转中心点需是图片区域+偏移区域的中心点
|
||||||
|
// 翻转x轴中心点 = (超出裁剪区域右侧的图片宽度 - 超出裁剪区域左侧的图片宽度) / 2
|
||||||
|
// 翻转y轴中心点 = (超出裁剪区域下方的图片宽度 - 超出裁剪区域上方的图片宽度) / 2
|
||||||
|
var ox = ((offset.x + img.width - area.right) - (area.left - offset.x)) / 2;
|
||||||
|
var oy = ((offset.y + img.height - area.bottom) - (area.top - offset.y)) / 2;
|
||||||
|
changeImageRect({
|
||||||
|
instance: o,
|
||||||
|
check: true,
|
||||||
|
x: -ox - oy,
|
||||||
|
y: -oy + ox
|
||||||
|
});
|
||||||
|
};
|
||||||
|
module.exports = {
|
||||||
|
/**
|
||||||
|
* 初始化:观察数据变更
|
||||||
|
* @param {Object} newVal 新数据
|
||||||
|
* @param {Object} oldVal 旧数据
|
||||||
|
* @param {Object} o 组件实例对象
|
||||||
|
*/
|
||||||
|
initObserver: function(newVal, oldVal, o, i) {
|
||||||
|
if(newVal) {
|
||||||
|
img = newVal.img;
|
||||||
|
sys = newVal.sys;
|
||||||
|
area = newVal.area;
|
||||||
|
minScale = img.minScale;
|
||||||
|
resetData();
|
||||||
|
img.src && changeImageRect({
|
||||||
|
instance: o,
|
||||||
|
x: (sys.windowWidth - img.width) / 2,
|
||||||
|
y: (sys.windowHeight - sys.offsetBottom - img.height) / 2
|
||||||
|
});
|
||||||
|
changeAreaRect({
|
||||||
|
instance: o
|
||||||
|
});
|
||||||
|
// console.log('initRect', JSON.stringify(newVal))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 鼠标滚轮滚动
|
||||||
|
* @param {Object} e 事件对象
|
||||||
|
* @param {Object} o 组件实例对象
|
||||||
|
*/
|
||||||
|
mousewheel: function(e, o) {
|
||||||
|
if(!img.src) return;
|
||||||
|
scaleImage({
|
||||||
|
instance: o,
|
||||||
|
check: true,
|
||||||
|
// 鼠标向上滚动时,deltaY 固定 -100,鼠标向下滚动时,deltaY 固定 100
|
||||||
|
scale: e.detail.deltaY > 0 ? -0.05 : 0.05,
|
||||||
|
x: e.touches[0].pageX,
|
||||||
|
y: e.touches[0].pageY
|
||||||
|
});
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 触摸开始
|
||||||
|
* @param {Object} e 事件对象
|
||||||
|
* @param {Object} o 组件实例对象
|
||||||
|
*/
|
||||||
|
touchstart: function(e, o) {
|
||||||
|
if(!img.src) return;
|
||||||
|
touches = e.touches;
|
||||||
|
activeAngle = area.showAngle ? getToucheAngle(touches[0].pageX, touches[0].pageY) : 0;
|
||||||
|
if(touches.length === 1 && activeAngle !== 0) {
|
||||||
|
touchType = 'stretch'; // 伸缩裁剪区域
|
||||||
|
} else {
|
||||||
|
touchType = '';
|
||||||
|
}
|
||||||
|
// console.log('touchstart', JSON.stringify(e), activeAngle)
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 触摸移动
|
||||||
|
* @param {Object} e 事件对象
|
||||||
|
* @param {Object} o 组件实例对象
|
||||||
|
*/
|
||||||
|
touchmove: function(e, o) {
|
||||||
|
if(!img.src) return;
|
||||||
|
// console.log('touchmove', JSON.stringify(e), JSON.stringify(o))
|
||||||
|
if(touchType === 'stretch') { // 触摸四个角进行拉伸
|
||||||
|
var point = e.touches[0];
|
||||||
|
var start = touches[0];
|
||||||
|
var x = point.pageX - start.pageX;
|
||||||
|
var y = point.pageY - start.pageY;
|
||||||
|
if(x !== 0 || y !== 0) {
|
||||||
|
var maxX = num(area.width * (1 - area.minScale));
|
||||||
|
var maxY = num(area.height * (1 - area.minScale));
|
||||||
|
// console.log(x, y, maxX, maxY, offset, area)
|
||||||
|
touches[0] = point;
|
||||||
|
var r = rotate / 90 % 2;
|
||||||
|
var m = r === 1 ? num((img.height - img.width) / 2) : 0; // 宽高差值一半
|
||||||
|
var xCompare = r === 1 ? lessThanByFault(img.height, area.width) : lessThanByFault(img.width, area.width);
|
||||||
|
var yCompare = r === 1 ? lessThanByFault(img.width, area.height) : lessThanByFault(img.height, area.height)
|
||||||
|
var isInclude = xCompare && yCompare;
|
||||||
|
var isIntersect = area.checkRange && (xCompare || yCompare); // 图片是否包含在裁剪区域内
|
||||||
|
var isReverse = !isInclude || num((offset.x - area.left) / area.width) <= num((offset.y - area.top) / area.height) || (area.width > area.height && img.width < img.height && r === 1);
|
||||||
|
switch(activeAngle) {
|
||||||
|
case 1: // 左上角
|
||||||
|
x = num(x + areaOffset.left);
|
||||||
|
y = num(y + areaOffset.top);
|
||||||
|
if(x >= 0 && y >= 0) { // 有效滑动
|
||||||
|
var t = num(offset.y + m - area.top);
|
||||||
|
var l = num(offset.x - m - area.left);
|
||||||
|
// && (offset.x + img.width < area.right || offset.y + img.height < area.bottom)
|
||||||
|
var max = isIntersect && ((l >= 0) || (t >= 0))
|
||||||
|
? minimum(t, l)
|
||||||
|
: false;
|
||||||
|
if(x > y && isReverse) { // 以x轴滑动距离为缩放基准
|
||||||
|
maxX = validMax(maxX, max, isInclude, l, t, area.width / area.height);
|
||||||
|
if(x > maxX) x = maxX;
|
||||||
|
y = num(x * area.height / area.width);
|
||||||
|
} else { // 以y轴滑动距离为缩放基准
|
||||||
|
maxY = validMax(maxY, max, isInclude, t, l, area.height / area.width);
|
||||||
|
if(y > maxY) y = maxY;
|
||||||
|
x = num(y * area.width / area.height);
|
||||||
|
}
|
||||||
|
areaOffset.left = x;
|
||||||
|
areaOffset.top = y;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 2: // 右上角
|
||||||
|
x = num(x + areaOffset.right);
|
||||||
|
y = num(y + areaOffset.top);
|
||||||
|
if(x <= 0 && y >= 0) { // 有效滑动
|
||||||
|
var w = (r === 1 ? img.height : img.width);
|
||||||
|
var t = num(offset.y + m - area.top);
|
||||||
|
var l = num(area.right + m - offset.x - w);
|
||||||
|
var max = isIntersect && ((t >= 0) || (l >= 0))
|
||||||
|
? minimum(t, l)
|
||||||
|
: false;
|
||||||
|
// var max = isInclude && ((offset.x > 0 && offset.x + img.width <= area.right) || (offset.y > 0 && offset.y >= area.top))
|
||||||
|
// ? minimum(offset.y - area.top, area.right - offset.x - img.width)
|
||||||
|
// : false;
|
||||||
|
// console.log(offset.x, offset.y, img.width, img.height, area.top, area.right, m, max)
|
||||||
|
// console.log(offset.y + m - area.top, area.right + m - offset.x - w)
|
||||||
|
if(-x > y && isReverse) { // 以x轴滑动距离为缩放基准
|
||||||
|
maxX = validMax(maxX, max, isInclude, l, t, area.width / area.height);
|
||||||
|
if(-x > maxX) x = -maxX;
|
||||||
|
y = num(-x * area.height / area.width);
|
||||||
|
} else { // 以y轴滑动距离为缩放基准
|
||||||
|
maxY = validMax(maxY, max, isInclude, t, l, area.height / area.width);
|
||||||
|
if(y > maxY) y = maxY;
|
||||||
|
x = num(-y * area.width / area.height);
|
||||||
|
}
|
||||||
|
areaOffset.right = x;
|
||||||
|
areaOffset.top = y;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 3: // 左下角
|
||||||
|
x += num(x + areaOffset.left);
|
||||||
|
y += num(y + areaOffset.bottom);
|
||||||
|
if(x >= 0 && y <= 0) { // 有效滑动
|
||||||
|
var w = (r === 1 ? img.width : img.height);
|
||||||
|
var t = num(area.bottom - m - offset.y - w);
|
||||||
|
var l = num(offset.x - m - area.left);
|
||||||
|
var max = isIntersect && ((l >= 0) || (t >= 0))
|
||||||
|
? minimum(t, l)
|
||||||
|
: false;
|
||||||
|
if(x > -y && isReverse) { // 以x轴滑动距离为缩放基准
|
||||||
|
maxX = validMax(maxX, max, isInclude, l, t, area.width / area.height);
|
||||||
|
if(x > maxX) x = maxX;
|
||||||
|
y = num(-x * area.height / area.width);
|
||||||
|
} else { // 以y轴滑动距离为缩放基准
|
||||||
|
maxY = validMax(maxY, max, isInclude, t, l, area.height / area.width);
|
||||||
|
if(-y > maxY) y = -maxY;
|
||||||
|
x = num(-y * area.width / area.height);
|
||||||
|
}
|
||||||
|
areaOffset.left = x;
|
||||||
|
areaOffset.bottom = y;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 4: // 右下角
|
||||||
|
x = num(x + areaOffset.right);
|
||||||
|
y = num(y + areaOffset.bottom);
|
||||||
|
if(x <= 0 && y <= 0) { // 有效滑动
|
||||||
|
var w = (r === 1 ? img.height : img.width);
|
||||||
|
var h = (r === 1 ? img.width : img.height);
|
||||||
|
var t = num(area.bottom - offset.y - h - m);
|
||||||
|
var l = num(area.right + m - offset.x - w);
|
||||||
|
var max = isIntersect && ((l >= 0) || (t >= 0))
|
||||||
|
? minimum(t, l)
|
||||||
|
: false;
|
||||||
|
if(-x > -y && isReverse) { // 以x轴滑动距离为缩放基准
|
||||||
|
maxX = validMax(maxX, max, isInclude, l, t, area.width / area.height);
|
||||||
|
if(-x > maxX) x = -maxX;
|
||||||
|
y = num(x * area.height / area.width);
|
||||||
|
} else { // 以y轴滑动距离为缩放基准
|
||||||
|
maxY = validMax(maxY, max, isInclude, t, l, area.height / area.width);
|
||||||
|
if(-y > maxY) y = -maxY;
|
||||||
|
x = num(y * area.width / area.height);
|
||||||
|
}
|
||||||
|
areaOffset.right = x;
|
||||||
|
areaOffset.bottom = y;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// console.log(x, y, JSON.stringify(areaOffset))
|
||||||
|
changeAreaRect({
|
||||||
|
instance: o,
|
||||||
|
});
|
||||||
|
// this.draw();
|
||||||
|
}
|
||||||
|
} else if (e.touches.length == 2) { // 双点触摸缩放
|
||||||
|
var start = getDistanceByTouches(touches);
|
||||||
|
var end = getDistanceByTouches(e.touches);
|
||||||
|
scaleImage({
|
||||||
|
instance: o,
|
||||||
|
check: !area.bounce,
|
||||||
|
scale: (end.c - start.c) / 100,
|
||||||
|
x: end.x,
|
||||||
|
y: end.y
|
||||||
|
});
|
||||||
|
touchType = 'scale';
|
||||||
|
} else if(touchType === 'scale') {// 从双点触摸变成单点触摸 / 从缩放变成拖动
|
||||||
|
touchType = 'move';
|
||||||
|
} else {
|
||||||
|
changeImageRect({
|
||||||
|
instance: o,
|
||||||
|
check: !area.bounce,
|
||||||
|
x: e.touches[0].pageX - touches[0].pageX,
|
||||||
|
y: e.touches[0].pageY - touches[0].pageY
|
||||||
|
});
|
||||||
|
touchType = 'move';
|
||||||
|
}
|
||||||
|
touches = e.touches;
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 触摸结束
|
||||||
|
* @param {Object} e 事件对象
|
||||||
|
* @param {Object} o 组件实例对象
|
||||||
|
*/
|
||||||
|
touchend: function(e, o) {
|
||||||
|
if(!img.src) return;
|
||||||
|
if(touchType === 'stretch') { // 拉伸裁剪区域的四个角缩放
|
||||||
|
// 裁剪区域宽度被缩放到多少
|
||||||
|
var left = areaOffset.left;
|
||||||
|
var right = areaOffset.right;
|
||||||
|
var top = areaOffset.top;
|
||||||
|
var bottom = areaOffset.bottom;
|
||||||
|
var w = area.width + right - left;
|
||||||
|
var h = area.height + bottom - top;
|
||||||
|
// 图像放大倍数
|
||||||
|
var p = scale * (area.width / w) - scale;
|
||||||
|
// 复原裁剪区域
|
||||||
|
areaOffset = { left: 0, right: 0, top: 0, bottom: 0 };
|
||||||
|
changeAreaRect({
|
||||||
|
instance: o,
|
||||||
|
});
|
||||||
|
scaleImage({
|
||||||
|
instance: o,
|
||||||
|
scale: p,
|
||||||
|
x: area.left + left + (1 === activeAngle || 3 === activeAngle ? w : 0),
|
||||||
|
y: area.top + top + (1 === activeAngle || 2 === activeAngle ? h : 0)
|
||||||
|
});
|
||||||
|
} else if (area.bounce) { // 检查边界并矫正,实现拖动到边界时有回弹效果
|
||||||
|
changeImageRect({
|
||||||
|
instance: o,
|
||||||
|
check: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 顺时针翻转图片90°
|
||||||
|
* @param {Object} e 事件对象
|
||||||
|
* @param {Object} o 组件实例对象
|
||||||
|
*/
|
||||||
|
rotateImage: function(e, o) {
|
||||||
|
rotateImage(e, o, 90);
|
||||||
|
},
|
||||||
|
rotateImage90: function(e, o) {
|
||||||
|
rotateImage(e, o, 90)
|
||||||
|
},
|
||||||
|
rotateImage270: function(e, o) {
|
||||||
|
rotateImage(e, o, 270)
|
||||||
|
},
|
||||||
|
// 此处只用于对齐其他平台端的样式参数,防止异常,无作用
|
||||||
|
imageStyles: '',
|
||||||
|
maskStylesList: ['', '', '', ''],
|
||||||
|
borderStyles: '',
|
||||||
|
gridStylesList: ['', '', '', ''],
|
||||||
|
angleStylesList: ['', '', '', ''],
|
||||||
|
circleBoxStyles: '',
|
||||||
|
circleStyles: '',
|
||||||
|
}
|
||||||
81
src/uni_modules/qf-image-cropper/package.json
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
{
|
||||||
|
"id": "qf-image-cropper",
|
||||||
|
"displayName": "图片裁剪插件",
|
||||||
|
"version": "2.2.5",
|
||||||
|
"description": "图片裁剪插件,支持自定义尺寸、定点等比例缩放、拖动、图片翻转、剪切圆形/圆角图片、定制样式,功能多性能高体验好注释全。",
|
||||||
|
"keywords": [
|
||||||
|
"qf-image-cropper",
|
||||||
|
"图片裁剪",
|
||||||
|
"图片编辑",
|
||||||
|
"头像裁剪",
|
||||||
|
"小程序"
|
||||||
|
],
|
||||||
|
"repository": "",
|
||||||
|
"engines": {
|
||||||
|
"HBuilderX": "^3.1.0"
|
||||||
|
},
|
||||||
|
"dcloudext": {
|
||||||
|
"type": "component-vue",
|
||||||
|
"sale": {
|
||||||
|
"regular": {
|
||||||
|
"price": "0.00"
|
||||||
|
},
|
||||||
|
"sourcecode": {
|
||||||
|
"price": "0.00"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"contact": {
|
||||||
|
"qq": ""
|
||||||
|
},
|
||||||
|
"declaration": {
|
||||||
|
"ads": "无",
|
||||||
|
"data": "插件不采集任何数据",
|
||||||
|
"permissions": "无"
|
||||||
|
},
|
||||||
|
"npmurl": ""
|
||||||
|
},
|
||||||
|
"uni_modules": {
|
||||||
|
"dependencies": [],
|
||||||
|
"encrypt": [],
|
||||||
|
"platforms": {
|
||||||
|
"client": {
|
||||||
|
"Vue": {
|
||||||
|
"vue2": "y",
|
||||||
|
"vue3": "y"
|
||||||
|
},
|
||||||
|
"App": {
|
||||||
|
"app-vue": "y",
|
||||||
|
"app-nvue": "n"
|
||||||
|
},
|
||||||
|
"H5-mobile": {
|
||||||
|
"Safari": "y",
|
||||||
|
"Android Browser": "y",
|
||||||
|
"微信浏览器(Android)": "y",
|
||||||
|
"QQ浏览器(Android)": "u"
|
||||||
|
},
|
||||||
|
"H5-pc": {
|
||||||
|
"Chrome": "u",
|
||||||
|
"IE": "u",
|
||||||
|
"Edge": "u",
|
||||||
|
"Firefox": "u",
|
||||||
|
"Safari": "u"
|
||||||
|
},
|
||||||
|
"小程序": {
|
||||||
|
"微信": "y",
|
||||||
|
"阿里": "n",
|
||||||
|
"百度": "n",
|
||||||
|
"字节跳动": "n",
|
||||||
|
"QQ": "u",
|
||||||
|
"钉钉": "n",
|
||||||
|
"快手": "n",
|
||||||
|
"飞书": "n",
|
||||||
|
"京东": "n"
|
||||||
|
},
|
||||||
|
"快应用": {
|
||||||
|
"华为": "n",
|
||||||
|
"联盟": "n"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
97
src/uni_modules/qf-image-cropper/readme.md
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
# qf-image-cropper
|
||||||
|
## 图片裁剪插件
|
||||||
|
uniapp微信小程序图片裁剪插件,支持自定义尺寸、定点等比例缩放、拖动、图片翻转、剪切圆形/圆角图片、定制样式,功能多性能高体验好注释全。
|
||||||
|
|
||||||
|
### 平台支持:
|
||||||
|
1. 支持微信小程序:移动端、PC端、开发者工具
|
||||||
|
2. 支持H5平台(2.1.0版本起)
|
||||||
|
3. 支持APP平台(2.1.5版本起):Android、IOS
|
||||||
|
4. 其他平台暂未测试兼容性未知
|
||||||
|
|
||||||
|
### 支持功能:
|
||||||
|
1. 自定义裁剪尺寸
|
||||||
|
2. 定点等比例缩放:移动端以双指触摸中心点为缩放中心点,PC端以鼠标所在点为缩放中心点
|
||||||
|
3. 自由拖动:支持限制滑出边界,也支持回弹效果(滑动时可滑出边界,释放时回弹到边界)
|
||||||
|
4. 图片翻转:在裁剪尺寸非 1:1 的情况下,翻转时宽高无法铺满裁剪区域时,图片会自动放大到合适尺寸
|
||||||
|
5. 裁剪生成新图片
|
||||||
|
6. 本地选择图片
|
||||||
|
7. 可定制样式:可自由选择是否渲染裁剪边框、可伸缩裁剪顶角、参考线
|
||||||
|
8. 裁剪圆角图片:圆形、圆角矩形
|
||||||
|
|
||||||
|
### 属性说明
|
||||||
|
| 属性名 | 类型 | 默认值 | 说明 |
|
||||||
|
|:---|:---|:---|:---|
|
||||||
|
| src | String | | 图片资源地址 |
|
||||||
|
| width | Number | 300 | 裁剪宽度 |
|
||||||
|
| height | Number | 300 | 裁剪高度 |
|
||||||
|
| showBorder | Boolean | true | 是否绘制裁剪区域边框 |
|
||||||
|
| showGrid | Boolean | true | 是否绘制裁剪区域网格参考线 |
|
||||||
|
| showAngle | Boolean | true | 是否展示四个支持伸缩的角 |
|
||||||
|
| areaScale | Number | 0.3 | 裁剪区域最小缩放倍数 |
|
||||||
|
| minScale | Number | 1 | 图片最小缩放倍数 |
|
||||||
|
| maxScale | Number | 5 | 图片最大缩放倍数 |
|
||||||
|
| checkRange | Boolean | true | 检查图片位置是否超出裁剪边界,如果超出则会矫正位置 |
|
||||||
|
| backgroundColor | String | | 生成图片背景色:如果裁剪区域没有完全包含在图片中时,不设置该属性则生成图片存在一定的透明块 |
|
||||||
|
| bounce | Boolean | true | 是否有回弹效果:当 checkRange 为 true 时有效,拖动时可以拖出边界,释放时会弹回边界 |
|
||||||
|
| rotatable | Boolean | true | 是否支持翻转 |
|
||||||
|
| reverseRotatable | Boolean | false | 是否支持逆向翻转 |
|
||||||
|
| choosable | Boolean | true | 是否支持从本地选择素材 |
|
||||||
|
| gpu | Boolean | false | 是否开启硬件加速,图片缩放过程中如果出现元素的“留影”或“重影”效果,可通过该方式解决或减轻这一问题 |
|
||||||
|
| angleSize | Number | 20 | 四个角尺寸,单位px |
|
||||||
|
| angleBorderWidth | Number | 2 | 四个角边框宽度,单位px |
|
||||||
|
| zIndex | Number/String | | 调整组件层级 |
|
||||||
|
| radius | Number | | 裁剪图片圆角半径,单位px |
|
||||||
|
| fileType | String | png | 生成文件的类型,只支持 'jpg' 或 'png'。默认为 'png' |
|
||||||
|
| delay | Number | 1000 | 图片从绘制到生成所需时间,单位ms<br>微信小程序平台使用 `Canvas 2D` 绘制时有效<br>如绘制大图或出现裁剪图片空白等情况应适当调大该值,因 `Canvas 2d` 采用同步绘制,需自己把控绘制完成时间 |
|
||||||
|
| navigation | Boolean | true | 页面是否是原生标题栏:<br>H5平台当 showAngle 为 true 时,使用插件的页面在 `page.json` 中配置了 `"navigationStyle": "custom"` 时,必须将此值设为 false ,否则四个可拉伸角的触发位置会有偏差。<br>注:因H5平台的窗口高度是包含标题栏的,而屏幕触摸点的坐标是不包含的 |
|
||||||
|
| @crop | EventHandle | | 剪裁完成后触发,event = { tempFilePath }。在H5平台下,tempFilePath 为 base64 |
|
||||||
|
|
||||||
|
### 基本用法
|
||||||
|
```
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<qf-image-cropper :width="500" :height="500" :radius="30" @crop="handleCrop"></qf-image-cropper>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import QfImageCropper from '@/components/qf-image-cropper/qf-image-cropper.vue';
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
QfImageCropper
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
handleCrop(e) {
|
||||||
|
uni.previewImage({
|
||||||
|
urls: [e.tempFilePath],
|
||||||
|
current: 0
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
```
|
||||||
|
通过ref组件实例可在进入页面后直接打开相册选择图片
|
||||||
|
```
|
||||||
|
mounted() {
|
||||||
|
this.$refs.qfImageCropper.chooseImage({ sourceType: ['album'] });
|
||||||
|
}
|
||||||
|
```
|
||||||
|
### 使用说明
|
||||||
|
1.建议在`pages.json`中将引用插件的页面添加一下配置禁止下拉刷新和禁止页面滑动,防止出现性能或页面抖动等问题。
|
||||||
|
```
|
||||||
|
{
|
||||||
|
"enablePullDownRefresh": false,
|
||||||
|
"disableScroll": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
2.建议使用本插件不要设置过大宽高的目标图片尺寸,建议1365x1365以内,否则可能会导致如下问题:
|
||||||
|
```
|
||||||
|
1.界面卡顿,内存占用过高
|
||||||
|
2.生成图片失真(模糊)
|
||||||
|
3.确定裁剪后一直显示 `裁剪中...`,该问题是由 `uni.canvasToTempFilePath` 无法回调导致,不同平台不同设备限制可能有所不同。
|
||||||
|
```
|
||||||
|
3.如裁剪后的图片存在偏移的问题,请检查是否受自己项目中父组件或全局样式影响。
|
||||||
|
4.src属性设置网络图片时,图片资源必须是能触发 `getImageInfo` API 的 success 回调才可用于插件裁剪。因此小程序平台获取网络图片信息需先配置download域名白名单才能生效。
|
||||||
|
5.如果组件无法正常渲染且使用了 `v-if` 时,可尝试将 `v-if` 替换为 `v-show`
|
||||||
|
6.如果App端导入组件后无法正常渲染,请尝试重新运行
|
||||||
13
tsconfig.json
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"extends": "@vue/tsconfig/tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"sourceMap": true,
|
||||||
|
"baseUrl": ".",
|
||||||
|
"paths": {
|
||||||
|
"@/*": ["./src/*"]
|
||||||
|
},
|
||||||
|
"lib": ["esnext", "dom"],
|
||||||
|
"types": ["@dcloudio/types"]
|
||||||
|
},
|
||||||
|
"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"]
|
||||||
|
}
|
||||||
BIN
unpackage/res/icons/1024x1024.png
Normal file
|
After Width: | Height: | Size: 24 KiB |
BIN
unpackage/res/icons/120x120.png
Normal file
|
After Width: | Height: | Size: 2.7 KiB |
BIN
unpackage/res/icons/144x144.png
Normal file
|
After Width: | Height: | Size: 3.3 KiB |
BIN
unpackage/res/icons/152x152.png
Normal file
|
After Width: | Height: | Size: 3.5 KiB |
BIN
unpackage/res/icons/167x167.png
Normal file
|
After Width: | Height: | Size: 3.5 KiB |
BIN
unpackage/res/icons/180x180.png
Normal file
|
After Width: | Height: | Size: 4.0 KiB |
BIN
unpackage/res/icons/192x192.png
Normal file
|
After Width: | Height: | Size: 4.3 KiB |
BIN
unpackage/res/icons/20x20.png
Normal file
|
After Width: | Height: | Size: 471 B |
BIN
unpackage/res/icons/29x29.png
Normal file
|
After Width: | Height: | Size: 678 B |
BIN
unpackage/res/icons/40x40.png
Normal file
|
After Width: | Height: | Size: 966 B |
BIN
unpackage/res/icons/58x58.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
unpackage/res/icons/60x60.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
unpackage/res/icons/72x72.png
Normal file
|
After Width: | Height: | Size: 1.7 KiB |
BIN
unpackage/res/icons/76x76.png
Normal file
|
After Width: | Height: | Size: 1.7 KiB |
BIN
unpackage/res/icons/80x80.png
Normal file
|
After Width: | Height: | Size: 1.8 KiB |
BIN
unpackage/res/icons/87x87.png
Normal file
|
After Width: | Height: | Size: 2.0 KiB |
BIN
unpackage/res/icons/96x96.png
Normal file
|
After Width: | Height: | Size: 2.2 KiB |
7
vite.config.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
import { defineConfig } from "vite";
|
||||||
|
import uni from "@dcloudio/vite-plugin-uni";
|
||||||
|
|
||||||
|
// https://vitejs.dev/config/
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [uni()],
|
||||||
|
});
|
||||||